001 package org.apache.maven.tools.plugin.generator; 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 022 import org.apache.maven.plugin.descriptor.DuplicateMojoDescriptorException; 023 import org.apache.maven.plugin.descriptor.MojoDescriptor; 024 import org.apache.maven.plugin.descriptor.Parameter; 025 import org.apache.maven.plugin.descriptor.PluginDescriptor; 026 import org.apache.maven.plugin.descriptor.Requirement; 027 import org.apache.maven.project.MavenProject; 028 import org.apache.maven.tools.plugin.ExtendedMojoDescriptor; 029 import org.apache.maven.tools.plugin.PluginToolsRequest; 030 import org.apache.maven.tools.plugin.util.PluginUtils; 031 import org.codehaus.plexus.util.IOUtil; 032 import org.codehaus.plexus.util.PropertyUtils; 033 import org.codehaus.plexus.util.StringUtils; 034 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; 035 import org.codehaus.plexus.util.xml.XMLWriter; 036 import org.objectweb.asm.ClassReader; 037 import org.objectweb.asm.ClassVisitor; 038 import org.objectweb.asm.ClassWriter; 039 import org.objectweb.asm.commons.RemappingClassAdapter; 040 import org.objectweb.asm.commons.SimpleRemapper; 041 042 import java.io.File; 043 import java.io.FileInputStream; 044 import java.io.FileOutputStream; 045 import java.io.IOException; 046 import java.io.OutputStreamWriter; 047 import java.io.Writer; 048 import java.text.SimpleDateFormat; 049 import java.util.Date; 050 import java.util.LinkedHashMap; 051 import java.util.LinkedHashSet; 052 import java.util.List; 053 import java.util.Map; 054 import java.util.Properties; 055 import java.util.Set; 056 057 /** 058 * Generate a <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a> and 059 * corresponding help content. 060 * 061 * @version $Id: PluginDescriptorGenerator.java 1343173 2012-05-28 09:30:23Z hboutemy $ 062 * @todo add example usage tag that can be shown in the doco 063 * @todo need to add validation directives so that systems embedding maven2 can 064 * get validation directives to help users in IDEs. 065 */ 066 public class PluginDescriptorGenerator 067 implements Generator 068 { 069 070 /** 071 * {@inheritDoc} 072 */ 073 public void execute( File destinationDirectory, PluginToolsRequest request ) 074 throws GeneratorException 075 { 076 077 File tmpPropertiesFile = 078 new File( request.getProject().getBuild().getDirectory(), "maven-plugin-help.properties" ); 079 080 if ( tmpPropertiesFile.exists() ) 081 { 082 Properties properties = PropertyUtils.loadProperties( tmpPropertiesFile ); 083 084 String helpPackageName = properties.getProperty( "helpPackageName" ); 085 086 // if helpPackageName property is empty we have to rewrite the class with a better package name than empty 087 if ( StringUtils.isEmpty( helpPackageName ) ) 088 { 089 String helpMojoImplementation = rewriteHelpClassToMojoPackage( request ); 090 if ( helpMojoImplementation != null ) 091 { 092 // rewrite plugin descriptor with new HelpMojo implementation class 093 rewriteDescriptor( request.getPluginDescriptor(), helpMojoImplementation ); 094 } 095 096 } 097 } 098 099 try 100 { 101 // write complete plugin.xml descriptor 102 File f = new File( destinationDirectory, "plugin.xml" ); 103 writeDescriptor( f, request, false ); 104 105 // write plugin-help.xml help-descriptor 106 MavenProject mavenProject = request.getProject(); 107 String pluginHelpFilePath = 108 "META-INF/maven/" + mavenProject.getGroupId() + "/" + mavenProject.getArtifactId() 109 + "/plugin-help.xml"; 110 f = new File( request.getProject().getBuild().getOutputDirectory(), pluginHelpFilePath ); 111 writeDescriptor( f, request, true ); 112 } 113 catch ( IOException e ) 114 { 115 throw new GeneratorException( e.getMessage(), e ); 116 } 117 catch ( DuplicateMojoDescriptorException e ) 118 { 119 throw new GeneratorException( e.getMessage(), e ); 120 } 121 } 122 123 private String getVersion() 124 { 125 Package p = this.getClass().getPackage(); 126 String version = ( p == null ) ? null : p.getSpecificationVersion(); 127 return ( version == null ) ? "SNAPSHOT" : version; 128 } 129 130 public void writeDescriptor( File destinationFile, PluginToolsRequest request, boolean helpDescriptor ) 131 throws IOException, DuplicateMojoDescriptorException 132 { 133 PluginDescriptor pluginDescriptor = request.getPluginDescriptor(); 134 135 if ( destinationFile.exists() ) 136 { 137 destinationFile.delete(); 138 } 139 else 140 { 141 if ( !destinationFile.getParentFile().exists() ) 142 { 143 destinationFile.getParentFile().mkdirs(); 144 } 145 } 146 147 String encoding = "UTF-8"; 148 149 Writer writer = null; 150 try 151 { 152 writer = new OutputStreamWriter( new FileOutputStream( destinationFile ), encoding ); 153 154 XMLWriter w = new PrettyPrintXMLWriter( writer, encoding, null ); 155 156 w.writeMarkup( "\n<!-- Generated by maven-plugin-tools " + getVersion() + " on " 157 + new SimpleDateFormat( "yyyy-MM-dd" ).format( new Date() ) + " -->\n\n" ); 158 159 w.startElement( "plugin" ); 160 161 GeneratorUtils.element( w, "name", pluginDescriptor.getName() ); 162 163 GeneratorUtils.element( w, "description", pluginDescriptor.getDescription(), helpDescriptor ); 164 165 GeneratorUtils.element( w, "groupId", pluginDescriptor.getGroupId() ); 166 167 GeneratorUtils.element( w, "artifactId", pluginDescriptor.getArtifactId() ); 168 169 GeneratorUtils.element( w, "version", pluginDescriptor.getVersion() ); 170 171 GeneratorUtils.element( w, "goalPrefix", pluginDescriptor.getGoalPrefix() ); 172 173 if ( !helpDescriptor ) 174 { 175 GeneratorUtils.element( w, "isolatedRealm", String.valueOf( pluginDescriptor.isIsolatedRealm() ) ); 176 177 GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( pluginDescriptor.isInheritedByDefault() ) ); 178 } 179 180 w.startElement( "mojos" ); 181 182 if ( pluginDescriptor.getMojos() != null ) 183 { 184 @SuppressWarnings( "unchecked" ) List<MojoDescriptor> descriptors = pluginDescriptor.getMojos(); 185 186 if ( helpDescriptor ) 187 { 188 PluginUtils.sortMojos( descriptors ); 189 } 190 191 for ( MojoDescriptor descriptor : descriptors ) 192 { 193 processMojoDescriptor( descriptor, w, helpDescriptor ); 194 } 195 } 196 197 w.endElement(); 198 199 if ( !helpDescriptor ) 200 { 201 GeneratorUtils.writeDependencies( w, pluginDescriptor ); 202 } 203 204 w.endElement(); 205 206 writer.flush(); 207 208 } 209 finally 210 { 211 IOUtil.close( writer ); 212 } 213 } 214 215 protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w ) 216 { 217 processMojoDescriptor( mojoDescriptor, w, false ); 218 } 219 220 /** 221 * @param mojoDescriptor not null 222 * @param w not null 223 * @param helpDescriptor will clean html content from description fields 224 */ 225 protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, boolean helpDescriptor ) 226 { 227 w.startElement( "mojo" ); 228 229 // ---------------------------------------------------------------------- 230 // 231 // ---------------------------------------------------------------------- 232 233 w.startElement( "goal" ); 234 w.writeText( mojoDescriptor.getGoal() ); 235 w.endElement(); 236 237 // ---------------------------------------------------------------------- 238 // 239 // ---------------------------------------------------------------------- 240 241 String description = mojoDescriptor.getDescription(); 242 243 if ( description != null ) 244 { 245 w.startElement( "description" ); 246 if ( helpDescriptor ) 247 { 248 w.writeText( GeneratorUtils.toText( mojoDescriptor.getDescription() ) ); 249 } 250 else 251 { 252 w.writeText( mojoDescriptor.getDescription() ); 253 } 254 w.endElement(); 255 } 256 257 // ---------------------------------------------------------------------- 258 // 259 // ---------------------------------------------------------------------- 260 261 if ( mojoDescriptor.isDependencyResolutionRequired() != null ) 262 { 263 GeneratorUtils.element( w, "requiresDependencyResolution", mojoDescriptor.isDependencyResolutionRequired() ); 264 } 265 266 // ---------------------------------------------------------------------- 267 // 268 // ---------------------------------------------------------------------- 269 270 GeneratorUtils.element( w, "requiresDirectInvocation", String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) ); 271 272 // ---------------------------------------------------------------------- 273 // 274 // ---------------------------------------------------------------------- 275 276 GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) ); 277 278 // ---------------------------------------------------------------------- 279 // 280 // ---------------------------------------------------------------------- 281 282 GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) ); 283 284 // ---------------------------------------------------------------------- 285 // 286 // ---------------------------------------------------------------------- 287 288 GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) ); 289 290 // ---------------------------------------------------------------------- 291 // 292 // ---------------------------------------------------------------------- 293 294 GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) ); 295 296 // ---------------------------------------------------------------------- 297 // 298 // ---------------------------------------------------------------------- 299 300 GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) ); 301 302 // ---------------------------------------------------------------------- 303 // 304 // ---------------------------------------------------------------------- 305 306 if ( mojoDescriptor.getPhase() != null ) 307 { 308 GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() ); 309 } 310 311 // ---------------------------------------------------------------------- 312 // 313 // ---------------------------------------------------------------------- 314 315 if ( mojoDescriptor.getExecutePhase() != null ) 316 { 317 GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() ); 318 } 319 320 if ( mojoDescriptor.getExecuteGoal() != null ) 321 { 322 GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() ); 323 } 324 325 if ( mojoDescriptor.getExecuteLifecycle() != null ) 326 { 327 GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() ); 328 } 329 330 // ---------------------------------------------------------------------- 331 // 332 // ---------------------------------------------------------------------- 333 334 w.startElement( "implementation" ); 335 w.writeText( mojoDescriptor.getImplementation() ); 336 w.endElement(); 337 338 // ---------------------------------------------------------------------- 339 // 340 // ---------------------------------------------------------------------- 341 342 w.startElement( "language" ); 343 w.writeText( mojoDescriptor.getLanguage() ); 344 w.endElement(); 345 346 // ---------------------------------------------------------------------- 347 // 348 // ---------------------------------------------------------------------- 349 350 if ( mojoDescriptor.getComponentConfigurator() != null ) 351 { 352 w.startElement( "configurator" ); 353 w.writeText( mojoDescriptor.getComponentConfigurator() ); 354 w.endElement(); 355 } 356 357 // ---------------------------------------------------------------------- 358 // 359 // ---------------------------------------------------------------------- 360 361 if ( mojoDescriptor.getComponentComposer() != null ) 362 { 363 w.startElement( "composer" ); 364 w.writeText( mojoDescriptor.getComponentComposer() ); 365 w.endElement(); 366 } 367 368 // ---------------------------------------------------------------------- 369 // 370 // ---------------------------------------------------------------------- 371 372 w.startElement( "instantiationStrategy" ); 373 w.writeText( mojoDescriptor.getInstantiationStrategy() ); 374 w.endElement(); 375 376 // ---------------------------------------------------------------------- 377 // Strategy for handling repeated reference to mojo in 378 // the calculated (decorated, resolved) execution stack 379 // ---------------------------------------------------------------------- 380 w.startElement( "executionStrategy" ); 381 w.writeText( mojoDescriptor.getExecutionStrategy() ); 382 w.endElement(); 383 384 // ---------------------------------------------------------------------- 385 // 386 // ---------------------------------------------------------------------- 387 388 if ( mojoDescriptor.getSince() != null ) 389 { 390 w.startElement( "since" ); 391 392 if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) ) 393 { 394 w.writeText( "No version given" ); 395 } 396 else 397 { 398 w.writeText( mojoDescriptor.getSince() ); 399 } 400 401 w.endElement(); 402 } 403 404 // ---------------------------------------------------------------------- 405 // 406 // ---------------------------------------------------------------------- 407 408 if ( mojoDescriptor.getDeprecated() != null ) 409 { 410 w.startElement( "deprecated" ); 411 412 if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) ) 413 { 414 w.writeText( "No reason given" ); 415 } 416 else 417 { 418 w.writeText( mojoDescriptor.getDeprecated() ); 419 } 420 421 w.endElement(); 422 } 423 424 // ---------------------------------------------------------------------- 425 // Extended (3.0) descriptor 426 // ---------------------------------------------------------------------- 427 428 if ( mojoDescriptor instanceof ExtendedMojoDescriptor ) 429 { 430 ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor; 431 if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null ) 432 { 433 GeneratorUtils.element( w, "requiresDependencyCollection", 434 extendedMojoDescriptor.getDependencyCollectionRequired() ); 435 } 436 437 GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) ); 438 } 439 440 // ---------------------------------------------------------------------- 441 // Parameters 442 // ---------------------------------------------------------------------- 443 444 @SuppressWarnings( "unchecked" ) List<Parameter> parameters = mojoDescriptor.getParameters(); 445 446 w.startElement( "parameters" ); 447 448 Map<String, Requirement> requirements = new LinkedHashMap<String, Requirement>(); 449 450 Set<Parameter> configuration = new LinkedHashSet<Parameter>(); 451 452 if ( parameters != null ) 453 { 454 if ( helpDescriptor ) 455 { 456 PluginUtils.sortMojoParameters( parameters ); 457 } 458 459 for ( Parameter parameter : parameters ) 460 { 461 String expression = getExpression( parameter ); 462 463 if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) ) 464 { 465 // treat it as a component...a requirement, in other words. 466 467 // remove "component." plus expression delimiters 468 String role = expression.substring( "${component.".length(), expression.length() - 1 ); 469 470 String roleHint = null; 471 472 int posRoleHintSeparator = role.indexOf( "#" ); 473 if ( posRoleHintSeparator > 0 ) 474 { 475 roleHint = role.substring( posRoleHintSeparator + 1 ); 476 477 role = role.substring( 0, posRoleHintSeparator ); 478 } 479 480 // TODO: remove deprecated expression 481 requirements.put( parameter.getName(), new Requirement( role, roleHint ) ); 482 } 483 else if ( parameter.getRequirement() != null ) 484 { 485 requirements.put( parameter.getName(), parameter.getRequirement() ); 486 } 487 else if ( !helpDescriptor || parameter.isEditable() ) // don't show readonly parameters in help 488 { 489 // treat it as a normal parameter. 490 491 w.startElement( "parameter" ); 492 493 GeneratorUtils.element( w, "name", parameter.getName() ); 494 495 if ( parameter.getAlias() != null ) 496 { 497 GeneratorUtils.element( w, "alias", parameter.getAlias() ); 498 } 499 500 GeneratorUtils.element( w, "type", parameter.getType() ); 501 502 if ( parameter.getSince() != null ) 503 { 504 w.startElement( "since" ); 505 506 if ( StringUtils.isEmpty( parameter.getSince() ) ) 507 { 508 w.writeText( "No version given" ); 509 } 510 else 511 { 512 w.writeText( parameter.getSince() ); 513 } 514 515 w.endElement(); 516 } 517 518 if ( parameter.getDeprecated() != null ) 519 { 520 if ( StringUtils.isEmpty( parameter.getDeprecated() ) ) 521 { 522 GeneratorUtils.element( w, "deprecated", "No reason given" ); 523 } 524 else 525 { 526 GeneratorUtils.element( w, "deprecated", parameter.getDeprecated() ); 527 } 528 } 529 530 if ( parameter.getImplementation() != null ) 531 { 532 GeneratorUtils.element( w, "implementation", parameter.getImplementation() ); 533 } 534 535 GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) ); 536 537 GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) ); 538 539 GeneratorUtils.element( w, "description", parameter.getDescription(), helpDescriptor ); 540 541 if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) 542 || StringUtils.isNotEmpty( parameter.getExpression() ) ) 543 { 544 configuration.add( parameter ); 545 } 546 547 w.endElement(); 548 } 549 550 } 551 } 552 553 w.endElement(); 554 555 // ---------------------------------------------------------------------- 556 // Configuration 557 // ---------------------------------------------------------------------- 558 559 if ( !configuration.isEmpty() ) 560 { 561 w.startElement( "configuration" ); 562 563 for ( Parameter parameter : configuration ) 564 { 565 if ( helpDescriptor && !parameter.isEditable() ) 566 { 567 // don't show readonly parameters in help 568 continue; 569 } 570 571 w.startElement( parameter.getName() ); 572 573 String type = parameter.getType(); 574 if ( type != null ) 575 { 576 w.addAttribute( "implementation", type ); 577 } 578 579 if ( parameter.getDefaultValue() != null ) 580 { 581 w.addAttribute( "default-value", parameter.getDefaultValue() ); 582 } 583 584 if ( parameter.getExpression() != null ) 585 { 586 w.writeText( parameter.getExpression() ); 587 } 588 589 w.endElement(); 590 } 591 592 w.endElement(); 593 } 594 595 // ---------------------------------------------------------------------- 596 // Requirements 597 // ---------------------------------------------------------------------- 598 599 if ( !requirements.isEmpty() && !helpDescriptor ) 600 { 601 w.startElement( "requirements" ); 602 603 for ( Map.Entry<String, Requirement> entry : requirements.entrySet() ) 604 { 605 String key = entry.getKey(); 606 Requirement requirement = entry.getValue(); 607 608 w.startElement( "requirement" ); 609 610 GeneratorUtils.element( w, "role", requirement.getRole() ); 611 612 if ( requirement.getRoleHint() != null ) 613 { 614 GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() ); 615 } 616 617 GeneratorUtils.element( w, "field-name", key ); 618 619 w.endElement(); 620 } 621 622 w.endElement(); 623 } 624 625 w.endElement(); 626 } 627 628 /** 629 * Get the expression value, eventually surrounding it with <code>${ }</code>. 630 * 631 * @param parameter the parameter 632 * @return the expression value 633 */ 634 private String getExpression( Parameter parameter ) 635 { 636 String expression = parameter.getExpression(); 637 if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) ) 638 { 639 expression = "${" + expression.trim() + "}"; 640 parameter.setExpression( expression ); 641 } 642 return expression; 643 } 644 645 protected String rewriteHelpClassToMojoPackage( PluginToolsRequest request ) 646 throws GeneratorException 647 { 648 String destinationPackage = GeneratorUtils.discoverPackageName( request.getPluginDescriptor() ); 649 if ( StringUtils.isEmpty( destinationPackage ) ) 650 { 651 return null; 652 } 653 File helpClassFile = new File( request.getProject().getBuild().getOutputDirectory(), "HelpMojo.class" ); 654 if ( !helpClassFile.exists() ) 655 { 656 return null; 657 } 658 File rewriteHelpClassFile = new File( 659 request.getProject().getBuild().getOutputDirectory() + "/" + StringUtils.replace( destinationPackage, ".", 660 "/" ), "HelpMojo.class" ); 661 if ( !rewriteHelpClassFile.getParentFile().exists() ) 662 { 663 rewriteHelpClassFile.getParentFile().mkdirs(); 664 } 665 666 ClassReader cr = null; 667 try 668 { 669 cr = new ClassReader( new FileInputStream( helpClassFile ) ); 670 } 671 catch ( IOException e ) 672 { 673 throw new GeneratorException( e.getMessage(), e ); 674 } 675 676 ClassWriter cw = new ClassWriter( 0 ); 677 678 ClassVisitor cv = new RemappingClassAdapter( cw, new SimpleRemapper( "HelpMojo", 679 StringUtils.replace( destinationPackage, 680 ".", "/" ) 681 + "/HelpMojo" ) ); 682 683 try 684 { 685 cr.accept( cv, ClassReader.EXPAND_FRAMES ); 686 } 687 catch ( Throwable e ) 688 { 689 throw new GeneratorException( "ASM issue processing classFile " + helpClassFile.getPath(), e ); 690 } 691 692 byte[] renamedClass = cw.toByteArray(); 693 FileOutputStream fos = null; 694 try 695 { 696 fos = new FileOutputStream( rewriteHelpClassFile ); 697 fos.write( renamedClass ); 698 } 699 catch ( IOException e ) 700 { 701 throw new GeneratorException( "Error rewriting help class: " + e.getMessage(), e ); 702 } 703 finally 704 { 705 IOUtil.close( fos ); 706 } 707 helpClassFile.delete(); 708 return destinationPackage + ".HelpMojo"; 709 } 710 711 712 private void rewriteDescriptor( PluginDescriptor pluginDescriptor, String helpMojoImplementation ) 713 { 714 MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( "help" ); 715 if ( mojoDescriptor != null ) 716 { 717 mojoDescriptor.setImplementation( helpMojoImplementation ); 718 } 719 } 720 }