001/* 002 * Copyright 2007 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kualigan.maven.plugins.api; 017 018import org.apache.commons.cli.CommandLine; 019import org.apache.commons.cli.OptionBuilder; 020import org.apache.commons.cli.Options; 021import org.apache.commons.cli.PosixParser; 022 023import org.apache.maven.archetype.Archetype; 024import org.apache.maven.artifact.repository.ArtifactRepository; 025import org.apache.maven.artifact.repository.ArtifactRepositoryFactory; 026import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; 027import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout; 028 029import org.apache.maven.shared.invoker.DefaultInvocationRequest; 030import org.apache.maven.shared.invoker.DefaultInvoker; 031import org.apache.maven.shared.invoker.InvocationOutputHandler; 032import org.apache.maven.shared.invoker.InvocationRequest; 033import org.apache.maven.shared.invoker.InvocationResult; 034import org.apache.maven.shared.invoker.Invoker; 035import org.apache.maven.shared.invoker.InvokerLogger; 036import org.apache.maven.shared.invoker.MavenInvocationException; 037 038import org.apache.maven.plugin.AbstractMojo; 039import org.apache.maven.plugin.MojoExecutionException; 040import org.apache.maven.project.MavenProject; 041 042import org.codehaus.plexus.archiver.Archiver; 043import org.codehaus.plexus.archiver.ArchiverException; 044import org.codehaus.plexus.archiver.UnArchiver; 045import org.codehaus.plexus.archiver.manager.ArchiverManager; 046import org.codehaus.plexus.archiver.manager.NoSuchArchiverException; 047import org.codehaus.plexus.archiver.util.DefaultFileSet; 048import org.codehaus.plexus.components.io.fileselectors.IncludeExcludeFileSelector; 049 050import org.codehaus.plexus.component.annotations.Component; 051import org.codehaus.plexus.component.annotations.Requirement; 052import org.codehaus.plexus.components.interactivity.Prompter; 053import org.codehaus.plexus.util.FileUtils; 054import org.codehaus.plexus.util.IOUtil; 055import org.codehaus.plexus.util.StringUtils; 056import org.codehaus.plexus.util.cli.CommandLineUtils; 057 058import java.io.DataInputStream; 059import java.io.File; 060import java.io.FileOutputStream; 061import java.io.FileWriter; 062import java.io.InputStream; 063import java.util.ArrayList; 064import java.util.HashMap; 065import java.util.List; 066import java.util.Map; 067import java.util.Properties; 068import java.util.StringTokenizer; 069 070/** 071 * Creates a prototype from the given KFS project resource. A KFS project resource can be either 072 * of the following: 073 * <ul> 074 * <li>KFS war file</li> 075 * <li>KFS project directory with source</li> 076 * <li>KFS svn repo</li> 077 * </ul> 078 * 079 * @author Leo Przybylski 080 */ 081@Component(role = org.kualigan.maven.plugins.api.PrototypeHelper.class, hint="default") 082public class DefaultPrototypeHelper implements PrototypeHelper { 083 public static final String ROLE_HINT = "default"; 084 085 private static final Options OPTIONS = new Options(); 086 087 private static final char SET_SYSTEM_PROPERTY = 'D'; 088 089 private static final char OFFLINE = 'o'; 090 091 private static final char REACTOR = 'r'; 092 093 private static final char QUIET = 'q'; 094 095 private static final char DEBUG = 'X'; 096 097 private static final char ERRORS = 'e'; 098 099 private static final char NON_RECURSIVE = 'N'; 100 101 private static final char UPDATE_SNAPSHOTS = 'U'; 102 103 private static final char ACTIVATE_PROFILES = 'P'; 104 105 private static final String FORCE_PLUGIN_UPDATES = "cpu"; 106 107 private static final String FORCE_PLUGIN_UPDATES2 = "up"; 108 109 private static final String SUPPRESS_PLUGIN_UPDATES = "npu"; 110 111 private static final String SUPPRESS_PLUGIN_REGISTRY = "npr"; 112 113 private static final char CHECKSUM_FAILURE_POLICY = 'C'; 114 115 private static final char CHECKSUM_WARNING_POLICY = 'c'; 116 117 private static final char ALTERNATE_USER_SETTINGS = 's'; 118 119 private static final String FAIL_FAST = "ff"; 120 121 private static final String FAIL_AT_END = "fae"; 122 123 private static final String FAIL_NEVER = "fn"; 124 125 private static final String ALTERNATE_POM_FILE = "f"; 126 127 /** 128 */ 129 @Requirement 130 protected Archetype archetype; 131 132 /** 133 */ 134 @Requirement 135 protected Prompter prompter; 136 137 /** 138 */ 139 @Requirement 140 protected ArtifactRepositoryFactory artifactRepositoryFactory; 141 142 /** 143 */ 144 @Requirement(role=org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout.class, hint="default") 145 protected ArtifactRepositoryLayout defaultArtifactRepositoryLayout; 146 147 @Requirement 148 protected ArchiverManager archiverManager; 149 150 private AbstractMojo caller; 151 152 protected void logUnpack(final File file, final File location, final String includes, final String excludes ) { 153 if (!getCaller().getLog().isInfoEnabled()) { 154 return; 155 } 156 157 StringBuffer msg = new StringBuffer(); 158 msg.append( "Unpacking " ); 159 msg.append( file ); 160 msg.append( " to " ); 161 msg.append( location ); 162 163 if ( includes != null && excludes != null ) { 164 msg.append( " with includes \"" ); 165 msg.append( includes ); 166 msg.append( "\" and excludes \"" ); 167 msg.append( excludes ); 168 msg.append( "\"" ); 169 } 170 else if (includes != null) { 171 msg.append( " with includes \"" ); 172 msg.append( includes ); 173 msg.append( "\"" ); 174 } 175 else if (excludes != null) { 176 msg.append( " with excludes \"" ); 177 msg.append( excludes ); 178 msg.append( "\"" ); 179 } 180 181 getCaller().getLog().info(msg.toString()); 182 } 183 184 /** 185 * Handles repacking of the war as a jar file with classes, etc... Basically makes a jar of everything 186 * in the war file's WEB-INF/classes path. 187 * 188 * @param file is the war file to repackage 189 * @return {@link File} instance of the repacked jar 190 */ 191 public File repack(final File file, final String artifactId) throws MojoExecutionException { 192 final File workingDirectory = new File(System.getProperty("java.io.tmpdir") + File.separator 193 + artifactId + "-repack"); 194 final File warDirectory = new File(workingDirectory, "war"); 195 final File repackDirectory = new File(workingDirectory, artifactId); 196 final File classesDirectory = new File(warDirectory, "WEB-INF/classes"); 197 final File retval = new File(workingDirectory, artifactId + ".jar"); 198 199 try { 200 workingDirectory.mkdirs(); 201 workingDirectory.mkdir(); 202 workingDirectory.deleteOnExit(); 203 warDirectory.mkdir(); 204 } 205 catch (Exception e) { 206 throw new MojoExecutionException("Unable to create working directory for repackaging", e); 207 } 208 209 unpack(file, warDirectory, "**/classes/**", null); 210 211 try { 212 FileUtils.copyDirectoryStructure(classesDirectory, repackDirectory); 213 } 214 catch (Exception e) { 215 throw new MojoExecutionException("Unable to copy files into the repack directory"); 216 } 217 218 try { 219 pack(retval, repackDirectory, "**/**", null); 220 } 221 catch (Exception e) { 222 throw new MojoExecutionException("Was unable to create the jar", e); 223 } 224 225 return retval; 226 } 227 228 /** 229 * Unpacks the archive file. 230 * 231 * @param file File to be unpacked. 232 * @param location Location where to put the unpacked files. 233 * @param includes Comma separated list of file patterns to include i.e. <code>**/.xml, 234 * **/*.properties</code> 235 * @param excludes Comma separated list of file patterns to exclude i.e. <code>**/*.xml, 236 * **/*.properties</code> 237 */ 238 protected void unpack(final File file, final File location, final String includes, final String excludes) throws MojoExecutionException { 239 try { 240 logUnpack(file, location, includes, excludes); 241 242 location.mkdirs(); 243 244 final UnArchiver unArchiver; 245 unArchiver = archiverManager.getUnArchiver(file); 246 unArchiver.setSourceFile(file); 247 unArchiver.setDestDirectory(location); 248 249 if (StringUtils.isNotEmpty(excludes) || StringUtils.isNotEmpty(includes)) { 250 final IncludeExcludeFileSelector[] selectors = 251 new IncludeExcludeFileSelector[]{ new IncludeExcludeFileSelector() }; 252 253 if (StringUtils.isNotEmpty( excludes )) { 254 selectors[0].setExcludes(excludes.split( "," )); 255 } 256 257 if (StringUtils.isNotEmpty( includes )) { 258 selectors[0].setIncludes(includes.split( "," )); 259 } 260 261 unArchiver.setFileSelectors(selectors); 262 } 263 264 unArchiver.extract(); 265 } 266 catch ( NoSuchArchiverException e ) { 267 throw new MojoExecutionException("Unknown archiver type", e); 268 } 269 catch (ArchiverException e) { 270 e.printStackTrace(); 271 throw new MojoExecutionException( 272 "Error unpacking file: " + file + " to: " + location + "\r\n" + e.toString(), e ); 273 } 274 } 275 276 /** 277 * Packs a jar 278 * 279 * @param file Destination file. 280 * @param location Directory source. 281 * @param includes Comma separated list of file patterns to include i.e. <code>**/.xml, 282 * **/*.properties</code> 283 * @param excludes Comma separated list of file patterns to exclude i.e. <code>**/*.xml, 284 * **/*.properties</code> 285 */ 286 protected void pack(final File file, final File location, final String includes, final String excludes) throws MojoExecutionException { 287 try { 288 289 final Archiver archiver; 290 archiver = archiverManager.getArchiver(file); 291 archiver.addFileSet(new DefaultFileSet() {{ 292 setDirectory(location); 293 if (includes != null) { 294 setIncludes(includes.split(",")); 295 } 296 if (excludes != null) { 297 setExcludes(excludes.split(",")); 298 } 299 }}); 300 archiver.setDestFile(file); 301 302 archiver.createArchive(); 303 } 304 catch ( NoSuchArchiverException e ) { 305 throw new MojoExecutionException("Unknown archiver type", e); 306 } 307 catch (Exception e) { 308 e.printStackTrace(); 309 throw new MojoExecutionException( 310 "Error packing directory: " + location + " to: " + file + "\r\n" + e.toString(), e ); 311 } 312 } 313 314 /** 315 * 316 */ 317 private void setupRequest(final InvocationRequest req, 318 final String additionalArguments) throws MojoExecutionException { 319 try { 320 final String[] args = CommandLineUtils.translateCommandline(additionalArguments); 321 CommandLine cli = new PosixParser().parse(OPTIONS, args); 322 323 if (cli.hasOption( SET_SYSTEM_PROPERTY)) 324 { 325 String[] properties = cli.getOptionValues( SET_SYSTEM_PROPERTY ); 326 Properties props = new Properties(); 327 for ( int i = 0; i < properties.length; i++ ) 328 { 329 String property = properties[i]; 330 String name, value; 331 int sep = property.indexOf( "=" ); 332 if ( sep <= 0 ) 333 { 334 name = property.trim(); 335 value = "true"; 336 } 337 else 338 { 339 name = property.substring( 0, sep ).trim(); 340 value = property.substring( sep + 1 ).trim(); 341 } 342 props.setProperty( name, value ); 343 } 344 345 req.setProperties( props ); 346 } 347 348 if ( cli.hasOption( OFFLINE ) ) 349 { 350 req.setOffline( true ); 351 } 352 353 if ( cli.hasOption( QUIET ) ) 354 { 355 // TODO: setQuiet() currently not supported by InvocationRequest 356 req.setDebug( false ); 357 } 358 else if ( cli.hasOption( DEBUG ) ) 359 { 360 req.setDebug( true ); 361 } 362 else if ( cli.hasOption( ERRORS ) ) 363 { 364 req.setShowErrors( true ); 365 } 366 367 if ( cli.hasOption( REACTOR ) ) 368 { 369 req.setRecursive( true ); 370 } 371 else if ( cli.hasOption( NON_RECURSIVE ) ) 372 { 373 req.setRecursive( false ); 374 } 375 376 if ( cli.hasOption( UPDATE_SNAPSHOTS ) ) 377 { 378 req.setUpdateSnapshots( true ); 379 } 380 381 if ( cli.hasOption( ACTIVATE_PROFILES ) ) 382 { 383 String[] profiles = cli.getOptionValues( ACTIVATE_PROFILES ); 384 List<String> activatedProfiles = new ArrayList<String>(); 385 List<String> deactivatedProfiles = new ArrayList<String>(); 386 387 if ( profiles != null ) 388 { 389 for ( int i = 0; i < profiles.length; ++i ) 390 { 391 StringTokenizer profileTokens = new StringTokenizer( profiles[i], "," ); 392 393 while ( profileTokens.hasMoreTokens() ) 394 { 395 String profileAction = profileTokens.nextToken().trim(); 396 397 if ( profileAction.startsWith( "-" ) || profileAction.startsWith( "!" ) ) 398 { 399 deactivatedProfiles.add( profileAction.substring( 1 ) ); 400 } 401 else if ( profileAction.startsWith( "+" ) ) 402 { 403 activatedProfiles.add( profileAction.substring( 1 ) ); 404 } 405 else 406 { 407 activatedProfiles.add( profileAction ); 408 } 409 } 410 } 411 } 412 413 if (!deactivatedProfiles.isEmpty()) { 414 getCaller().getLog().warn("Explicit profile deactivation is not yet supported. " 415 + "The following profiles will NOT be deactivated: " + StringUtils.join( 416 deactivatedProfiles.iterator(), ", ")); 417 } 418 419 if (!activatedProfiles.isEmpty()) { 420 req.setProfiles(activatedProfiles); 421 } 422 } 423 424 if (cli.hasOption(FORCE_PLUGIN_UPDATES) || cli.hasOption( FORCE_PLUGIN_UPDATES2)) { 425 getCaller().getLog().warn("Forcing plugin updates is not supported currently."); 426 } 427 else if (cli.hasOption( SUPPRESS_PLUGIN_UPDATES)) { 428 req.setNonPluginUpdates( true ); 429 } 430 431 if (cli.hasOption( SUPPRESS_PLUGIN_REGISTRY)) { 432 getCaller().getLog().warn("Explicit suppression of the plugin registry is not supported currently." ); 433 } 434 435 if (cli.hasOption(CHECKSUM_FAILURE_POLICY)) { 436 req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_FAIL ); 437 } 438 else if ( cli.hasOption( CHECKSUM_WARNING_POLICY ) ) 439 { 440 req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_WARN ); 441 } 442 443 if ( cli.hasOption( ALTERNATE_USER_SETTINGS ) ) 444 { 445 req.setUserSettingsFile( new File( cli.getOptionValue( ALTERNATE_USER_SETTINGS ) ) ); 446 } 447 448 if ( cli.hasOption( FAIL_AT_END ) ) 449 { 450 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_AT_END ); 451 } 452 else if ( cli.hasOption( FAIL_FAST ) ) 453 { 454 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_FAST ); 455 } 456 if ( cli.hasOption( FAIL_NEVER ) ) 457 { 458 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_NEVER ); 459 } 460 if ( cli.hasOption( ALTERNATE_POM_FILE ) ) 461 { 462 if (req.getPomFileName() != null) { 463 getCaller().getLog().info("pomFileName is already set, ignoring the -f argument" ); 464 } 465 else { 466 req.setPomFileName( cli.getOptionValue( ALTERNATE_POM_FILE ) ); 467 } 468 } 469 } 470 catch ( Exception e ) { 471 throw new MojoExecutionException("Failed to re-parse additional arguments for Maven invocation.", e ); 472 } 473 } 474 475 /** 476 * Executes the {@code install-file} goal with the new pom against the artifact file. 477 * 478 * @param artifact {@link File} instance to install 479 */ 480 public void installArtifact(final File artifact, 481 final File sources, 482 final File mavenHome, 483 final String groupId, 484 final String artifactId, 485 final String version, 486 final String repositoryId) throws MojoExecutionException { 487 final Invoker invoker = new DefaultInvoker().setMavenHome(mavenHome); 488 489 final String additionalArguments = ""; 490 491 final InvocationRequest req = new DefaultInvocationRequest() 492 .setInteractive(false) 493 .setProperties(new Properties() {{ 494 setProperty("groupId", groupId); 495 setProperty("artifactId", artifactId); 496 setProperty("version", version); 497 setProperty("packaging", artifact.getName().endsWith("jar") ? "jar" : "war"); 498 setProperty("pomFile", getTempPomPath()); 499 if (repositoryId != null) { 500 setProperty("repositoryId", repositoryId); 501 } 502 if (sources != null) { 503 try { 504 setProperty("sources", sources.getCanonicalPath()); 505 } 506 catch (Exception e) { 507 throw new MojoExecutionException("Cannot get path for the sources file ", e); 508 } 509 } 510 try { 511 setProperty("file", artifact.getCanonicalPath()); 512 } 513 catch (Exception e) { 514 throw new MojoExecutionException("Cannot get path for the war file ", e); 515 } 516 setProperty("updateReleaseInfo", "true"); 517 }}); 518 519 try { 520 setupRequest(req, additionalArguments); 521 522 if (repositoryId == null) { 523 req.setGoals(new ArrayList<String>() {{ add("install:install-file"); }}); 524 } 525 else { 526 req.setGoals(new ArrayList<String>() {{ add("deploy:deploy-file"); }}); 527 } 528 529 try { 530 final InvocationResult invocationResult = invoker.execute(req); 531 532 if ( invocationResult.getExecutionException() != null ) { 533 throw new MojoExecutionException("Error executing Maven.", 534 invocationResult.getExecutionException()); 535 } 536 537 if (invocationResult.getExitCode() != 0) { 538 throw new MojoExecutionException( 539 "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'"); 540 } 541 } 542 catch (MavenInvocationException e) { 543 throw new MojoExecutionException( "Failed to invoke Maven build.", e ); 544 } 545 } 546 finally { 547 /* 548 if ( settingsFile != null && settingsFile.exists() && !settingsFile.delete() ) 549 { 550 settingsFile.deleteOnExit(); 551 } 552 */ 553 } 554 } 555 556 /** 557 * Temporary POM location 558 * 559 * @return String value the path of the temporary POM 560 */ 561 protected String getTempPomPath() { 562 return System.getProperty("java.io.tmpdir") + File.separator + "prototype-pom.xml"; 563 } 564 565 /** 566 * Puts a POM file in the system temp directory for prototype-pom.xml. prototype-pom.xml is extracted 567 * from the plugin. 568 */ 569 public void extractTempPom() throws MojoExecutionException { 570 getCaller().getLog().info("Extracting the Temp POM"); 571 572 final InputStream pom_is = getClass().getClassLoader().getResourceAsStream("prototype-resources/pom.xml"); 573 574 byte[] fileBytes = null; 575 try { 576 final DataInputStream dis = new DataInputStream(pom_is); 577 fileBytes = new byte[dis.available()]; 578 dis.readFully(fileBytes); 579 dis.close(); 580 } 581 catch (Exception e) { 582 throw new MojoExecutionException("Wasn't able to read in the prototype pom", e); 583 } 584 finally { 585 try { 586 pom_is.close(); 587 } 588 catch (Exception e) { 589 // Ignore exceptions 590 } 591 } 592 593 try { 594 final FileOutputStream fos = new FileOutputStream(getTempPomPath()); 595 try { 596 fos.write(fileBytes); 597 } 598 finally { 599 fos.close(); 600 } 601 } 602 catch (Exception e) { 603 throw new MojoExecutionException("Could not write temporary pom file", e); 604 } 605 } 606 607 public void setCaller(final AbstractMojo caller) { 608 this.caller = caller; 609 } 610 611 public AbstractMojo getCaller() { 612 return this.caller; 613 } 614}