001package 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
022import org.apache.maven.scm.CommandParameters;
023import org.apache.maven.scm.ScmException;
024import org.apache.maven.scm.ScmFileSet;
025import org.apache.maven.scm.command.add.AddScmResult;
026import org.apache.maven.scm.command.blame.BlameScmResult;
027import org.apache.maven.scm.command.branch.BranchScmResult;
028import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
029import org.apache.maven.scm.command.checkin.CheckInScmResult;
030import org.apache.maven.scm.command.checkout.CheckOutScmResult;
031import org.apache.maven.scm.command.diff.DiffScmResult;
032import org.apache.maven.scm.command.edit.EditScmResult;
033import org.apache.maven.scm.command.export.ExportScmResult;
034import org.apache.maven.scm.command.list.ListScmResult;
035import org.apache.maven.scm.command.status.StatusScmResult;
036import org.apache.maven.scm.command.tag.TagScmResult;
037import org.apache.maven.scm.command.unedit.UnEditScmResult;
038import org.apache.maven.scm.command.update.UpdateScmResult;
039import org.apache.maven.scm.provider.AbstractScmProvider;
040import org.apache.maven.scm.provider.ScmProviderRepository;
041import org.apache.maven.scm.provider.jazz.command.JazzConstants;
042import org.apache.maven.scm.provider.jazz.command.add.JazzAddCommand;
043import org.apache.maven.scm.provider.jazz.command.blame.JazzBlameCommand;
044import org.apache.maven.scm.provider.jazz.command.branch.JazzBranchCommand;
045import org.apache.maven.scm.provider.jazz.command.changelog.JazzChangeLogCommand;
046import org.apache.maven.scm.provider.jazz.command.checkin.JazzCheckInCommand;
047import org.apache.maven.scm.provider.jazz.command.checkout.JazzCheckOutCommand;
048import org.apache.maven.scm.provider.jazz.command.diff.JazzDiffCommand;
049import org.apache.maven.scm.provider.jazz.command.edit.JazzEditCommand;
050import org.apache.maven.scm.provider.jazz.command.list.JazzListCommand;
051import org.apache.maven.scm.provider.jazz.command.status.JazzStatusCommand;
052import org.apache.maven.scm.provider.jazz.command.tag.JazzTagCommand;
053import org.apache.maven.scm.provider.jazz.command.unedit.JazzUnEditCommand;
054import org.apache.maven.scm.provider.jazz.command.update.JazzUpdateCommand;
055import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository;
056import org.apache.maven.scm.repository.ScmRepositoryException;
057
058import 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 */
076public 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}