001package org.apache.maven.scm.provider.jazz.command; 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 org.apache.maven.scm.ScmException; 023import org.apache.maven.scm.ScmFileSet; 024import org.apache.maven.scm.log.ScmLogger; 025import org.apache.maven.scm.provider.ScmProviderRepository; 026import org.apache.maven.scm.provider.jazz.command.consumer.ErrorConsumer; 027import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository; 028import org.codehaus.plexus.util.Os; 029import org.codehaus.plexus.util.StringUtils; 030import org.codehaus.plexus.util.cli.CommandLineException; 031import org.codehaus.plexus.util.cli.CommandLineUtils; 032import org.codehaus.plexus.util.cli.Commandline; 033import org.codehaus.plexus.util.cli.StreamConsumer; 034 035import java.io.File; 036import java.util.Iterator; 037 038/** 039 * The base class for the underlying jazz "scm.sh"/"scm.exe" command. 040 * <p> 041 * The SCM command is documented here: 042 * <p> 043 * V2.0.2: http://publib.boulder.ibm.com/infocenter/rtc/v2r0m0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html 044 * V3.0: http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html 045 * V3.0.1: http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0m1/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html 046 * 047 * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a> 048 */ 049public class JazzScmCommand 050{ 051 // The logger to use. 052 private ScmLogger fLogger; 053 054 // The Commandline that we build up and execute. 055 private Commandline fCommand; 056 057 /** 058 * Create a JazzScmCommand when no sub-command is needed. 059 */ 060 public JazzScmCommand( String cmd, ScmProviderRepository repo, ScmFileSet fileSet, ScmLogger logger ) 061 { 062 this( cmd, null, repo, true, fileSet, logger ); 063 } 064 065 /** 066 * Create a JazzScmCommand when a sub-command is needed. 067 * eg: "create snapshot ..." 068 */ 069 public JazzScmCommand( String cmd, String subCmd, ScmProviderRepository repo, ScmFileSet fileSet, ScmLogger logger ) 070 { 071 this( cmd, subCmd, repo, true, fileSet, logger ); 072 } 073 074 /** 075 * Create a JazzScmCommand, adding the repository-uri as needed. 076 */ 077 public JazzScmCommand( String cmd, String subCmd, ScmProviderRepository repo, boolean addRepositoryWorkspaceArg, 078 ScmFileSet fileSet, ScmLogger logger ) 079 { 080 fLogger = logger; 081 fCommand = new Commandline(); 082 083 // TODO This was developed and tested in Windows (in which scm (scm.exe) was the valid executable) 084 // Verify that the executable is valid in other operating systems. 085 fCommand.setExecutable( JazzConstants.SCM_EXECUTABLE ); 086 087 if ( fileSet != null ) 088 { 089 fCommand.setWorkingDirectory( fileSet.getBasedir().getAbsolutePath() ); 090 091 // Make the directory, if need be. 092 if ( !fCommand.getWorkingDirectory().exists() ) 093 { 094 boolean success = fCommand.getWorkingDirectory().mkdirs(); 095 if ( !success ) 096 { 097 // Just log the error, don't throw an error, as it is going to fail anyway. 098 logErrorMessage( "Working directory did not exist" + " and it couldn't be created: " 099 + 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}