001    package org.apache.maven.scm.provider.jazz;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     * http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.maven.scm.CommandParameters;
023    import org.apache.maven.scm.ScmException;
024    import org.apache.maven.scm.ScmFileSet;
025    import org.apache.maven.scm.command.add.AddScmResult;
026    import org.apache.maven.scm.command.blame.BlameScmResult;
027    import org.apache.maven.scm.command.branch.BranchScmResult;
028    import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
029    import org.apache.maven.scm.command.checkin.CheckInScmResult;
030    import org.apache.maven.scm.command.checkout.CheckOutScmResult;
031    import org.apache.maven.scm.command.diff.DiffScmResult;
032    import org.apache.maven.scm.command.edit.EditScmResult;
033    import org.apache.maven.scm.command.export.ExportScmResult;
034    import org.apache.maven.scm.command.list.ListScmResult;
035    import org.apache.maven.scm.command.status.StatusScmResult;
036    import org.apache.maven.scm.command.tag.TagScmResult;
037    import org.apache.maven.scm.command.unedit.UnEditScmResult;
038    import org.apache.maven.scm.command.update.UpdateScmResult;
039    import org.apache.maven.scm.provider.AbstractScmProvider;
040    import org.apache.maven.scm.provider.ScmProviderRepository;
041    import org.apache.maven.scm.provider.jazz.command.JazzConstants;
042    import org.apache.maven.scm.provider.jazz.command.add.JazzAddCommand;
043    import org.apache.maven.scm.provider.jazz.command.blame.JazzBlameCommand;
044    import org.apache.maven.scm.provider.jazz.command.branch.JazzBranchCommand;
045    import org.apache.maven.scm.provider.jazz.command.changelog.JazzChangeLogCommand;
046    import org.apache.maven.scm.provider.jazz.command.checkin.JazzCheckInCommand;
047    import org.apache.maven.scm.provider.jazz.command.checkout.JazzCheckOutCommand;
048    import org.apache.maven.scm.provider.jazz.command.diff.JazzDiffCommand;
049    import org.apache.maven.scm.provider.jazz.command.edit.JazzEditCommand;
050    import org.apache.maven.scm.provider.jazz.command.list.JazzListCommand;
051    import org.apache.maven.scm.provider.jazz.command.status.JazzStatusCommand;
052    import org.apache.maven.scm.provider.jazz.command.tag.JazzTagCommand;
053    import org.apache.maven.scm.provider.jazz.command.unedit.JazzUnEditCommand;
054    import org.apache.maven.scm.provider.jazz.command.update.JazzUpdateCommand;
055    import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository;
056    import org.apache.maven.scm.repository.ScmRepositoryException;
057    
058    import java.net.URI;
059    
060    /**
061     * The maven scm provider for Jazz.
062     * <p/>
063     * This provider is a wrapper for the command line tool, "scm.sh" or "scm.exe" is that is
064     * part of the Jazz SCM Server.
065     * <p/>
066     * This provider does not use a native API to communicate with the Jazz SCM server.
067     * <p/>
068     * The scm tool itself is documented at:
069     * V2.0.0  - http://publib.boulder.ibm.com/infocenter/rtc/v2r0m0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
070     * V3.0    - http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
071     * V3.0.1  - http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0m1/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
072     *
073     * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
074     * @plexus.component role="org.apache.maven.scm.provider.ScmProvider" role-hint="jazz"
075     */
076    public class JazzScmProvider
077        extends AbstractScmProvider
078    {
079        // Example: scm:jazz:daviddl;passw0rd123@https://localhost:9443/jazz:Dave's Repository Workspace
080        // If the username or password is supplied, then the @ must be used to delimit them.
081        public static final String JAZZ_URL_FORMAT =
082            "scm:jazz:[username[;password]@]http[s]://server_name[:port]/contextRoot:repositoryWorkspace";
083    
084        // ----------------------------------------------------------------------
085        // ScmProvider Implementation
086        // ----------------------------------------------------------------------
087    
088        public String getScmType()
089        {
090            return JazzConstants.SCM_TYPE;
091        }
092    
093        /**
094         * This method parses the scm URL and returns a SCM provider repository.
095         * At this point, the scmUrl is the part after scm:provider_name: in your SCM URL.
096         * <p/>
097         * The basic url parsing approach is to be as loose as possible.
098         * If you specify as per the docs you'll get what you expect.
099         * 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    }