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() + ">>" + 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}