View Javadoc
1   package org.apache.maven.scm.provider.jazz;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.scm.CommandParameters;
23  import org.apache.maven.scm.ScmException;
24  import org.apache.maven.scm.ScmFileSet;
25  import org.apache.maven.scm.command.add.AddScmResult;
26  import org.apache.maven.scm.command.blame.BlameScmResult;
27  import org.apache.maven.scm.command.branch.BranchScmResult;
28  import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
29  import org.apache.maven.scm.command.checkin.CheckInScmResult;
30  import org.apache.maven.scm.command.checkout.CheckOutScmResult;
31  import org.apache.maven.scm.command.diff.DiffScmResult;
32  import org.apache.maven.scm.command.edit.EditScmResult;
33  import org.apache.maven.scm.command.export.ExportScmResult;
34  import org.apache.maven.scm.command.list.ListScmResult;
35  import org.apache.maven.scm.command.status.StatusScmResult;
36  import org.apache.maven.scm.command.tag.TagScmResult;
37  import org.apache.maven.scm.command.unedit.UnEditScmResult;
38  import org.apache.maven.scm.command.update.UpdateScmResult;
39  import org.apache.maven.scm.provider.AbstractScmProvider;
40  import org.apache.maven.scm.provider.ScmProviderRepository;
41  import org.apache.maven.scm.provider.jazz.command.JazzConstants;
42  import org.apache.maven.scm.provider.jazz.command.add.JazzAddCommand;
43  import org.apache.maven.scm.provider.jazz.command.blame.JazzBlameCommand;
44  import org.apache.maven.scm.provider.jazz.command.branch.JazzBranchCommand;
45  import org.apache.maven.scm.provider.jazz.command.changelog.JazzChangeLogCommand;
46  import org.apache.maven.scm.provider.jazz.command.checkin.JazzCheckInCommand;
47  import org.apache.maven.scm.provider.jazz.command.checkout.JazzCheckOutCommand;
48  import org.apache.maven.scm.provider.jazz.command.diff.JazzDiffCommand;
49  import org.apache.maven.scm.provider.jazz.command.edit.JazzEditCommand;
50  import org.apache.maven.scm.provider.jazz.command.list.JazzListCommand;
51  import org.apache.maven.scm.provider.jazz.command.status.JazzStatusCommand;
52  import org.apache.maven.scm.provider.jazz.command.tag.JazzTagCommand;
53  import org.apache.maven.scm.provider.jazz.command.unedit.JazzUnEditCommand;
54  import org.apache.maven.scm.provider.jazz.command.update.JazzUpdateCommand;
55  import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository;
56  import org.apache.maven.scm.repository.ScmRepositoryException;
57  
58  import java.net.URI;
59  
60  /**
61   * The maven scm provider for Jazz.
62   * <p/>
63   * This provider is a wrapper for the command line tool, "scm.sh" or "scm.exe" is that is
64   * part of the Jazz SCM Server.
65   * <p/>
66   * This provider does not use a native API to communicate with the Jazz SCM server.
67   * <p/>
68   * The scm tool itself is documented at:
69   * V2.0.0  - http://publib.boulder.ibm.com/infocenter/rtc/v2r0m0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
70   * V3.0    - http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
71   * V3.0.1  -
72   *  http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0m1/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
73   *
74   * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
75   * @plexus.component role="org.apache.maven.scm.provider.ScmProvider" role-hint="jazz"
76   */
77  public class JazzScmProvider
78      extends AbstractScmProvider
79  {
80      // Example: scm:jazz:daviddl;passw0rd123@https://localhost:9443/jazz:Dave's Repository Workspace
81      // If the username or password is supplied, then the @ must be used to delimit them.
82      public static final String JAZZ_URL_FORMAT =
83          "scm:jazz:[username[;password]@]http[s]://server_name[:port]/contextRoot:repositoryWorkspace";
84  
85      // ----------------------------------------------------------------------
86      // ScmProvider Implementation
87      // ----------------------------------------------------------------------
88  
89      public String getScmType()
90      {
91          return JazzConstants.SCM_TYPE;
92      }
93  
94      /**
95       * This method parses the scm URL and returns a SCM provider repository.
96       * At this point, the scmUrl is the part after scm:provider_name: in your SCM URL.
97       * <p/>
98       * The basic url parsing approach is to be as loose as possible.
99       * If you specify as per the docs you'll get what you expect.
100      * If you do something else the result is undefined.
101      * Don't use "/" "\" or "@" as the delimiter.
102      * <p/>
103      * Parse the scmUrl, which will be of the form:
104      * [username[;password]@]http[s]://server_name[:port]/contextRoot:repositoryWorkspace
105      * eg:
106      * Deb;Deb@https://rtc:9444/jazz:BogusRepositoryWorkspace
107      * {@inheritDoc}
108      */
109     public ScmProviderRepository makeProviderScmRepository( String scmUrl, char delimiter )
110         throws ScmRepositoryException
111     {
112         // Called from:
113         // AbstractScmProvider.makeScmRepository()
114         // AbstractScmProvider.validateScmUrl()
115         getLogger().debug( "JazzScmProvider:makeProviderScmRepository()" );
116         getLogger().debug( "Provided scm url   - " + scmUrl );
117         getLogger().debug( "Provided delimiter - '" + delimiter + "'" );
118 
119         String jazzUrlAndWorkspace = null;
120         String usernameAndPassword = null;
121 
122         // Look for the Jazz URL after any '@' delimiter used to pass
123         // username/password etc (which may not have been specified)
124         int lastAtPosition = scmUrl.lastIndexOf( '@' );
125         if ( lastAtPosition == -1 )
126         {
127             // The username;password@ was not supplied.
128             jazzUrlAndWorkspace = scmUrl;
129         }
130         else
131         {
132             // The username@ or username;password@ was supplied.
133             jazzUrlAndWorkspace = ( lastAtPosition < 0 ) ? scmUrl : scmUrl.substring( lastAtPosition + 1 );
134             usernameAndPassword = ( lastAtPosition < 0 ) ? null : scmUrl.substring( 0, lastAtPosition );
135         }
136 
137         // jazzUrlAndWorkspace should be: http[s]://server_name:port/contextRoot:repositoryWorkspace
138         // usernameAndPassword should be: username;password or null
139 
140         // username and password may not be supplied, and so may remain null.
141         String username = null;
142         String password = null;
143 
144         if ( usernameAndPassword != null )
145         {
146             // Can be:
147             // username
148             // username;password
149             int delimPosition = usernameAndPassword.indexOf( ';' );
150             username = delimPosition >= 0 ? usernameAndPassword.substring( 0, delimPosition ) : usernameAndPassword;
151             password = delimPosition >= 0 ? usernameAndPassword.substring( delimPosition + 1 ) : null;
152         }
153 
154         // We will now validate the jazzUrlAndWorkspace for right number of colons.
155         // This has been observed in the wild, where the contextRoot:repositoryWorkspace was not properly formed
156         // and this resulted in very strange results in the way in which things were parsed.
157         int colonsCounted = 0;
158         int colonIndex = 0;
159         while ( colonIndex != -1 )
160         {
161             colonIndex = jazzUrlAndWorkspace.indexOf( ":", colonIndex + 1 );
162             if ( colonIndex != -1 )
163             {
164                 colonsCounted++;
165             }
166         }
167         // havePort may also be true when port is supplied, but otherwise have a malformed URL.
168         boolean havePort = colonsCounted == 3;
169 
170         // Look for workspace after the end of the Jazz URL
171         int repositoryWorkspacePosition = jazzUrlAndWorkspace.lastIndexOf( delimiter );
172         String repositoryWorkspace = jazzUrlAndWorkspace.substring( repositoryWorkspacePosition + 1 );
173         String jazzUrl = jazzUrlAndWorkspace.substring( 0, repositoryWorkspacePosition );
174 
175         // Validate the protocols.
176         try
177         {
178             // Determine if it is a valid URI.
179             URI jazzUri = URI.create( jazzUrl );
180             String scheme = jazzUri.getScheme();
181             getLogger().debug( "Scheme - " + scheme );
182             if ( scheme == null || !( scheme.equalsIgnoreCase( "http" ) || scheme.equalsIgnoreCase( "https" ) ) )
183             {
184                 throw new ScmRepositoryException(
185                     "Jazz Url \"" + jazzUrl + "\" is not a valid URL. The Jazz Url syntax is " + JAZZ_URL_FORMAT );
186             }
187         }
188         catch ( IllegalArgumentException e )
189         {
190             throw new ScmRepositoryException(
191                 "Jazz Url \"" + jazzUrl + "\" is not a valid URL. The Jazz Url syntax is " + JAZZ_URL_FORMAT );
192         }
193 
194         // At this point, jazzUrl is guaranteed to start with either http:// or https://
195         // Further process the jazzUrl to extract the server name and port.
196         String hostname = null;
197         int port = 0;
198 
199         if ( havePort )
200         {
201             // jazzUrlAndWorkspace should be: http[s]://server_name:port/contextRoot:repositoryWorkspace
202             // jazzUrl should be            : http[s]://server_name:port/contextRoot
203             int protocolIndex = jazzUrl.indexOf( ":" ) + 3;     // The +3 accounts for the "://"
204             int portIndex = jazzUrl.indexOf( ":", protocolIndex + 1 );
205             hostname = jazzUrl.substring( protocolIndex, portIndex );
206             int pathIndex = jazzUrl.indexOf( "/", portIndex + 1 );
207             String portNo = jazzUrl.substring( portIndex + 1, pathIndex );
208             try
209             {
210                 port = Integer.parseInt( portNo );
211             }
212             catch ( NumberFormatException nfe )
213             {
214                 throw new ScmRepositoryException(
215                     "Jazz Url \"" + jazzUrl + "\" is not a valid URL. The Jazz Url syntax is " + JAZZ_URL_FORMAT );
216             }
217         }
218         else
219         {
220             // jazzUrlAndWorkspace should be: http[s]://server_name/contextRoot:repositoryWorkspace
221             // jazzUrl should be            : http[s]://server_name/contextRoot
222             // So we will set port to zero.
223             int protocolIndex = jazzUrl.indexOf( ":" ) + 3;     // The +3 accounts for the "://"
224             int pathIndex = jazzUrl.indexOf( "/", protocolIndex + 1 );
225             if ( ( protocolIndex != -1 ) && ( pathIndex != -1 ) )
226             {
227                 hostname = jazzUrl.substring( protocolIndex, pathIndex );
228             }
229             else
230             {
231                 throw new ScmRepositoryException(
232                     "Jazz Url \"" + jazzUrl + "\" is not a valid URL. The Jazz Url syntax is " + JAZZ_URL_FORMAT );
233             }
234         }
235 
236         getLogger().debug( "Creating JazzScmProviderRepository with the following values:" );
237         getLogger().debug( "jazzUrl             - " + jazzUrl );
238         getLogger().debug( "username            - " + username );
239         getLogger().debug( "password            - " + password );
240         getLogger().debug( "hostname            - " + hostname );
241         getLogger().debug( "port                - " + port );
242         getLogger().debug( "repositoryWorkspace - " + repositoryWorkspace );
243 
244         return new JazzScmProviderRepository( jazzUrl, username, password, hostname, port, repositoryWorkspace );
245     }
246 
247     /**
248      * {@inheritDoc}
249      */
250     public AddScmResult add( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
251         throws ScmException
252     {
253         getLogger().debug( "JazzScmProvider:add()" );
254         JazzAddCommand command = new JazzAddCommand();
255         command.setLogger( getLogger() );
256         return (AddScmResult) command.execute( repository, fileSet, parameters );
257     }
258 
259     /**
260      * {@inheritDoc}
261      */
262     protected BranchScmResult branch( ScmProviderRepository repository, ScmFileSet fileSet,
263                                       CommandParameters parameters )
264         throws ScmException
265     {
266         getLogger().debug( "JazzScmProvider:branch()" );
267         JazzBranchCommand command = new JazzBranchCommand();
268         command.setLogger( getLogger() );
269         return (BranchScmResult) command.execute( repository, fileSet, parameters );
270     }
271 
272     /**
273      * {@inheritDoc}
274      */
275     protected BlameScmResult blame( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
276         throws ScmException
277     {
278         getLogger().debug( "JazzScmProvider:blame()" );
279         JazzBlameCommand command = new JazzBlameCommand();
280         command.setLogger( getLogger() );
281         return (BlameScmResult) command.execute( repository, fileSet, parameters );
282     }
283 
284     /**
285      * {@inheritDoc}
286      */
287     protected ChangeLogScmResult changelog( ScmProviderRepository repository, ScmFileSet fileSet,
288                                             CommandParameters parameters )
289         throws ScmException
290     {
291         getLogger().debug( "JazzScmProvider:changelog()" );
292         // We need to call the status command first, so that we can get the details of the workspace.
293         // This is needed for the list changesets command.
294         // We could also 'trust' the value in the pom.
295         JazzStatusCommand statusCommand = new JazzStatusCommand();
296         statusCommand.setLogger( getLogger() );
297         statusCommand.execute( repository, fileSet, parameters );
298 
299         JazzChangeLogCommand command = new JazzChangeLogCommand();
300         command.setLogger( getLogger() );
301         return (ChangeLogScmResult) command.execute( repository, fileSet, parameters );
302     }
303 
304     /**
305      * {@inheritDoc}
306      */
307     protected CheckInScmResult checkin( ScmProviderRepository repository, ScmFileSet fileSet,
308                                         CommandParameters parameters )
309         throws ScmException
310     {
311         getLogger().debug( "JazzScmProvider:checkin()" );
312         JazzCheckInCommand command = new JazzCheckInCommand();
313         command.setLogger( getLogger() );
314         return (CheckInScmResult) command.execute( repository, fileSet, parameters );
315     }
316 
317     /**
318      * {@inheritDoc}
319      */
320     protected CheckOutScmResult checkout( ScmProviderRepository repository, ScmFileSet fileSet,
321                                           CommandParameters parameters )
322         throws ScmException
323     {
324         getLogger().debug( "JazzScmProvider:checkout()" );
325         JazzCheckOutCommand command = new JazzCheckOutCommand();
326         command.setLogger( getLogger() );
327         return (CheckOutScmResult) command.execute( repository, fileSet, parameters );
328     }
329 
330     /**
331      * {@inheritDoc}
332      */
333     protected DiffScmResult diff( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
334         throws ScmException
335     {
336         getLogger().debug( "JazzScmProvider:diff()" );
337         JazzDiffCommand command = new JazzDiffCommand();
338         command.setLogger( getLogger() );
339         return (DiffScmResult) command.execute( repository, fileSet, parameters );
340     }
341 
342     /**
343      * {@inheritDoc}
344      */
345     protected EditScmResult edit( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
346         throws ScmException
347     {
348         getLogger().debug( "JazzScmProvider:edit()" );
349         JazzEditCommand command = new JazzEditCommand();
350         command.setLogger( getLogger() );
351         return (EditScmResult) command.execute( repository, fileSet, parameters );
352     }
353 
354     /**
355      * {@inheritDoc}
356      */
357     protected ExportScmResult export( ScmProviderRepository repository, ScmFileSet fileSet,
358                                       CommandParameters parameters )
359         throws ScmException
360     {
361         getLogger().debug( "JazzScmProvider:export()" );
362         // Use checkout instead
363         return super.export( repository, fileSet, parameters );
364     }
365 
366     /**
367      * {@inheritDoc}
368      */
369     protected ListScmResult list( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
370         throws ScmException
371     {
372         getLogger().debug( "JazzScmProvider:list()" );
373         // We need to call the status command first, so that we can get the details of the stream etc.
374         // This is needed for workspace and component names.
375         JazzStatusCommand statusCommand = new JazzStatusCommand();
376         statusCommand.setLogger( getLogger() );
377         statusCommand.execute( repository, fileSet, parameters );
378 
379         JazzListCommand command = new JazzListCommand();
380         command.setLogger( getLogger() );
381         return (ListScmResult) command.execute( repository, fileSet, parameters );
382     }
383 
384     /**
385      * {@inheritDoc}
386      */
387     protected StatusScmResult status( ScmProviderRepository repository, ScmFileSet fileSet,
388                                       CommandParameters parameters )
389         throws ScmException
390     {
391         getLogger().debug( "JazzScmProvider:status()" );
392         JazzStatusCommand command = new JazzStatusCommand();
393         command.setLogger( getLogger() );
394         return (StatusScmResult) command.execute( repository, fileSet, parameters );
395     }
396 
397     /**
398      * {@inheritDoc}
399      */
400     protected TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
401         throws ScmException
402     {
403         getLogger().debug( "JazzScmProvider:tag()" );
404         // We need to call the status command first, so that we can get the details of the stream etc.
405         // This is needed for workspace deliveries and snapshot promotions.
406         JazzStatusCommand statusCommand = new JazzStatusCommand();
407         statusCommand.setLogger( getLogger() );
408         statusCommand.execute( repository, fileSet, parameters );
409 
410         JazzTagCommand command = new JazzTagCommand();
411         command.setLogger( getLogger() );
412         return (TagScmResult) command.execute( repository, fileSet, parameters );
413     }
414 
415     /**
416      * {@inheritDoc}
417      */
418     protected UpdateScmResult update( ScmProviderRepository repository, ScmFileSet fileSet,
419                                       CommandParameters parameters )
420         throws ScmException
421     {
422         getLogger().debug( "JazzScmProvider:update()" );
423         JazzUpdateCommand command = new JazzUpdateCommand();
424         command.setLogger( getLogger() );
425         return (UpdateScmResult) command.execute( repository, fileSet, parameters );
426     }
427 
428     /**
429      * {@inheritDoc}
430      */
431     protected UnEditScmResult unedit( ScmProviderRepository repository, ScmFileSet fileSet,
432                                       CommandParameters parameters )
433         throws ScmException
434     {
435         getLogger().debug( "JazzScmProvider:unedit()" );
436         JazzUnEditCommand command = new JazzUnEditCommand();
437         command.setLogger( getLogger() );
438         return (UnEditScmResult) command.execute( repository, fileSet, parameters );
439     }
440 
441     /**
442      * {@inheritDoc}
443      */
444     public String getScmSpecificFilename()
445     {
446         return JazzConstants.SCM_META_DATA_FOLDER;
447     }
448 
449 }