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  -
072 *  http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0m1/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
073 *
074 * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
075 * @plexus.component role="org.apache.maven.scm.provider.ScmProvider" role-hint="jazz"
076 */
077public class JazzScmProvider
078    extends AbstractScmProvider
079{
080    // Example: scm:jazz:daviddl;passw0rd123@https://localhost:9443/jazz:Dave's Repository Workspace
081    // If the username or password is supplied, then the @ must be used to delimit them.
082    public static final String JAZZ_URL_FORMAT =
083        "scm:jazz:[username[;password]@]http[s]://server_name[:port]/contextRoot:repositoryWorkspace";
084
085    // ----------------------------------------------------------------------
086    // ScmProvider Implementation
087    // ----------------------------------------------------------------------
088
089    public String getScmType()
090    {
091        return JazzConstants.SCM_TYPE;
092    }
093
094    /**
095     * This method parses the scm URL and returns a SCM provider repository.
096     * At this point, the scmUrl is the part after scm:provider_name: in your SCM URL.
097     * <p/>
098     * The basic url parsing approach is to be as loose as possible.
099     * 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        JazzAddCommand command = new JazzAddCommand();
254        command.setLogger( getLogger() );
255        return (AddScmResult) command.execute( repository, fileSet, parameters );
256    }
257
258    /**
259     * {@inheritDoc}
260     */
261    protected BranchScmResult branch( ScmProviderRepository repository, ScmFileSet fileSet,
262                                      CommandParameters parameters )
263        throws ScmException
264    {
265        JazzBranchCommand command = new JazzBranchCommand();
266        command.setLogger( getLogger() );
267        return (BranchScmResult) command.execute( repository, fileSet, parameters );
268    }
269
270    /**
271     * {@inheritDoc}
272     */
273    protected BlameScmResult blame( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
274        throws ScmException
275    {
276        JazzBlameCommand command = new JazzBlameCommand();
277        command.setLogger( getLogger() );
278        return (BlameScmResult) command.execute( repository, fileSet, parameters );
279    }
280
281    /**
282     * {@inheritDoc}
283     */
284    protected ChangeLogScmResult changelog( ScmProviderRepository repository, ScmFileSet fileSet,
285                                            CommandParameters parameters )
286        throws ScmException
287    {
288        // We need to call the status command first, so that we can get the details of the workspace.
289        // This is needed for the list changesets command.
290        // We could also 'trust' the value in the pom.
291        JazzStatusCommand statusCommand = new JazzStatusCommand();
292        statusCommand.setLogger( getLogger() );
293        statusCommand.execute( repository, fileSet, parameters );
294
295        JazzChangeLogCommand command = new JazzChangeLogCommand();
296        command.setLogger( getLogger() );
297        return (ChangeLogScmResult) command.execute( repository, fileSet, parameters );
298    }
299
300    /**
301     * {@inheritDoc}
302     */
303    protected CheckInScmResult checkin( ScmProviderRepository repository, ScmFileSet fileSet,
304                                        CommandParameters parameters )
305        throws ScmException
306    {
307        JazzCheckInCommand command = new JazzCheckInCommand();
308        command.setLogger( getLogger() );
309        return (CheckInScmResult) command.execute( repository, fileSet, parameters );
310    }
311
312    /**
313     * {@inheritDoc}
314     */
315    protected CheckOutScmResult checkout( ScmProviderRepository repository, ScmFileSet fileSet,
316                                          CommandParameters parameters )
317        throws ScmException
318    {
319        JazzCheckOutCommand command = new JazzCheckOutCommand();
320        command.setLogger( getLogger() );
321        return (CheckOutScmResult) command.execute( repository, fileSet, parameters );
322    }
323
324    /**
325     * {@inheritDoc}
326     */
327    protected DiffScmResult diff( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
328        throws ScmException
329    {
330        JazzDiffCommand command = new JazzDiffCommand();
331        command.setLogger( getLogger() );
332        return (DiffScmResult) command.execute( repository, fileSet, parameters );
333    }
334
335    /**
336     * {@inheritDoc}
337     */
338    protected EditScmResult edit( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
339        throws ScmException
340    {
341        JazzEditCommand command = new JazzEditCommand();
342        command.setLogger( getLogger() );
343        return (EditScmResult) command.execute( repository, fileSet, parameters );
344    }
345
346    /**
347     * {@inheritDoc}
348     */
349    protected ExportScmResult export( ScmProviderRepository repository, ScmFileSet fileSet,
350                                      CommandParameters parameters )
351        throws ScmException
352    {
353        // Use checkout instead
354        return super.export( repository, fileSet, parameters );
355    }
356
357    /**
358     * {@inheritDoc}
359     */
360    protected ListScmResult list( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
361        throws ScmException
362    {
363        // We need to call the status command first, so that we can get the details of the stream etc.
364        // This is needed for workspace deliveries and snapshot promotions.
365        JazzStatusCommand statusCommand = new JazzStatusCommand();
366        statusCommand.setLogger( getLogger() );
367        statusCommand.execute( repository, fileSet, parameters );
368
369        JazzListCommand command = new JazzListCommand();
370        command.setLogger( getLogger() );
371        return (ListScmResult) command.execute( repository, fileSet, parameters );
372    }
373
374    /**
375     * {@inheritDoc}
376     */
377    protected StatusScmResult status( ScmProviderRepository repository, ScmFileSet fileSet,
378                                      CommandParameters parameters )
379        throws ScmException
380    {
381        JazzStatusCommand command = new JazzStatusCommand();
382        command.setLogger( getLogger() );
383        return (StatusScmResult) command.execute( repository, fileSet, parameters );
384    }
385
386    /**
387     * {@inheritDoc}
388     */
389    protected TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
390        throws ScmException
391    {
392        // We need to call the status command first, so that we can get the details of the stream etc.
393        // This is needed for workspace deliveries and snapshot promotions.
394        JazzStatusCommand statusCommand = new JazzStatusCommand();
395        statusCommand.setLogger( getLogger() );
396        statusCommand.execute( repository, fileSet, parameters );
397
398        JazzTagCommand command = new JazzTagCommand();
399        command.setLogger( getLogger() );
400        return (TagScmResult) command.execute( repository, fileSet, parameters );
401    }
402
403    /**
404     * {@inheritDoc}
405     */
406    protected UpdateScmResult update( ScmProviderRepository repository, ScmFileSet fileSet,
407                                      CommandParameters parameters )
408        throws ScmException
409    {
410        JazzUpdateCommand command = new JazzUpdateCommand();
411        command.setLogger( getLogger() );
412        return (UpdateScmResult) command.execute( repository, fileSet, parameters );
413    }
414
415    /**
416     * {@inheritDoc}
417     */
418    protected UnEditScmResult unedit( ScmProviderRepository repository, ScmFileSet fileSet,
419                                      CommandParameters parameters )
420        throws ScmException
421    {
422        JazzUnEditCommand command = new JazzUnEditCommand();
423        command.setLogger( getLogger() );
424        return (UnEditScmResult) command.execute( repository, fileSet, parameters );
425    }
426
427    /**
428     * {@inheritDoc}
429     */
430    public String getScmSpecificFilename()
431    {
432        return JazzConstants.SCM_META_DATA_FOLDER;
433    }
434
435}