001    package org.apache.maven.scm.provider.accurev;
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 java.io.File;
023    import java.util.ArrayList;
024    import java.util.Collections;
025    import java.util.Date;
026    import java.util.HashMap;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Set;
030    
031    import org.apache.maven.scm.ChangeFile;
032    import org.apache.maven.scm.ChangeSet;
033    import org.apache.maven.scm.CommandParameter;
034    import org.apache.maven.scm.CommandParameters;
035    import org.apache.maven.scm.ScmException;
036    import org.apache.maven.scm.ScmFile;
037    import org.apache.maven.scm.ScmFileSet;
038    import org.apache.maven.scm.ScmRevision;
039    import org.apache.maven.scm.command.add.AddScmResult;
040    import org.apache.maven.scm.command.blame.BlameScmResult;
041    import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
042    import org.apache.maven.scm.command.checkin.CheckInScmResult;
043    import org.apache.maven.scm.command.checkout.CheckOutScmResult;
044    import org.apache.maven.scm.command.export.ExportScmResult;
045    import org.apache.maven.scm.command.login.LoginScmResult;
046    import org.apache.maven.scm.command.remove.RemoveScmResult;
047    import org.apache.maven.scm.command.status.StatusScmResult;
048    import org.apache.maven.scm.command.tag.TagScmResult;
049    import org.apache.maven.scm.command.update.UpdateScmResult;
050    import org.apache.maven.scm.provider.AbstractScmProvider;
051    import org.apache.maven.scm.provider.ScmProviderRepository;
052    import org.apache.maven.scm.provider.accurev.cli.AccuRevCommandLine;
053    import org.apache.maven.scm.provider.accurev.command.add.AccuRevAddCommand;
054    import org.apache.maven.scm.provider.accurev.command.blame.AccuRevBlameCommand;
055    import org.apache.maven.scm.provider.accurev.command.changelog.AccuRevChangeLogCommand;
056    import org.apache.maven.scm.provider.accurev.command.checkin.AccuRevCheckInCommand;
057    import org.apache.maven.scm.provider.accurev.command.checkout.AccuRevCheckOutCommand;
058    import org.apache.maven.scm.provider.accurev.command.export.AccuRevExportCommand;
059    import org.apache.maven.scm.provider.accurev.command.login.AccuRevLoginCommand;
060    import org.apache.maven.scm.provider.accurev.command.remove.AccuRevRemoveCommand;
061    import org.apache.maven.scm.provider.accurev.command.status.AccuRevStatusCommand;
062    import org.apache.maven.scm.provider.accurev.command.tag.AccuRevTagCommand;
063    import org.apache.maven.scm.provider.accurev.command.update.AccuRevUpdateCommand;
064    import org.apache.maven.scm.provider.accurev.command.update.AccuRevUpdateScmResult;
065    import org.apache.maven.scm.provider.accurev.util.QuotedPropertyParser;
066    import org.apache.maven.scm.repository.ScmRepositoryException;
067    import org.apache.maven.scm.repository.UnknownRepositoryStructure;
068    import org.codehaus.plexus.util.StringUtils;
069    
070    /**
071     * AccuRev integration with Maven SCM
072     * 
073     * @plexus.component role="org.apache.maven.scm.provider.ScmProvider" role-hint="accurev"
074     */
075    public class AccuRevScmProvider
076        extends AbstractScmProvider
077    {
078    
079        public static final String ACCUREV_EXECUTABLE_PROPERTY = "accurevExe";
080    
081        public static final String TAG_FORMAT_PROPERTY = "tagFormat";
082    
083        public static final String SYSTEM_PROPERTY_PREFIX = "maven.scm.accurev.";
084    
085        public String getScmType()
086        {
087    
088            return "accurev";
089        }
090    
091        /**
092         * The basic url parsing approach is to be as loose as possible. If you specify as per the docs you'll get what you
093         * expect. If you do something else the result is undefined. Don't use "/" "\" or "@" as the delimiter,
094         */
095        public ScmProviderRepository makeProviderScmRepository( String scmSpecificUrl, char delimiter )
096            throws ScmRepositoryException
097        {
098    
099            List<String> validationMessages = new ArrayList<String>();
100    
101            String[] tokens = StringUtils.split( scmSpecificUrl, Character.toString( delimiter ) );
102    
103            // [[user][/pass]@host[:port]][:stream][:\project\dir]
104    
105            String basisStream = null;
106            String projectPath = null;
107            int port = AccuRev.DEFAULT_PORT;
108            String host = null;
109            String user = null;
110            String password = null;
111            Map<String, String> properties = new HashMap<String, String>();
112            properties.put( TAG_FORMAT_PROPERTY, AccuRevScmProviderRepository.DEFAULT_TAG_FORMAT );
113            properties.put( ACCUREV_EXECUTABLE_PROPERTY, AccuRev.DEFAULT_ACCUREV_EXECUTABLE );
114    
115            fillSystemProperties( properties );
116    
117            int i = 0;
118            while ( i < tokens.length )
119            {
120                int at = tokens[i].indexOf( '@' );
121                // prefer "/", better not have a "/" or a "\\" in your password.
122                int slash = tokens[i].indexOf( '/' );
123                slash = slash < 0 ? tokens[i].indexOf( '\\' ) : slash;
124    
125                int qMark = tokens[i].indexOf( '?' );
126    
127                if ( qMark == 0 )
128                {
129                    QuotedPropertyParser.parse( tokens[i].substring( 1 ), properties );
130                }
131                else if ( slash == 0 )
132                {
133                    // this is the project path
134                    projectPath = tokens[i].substring( 1 );
135                    break;
136                }
137                else if ( ( slash > 0 || ( at >= 0 ) ) && host == null && user == null )
138                {
139                    // user/pass@host
140                    int len = tokens[i].length();
141                    if ( at >= 0 && len > at )
142                    {
143                        // everything after the "@"
144                        host = tokens[i].substring( at + 1 );
145                    }
146    
147                    if ( slash > 0 )
148                    {
149                        // user up to /
150                        user = tokens[i].substring( 0, slash );
151                        // pass between / and @
152                        password = tokens[i].substring( slash + 1, at < 0 ? len : at );
153                    }
154                    else
155                    {
156                        // no /, user from beginning to @
157                        user = tokens[i].substring( 0, at < 0 ? len : at );
158                    }
159    
160                }
161                else if ( host != null && tokens[i].matches( "^[0-9]+$" ) )
162                {
163                    // only valid entry with all digits is the port specification.
164                    port = Integer.parseInt( tokens[i] );
165                }
166                else
167                {
168                    basisStream = tokens[i];
169                }
170    
171                i++;
172            }
173    
174            if ( i < tokens.length )
175            {
176                validationMessages.add( "Unknown tokens in URL " + scmSpecificUrl );
177            }
178    
179            AccuRevScmProviderRepository repo = new AccuRevScmProviderRepository();
180            repo.setLogger( getLogger() );
181            if ( !StringUtils.isEmpty( user ) )
182            {
183                repo.setUser( user );
184            }
185            if ( !StringUtils.isEmpty( password ) )
186            {
187                repo.setPassword( password );
188            }
189            if ( !StringUtils.isEmpty( basisStream ) )
190            {
191                repo.setStreamName( basisStream );
192            }
193            if ( !StringUtils.isEmpty( projectPath ) )
194            {
195                repo.setProjectPath( projectPath );
196            }
197            if ( !StringUtils.isEmpty( host ) )
198            {
199                repo.setHost( host );
200            }
201            repo.setPort( port );
202            repo.setTagFormat( properties.get( TAG_FORMAT_PROPERTY ) );
203    
204            AccuRevCommandLine accuRev = new AccuRevCommandLine( host, port );
205            accuRev.setLogger( getLogger() );
206            accuRev.setExecutable( properties.get( ACCUREV_EXECUTABLE_PROPERTY ) );
207            repo.setAccuRev( accuRev );
208    
209            return repo;
210    
211        }
212    
213        private void fillSystemProperties( Map<String, String> properties )
214        {
215    
216            Set<String> propertyKeys = properties.keySet();
217            for ( String key : propertyKeys )
218            {
219                String systemPropertyKey = SYSTEM_PROPERTY_PREFIX + key;
220                String systemProperty = System.getProperty( systemPropertyKey );
221                if ( systemProperty != null )
222                {
223                    properties.put( key, systemProperty );
224                }
225            }
226    
227        }
228    
229        @Override
230        protected LoginScmResult login( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
231            throws ScmException
232        {
233    
234            if ( getLogger().isDebugEnabled() )
235            {
236                getLogger().debug( repository.toString() );
237            }
238    
239            AccuRevLoginCommand command = new AccuRevLoginCommand( getLogger() );
240            return command.login( repository, fileSet, parameters );
241        }
242    
243        @Override
244        protected CheckOutScmResult checkout( ScmProviderRepository repository, ScmFileSet fileSet,
245                                              CommandParameters parameters )
246            throws ScmException
247        {
248    
249            // workaround deprecated behaviour
250            // TODO pull up to AbstractScmProvider
251            AccuRevScmProviderRepository accuRevRepo = (AccuRevScmProviderRepository) repository;
252            if ( !repository.isPersistCheckout() && accuRevRepo.shouldUseExportForNonPersistentCheckout() )
253            {
254    
255                ExportScmResult result = export( repository, fileSet, parameters );
256                if ( result.isSuccess() )
257                {
258                    return new CheckOutScmResult( result.getCommandLine(), result.getExportedFiles(),
259                                                  accuRevRepo.getExportRelativePath() );
260                }
261                else
262                {
263                    return new CheckOutScmResult( result.getCommandLine(), result.getProviderMessage(),
264                                                  result.getCommandOutput(), false );
265                }
266            }
267    
268            AccuRevCheckOutCommand command = new AccuRevCheckOutCommand( getLogger() );
269    
270            return command.checkout( repository, fileSet, parameters );
271    
272        }
273    
274        @Override
275        protected CheckInScmResult checkin( ScmProviderRepository repository, ScmFileSet fileSet,
276                                            CommandParameters parameters )
277            throws ScmException
278        {
279    
280            AccuRevCheckInCommand command = new AccuRevCheckInCommand( getLogger() );
281    
282            return command.checkIn( repository, fileSet, parameters );
283        }
284    
285        @Override
286        public ScmProviderRepository makeProviderScmRepository( File path )
287            throws ScmRepositoryException, UnknownRepositoryStructure
288        {
289    
290            // TODO: accurev info with current dir = "path", find workspace. Find use-case for this.
291            return super.makeProviderScmRepository( path );
292        }
293    
294        @Override
295        public AddScmResult add( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
296            throws ScmException
297        {
298            AccuRevAddCommand command = new AccuRevAddCommand( getLogger() );
299            return command.add( repository, fileSet, parameters );
300        }
301    
302        @Override
303        protected TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
304            throws ScmException
305        {
306    
307            AccuRevTagCommand command = new AccuRevTagCommand( getLogger() );
308            return command.tag( repository, fileSet, parameters );
309    
310        }
311    
312        @Override
313        protected StatusScmResult status( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
314            throws ScmException
315        {
316    
317            AccuRevStatusCommand command = new AccuRevStatusCommand( getLogger() );
318            return command.status( repository, fileSet, parameters );
319    
320        }
321    
322        @Override
323        protected UpdateScmResult update( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
324            throws ScmException
325        {
326    
327            AccuRevScmProviderRepository accurevRepo = (AccuRevScmProviderRepository) repository;
328    
329            AccuRevUpdateCommand command = new AccuRevUpdateCommand( getLogger() );
330    
331            UpdateScmResult result = command.update( repository, fileSet, parameters );
332    
333            if ( result.isSuccess() && parameters.getBoolean( CommandParameter.RUN_CHANGELOG_WITH_UPDATE ) )
334            {
335                AccuRevUpdateScmResult accuRevResult = (AccuRevUpdateScmResult) result;
336    
337                ScmRevision fromRevision = new ScmRevision( accuRevResult.getFromRevision() );
338                ScmRevision toRevision = new ScmRevision( accuRevResult.getToRevision() );
339    
340                parameters.setScmVersion( CommandParameter.START_SCM_VERSION, fromRevision );
341                parameters.setScmVersion( CommandParameter.END_SCM_VERSION, toRevision );
342    
343                AccuRevVersion startVersion = accurevRepo.getAccuRevVersion( fromRevision );
344                AccuRevVersion endVersion = accurevRepo.getAccuRevVersion( toRevision );
345                if ( startVersion.getBasisStream().equals( endVersion.getBasisStream() ) )
346                {
347                    ChangeLogScmResult changeLogResult = changelog( repository, fileSet, parameters );
348    
349                    if ( changeLogResult.isSuccess() )
350                    {
351                        result.setChanges( changeLogResult.getChangeLog().getChangeSets() );
352                    }
353                    else
354                    {
355                        getLogger().warn( "Changelog from " + fromRevision + " to " + toRevision + " failed" );
356                    }
357                }
358                else
359                {
360                    String comment = "Cross stream update result from " + startVersion + " to " + endVersion;
361                    String author = "";
362                    List<ScmFile> files = result.getUpdatedFiles();
363                    List<ChangeFile> changeFiles = new ArrayList<ChangeFile>( files.size() );
364                    for (ScmFile scmFile : files)
365                    {
366                        changeFiles.add(new ChangeFile( scmFile.getPath() ));
367                    }
368                    ChangeSet dummyChangeSet = new ChangeSet( new Date(), comment, author, changeFiles );
369                    // different streams invalidates the change log, insert a dummy change instead.
370                    List<ChangeSet> changeSets = Collections.singletonList( dummyChangeSet );
371                    result.setChanges( changeSets );
372                }
373    
374            }
375            return result;
376        }
377    
378        @Override
379        protected ExportScmResult export( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
380            throws ScmException
381        {
382    
383            AccuRevExportCommand command = new AccuRevExportCommand( getLogger() );
384            return command.export( repository, fileSet, parameters );
385        }
386    
387        @Override
388        protected ChangeLogScmResult changelog( ScmProviderRepository repository, ScmFileSet fileSet,
389                                                CommandParameters parameters )
390            throws ScmException
391        {
392    
393            AccuRevChangeLogCommand command = new AccuRevChangeLogCommand( getLogger() );
394            return command.changelog( repository, fileSet, parameters );
395        }
396    
397        @Override
398        protected RemoveScmResult remove( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
399            throws ScmException
400        {
401    
402            AccuRevRemoveCommand command = new AccuRevRemoveCommand( getLogger() );
403            return command.remove( repository, fileSet, parameters );
404        }
405    
406        /** {@inheritDoc} */
407        @Override
408        protected BlameScmResult blame( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
409            throws ScmException
410        {
411    
412            AccuRevBlameCommand blameCommand = new AccuRevBlameCommand( getLogger() );
413            return blameCommand.blame( repository, fileSet, parameters );
414        }
415    }