View Javadoc
1   package org.apache.maven.scm.provider.accurev.cli;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.File;
24  import java.io.InputStream;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.regex.Pattern;
32  
33  import org.apache.maven.scm.command.blame.BlameLine;
34  import org.apache.maven.scm.log.ScmLogger;
35  import org.apache.maven.scm.provider.accurev.AccuRev;
36  import org.apache.maven.scm.provider.accurev.AccuRevException;
37  import org.apache.maven.scm.provider.accurev.AccuRevInfo;
38  import org.apache.maven.scm.provider.accurev.AccuRevStat;
39  import org.apache.maven.scm.provider.accurev.AccuRevVersion;
40  import org.apache.maven.scm.provider.accurev.CategorisedElements;
41  import org.apache.maven.scm.provider.accurev.FileDifference;
42  import org.apache.maven.scm.provider.accurev.Stream;
43  import org.apache.maven.scm.provider.accurev.Transaction;
44  import org.apache.maven.scm.provider.accurev.WorkSpace;
45  import org.codehaus.plexus.util.Os;
46  import org.codehaus.plexus.util.StringUtils;
47  import org.codehaus.plexus.util.cli.CommandLineException;
48  import org.codehaus.plexus.util.cli.CommandLineUtils;
49  import org.codehaus.plexus.util.cli.Commandline;
50  import org.codehaus.plexus.util.cli.StreamConsumer;
51  
52  public class AccuRevCommandLine
53      implements AccuRev
54  {
55  
56      private static final String[] EMPTY_STRING_ARRAY = new String[] {};
57  
58      private static final File CURRENT_DIR = new File( "." );
59  
60      private ScmLogger logger;
61  
62      private Commandline cl = new Commandline();
63  
64      private StringBuilder commandLines = new StringBuilder();
65  
66      private StringBuilder errorOutput = new StringBuilder();
67  
68      private StreamConsumer systemErr;
69  
70      private String[] hostArgs = EMPTY_STRING_ARRAY;
71  
72      private String[] authArgs = EMPTY_STRING_ARRAY;
73  
74      private String executable = "accurev";
75  
76      private long executableModTime;
77  
78      private String clientVersion;
79  
80      public AccuRevCommandLine()
81      {
82          super();
83          reset();
84      }
85  
86      public AccuRevCommandLine( String host, int port )
87      {
88          this();
89          setServer( host, port );
90      }
91  
92      public void setServer( String host, int port )
93      {
94  
95          if ( host != null )
96          {
97              hostArgs = new String[] { "-H", host + ":" + port };
98          }
99          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 }