001package 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
022import java.io.File;
023import java.io.FileWriter;
024import java.io.IOException;
025import java.net.InetAddress;
026import java.net.UnknownHostException;
027
028import org.apache.maven.scm.ScmException;
029import org.apache.maven.scm.ScmFileSet;
030import org.apache.maven.scm.ScmVersion;
031import org.apache.maven.scm.command.checkout.AbstractCheckOutCommand;
032import org.apache.maven.scm.command.checkout.CheckOutScmResult;
033import org.apache.maven.scm.provider.ScmProviderRepository;
034import org.apache.maven.scm.provider.clearcase.command.ClearCaseCommand;
035import org.apache.maven.scm.provider.clearcase.repository.ClearCaseScmProviderRepository;
036import org.apache.maven.scm.providers.clearcase.settings.Settings;
037import org.codehaus.plexus.util.FileUtils;
038import org.codehaus.plexus.util.StringUtils;
039import org.codehaus.plexus.util.cli.CommandLineException;
040import org.codehaus.plexus.util.cli.CommandLineUtils;
041import 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 *
047 */
048public 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()
162                                      + ">>" + cl.toString() );
163                }
164                exitCode = CommandLineUtils.executeCommandLine( cl, consumer, stderr );
165
166            }
167        }
168        catch ( CommandLineException ex )
169        {
170            throw new ScmException( "Error while executing clearcase command.", ex );
171        }
172        catch ( IOException ex )
173        {
174            throw new ScmException( "Error while deleting working directory.", ex );
175        }
176
177        if ( exitCode != 0 )
178        {
179            return new CheckOutScmResult( cl.toString(), "The cleartool command failed.", stderr.getOutput(), false );
180        }
181
182        return new CheckOutScmResult( cl.toString(), consumer.getCheckedOutFiles(), projectDirectory );
183    }
184
185    // ----------------------------------------------------------------------
186    //
187    // ----------------------------------------------------------------------
188
189    /**
190     * Creates a temporary config spec file with the given contents that will be
191     * deleted on VM exit.
192     *
193     * @param configSpecContents The contents for the file
194     * @param viewName           The name of the view; used to determine an appropriate file
195     *                           name
196     * @throws IOException
197     */
198    protected File writeTemporaryConfigSpecFile( String configSpecContents, String viewName )
199        throws IOException
200    {
201        File configSpecLocation = File.createTempFile( "configspec-" + viewName, ".txt" );
202        FileWriter fw = new FileWriter( configSpecLocation );
203        try
204        {
205            fw.write( configSpecContents );
206        }
207        finally
208        {
209            try
210            {
211                fw.close();
212            }
213            catch ( IOException e )
214            {
215                // ignore
216            }
217        }
218        configSpecLocation.deleteOnExit();
219        return configSpecLocation;
220    }
221
222    /**
223     * Creates a config spec that loads the given loadDirectory and uses the
224     * given version tag
225     *
226     * @param loadDirectory the VOB directory to be loaded
227     * @param version       ClearCase label type; notice that branch types are not
228     *                      supported
229     * @return Config Spec as String
230     */
231    protected String createConfigSpec( String loadDirectory, ScmVersion version )
232    {
233        // create config spec
234        StringBuilder configSpec = new StringBuilder();
235        configSpec.append( "element * CHECKEDOUT\n" );
236        if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
237        {
238            configSpec.append( "element * " + version.getName() + "\n" );
239            configSpec.append( "element -directory * /main/LATEST\n" );
240            // configSpec.append( "element * /main/QualityControl_INT/RAD7_Migration/LATEST\n" );
241        }
242        else
243        {
244            configSpec.append( "element * /main/LATEST\n" );
245        }
246        configSpec.append( "load " + loadDirectory + "\n" );
247        return configSpec.toString();
248    }
249
250    protected String createConfigSpec( String loadDirectory, String elementName, ScmVersion version )
251    {
252        // create config spec
253        StringBuilder configSpec = new StringBuilder();
254        configSpec.append( "element * CHECKEDOUT\n" );
255        if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
256        {
257            configSpec.append( "element * " + version.getName() + "\n" );
258            configSpec.append( "element * " + elementName + "\n" );
259        }
260        else
261        {
262            configSpec.append( "element * /main/LATEST\n" );
263        }
264        configSpec.append( "load " + loadDirectory + "\n" );
265        return configSpec.toString();
266    }
267
268//    private static Commandline createDeleteViewCommandLine( ClearCaseScmProviderRepository repository,
269//                                                            File workingDirectory )
270//    {
271//        Commandline command = new Commandline();
272//
273//        command.setWorkingDirectory( workingDirectory.getAbsolutePath() );
274//
275//        command.setExecutable( "cleartool" );
276//
277//        command.createArg().setValue( "rmview" );
278//        command.createArg().setValue( "-force" );
279//        command.createArg().setValue( "-tag" );
280//        if ( isClearCaseLT() )
281//        {
282//            command.createArg().setValue( getViewStore() );
283//        }
284//        else
285//        {
286//            command.createArg().setValue( getUniqueViewName( repository, workingDirectory.getAbsolutePath() ) );
287//        }
288//
289//        return command;
290//    }
291
292    protected Commandline createCreateViewCommandLine( File workingDirectory, String viewName, String streamIdentifier )
293        throws IOException
294    {
295        Commandline command = new Commandline();
296
297        // We have to execute from 1 level up from the working dir, since we had to delete the working dir
298        command.setWorkingDirectory( workingDirectory.getParentFile().getAbsolutePath() );
299
300        command.setExecutable( "cleartool" );
301
302        command.createArg().setValue( "mkview" );
303        command.createArg().setValue( "-snapshot" );
304        command.createArg().setValue( "-tag" );
305        command.createArg().setValue( viewName );
306
307        if ( isClearCaseUCM() )
308        {
309            command.createArg().setValue( "-stream" );
310            command.createArg().setValue( streamIdentifier );
311        }
312
313        if ( !isClearCaseLT() )
314        {
315            if ( useVWS() )
316            {
317                command.createArg().setValue( "-vws" );
318                command.createArg().setValue( getViewStore() + viewName + ".vws" );
319            }
320        }
321
322        command.createArg().setValue( workingDirectory.getCanonicalPath() );
323
324        return command;
325    }
326
327    /**
328     * Format the stream identifier for ClearCaseUCM
329     * @param streamName
330     * @param vobName
331     * @return the formatted stream identifier if the two parameter are not null
332     */
333    protected String getStreamIdentifier( String streamName, String vobName )
334    {
335        if ( streamName == null || vobName == null )
336        {
337            return null;
338        }
339        return "stream:" + streamName + "@" + vobName;
340    }
341
342    protected Commandline createUpdateConfigSpecCommandLine( File workingDirectory, File configSpecLocation,
343                                                                    String viewName )
344    {
345        Commandline command = new Commandline();
346
347        command.setWorkingDirectory( workingDirectory.getAbsolutePath() );
348
349        command.setExecutable( "cleartool" );
350
351        command.createArg().setValue( "setcs" );
352        command.createArg().setValue( "-tag" );
353        command.createArg().setValue( viewName );
354        command.createArg().setValue( configSpecLocation.getAbsolutePath() );
355
356        return command;
357
358    }
359
360    private String getUniqueViewName( ClearCaseScmProviderRepository repository, String absolutePath )
361    {
362        String uniqueId;
363        int lastIndexBack = absolutePath.lastIndexOf( '\\' );
364        int lastIndexForward = absolutePath.lastIndexOf( '/' );
365        if ( lastIndexBack != -1 )
366        {
367            uniqueId = absolutePath.substring( lastIndexBack + 1 );
368        }
369        else
370        {
371            uniqueId = absolutePath.substring( lastIndexForward + 1 );
372        }
373        return repository.getViewName( uniqueId );
374    }
375
376    protected String getViewStore()
377    {
378        String result = null;
379
380        if ( settings.getViewstore() != null )
381        {
382            result = settings.getViewstore();
383        }
384
385        if ( result == null )
386        {
387            result = "\\\\" + getHostName() + "\\viewstore\\";
388        }
389        else
390        {
391            // If ClearCase LT are use, the View store is identify by the
392            // username.
393            if ( isClearCaseLT() )
394            {
395                result = result + getUserName() + "\\";
396            }
397        }
398        return result;
399    }
400
401    protected boolean isClearCaseLT()
402    {
403        return ClearCaseScmProviderRepository.CLEARCASE_LT.equals( settings.getClearcaseType() );
404    }
405
406    protected boolean isClearCaseUCM()
407    {
408        return ClearCaseScmProviderRepository.CLEARCASE_UCM.equals( settings.getClearcaseType() );
409    }
410
411    /**
412     * @return the value of the setting property 'useVWS'
413     */
414    protected boolean useVWS()
415    {
416        return settings.isUseVWSParameter();
417    }
418
419    private String getHostName()
420    {
421        String hostname;
422        try
423        {
424            hostname = InetAddress.getLocalHost().getHostName();
425        }
426        catch ( UnknownHostException e )
427        {
428            // Should never happen
429            throw new RuntimeException( e );
430        }
431        return hostname;
432    }
433
434    private String getUserName()
435    {
436        String username;
437        username = System.getProperty( "user.name" );
438        return username;
439    }
440
441    public void setSettings( Settings settings )
442    {
443        this.settings = settings;
444    }
445}