View Javadoc
1   package org.apache.maven.scm.provider.jazz.command;
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 org.apache.maven.scm.ScmException;
23  import org.apache.maven.scm.ScmFileSet;
24  import org.apache.maven.scm.log.ScmLogger;
25  import org.apache.maven.scm.provider.ScmProviderRepository;
26  import org.apache.maven.scm.provider.jazz.command.consumer.ErrorConsumer;
27  import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository;
28  import org.codehaus.plexus.util.Os;
29  import org.codehaus.plexus.util.StringUtils;
30  import org.codehaus.plexus.util.cli.CommandLineException;
31  import org.codehaus.plexus.util.cli.CommandLineUtils;
32  import org.codehaus.plexus.util.cli.Commandline;
33  import org.codehaus.plexus.util.cli.StreamConsumer;
34  
35  import java.io.File;
36  import java.util.Iterator;
37  
38  /**
39   * The base class for the underlying jazz "scm.sh"/"scm.exe" command.
40   * <p>
41   * The SCM command is documented here:
42   * <p>
43   * V2.0.2: http://publib.boulder.ibm.com/infocenter/rtc/v2r0m0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
44   * V3.0:   http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
45   * V3.0.1: http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0m1/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html
46   *
47   * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
48   */
49  public class JazzScmCommand
50  {
51      // The logger to use.
52      private ScmLogger fLogger;
53  
54      // The Commandline that we build up and execute.
55      private Commandline fCommand;
56  
57      /**
58       * Create a JazzScmCommand when no sub-command is needed.
59       */
60      public JazzScmCommand( String cmd, ScmProviderRepository repo, ScmFileSet fileSet, ScmLogger logger )
61      {
62          this( cmd, null, repo, true, fileSet, logger );
63      }
64  
65      /**
66       * Create a JazzScmCommand when a sub-command is needed.
67       * eg: "create snapshot ..."
68       */
69      public JazzScmCommand( String cmd, String subCmd, ScmProviderRepository repo, ScmFileSet fileSet, ScmLogger logger )
70      {
71          this( cmd, subCmd, repo, true, fileSet, logger );
72      }
73  
74      /**
75       * Create a JazzScmCommand, adding the repository-uri as needed.
76       */
77      public JazzScmCommand( String cmd, String subCmd, ScmProviderRepository repo, boolean addRepositoryWorkspaceArg,
78                             ScmFileSet fileSet, ScmLogger logger )
79      {
80          fLogger = logger;
81          fCommand = new Commandline();
82  
83          // TODO This was developed and tested in Windows (in which scm (scm.exe) was the valid executable)
84          // Verify that the executable is valid in other operating systems.
85          fCommand.setExecutable( JazzConstants.SCM_EXECUTABLE );
86  
87          if ( fileSet != null )
88          {
89              fCommand.setWorkingDirectory( fileSet.getBasedir().getAbsolutePath() );
90  
91              // Make the directory, if need be.
92              if ( !fCommand.getWorkingDirectory().exists() )
93              {
94                  boolean success = fCommand.getWorkingDirectory().mkdirs();
95                  if ( !success )
96                  {
97                      // Just log the error, don't throw an error, as it is going to fail anyway.
98                      logErrorMessage( "Working directory did not exist" + " and it couldn't be created: "
99                                           + fCommand.getWorkingDirectory() );
100                 }
101             }
102         }
103 
104         // Add the main command
105         if ( !StringUtils.isEmpty( cmd ) )
106         {
107             addArgument( cmd );
108         }
109 
110         // Add the sub-command if present
111         if ( !StringUtils.isEmpty( subCmd ) )
112         {
113             addArgument( subCmd );
114         }
115 
116         JazzScmProviderRepository jazzRepo = (JazzScmProviderRepository) repo;
117 
118         // Add the repository argument if needed (most commands need this, but not all)
119         if ( addRepositoryWorkspaceArg )
120         {
121             String repositoryWorkspace = jazzRepo.getRepositoryURI();
122             if ( !StringUtils.isEmpty( repositoryWorkspace ) )
123             {
124                 addArgument( JazzConstants.ARG_REPOSITORY_URI );
125                 addArgument( jazzRepo.getRepositoryURI() );
126             }
127         }
128 
129         // Add the username argument
130         // TODO Figure out how we would use the login command / username caching so this is not required on each
131         // command.
132         String user = jazzRepo.getUser();
133         if ( !StringUtils.isEmpty( user ) )
134         {
135             addArgument( JazzConstants.ARG_USER_NAME );
136             addArgument( jazzRepo.getUser() );
137         }
138 
139         // Add the password argument
140         // TODO Figure out how we would use the login command / password caching so this is not required on each
141         // command.
142         String password = jazzRepo.getPassword();
143         if ( !StringUtils.isEmpty( password ) )
144         {
145             addArgument( JazzConstants.ARG_USER_PASSWORD );
146             addArgument( jazzRepo.getPassword() );
147         }
148     }
149 
150     public void addArgument( ScmFileSet fileSet )
151     {
152         logInfoMessage( "files: " + fileSet.getBasedir().getAbsolutePath() );
153         Iterator<File> iter = fileSet.getFileList().iterator();
154         while ( iter.hasNext() )
155         {
156             fCommand.createArg().setValue(  iter.next().getPath() );
157         }
158     }
159 
160     public void addArgument( String arg )
161     {
162         fCommand.createArg().setValue( arg );
163     }
164 
165     public int execute( StreamConsumer out, ErrorConsumer err )
166         throws ScmException
167     {
168         logInfoMessage( "Executing: " + cryptPassword( fCommand ) );
169         if ( fCommand.getWorkingDirectory() != null )
170         {
171             logInfoMessage( "Working directory: " + fCommand.getWorkingDirectory().getAbsolutePath() );
172         }
173 
174         int status = 0;
175         try
176         {
177             status = CommandLineUtils.executeCommandLine( fCommand, out, err );
178         }
179         catch ( CommandLineException e )
180         {
181             String errorOutput = err.getOutput();
182             if ( errorOutput.length() > 0 )
183             {
184                 logErrorMessage( "Error: " + err.getOutput() );
185             }
186             throw new ScmException( "Error while executing Jazz SCM command line - " + getCommandString(), e );
187         }
188         String errorOutput = err.getOutput();
189         if ( errorOutput.length() > 0 )
190         {
191             logErrorMessage( "Error: " + err.getOutput() );
192         }
193         return status;
194     }
195 
196     public String getCommandString()
197     {
198         return fCommand.toString();
199     }
200 
201     public Commandline getCommandline()
202     {
203         return fCommand;
204     }
205 
206     private void logErrorMessage( String message )
207     {
208         if ( fLogger != null )
209         {
210             fLogger.error( message );
211         }
212     }
213 
214     private void logInfoMessage( String message )
215     {
216         if ( fLogger != null )
217         {
218             fLogger.info( message );
219         }
220     }
221 
222     private void logDebugMessage( String message )
223     {
224         if ( fLogger != null )
225         {
226             fLogger.debug( message );
227         }
228     }
229 
230     // Unashamedly 'borrowed' from SvnCommandLineUtils
231     // (but fixed for cases where the line ends in the password (no trailing space or further input).
232     public static String cryptPassword( Commandline cl )
233     {
234         String clString = cl.toString();
235 
236         int pos = clString.indexOf( "--password" );
237 
238         if ( pos > 0 )
239         {
240             String beforePassword = clString.substring( 0, pos + "--password ".length() );
241             String afterPassword = clString.substring( pos + "--password ".length() );
242             pos = afterPassword.indexOf( ' ' );
243             if ( pos > 0 )
244             {
245                 afterPassword = afterPassword.substring( pos );
246             }
247             else
248             {
249                 if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
250                 {
251                     afterPassword = "\"";
252                 }
253                 else
254                 {
255                     afterPassword = "";
256                 }
257             }
258             if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
259             {
260                 clString = beforePassword + "*****" + afterPassword;
261             }
262             else
263             {
264                 clString = beforePassword + "'*****'" + afterPassword;
265             }
266         }
267 
268         return clString;
269     }
270 
271     /**
272      * Check if the exit status is meant to be an error:
273      * https://jazz.net/help-dev/clm/index.jsp?topic=%2Fcom.ibm.team.scm.doc%2Ftopics%2Fr_scm_cli_retcodes.html
274      */
275     public static boolean isCommandExitError(int status) {
276         return status != 0 && status != 52 && status != 53;
277     }
278 }