001package org.apache.maven.scm.provider.accurev.cli;
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.ByteArrayInputStream;
023import java.io.File;
024import java.io.InputStream;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.regex.Pattern;
032
033import org.apache.maven.scm.command.blame.BlameLine;
034import org.apache.maven.scm.log.ScmLogger;
035import org.apache.maven.scm.provider.accurev.AccuRev;
036import org.apache.maven.scm.provider.accurev.AccuRevException;
037import org.apache.maven.scm.provider.accurev.AccuRevInfo;
038import org.apache.maven.scm.provider.accurev.AccuRevStat;
039import org.apache.maven.scm.provider.accurev.AccuRevVersion;
040import org.apache.maven.scm.provider.accurev.CategorisedElements;
041import org.apache.maven.scm.provider.accurev.FileDifference;
042import org.apache.maven.scm.provider.accurev.Stream;
043import org.apache.maven.scm.provider.accurev.Transaction;
044import org.apache.maven.scm.provider.accurev.WorkSpace;
045import org.codehaus.plexus.util.Os;
046import org.codehaus.plexus.util.StringUtils;
047import org.codehaus.plexus.util.cli.CommandLineException;
048import org.codehaus.plexus.util.cli.CommandLineUtils;
049import org.codehaus.plexus.util.cli.Commandline;
050import org.codehaus.plexus.util.cli.StreamConsumer;
051
052public class AccuRevCommandLine
053    implements AccuRev
054{
055
056    private static final String[] EMPTY_STRING_ARRAY = new String[] {};
057
058    private static final File CURRENT_DIR = new File( "." );
059
060    private ScmLogger logger;
061
062    private Commandline cl = new Commandline();
063
064    private StringBuilder commandLines = new StringBuilder();
065
066    private StringBuilder errorOutput = new StringBuilder();
067
068    private StreamConsumer systemErr;
069
070    private String[] hostArgs = EMPTY_STRING_ARRAY;
071
072    private String[] authArgs = EMPTY_STRING_ARRAY;
073
074    private String executable = "accurev";
075
076    private long executableModTime;
077
078    private String clientVersion;
079
080    public AccuRevCommandLine()
081    {
082        super();
083        reset();
084    }
085
086    public AccuRevCommandLine( String host, int port )
087    {
088        this();
089        setServer( host, port );
090    }
091
092    public void setServer( String host, int port )
093    {
094
095        if ( host != null )
096        {
097            hostArgs = new String[] { "-H", host + ":" + port };
098        }
099        else
100        {
101            hostArgs = EMPTY_STRING_ARRAY;
102        }
103
104    }
105
106    public void setExecutable( String accuRevExe )
107    {
108
109        executable = accuRevExe;
110        reset();
111    }
112
113    private boolean executeCommandLine( File basedir, String[] args, Iterable<File> elements, Pattern matchPattern,
114                                        List<File> matchedFiles )
115        throws AccuRevException
116    {
117
118        FileConsumer stdoutConsumer = new FileConsumer( matchedFiles, matchPattern );
119
120        return executeCommandLine( basedir, args, elements, stdoutConsumer );
121    }
122
123    private boolean executeCommandLine( File basedir, String[] args, Iterable<File> elements,
124                                        StreamConsumer stdoutConsumer )
125        throws AccuRevException
126    {
127
128        setWorkingDirectory( basedir );
129        setCommandLineArgs( args );
130
131        if ( elements != null )
132        {
133            for ( File file : elements )
134            {
135                String path = file.getPath();
136                // Hack for Windows "/./". TODO find a nicer way to handle this.
137                if ( "\\.".equals( path ) )
138                {
139                    path = "\\.\\";
140                }
141                cl.createArg().setValue( path );
142            }
143        }
144        return executeCommandLine( null, stdoutConsumer ) == 0;
145    }
146
147    private void setCommandLineArgs( String[] args )
148    {
149
150        cl.clearArgs();
151
152        if ( args.length > 0 )
153        {
154            // First arg is the accurev command
155            cl.createArg().setValue( args[0] );
156
157            // Inject -H <host:port> and -A <token> here
158            cl.addArguments( hostArgs );
159            cl.addArguments( authArgs );
160        }
161
162        for ( int i = 1; i < args.length; i++ )
163        {
164            cl.createArg().setValue( args[i] );
165        }
166
167    }
168
169    private boolean executeCommandLine( String[] args )
170        throws AccuRevException
171    {
172
173        return executeCommandLine( args, null, null ) == 0;
174    }
175
176    private int executeCommandLine( String[] args, InputStream stdin, StreamConsumer stdout )
177        throws AccuRevException
178    {
179
180        setCommandLineArgs( args );
181
182        return executeCommandLine( stdin, stdout );
183
184    }
185
186    private int executeCommandLine( InputStream stdin, StreamConsumer stdout )
187        throws AccuRevException
188    {
189
190        commandLines.append( cl.toString() );
191        commandLines.append( ';' );
192
193        if ( getLogger().isDebugEnabled() )
194        {
195            getLogger().debug( cl.toString() );
196        }
197        try
198        {
199
200            int result = executeCommandLine( cl, stdin, new CommandOutputConsumer( getLogger(), stdout ), systemErr );
201            if ( result != 0 )
202            {
203                getLogger().debug( "Non zero result - " + result );
204            }
205            return result;
206        }
207        catch ( CommandLineException ex )
208        {
209            throw new AccuRevException( "Error executing command " + cl.toString(), ex );
210        }
211
212    }
213
214    /**
215     * Extracted so test class can override
216     * 
217     * @param stdin
218     * @param stdout
219     * @param stderr
220     * @return
221     * @throws CommandLineException
222     */
223    protected int executeCommandLine( Commandline cl, InputStream stdin, CommandOutputConsumer stdout,
224                                      StreamConsumer stderr )
225        throws CommandLineException
226    {
227
228        int result = CommandLineUtils.executeCommandLine( cl, stdin, stdout, stderr );
229        stdout.waitComplete();
230
231        return result;
232    }
233
234    protected Commandline getCommandline()
235    {
236
237        return cl;
238    }
239
240    public void reset()
241    {
242
243        // TODO find out why Commandline allows executable, args etc to be initialised to
244        // null, but not allowing them to be reset to null. This results is weird "clear"
245        // behaviour. It is just safer to start again.
246
247        cl = new Commandline();
248        commandLines = new StringBuilder();
249        errorOutput = new StringBuilder();
250        systemErr = new ErrorConsumer( getLogger(), errorOutput );
251        cl.getShell().setQuotedArgumentsEnabled( true );
252        cl.setExecutable( executable );
253
254        try
255        {
256            cl.addSystemEnvironment();
257        }
258        catch ( Exception e )
259        {
260            if ( getLogger().isDebugEnabled() )
261            {
262                getLogger().debug( "Unable to obtain system environment", e );
263            }
264            else
265            {
266                getLogger().warn( "Unable to obtain system environment" );
267            }
268        }
269    }
270
271    /**
272     * {@inheritDoc}
273     */
274    public boolean mkws( String basisStream, String workspaceName, File basedir )
275        throws AccuRevException
276    {
277
278        setWorkingDirectory( basedir );
279        String[] mkws = { "mkws", "-b", basisStream, "-w", workspaceName, "-l", basedir.getAbsolutePath() };
280
281        return executeCommandLine( mkws );
282    }
283
284    /**
285     * {@inheritDoc}
286     */
287    public List<File> update( File baseDir, String transactionId )
288        throws AccuRevException
289    {
290
291        if ( transactionId == null )
292        {
293            transactionId = "highest";
294        }
295        String[] update = { "update", "-t", transactionId };
296        setWorkingDirectory( baseDir );
297
298        List<File> updatedFiles = new ArrayList<File>();
299        return executeCommandLine( update, null, new FileConsumer( updatedFiles, FileConsumer.UPDATE_PATTERN ) ) == 0 ? updatedFiles
300                        : null;
301
302    }
303
304    /**
305     * {@inheritDoc}
306     */
307    public List<File> add( File basedir, List<File> elements, String message )
308        throws AccuRevException
309    {
310
311        if ( StringUtils.isBlank( message ) )
312        {
313            message = AccuRev.DEFAULT_ADD_MESSAGE;
314        }
315
316        boolean recursive = false;
317
318        if ( elements == null || elements.isEmpty() )
319        {
320            elements = Collections.singletonList( CURRENT_DIR );
321            recursive = true;
322        }
323        else if ( elements.size() == 1 && elements.toArray()[0].equals( CURRENT_DIR ) )
324        {
325            recursive = true;
326        }
327
328        List<File> addedFiles = new ArrayList<File>();
329        return executeCommandLine( basedir, new String[] { "add", "-c", message, recursive ? "-R" : null }, elements,
330                                   FileConsumer.ADD_PATTERN, addedFiles ) ? addedFiles : null;
331
332    }
333
334    public List<File> defunct( File basedir, List<File> files, String message )
335        throws AccuRevException
336    {
337
338        if ( StringUtils.isBlank( message ) )
339        {
340            message = AccuRev.DEFAULT_REMOVE_MESSAGE;
341        }
342
343        if ( files == null || files.isEmpty() )
344        {
345            files = Collections.singletonList( CURRENT_DIR );
346        }
347
348        ArrayList<File> defunctFiles = new ArrayList<File>();
349        return executeCommandLine( basedir, new String[] { "defunct", "-c", message }, files,
350                                   FileConsumer.DEFUNCT_PATTERN, defunctFiles ) ? defunctFiles : null;
351    }
352
353    public List<File> promote( File basedir, List<File> files, String message )
354        throws AccuRevException
355    {
356
357        if ( StringUtils.isBlank( message ) )
358        {
359            message = AccuRev.DEFAULT_PROMOTE_MESSAGE;
360        }
361        List<File> promotedFiles = new ArrayList<File>();
362        return executeCommandLine( basedir, new String[] { "promote", "-K", "-c", message }, files,
363                                   FileConsumer.PROMOTE_PATTERN, promotedFiles ) ? promotedFiles : null;
364
365    }
366
367    public String getCommandLines()
368    {
369
370        return commandLines.toString();
371    }
372
373    public String getErrorOutput()
374    {
375
376        return errorOutput.toString();
377    }
378
379    public void setLogger( ScmLogger logger )
380    {
381        this.logger = logger;
382    }
383
384    public ScmLogger getLogger()
385    {
386
387        return logger;
388    }
389
390    public boolean mkdepot( String depotName )
391        throws AccuRevException
392    {
393
394        String[] mkdepot = { "mkdepot", "-p", depotName };
395
396        return executeCommandLine( mkdepot );
397
398    }
399
400    public boolean mkstream( String backingStream, String newStreamName )
401        throws AccuRevException
402    {
403        String[] mkstream = { "mkstream", "-b", backingStream, "-s", newStreamName };
404        return executeCommandLine( mkstream );
405
406    }
407
408    public boolean promoteStream( String subStream, String commitMessage, List<File> promotedFiles )
409        throws AccuRevException
410    {
411        String[] promote = { "promote", "-s", subStream, "-d" };
412        return executeCommandLine( promote );
413
414    }
415
416    /**
417     * {@inheritDoc}
418     */
419    public List<File> promoteAll( File baseDir, String commitMessage )
420        throws AccuRevException
421    {
422
423        setWorkingDirectory( baseDir );
424        String[] promote = { "promote", "-p", "-K", "-c", commitMessage };
425
426        List<File> promotedFiles = new ArrayList<File>();
427        return executeCommandLine( promote, null, new FileConsumer( promotedFiles, FileConsumer.PROMOTE_PATTERN ) ) == 0 ? promotedFiles
428                        : null;
429
430    }
431
432    public AccuRevInfo info( File basedir )
433        throws AccuRevException
434    {
435
436        setWorkingDirectory( basedir );
437        String[] info = { "info" };
438        AccuRevInfo result = new AccuRevInfo( basedir );
439
440        executeCommandLine( info, null, new InfoConsumer( result ) );
441        return result;
442    }
443
444    private void setWorkingDirectory( File basedir )
445    {
446
447        // TODO raise bug against plexus. Null is OK for working directory
448        // but once set to not-null cannot be set back to null!
449        // this is a problem if the old workingdir has been deleted
450        // probably safer to use a new commandline
451
452        if ( basedir == null )
453        {
454            cl.setWorkingDirectory( "." );
455        }
456        cl.setWorkingDirectory( basedir );
457    }
458
459    public boolean reactivate( String workSpaceName )
460        throws AccuRevException
461    {
462
463        String[] reactivate = { "reactivate", "wspace", workSpaceName };
464
465        return executeCommandLine( reactivate, null, new CommandOutputConsumer( getLogger(), null ) ) == 0;
466
467    }
468
469    public boolean rmws( String workSpaceName )
470        throws AccuRevException
471    {
472
473        String[] rmws = { "rmws", "-s", workSpaceName };
474
475        return executeCommandLine( rmws );
476
477    }
478
479    public String stat( File element )
480        throws AccuRevException
481    {
482
483        String[] stat = { "stat", "-fx", element.getAbsolutePath() };
484
485        StatConsumer statConsumer = new StatConsumer( getLogger() );
486        executeCommandLine( stat, null, statConsumer );
487        return statConsumer.getStatus();
488
489    }
490
491    public boolean chws( File basedir, String workSpaceName, String newBasisStream )
492        throws AccuRevException
493    {
494
495        setWorkingDirectory( basedir );
496        return executeCommandLine( new String[] { "chws", "-s", workSpaceName, "-b", newBasisStream, "-l", "." } );
497
498    }
499
500    public boolean mksnap( String snapShotName, String basisStream )
501        throws AccuRevException
502    {
503
504        return executeCommandLine( new String[] { "mksnap", "-s", snapShotName, "-b", basisStream, "-t", "now" } );
505    }
506
507    public List<File> statTag( String streamName )
508        throws AccuRevException
509    {
510
511        List<File> taggedFiles = new ArrayList<File>();
512        String[] stat = new String[] { "stat", "-a", "-ffl", "-s", streamName };
513        return executeCommandLine( null, stat, null, FileConsumer.STAT_PATTERN, taggedFiles ) ? taggedFiles : null;
514    }
515
516    public List<File> stat( File basedir, Collection<File> elements, AccuRevStat statType )
517        throws AccuRevException
518    {
519
520        boolean recursive = false;
521
522        if ( elements == null || elements.isEmpty() )
523        {
524            elements = Collections.singletonList( CURRENT_DIR );
525            recursive = true;
526        }
527        else if ( elements.size() == 1 && elements.toArray()[0].equals( CURRENT_DIR ) )
528        {
529            recursive = true;
530        }
531
532        String[] args = { "stat", "-ffr", statType.getStatArg(), recursive ? "-R" : null };
533
534        List<File> matchingElements = new ArrayList<File>();
535        return executeCommandLine( basedir, args, elements, statType.getMatchPattern(), matchingElements ) ? matchingElements
536                        : null;
537    }
538
539    public List<File> pop( File basedir, Collection<File> elements )
540        throws AccuRevException
541    {
542
543        if ( elements == null || elements.isEmpty() )
544        {
545            elements = Collections.singletonList( CURRENT_DIR );
546        }
547
548        String[] popws = { "pop", "-R" };
549
550        List<File> poppedFiles = new ArrayList<File>();
551        return executeCommandLine( basedir, popws, elements, FileConsumer.POPULATE_PATTERN, poppedFiles ) ? poppedFiles
552                        : null;
553    }
554
555    public List<File> popExternal( File basedir, String versionSpec, String tranSpec, Collection<File> elements )
556        throws AccuRevException
557    {
558
559        if ( elements == null || elements.isEmpty() )
560        {
561            elements = Collections.singletonList( new File( "/./" ) );
562        }
563
564        if ( StringUtils.isBlank( tranSpec ) )
565        {
566            tranSpec = "now";
567        }
568
569        String[] popArgs;
570        if ( AccuRevVersion.isNow( tranSpec ) )
571        {
572            popArgs = new String[] { "pop", "-v", versionSpec, "-L", basedir.getAbsolutePath(), "-R" };
573        }
574        else
575        // this will BARF for pre 4.9.0, but clients are expected to check AccuRevCapability before calling.
576        {
577            popArgs = new String[] { "pop", "-v", versionSpec, "-L", basedir.getAbsolutePath(), "-t", tranSpec, "-R" };
578        }
579
580        List<File> poppedFiles = new ArrayList<File>();
581        return executeCommandLine( basedir, popArgs, elements, FileConsumer.POPULATE_PATTERN, poppedFiles ) ? poppedFiles
582                        : null;
583    }
584
585    public CategorisedElements statBackingStream( File basedir, Collection<File> elements )
586        throws AccuRevException
587    {
588
589        CategorisedElements catElems = new CategorisedElements();
590
591        if ( elements.isEmpty() )
592        {
593            return catElems;
594        }
595        String[] args = { "stat", "-b", "-ffr" };
596
597        return executeCommandLine( basedir, args, elements, new StatBackingConsumer( catElems.getMemberElements(),
598                                                                                     catElems.getNonMemberElements() ) ) ? catElems
599                        : null;
600
601    }
602
603    public List<Transaction> history( String baseStream, String fromTimeSpec, String toTimeSpec, int count,
604                                      boolean depotHistory, boolean transactionsOnly )
605        throws AccuRevException
606    {
607
608        String timeSpec = fromTimeSpec;
609
610        if ( toTimeSpec != null )
611        {
612            timeSpec = timeSpec + "-" + toTimeSpec;
613        }
614
615        if ( count > 0 )
616        {
617            timeSpec = timeSpec + "." + count;
618        }
619
620        String[] hist =
621            { "hist", transactionsOnly ? "-ftx" : "-fx", depotHistory ? "-p" : "-s", baseStream, "-t", timeSpec };
622
623        ArrayList<Transaction> transactions = new ArrayList<Transaction>();
624        HistoryConsumer stdout = new HistoryConsumer( getLogger(), transactions );
625        return executeCommandLine( hist, null, stdout ) == 0 ? transactions : null;
626    }
627
628    public List<FileDifference> diff( String baseStream, String fromTimeSpec, String toTimeSpec )
629        throws AccuRevException
630    {
631        String timeSpec = fromTimeSpec + "-" + toTimeSpec;
632        String[] diff = { "diff", "-fx", "-a", "-i", "-v", baseStream, "-V", baseStream, "-t", timeSpec };
633
634        List<FileDifference> results = new ArrayList<FileDifference>();
635        DiffConsumer stdout = new DiffConsumer( getLogger(), results );
636        return executeCommandLine( diff, null, stdout ) < 2 ? results : null;
637    }
638
639    public boolean login( String user, String password )
640        throws AccuRevException
641    {
642
643        // TODO Raise bug against plexus commandline - can't set workingdir to null
644        // and will get an error if the working directory is deleted.
645        cl.setWorkingDirectory( "." );
646        authArgs = EMPTY_STRING_ARRAY;
647        AuthTokenConsumer stdout = new AuthTokenConsumer();
648
649        boolean result;
650        if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
651        {
652            if ( StringUtils.isBlank( password ) )
653            {
654                // Ensure blank is passed in.
655                password = "\"\"";
656            }
657            String[] login = { "login", "-A", user, password };
658            result = executeCommandLine( login, null, stdout ) == 0;
659        }
660        else
661        {
662            String[] login = { "login", "-A", user };
663            password = StringUtils.clean( password ) + "\n";
664            byte[] bytes = password.getBytes();
665            ByteArrayInputStream stdin = new ByteArrayInputStream( bytes );
666            result = executeCommandLine( login, stdin, stdout ) == 0;
667
668        }
669
670        authArgs = new String[] { "-A", stdout.getAuthToken() };
671        return result;
672    }
673
674    public boolean logout()
675        throws AccuRevException
676    {
677
678        String[] logout = { "logout" };
679        return executeCommandLine( logout );
680
681    }
682
683    public List<BlameLine> annotate( File basedir, File file )
684        throws AccuRevException
685    {
686
687        String[] annotate = { "annotate", "-ftud" };
688        List<BlameLine> lines = new ArrayList<BlameLine>();
689        AnnotateConsumer stdout = new AnnotateConsumer( lines, getLogger() );
690
691        return executeCommandLine( basedir, annotate, Collections.singletonList( file ), stdout ) ? lines : null;
692    }
693
694    public Map<String, WorkSpace> showRefTrees()
695        throws AccuRevException
696    {
697
698        String[] show = { "show", "-fx", "refs" };
699        Map<String, WorkSpace> refTrees = new HashMap<String, WorkSpace>();
700        WorkSpaceConsumer stdout = new WorkSpaceConsumer( getLogger(), refTrees );
701        return executeCommandLine( show, null, stdout ) == 0 ? refTrees : null;
702    }
703
704    public Map<String, WorkSpace> showWorkSpaces()
705        throws AccuRevException
706    {
707
708        String[] show = { "show", "-a", "-fx", "wspaces" };
709        Map<String, WorkSpace> workSpaces = new HashMap<String, WorkSpace>();
710        WorkSpaceConsumer stdout = new WorkSpaceConsumer( getLogger(), workSpaces );
711        return executeCommandLine( show, null, stdout ) == 0 ? workSpaces : null;
712    }
713
714    public Stream showStream( String stream )
715        throws AccuRevException
716    {
717        String[] show = { "show", "-s", stream, "-fx", "streams" };
718        List<Stream> streams = new ArrayList<Stream>();
719        StreamsConsumer stdout = new StreamsConsumer( getLogger(), streams );
720
721        return executeCommandLine( show, null, stdout ) == 0 && streams.size() == 1 ? streams.get( 0 ) : null;
722    }
723
724    public String getExecutable()
725    {
726
727        return executable;
728    }
729
730    public String getClientVersion()
731        throws AccuRevException
732    {
733
734        long lastModified = new File( getExecutable() ).lastModified();
735        if ( clientVersion == null || executableModTime != lastModified )
736        {
737            executableModTime = lastModified;
738
739            ClientVersionConsumer stdout = new ClientVersionConsumer();
740            executeCommandLine( new String[] {}, null, stdout );
741            clientVersion = stdout.getClientVersion();
742        }
743        return clientVersion;
744
745    }
746
747    public boolean syncReplica()
748        throws AccuRevException
749    {
750        return executeCommandLine( new String[] { "replica", "sync" } );
751    }
752
753}