001    package 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    
022    import org.apache.maven.scm.ScmException;
023    import org.apache.maven.scm.ScmFileSet;
024    import org.apache.maven.scm.log.ScmLogger;
025    import org.apache.maven.scm.provider.ScmProviderRepository;
026    import org.apache.maven.scm.provider.jazz.command.consumer.ErrorConsumer;
027    import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository;
028    import org.codehaus.plexus.util.Os;
029    import org.codehaus.plexus.util.StringUtils;
030    import org.codehaus.plexus.util.cli.CommandLineException;
031    import org.codehaus.plexus.util.cli.CommandLineUtils;
032    import org.codehaus.plexus.util.cli.Commandline;
033    import org.codehaus.plexus.util.cli.StreamConsumer;
034    
035    import java.io.File;
036    import 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     */
049    public 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    }