001package org.apache.maven.scm.plugin; 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.IOException; 024import java.util.ArrayList; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028import java.util.Map.Entry; 029import java.util.Objects; 030import java.util.Properties; 031 032import org.apache.maven.plugin.AbstractMojo; 033import org.apache.maven.plugin.MojoExecutionException; 034import org.apache.maven.plugins.annotations.Component; 035import org.apache.maven.plugins.annotations.Parameter; 036import org.apache.maven.scm.ScmBranch; 037import org.apache.maven.scm.ScmException; 038import org.apache.maven.scm.ScmFileSet; 039import org.apache.maven.scm.ScmResult; 040import org.apache.maven.scm.ScmRevision; 041import org.apache.maven.scm.ScmTag; 042import org.apache.maven.scm.ScmVersion; 043import org.apache.maven.scm.manager.ScmManager; 044import org.apache.maven.scm.provider.ScmProviderRepository; 045import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; 046import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; 047import org.apache.maven.scm.repository.ScmRepository; 048import org.apache.maven.scm.repository.ScmRepositoryException; 049import org.apache.maven.settings.Server; 050import org.apache.maven.settings.Settings; 051import org.apache.maven.shared.model.fileset.FileSet; 052import org.apache.maven.shared.model.fileset.util.FileSetManager; 053import org.codehaus.plexus.util.StringUtils; 054import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; 055import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException; 056 057/** 058 * @author <a href="evenisse@apache.org">Emmanuel Venisse</a> 059 * @author Olivier Lamy 060 */ 061public abstract class AbstractScmMojo 062 extends AbstractMojo 063{ 064 065 protected static final String VERSION_TYPE_BRANCH = "branch"; 066 067 protected static final String VERSION_TYPE_REVISION = "revision"; 068 069 protected static final String VERSION_TYPE_TAG = "tag"; 070 071 protected static final String[] VALID_VERSION_TYPES = { VERSION_TYPE_BRANCH, 072 VERSION_TYPE_REVISION, VERSION_TYPE_TAG }; 073 074 /** 075 * The SCM connection URL. 076 */ 077 @Parameter( property = "connectionUrl", defaultValue = "${project.scm.connection}" ) 078 private String connectionUrl; 079 080 /** 081 * The SCM connection URL for developers. 082 */ 083 @Parameter( property = "developerConnectionUrl", defaultValue = "${project.scm.developerConnection}" ) 084 private String developerConnectionUrl; 085 086 /** 087 * The type of connection to use (connection or developerConnection). 088 */ 089 @Parameter( property = "connectionType", defaultValue = "connection" ) 090 private String connectionType; 091 092 /** 093 * The working directory. 094 */ 095 @Parameter( property = "workingDirectory" ) 096 private File workingDirectory; 097 098 /** 099 * The user name. 100 */ 101 @Parameter( property = "username" ) 102 private String username; 103 104 /** 105 * The user password. 106 */ 107 @Parameter( property = "password" ) 108 private String password; 109 110 /** 111 * The private key. 112 */ 113 @Parameter( property = "privateKey" ) 114 private String privateKey; 115 116 /** 117 * The passphrase. 118 */ 119 @Parameter( property = "passphrase" ) 120 private String passphrase; 121 122 /** 123 * The url of tags base directory (used by svn protocol). It is not 124 * necessary to set it if you use the standard svn layout 125 * (branches/tags/trunk). 126 */ 127 @Parameter( property = "tagBase" ) 128 private String tagBase; 129 130 /** 131 * Comma separated list of includes file pattern. 132 */ 133 @Parameter( property = "includes" ) 134 private String includes; 135 136 /** 137 * Comma separated list of excludes file pattern. 138 */ 139 @Parameter( property = "excludes" ) 140 private String excludes; 141 142 @Component 143 private ScmManager manager; 144 145 /** 146 * When this plugin requires Maven 3.0 as minimum, this component can be removed and o.a.m.s.c.SettingsDecrypter be 147 * used instead. 148 */ 149 @Component( hint = "mng-4384" ) 150 private SecDispatcher secDispatcher; 151 152 /** 153 * The base directory. 154 */ 155 @Parameter( property = "basedir", required = true ) 156 private File basedir; 157 158 @Parameter( defaultValue = "${settings}", readonly = true ) 159 private Settings settings; 160 161 /** 162 * List of System properties to pass to the JUnit tests. 163 */ 164 @Parameter 165 private Properties systemProperties; 166 167 /** 168 * List of provider implementations. 169 */ 170 @Parameter 171 private Map<String, String> providerImplementations; 172 173 /** 174 * Should distributed changes be pushed to the central repository? 175 * For many distributed SCMs like Git, a change like a commit 176 * is only stored in your local copy of the repository. Pushing 177 * the change allows your to more easily share it with other users. 178 * 179 * @since 1.4 180 */ 181 @Parameter( property = "pushChanges", defaultValue = "true" ) 182 private boolean pushChanges; 183 184 /** 185 * A workItem for SCMs like RTC, TFS etc, that may require additional 186 * information to perform a pushChange operation. 187 * 188 * @since 1.9.5 189 */ 190 @Parameter( property = "workItem" ) 191 @Deprecated 192 private String workItem; 193 194 /** {@inheritDoc} */ 195 public void execute() 196 throws MojoExecutionException 197 { 198 if ( systemProperties != null ) 199 { 200 // Add all system properties configured by the user 201 Iterator<Object> iter = systemProperties.keySet().iterator(); 202 203 while ( iter.hasNext() ) 204 { 205 String key = (String) iter.next(); 206 207 String value = systemProperties.getProperty( key ); 208 209 System.setProperty( key, value ); 210 } 211 } 212 213 if ( providerImplementations != null && !providerImplementations.isEmpty() ) 214 { 215 for ( Entry<String, String> entry : providerImplementations.entrySet() ) 216 { 217 String providerType = entry.getKey(); 218 String providerImplementation = entry.getValue(); 219 getLog().info( 220 "Change the default '" + providerType + "' provider implementation to '" 221 + providerImplementation + "'." ); 222 getScmManager().setScmProviderImplementation( providerType, providerImplementation ); 223 } 224 } 225 } 226 227 protected void setConnectionType( String connectionType ) 228 { 229 this.connectionType = connectionType; 230 } 231 232 public String getConnectionUrl() 233 { 234 boolean requireDeveloperConnection = !"connection".equals( connectionType.toLowerCase() ); 235 if ( StringUtils.isNotEmpty( connectionUrl ) && !requireDeveloperConnection ) 236 { 237 return connectionUrl; 238 } 239 else if ( StringUtils.isNotEmpty( developerConnectionUrl ) ) 240 { 241 return developerConnectionUrl; 242 } 243 if ( requireDeveloperConnection ) 244 { 245 throw new NullPointerException( "You need to define a developerConnectionUrl parameter" ); 246 } 247 else 248 { 249 throw new NullPointerException( "You need to define a connectionUrl parameter" ); 250 } 251 } 252 253 public void setConnectionUrl( String connectionUrl ) 254 { 255 this.connectionUrl = connectionUrl; 256 } 257 258 public File getWorkingDirectory() 259 { 260 if ( workingDirectory == null ) 261 { 262 return basedir; 263 } 264 265 return workingDirectory; 266 } 267 268 public File getBasedir() 269 { 270 return this.basedir; 271 } 272 273 public void setWorkingDirectory( File workingDirectory ) 274 { 275 this.workingDirectory = workingDirectory; 276 } 277 278 public ScmManager getScmManager() 279 { 280 return manager; 281 } 282 283 public ScmFileSet getFileSet() 284 throws IOException 285 { 286 if ( includes != null || excludes != null ) 287 { 288 return new ScmFileSet( getWorkingDirectory(), includes, excludes ); 289 } 290 else 291 { 292 return new ScmFileSet( getWorkingDirectory() ); 293 } 294 } 295 296 public ScmRepository getScmRepository() 297 throws ScmException 298 { 299 ScmRepository repository; 300 301 try 302 { 303 repository = getScmManager().makeScmRepository( getConnectionUrl() ); 304 305 ScmProviderRepository providerRepo = repository.getProviderRepository(); 306 307 providerRepo.setPushChanges( pushChanges ); 308 309 if ( !StringUtils.isEmpty( workItem ) ) 310 { 311 providerRepo.setWorkItem( workItem ); 312 } 313 314 if ( !StringUtils.isEmpty( username ) ) 315 { 316 providerRepo.setUser( username ); 317 } 318 319 if ( !StringUtils.isEmpty( password ) ) 320 { 321 providerRepo.setPassword( password ); 322 } 323 324 if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost ) 325 { 326 ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); 327 328 loadInfosFromSettings( repo ); 329 330 if ( !StringUtils.isEmpty( username ) ) 331 { 332 repo.setUser( username ); 333 } 334 335 if ( !StringUtils.isEmpty( password ) ) 336 { 337 repo.setPassword( password ); 338 } 339 340 if ( !StringUtils.isEmpty( privateKey ) ) 341 { 342 repo.setPrivateKey( privateKey ); 343 } 344 345 if ( !StringUtils.isEmpty( passphrase ) ) 346 { 347 repo.setPassphrase( passphrase ); 348 } 349 } 350 351 if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) ) 352 { 353 SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); 354 355 svnRepo.setTagBase( tagBase ); 356 } 357 } 358 catch ( ScmRepositoryException e ) 359 { 360 if ( !e.getValidationMessages().isEmpty() ) 361 { 362 for ( String message : e.getValidationMessages() ) 363 { 364 getLog().error( message ); 365 } 366 } 367 368 throw new ScmException( "Can't load the scm provider.", e ); 369 } 370 catch ( Exception e ) 371 { 372 throw new ScmException( "Can't load the scm provider.", e ); 373 } 374 375 return repository; 376 } 377 378 /** 379 * Load username password from settings if user has not set them in JVM properties 380 * 381 * @param repo not null 382 */ 383 private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo ) 384 { 385 if ( username == null || password == null ) 386 { 387 String host = repo.getHost(); 388 389 int port = repo.getPort(); 390 391 if ( port > 0 ) 392 { 393 host += ":" + port; 394 } 395 396 Server server = this.settings.getServer( host ); 397 398 if ( server != null ) 399 { 400 if ( username == null ) 401 { 402 username = server.getUsername(); 403 } 404 405 if ( password == null ) 406 { 407 password = decrypt( server.getPassword(), host ); 408 } 409 410 if ( privateKey == null ) 411 { 412 privateKey = server.getPrivateKey(); 413 } 414 415 if ( passphrase == null ) 416 { 417 passphrase = decrypt( server.getPassphrase(), host ); 418 } 419 } 420 } 421 } 422 423 private String decrypt( String str, String server ) 424 { 425 try 426 { 427 return secDispatcher.decrypt( str ); 428 } 429 catch ( SecDispatcherException e ) 430 { 431 getLog().warn( "Failed to decrypt password/passphrase for server " + server + ", using auth token as is" ); 432 return str; 433 } 434 } 435 436 public void checkResult( ScmResult result ) 437 throws MojoExecutionException 438 { 439 if ( !result.isSuccess() ) 440 { 441 getLog().error( "Provider message:" ); 442 443 getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() ); 444 445 getLog().error( "Command output:" ); 446 447 getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() ); 448 449 throw new MojoExecutionException( 450 "Command failed: " + Objects.toString( result.getProviderMessage() ) ); 451 } 452 } 453 454 public String getIncludes() 455 { 456 return includes; 457 } 458 459 public void setIncludes( String includes ) 460 { 461 this.includes = includes; 462 } 463 464 public String getExcludes() 465 { 466 return excludes; 467 } 468 469 public void setExcludes( String excludes ) 470 { 471 this.excludes = excludes; 472 } 473 474 public ScmVersion getScmVersion( String versionType, String version ) 475 throws MojoExecutionException 476 { 477 if ( StringUtils.isEmpty( versionType ) && StringUtils.isNotEmpty( version ) ) 478 { 479 throw new MojoExecutionException( "You must specify the version type." ); 480 } 481 482 if ( StringUtils.isEmpty( version ) ) 483 { 484 return null; 485 } 486 487 if ( VERSION_TYPE_BRANCH.equals( versionType ) ) 488 { 489 return new ScmBranch( version ); 490 } 491 492 if ( VERSION_TYPE_TAG.equals( versionType ) ) 493 { 494 return new ScmTag( version ); 495 } 496 497 if ( VERSION_TYPE_REVISION.equals( versionType ) ) 498 { 499 return new ScmRevision( version ); 500 } 501 502 throw new MojoExecutionException( "Unknown '" + versionType + "' version type." ); 503 } 504 505 protected void handleExcludesIncludesAfterCheckoutAndExport( File checkoutDirectory ) 506 throws MojoExecutionException 507 { 508 List<String> includes = new ArrayList<String>(); 509 510 if ( ! StringUtils.isBlank( this.getIncludes() ) ) 511 { 512 String[] tokens = StringUtils.split( this.getIncludes(), "," ); 513 for ( int i = 0; i < tokens.length; ++i ) 514 { 515 includes.add( tokens[i] ); 516 } 517 } 518 519 List<String> excludes = new ArrayList<String>(); 520 521 if ( ! StringUtils.isBlank( this.getExcludes() ) ) 522 { 523 String[] tokens = StringUtils.split( this.getExcludes(), "," ); 524 for ( int i = 0; i < tokens.length; ++i ) 525 { 526 excludes.add( tokens[i] ); 527 } 528 } 529 530 if ( includes.isEmpty() && excludes.isEmpty() ) 531 { 532 return; 533 } 534 535 FileSetManager fileSetManager = new FileSetManager(); 536 537 FileSet fileset = new FileSet(); 538 fileset.setDirectory( checkoutDirectory.getAbsolutePath() ); 539 fileset.setIncludes( excludes ); // revert the order to do the delete 540 fileset.setExcludes( includes ); 541 fileset.setUseDefaultExcludes( false ); 542 543 try 544 { 545 fileSetManager.delete( fileset ); 546 } 547 catch ( IOException e ) 548 { 549 throw new MojoExecutionException( "Error found while cleaning up output directory base on " 550 + "excludes/includes configurations.", e ); 551 } 552 553 } 554}