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