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}