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, boolean shallow ) 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}