001    package org.apache.maven.scm.provider.clearcase.command.checkout;
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 java.io.File;
023    import java.io.FileWriter;
024    import java.io.IOException;
025    import java.net.InetAddress;
026    import java.net.UnknownHostException;
027    
028    import org.apache.maven.scm.ScmException;
029    import org.apache.maven.scm.ScmFileSet;
030    import org.apache.maven.scm.ScmVersion;
031    import org.apache.maven.scm.command.checkout.AbstractCheckOutCommand;
032    import org.apache.maven.scm.command.checkout.CheckOutScmResult;
033    import org.apache.maven.scm.provider.ScmProviderRepository;
034    import org.apache.maven.scm.provider.clearcase.command.ClearCaseCommand;
035    import org.apache.maven.scm.provider.clearcase.repository.ClearCaseScmProviderRepository;
036    import org.apache.maven.scm.providers.clearcase.settings.Settings;
037    import org.codehaus.plexus.util.FileUtils;
038    import org.codehaus.plexus.util.StringUtils;
039    import org.codehaus.plexus.util.cli.CommandLineException;
040    import org.codehaus.plexus.util.cli.CommandLineUtils;
041    import org.codehaus.plexus.util.cli.Commandline;
042    
043    /**
044     * @author <a href="mailto:wim.deblauwe@gmail.com">Wim Deblauwe</a>
045     * @author <a href="mailto:frederic.mura@laposte.net">Frederic Mura</a>
046     * @version $Id: ClearCaseCheckOutCommand.java 1306867 2012-03-29 13:45:10Z olamy $
047     */
048    public class ClearCaseCheckOutCommand
049        extends AbstractCheckOutCommand
050        implements ClearCaseCommand
051    {
052        private Settings settings = null;
053    
054        // ----------------------------------------------------------------------
055        // AbstractCheckOutCommand Implementation
056        // ----------------------------------------------------------------------
057    
058        /** {@inheritDoc} */
059        protected CheckOutScmResult executeCheckOutCommand( ScmProviderRepository repository, ScmFileSet fileSet,
060                                                            ScmVersion version, boolean recursive )
061            throws ScmException
062        {
063            if ( getLogger().isDebugEnabled() )
064            {
065                getLogger().debug( "executing checkout command..." );
066            }
067            ClearCaseScmProviderRepository repo = (ClearCaseScmProviderRepository) repository;
068            File workingDirectory = fileSet.getBasedir();
069    
070            if ( version != null && getLogger().isDebugEnabled() )
071            {
072                getLogger().debug( version.getType() + ": " + version.getName() );
073            }
074    
075            if ( getLogger().isDebugEnabled() )
076            {
077                getLogger().debug( "Running with CLEARCASE " + settings.getClearcaseType() );
078            }
079    
080            ClearCaseCheckOutConsumer consumer = new ClearCaseCheckOutConsumer( getLogger() );
081    
082            CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
083    
084            int exitCode;
085    
086            Commandline cl;
087            String projectDirectory = "";
088    
089            try
090            {
091                // Since clearcase only wants to checkout to a non-existent directory, first delete the working dir
092                // if it already exists
093                FileUtils.deleteDirectory( workingDirectory );
094                // First create the view
095                String viewName = getUniqueViewName( repo, workingDirectory.getAbsolutePath() );
096                String streamIdentifier = getStreamIdentifier( repo.getStreamName(), repo.getVobName() );
097                cl = createCreateViewCommandLine( workingDirectory, viewName, streamIdentifier );
098                if ( getLogger().isInfoEnabled() )
099                {
100                    getLogger().info( "Executing: " + cl.getWorkingDirectory().getAbsolutePath() + ">>" + cl.toString() );
101                }
102                exitCode =
103                    CommandLineUtils.executeCommandLine( cl, new CommandLineUtils.StringStreamConsumer(), stderr );
104    
105                if ( exitCode == 0 )
106                {
107                    File configSpecLocation;
108    
109                    if ( !repo.isAutoConfigSpec() )
110                    {
111                        configSpecLocation = repo.getConfigSpec();
112                        if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
113                        {
114                            // Another config spec is needed in this case.
115                            //
116                            // One option how to implement this would be to use a name convention for the config specs,
117                            // e.g. the tag name could be appended to the original config spec name.
118                            // If the config spec from the SCM URL would be \\myserver\configspecs\someproj.txt
119                            // and the tag name would be mytag, the new config spec location could be
120                            // \\myserver\configspecs\someproj-mytag.txt
121                            //
122                            throw new UnsupportedOperationException(
123                                "Building on a label not supported with user-specified config specs" );
124                        }
125                    }
126                    else
127                    {
128    
129                        // write config spec to temp file
130                        String configSpec;
131                        if ( !repo.hasElements() )
132                        {
133                            configSpec = createConfigSpec( repo.getLoadDirectory(), version );
134                        }
135                        else
136                        {
137                            configSpec = createConfigSpec( repo.getLoadDirectory(), repo.getElementName(), version );
138                        }
139                        if ( getLogger().isInfoEnabled() )
140                        {
141                            getLogger().info( "Created config spec for view '" + viewName + "':\n" + configSpec );
142                        }
143                        configSpecLocation = writeTemporaryConfigSpecFile( configSpec, viewName );
144    
145                        // When checking out from ClearCase, the directory structure of the
146                        // SCM system is repeated within the checkout directory. E.g. if you check out the
147                        // project "my/project" to "/some/dir", the project sources are actually checked out
148                        // to "my/project/some/dir".
149                        projectDirectory = repo.getLoadDirectory();
150                        // strip off leading / to make the path relative
151                        if ( projectDirectory.startsWith( "/" ) )
152                        {
153                            projectDirectory = projectDirectory.substring( 1 );
154                        }
155                    }
156    
157                    cl = createUpdateConfigSpecCommandLine( workingDirectory, configSpecLocation, viewName );
158    
159                    if ( getLogger().isInfoEnabled() )
160                    {
161                        getLogger().info( "Executing: " + cl.getWorkingDirectory().getAbsolutePath() + ">>" + cl.toString() );
162                    }
163                    exitCode = CommandLineUtils.executeCommandLine( cl, consumer, stderr );
164    
165                }
166            }
167            catch ( CommandLineException ex )
168            {
169                throw new ScmException( "Error while executing clearcase command.", ex );
170            }
171            catch ( IOException ex )
172            {
173                throw new ScmException( "Error while deleting working directory.", ex );
174            }
175    
176            if ( exitCode != 0 )
177            {
178                return new CheckOutScmResult( cl.toString(), "The cleartool command failed.", stderr.getOutput(), false );
179            }
180    
181            return new CheckOutScmResult( cl.toString(), consumer.getCheckedOutFiles(), projectDirectory );
182        }
183    
184        // ----------------------------------------------------------------------
185        //
186        // ----------------------------------------------------------------------
187    
188        /**
189         * Creates a temporary config spec file with the given contents that will be
190         * deleted on VM exit.
191         *
192         * @param configSpecContents The contents for the file
193         * @param viewName           The name of the view; used to determine an appropriate file
194         *                           name
195         * @throws IOException
196         */
197        protected File writeTemporaryConfigSpecFile( String configSpecContents, String viewName )
198            throws IOException
199        {
200            File configSpecLocation = File.createTempFile( "configspec-" + viewName, ".txt" );
201            FileWriter fw = new FileWriter( configSpecLocation );
202            try
203            {
204                fw.write( configSpecContents );
205            }
206            finally
207            {
208                try
209                {
210                    fw.close();
211                }
212                catch ( IOException e )
213                {
214                    // ignore
215                }
216            }
217            configSpecLocation.deleteOnExit();
218            return configSpecLocation;
219        }
220    
221        /**
222         * Creates a config spec that loads the given loadDirectory and uses the
223         * given version tag
224         *
225         * @param loadDirectory the VOB directory to be loaded
226         * @param version       ClearCase label type; notice that branch types are not
227         *                      supported
228         * @return Config Spec as String
229         */
230        protected String createConfigSpec( String loadDirectory, ScmVersion version )
231        {
232            // create config spec
233            StringBuilder configSpec = new StringBuilder();
234            configSpec.append( "element * CHECKEDOUT\n" );
235            if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
236            {
237                configSpec.append( "element * " + version.getName() + "\n" );
238                configSpec.append( "element -directory * /main/LATEST\n" );
239                // configSpec.append( "element * /main/QualityControl_INT/RAD7_Migration/LATEST\n" );
240            }
241            else
242            {
243                configSpec.append( "element * /main/LATEST\n" );
244            }
245            configSpec.append( "load " + loadDirectory + "\n" );
246            return configSpec.toString();
247        }
248    
249        protected String createConfigSpec( String loadDirectory, String elementName, ScmVersion version )
250        {
251            // create config spec
252            StringBuilder configSpec = new StringBuilder();
253            configSpec.append( "element * CHECKEDOUT\n" );
254            if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
255            {
256                configSpec.append( "element * " + version.getName() + "\n" );
257                configSpec.append( "element * " + elementName + "\n" );
258            }
259            else
260            {
261                configSpec.append( "element * /main/LATEST\n" );
262            }
263            configSpec.append( "load " + loadDirectory + "\n" );
264            return configSpec.toString();
265        }
266    
267    //    private static Commandline createDeleteViewCommandLine( ClearCaseScmProviderRepository repository,
268    //                                                            File workingDirectory )
269    //    {
270    //        Commandline command = new Commandline();
271    //
272    //        command.setWorkingDirectory( workingDirectory.getAbsolutePath() );
273    //
274    //        command.setExecutable( "cleartool" );
275    //
276    //        command.createArg().setValue( "rmview" );
277    //        command.createArg().setValue( "-force" );
278    //        command.createArg().setValue( "-tag" );
279    //        if ( isClearCaseLT() )
280    //        {
281    //            command.createArg().setValue( getViewStore() );
282    //        }
283    //        else
284    //        {
285    //            command.createArg().setValue( getUniqueViewName( repository, workingDirectory.getAbsolutePath() ) );
286    //        }
287    //
288    //        return command;
289    //    }
290    
291        protected Commandline createCreateViewCommandLine( File workingDirectory, String viewName, String streamIdentifier )
292            throws IOException
293        {
294            Commandline command = new Commandline();
295    
296            // We have to execute from 1 level up from the working dir, since we had to delete the working dir
297            command.setWorkingDirectory( workingDirectory.getParentFile().getAbsolutePath() );
298    
299            command.setExecutable( "cleartool" );
300    
301            command.createArg().setValue( "mkview" );
302            command.createArg().setValue( "-snapshot" );
303            command.createArg().setValue( "-tag" );
304            command.createArg().setValue( viewName );
305    
306            if ( isClearCaseUCM() )
307            {
308                command.createArg().setValue( "-stream" );
309                command.createArg().setValue( streamIdentifier );
310            }
311    
312            if ( !isClearCaseLT() )
313            {
314                if ( useVWS() )
315                {
316                    command.createArg().setValue( "-vws" );
317                    command.createArg().setValue( getViewStore() + viewName + ".vws" );
318                }
319            }
320    
321            command.createArg().setValue( workingDirectory.getCanonicalPath() );
322    
323            return command;
324        }
325    
326        /**
327         * Format the stream identifier for ClearCaseUCM
328         * @param streamName
329         * @param vobName
330         * @return the formatted stream identifier if the two parameter are not null
331         */
332        protected String getStreamIdentifier( String streamName, String vobName )
333        {
334            if ( streamName == null || vobName == null )
335            {
336                return null;
337            }
338            return "stream:" + streamName + "@" + vobName;
339        }
340    
341        protected Commandline createUpdateConfigSpecCommandLine( File workingDirectory, File configSpecLocation,
342                                                                        String viewName )
343        {
344            Commandline command = new Commandline();
345    
346            command.setWorkingDirectory( workingDirectory.getAbsolutePath() );
347    
348            command.setExecutable( "cleartool" );
349    
350            command.createArg().setValue( "setcs" );
351            command.createArg().setValue( "-tag" );
352            command.createArg().setValue( viewName );
353            command.createArg().setValue( configSpecLocation.getAbsolutePath() );
354    
355            return command;
356    
357        }
358    
359        private String getUniqueViewName( ClearCaseScmProviderRepository repository, String absolutePath )
360        {
361            String uniqueId;
362            int lastIndexBack = absolutePath.lastIndexOf( '\\' );
363            int lastIndexForward = absolutePath.lastIndexOf( '/' );
364            if ( lastIndexBack != -1 )
365            {
366                uniqueId = absolutePath.substring( lastIndexBack + 1 );
367            }
368            else
369            {
370                uniqueId = absolutePath.substring( lastIndexForward + 1 );
371            }
372            return repository.getViewName( uniqueId );
373        }
374    
375        protected String getViewStore()
376        {
377            String result = null;
378    
379            if ( settings.getViewstore() != null )
380            {
381                result = settings.getViewstore();
382            }
383    
384            if ( result == null )
385            {
386                result = "\\\\" + getHostName() + "\\viewstore\\";
387            }
388            else
389            {
390                // If ClearCase LT are use, the View store is identify by the
391                // username.
392                if ( isClearCaseLT() )
393                {
394                    result = result + getUserName() + "\\";
395                }
396            }
397            return result;
398        }
399    
400        protected boolean isClearCaseLT()
401        {
402            return ClearCaseScmProviderRepository.CLEARCASE_LT.equals( settings.getClearcaseType() );
403        }
404    
405        protected boolean isClearCaseUCM()
406        {
407            return ClearCaseScmProviderRepository.CLEARCASE_UCM.equals( settings.getClearcaseType() );
408        }
409    
410        /**
411         * @return the value of the setting property 'useVWS'
412         */
413        protected boolean useVWS()
414        {
415            return settings.isUseVWSParameter();
416        }
417    
418        private String getHostName()
419        {
420            String hostname;
421            try
422            {
423                hostname = InetAddress.getLocalHost().getHostName();
424            }
425            catch ( UnknownHostException e )
426            {
427                // Should never happen
428                throw new RuntimeException( e );
429            }
430            return hostname;
431        }
432    
433        private String getUserName()
434        {
435            String username;
436            username = System.getProperty( "user.name" );
437            return username;
438        }
439    
440        public void setSettings( Settings settings )
441        {
442            this.settings = settings;
443        }
444    }