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        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}