001    package 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    
022    import java.io.ByteArrayInputStream;
023    import java.io.File;
024    import java.io.InputStream;
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.regex.Pattern;
032    
033    import org.apache.maven.scm.command.blame.BlameLine;
034    import org.apache.maven.scm.log.ScmLogger;
035    import org.apache.maven.scm.provider.accurev.AccuRev;
036    import org.apache.maven.scm.provider.accurev.AccuRevException;
037    import org.apache.maven.scm.provider.accurev.AccuRevInfo;
038    import org.apache.maven.scm.provider.accurev.AccuRevStat;
039    import org.apache.maven.scm.provider.accurev.AccuRevVersion;
040    import org.apache.maven.scm.provider.accurev.CategorisedElements;
041    import org.apache.maven.scm.provider.accurev.FileDifference;
042    import org.apache.maven.scm.provider.accurev.Stream;
043    import org.apache.maven.scm.provider.accurev.Transaction;
044    import org.apache.maven.scm.provider.accurev.WorkSpace;
045    import org.codehaus.plexus.util.Os;
046    import org.codehaus.plexus.util.StringUtils;
047    import org.codehaus.plexus.util.cli.CommandLineException;
048    import org.codehaus.plexus.util.cli.CommandLineUtils;
049    import org.codehaus.plexus.util.cli.Commandline;
050    import org.codehaus.plexus.util.cli.StreamConsumer;
051    
052    public 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    }