001package 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
022import java.io.File;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.Date;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.maven.scm.ChangeFile;
032import org.apache.maven.scm.ChangeSet;
033import org.apache.maven.scm.CommandParameter;
034import org.apache.maven.scm.CommandParameters;
035import org.apache.maven.scm.ScmException;
036import org.apache.maven.scm.ScmFile;
037import org.apache.maven.scm.ScmFileSet;
038import org.apache.maven.scm.ScmRevision;
039import org.apache.maven.scm.command.add.AddScmResult;
040import org.apache.maven.scm.command.blame.BlameScmResult;
041import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
042import org.apache.maven.scm.command.checkin.CheckInScmResult;
043import org.apache.maven.scm.command.checkout.CheckOutScmResult;
044import org.apache.maven.scm.command.export.ExportScmResult;
045import org.apache.maven.scm.command.login.LoginScmResult;
046import org.apache.maven.scm.command.remove.RemoveScmResult;
047import org.apache.maven.scm.command.status.StatusScmResult;
048import org.apache.maven.scm.command.tag.TagScmResult;
049import org.apache.maven.scm.command.update.UpdateScmResult;
050import org.apache.maven.scm.provider.AbstractScmProvider;
051import org.apache.maven.scm.provider.ScmProviderRepository;
052import org.apache.maven.scm.provider.accurev.cli.AccuRevCommandLine;
053import org.apache.maven.scm.provider.accurev.command.add.AccuRevAddCommand;
054import org.apache.maven.scm.provider.accurev.command.blame.AccuRevBlameCommand;
055import org.apache.maven.scm.provider.accurev.command.changelog.AccuRevChangeLogCommand;
056import org.apache.maven.scm.provider.accurev.command.checkin.AccuRevCheckInCommand;
057import org.apache.maven.scm.provider.accurev.command.checkout.AccuRevCheckOutCommand;
058import org.apache.maven.scm.provider.accurev.command.export.AccuRevExportCommand;
059import org.apache.maven.scm.provider.accurev.command.login.AccuRevLoginCommand;
060import org.apache.maven.scm.provider.accurev.command.remove.AccuRevRemoveCommand;
061import org.apache.maven.scm.provider.accurev.command.status.AccuRevStatusCommand;
062import org.apache.maven.scm.provider.accurev.command.tag.AccuRevTagCommand;
063import org.apache.maven.scm.provider.accurev.command.update.AccuRevUpdateCommand;
064import org.apache.maven.scm.provider.accurev.command.update.AccuRevUpdateScmResult;
065import org.apache.maven.scm.provider.accurev.util.QuotedPropertyParser;
066import org.apache.maven.scm.repository.ScmRepositoryException;
067import org.apache.maven.scm.repository.UnknownRepositoryStructure;
068import 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 */
075public 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}