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     * @throws ScmException
061     */
062    public JazzScmCommand( String cmd, ScmProviderRepository repo, ScmFileSet fileSet, ScmLogger logger )
063    {
064        this( cmd, null, repo, true, fileSet, logger );
065    }
066
067    /**
068     * Create a JazzScmCommand when a sub-command is needed.
069     * eg: "create snapshot ..."
070     */
071    public JazzScmCommand( String cmd, String subCmd, ScmProviderRepository repo, ScmFileSet fileSet, ScmLogger logger )
072    {
073        this( cmd, subCmd, repo, true, fileSet, logger );
074    }
075
076    /**
077     * Create a JazzScmCommand, adding the repository-uri as needed.
078     */
079    public JazzScmCommand( String cmd, String subCmd, ScmProviderRepository repo, boolean addRepositoryWorkspaceArg,
080                           ScmFileSet fileSet, ScmLogger logger )
081    {
082        fLogger = logger;
083        fCommand = new Commandline();
084
085        // TODO This was developed and tested in Windows (in which scm (scm.exe) was the valid executable)
086        // Verify that the executable is valid in other operating systems.
087        fCommand.setExecutable( JazzConstants.SCM_EXECUTABLE );
088
089        if ( fileSet != null )
090        {
091            fCommand.setWorkingDirectory( fileSet.getBasedir().getAbsolutePath() );
092
093            // Make the directory, if need be.
094            if ( !fCommand.getWorkingDirectory().exists() )
095            {
096                boolean success = fCommand.getWorkingDirectory().mkdirs();
097                if ( !success )
098                {
099                    // Just log the error, don't throw an error, as it is going to fail anyway.
100                    logErrorMessage( "Working directory did not exist" + " and it couldn't be created: "
101                                         + fCommand.getWorkingDirectory() );
102                }
103            }
104        }
105
106        // Add the main command
107        if ( !StringUtils.isEmpty( cmd ) )
108        {
109            addArgument( cmd );
110        }
111
112        // Add the sub-command if present
113        if ( !StringUtils.isEmpty( subCmd ) )
114        {
115            addArgument( subCmd );
116        }
117
118        JazzScmProviderRepository jazzRepo = (JazzScmProviderRepository) repo;
119
120        // Add the repository argument if needed (most commands need this, but not all)
121        if ( addRepositoryWorkspaceArg )
122        {
123            String repositoryWorkspace = jazzRepo.getRepositoryURI();
124            if ( !StringUtils.isEmpty( repositoryWorkspace ) )
125            {
126                addArgument( JazzConstants.ARG_REPOSITORY_URI );
127                addArgument( jazzRepo.getRepositoryURI() );
128            }
129        }
130
131        // Add the username argument
132        // TODO Figure out how we would use the login command / username caching so this is not required on each
133        // command.
134        String user = jazzRepo.getUser();
135        if ( !StringUtils.isEmpty( user ) )
136        {
137            addArgument( JazzConstants.ARG_USER_NAME );
138            addArgument( jazzRepo.getUser() );
139        }
140
141        // Add the password argument
142        // TODO Figure out how we would use the login command / password caching so this is not required on each
143        // command.
144        String password = jazzRepo.getPassword();
145        if ( !StringUtils.isEmpty( password ) )
146        {
147            addArgument( JazzConstants.ARG_USER_PASSWORD );
148            addArgument( jazzRepo.getPassword() );
149        }
150    }
151
152    public void addArgument( ScmFileSet fileSet )
153    {
154        logInfoMessage( "files: " + fileSet.getBasedir().getAbsolutePath() );
155        Iterator<File> iter = fileSet.getFileList().iterator();
156        while ( iter.hasNext() )
157        {
158            fCommand.createArg().setValue(  iter.next().getPath() );
159        }
160    }
161
162    public void addArgument( String arg )
163    {
164        fCommand.createArg().setValue( arg );
165    }
166
167    public int execute( StreamConsumer out, ErrorConsumer err )
168        throws ScmException
169    {
170        logInfoMessage( "Executing: " + cryptPassword( fCommand ) );
171        if ( fCommand.getWorkingDirectory() != null )
172        {
173            logInfoMessage( "Working directory: " + fCommand.getWorkingDirectory().getAbsolutePath() );
174        }
175
176        int status = 0;
177        try
178        {
179            status = CommandLineUtils.executeCommandLine( fCommand, out, err );
180        }
181        catch ( CommandLineException e )
182        {
183            String errorOutput = err.getOutput();
184            if ( errorOutput.length() > 0 )
185            {
186                logErrorMessage( "Error: " + err.getOutput() );
187            }
188            throw new ScmException( "Error while executing Jazz SCM command line - " + getCommandString(), e );
189        }
190        String errorOutput = err.getOutput();
191        if ( errorOutput.length() > 0 )
192        {
193            logErrorMessage( "Error: " + err.getOutput() );
194        }
195        return status;
196    }
197
198    public String getCommandString()
199    {
200        return fCommand.toString();
201    }
202
203    public Commandline getCommandline()
204    {
205        return fCommand;
206    }
207
208    private void logErrorMessage( String message )
209    {
210        if ( fLogger != null )
211        {
212            fLogger.error( message );
213        }
214    }
215
216    private void logInfoMessage( String message )
217    {
218        if ( fLogger != null )
219        {
220            fLogger.info( message );
221        }
222    }
223
224    private void logDebugMessage( String message )
225    {
226        if ( fLogger != null )
227        {
228            fLogger.debug( message );
229        }
230    }
231
232    // Unashamedly 'borrowed' from SvnCommandLineUtils
233    // (but fixed for cases where the line ends in the password (no trailing space or further input).
234    public static String cryptPassword( Commandline cl )
235    {
236        String clString = cl.toString();
237
238        int pos = clString.indexOf( "--password" );
239
240        if ( pos > 0 )
241        {
242            String beforePassword = clString.substring( 0, pos + "--password ".length() );
243            String afterPassword = clString.substring( pos + "--password ".length() );
244            pos = afterPassword.indexOf( ' ' );
245            if ( pos > 0 )
246            {
247                afterPassword = afterPassword.substring( pos );
248            }
249            else
250            {
251                afterPassword = "\"";
252            }
253            if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
254            {
255                clString = beforePassword + "*****" + afterPassword;
256            }
257            else
258            {
259                clString = beforePassword + "'*****'";
260            }
261        }
262
263        return clString;
264    }
265
266}