001package 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 022import static java.nio.charset.StandardCharsets.UTF_8; 023 024import java.io.File; 025import java.io.FileOutputStream; 026import java.io.IOException; 027import java.io.OutputStreamWriter; 028import java.io.Writer; 029import java.util.LinkedHashMap; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import org.apache.maven.plugin.descriptor.DuplicateMojoDescriptorException; 035import org.apache.maven.plugin.descriptor.MojoDescriptor; 036import org.apache.maven.plugin.descriptor.Parameter; 037import org.apache.maven.plugin.descriptor.PluginDescriptor; 038import org.apache.maven.plugin.descriptor.Requirement; 039import org.apache.maven.plugin.logging.Log; 040import org.apache.maven.project.MavenProject; 041import org.apache.maven.tools.plugin.ExtendedMojoDescriptor; 042import org.apache.maven.tools.plugin.PluginToolsRequest; 043import org.apache.maven.tools.plugin.util.PluginUtils; 044import org.codehaus.plexus.util.StringUtils; 045import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; 046import org.codehaus.plexus.util.xml.XMLWriter; 047 048/** 049 * Generate a <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a> and 050 * corresponding <code>plugin-help.xml</code> help content for {@link PluginHelpGenerator}. 051 * 052 */ 053public class PluginDescriptorGenerator 054 implements Generator 055{ 056 057 private final Log log; 058 059 public PluginDescriptorGenerator( Log log ) 060 { 061 this.log = log; 062 } 063 064 @Override 065 public void execute( File destinationDirectory, PluginToolsRequest request ) 066 throws GeneratorException 067 { 068 // eventually rewrite help mojo class to match actual package name 069 PluginHelpGenerator.rewriteHelpMojo( request, log ); 070 071 try 072 { 073 // write complete plugin.xml descriptor 074 File f = new File( destinationDirectory, "plugin.xml" ); 075 writeDescriptor( f, request, false ); 076 077 // write plugin-help.xml help-descriptor 078 MavenProject mavenProject = request.getProject(); 079 080 f = new File( destinationDirectory, 081 PluginHelpGenerator.getPluginHelpPath( mavenProject ) ); 082 083 writeDescriptor( f, request, true ); 084 } 085 catch ( IOException e ) 086 { 087 throw new GeneratorException( e.getMessage(), e ); 088 } 089 catch ( DuplicateMojoDescriptorException e ) 090 { 091 throw new GeneratorException( e.getMessage(), e ); 092 } 093 } 094 095 private String getVersion() 096 { 097 Package p = this.getClass().getPackage(); 098 String version = ( p == null ) ? null : p.getSpecificationVersion(); 099 return ( version == null ) ? "SNAPSHOT" : version; 100 } 101 102 public void writeDescriptor( File destinationFile, PluginToolsRequest request, boolean helpDescriptor ) 103 throws IOException, DuplicateMojoDescriptorException 104 { 105 PluginDescriptor pluginDescriptor = request.getPluginDescriptor(); 106 107 if ( destinationFile.exists() ) 108 { 109 destinationFile.delete(); 110 } 111 else if ( !destinationFile.getParentFile().exists() ) 112 { 113 destinationFile.getParentFile().mkdirs(); 114 } 115 116 try ( Writer writer = new OutputStreamWriter( new FileOutputStream( destinationFile ), UTF_8 ) ) 117 { 118 XMLWriter w = new PrettyPrintXMLWriter( writer, UTF_8.name(), null ); 119 120 w.writeMarkup( "\n<!-- Generated by maven-plugin-tools " + getVersion() + " -->\n\n" ); 121 122 w.startElement( "plugin" ); 123 124 GeneratorUtils.element( w, "name", pluginDescriptor.getName() ); 125 126 GeneratorUtils.element( w, "description", pluginDescriptor.getDescription(), helpDescriptor ); 127 128 GeneratorUtils.element( w, "groupId", pluginDescriptor.getGroupId() ); 129 130 GeneratorUtils.element( w, "artifactId", pluginDescriptor.getArtifactId() ); 131 132 GeneratorUtils.element( w, "version", pluginDescriptor.getVersion() ); 133 134 GeneratorUtils.element( w, "goalPrefix", pluginDescriptor.getGoalPrefix() ); 135 136 if ( !helpDescriptor ) 137 { 138 GeneratorUtils.element( w, "isolatedRealm", String.valueOf( pluginDescriptor.isIsolatedRealm() ) ); 139 140 GeneratorUtils.element( w, "inheritedByDefault", 141 String.valueOf( pluginDescriptor.isInheritedByDefault() ) ); 142 } 143 144 w.startElement( "mojos" ); 145 146 if ( pluginDescriptor.getMojos() != null ) 147 { 148 List<MojoDescriptor> descriptors = pluginDescriptor.getMojos(); 149 150 PluginUtils.sortMojos( descriptors ); 151 152 for ( MojoDescriptor descriptor : descriptors ) 153 { 154 processMojoDescriptor( descriptor, w, helpDescriptor ); 155 } 156 } 157 158 w.endElement(); 159 160 if ( !helpDescriptor ) 161 { 162 GeneratorUtils.writeDependencies( w, pluginDescriptor ); 163 } 164 165 w.endElement(); 166 167 writer.flush(); 168 169 } 170 } 171 172 protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w ) 173 { 174 processMojoDescriptor( mojoDescriptor, w, false ); 175 } 176 177 /** 178 * @param mojoDescriptor not null 179 * @param w not null 180 * @param helpDescriptor will clean html content from description fields 181 */ 182 @SuppressWarnings( "deprecation" ) 183 protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, boolean helpDescriptor ) 184 { 185 w.startElement( "mojo" ); 186 187 // ---------------------------------------------------------------------- 188 // 189 // ---------------------------------------------------------------------- 190 191 w.startElement( "goal" ); 192 w.writeText( mojoDescriptor.getGoal() ); 193 w.endElement(); 194 195 // ---------------------------------------------------------------------- 196 // 197 // ---------------------------------------------------------------------- 198 199 String description = mojoDescriptor.getDescription(); 200 201 if ( StringUtils.isNotEmpty( description ) ) 202 { 203 w.startElement( "description" ); 204 if ( helpDescriptor ) 205 { 206 w.writeText( GeneratorUtils.toText( mojoDescriptor.getDescription() ) ); 207 } 208 else 209 { 210 w.writeText( mojoDescriptor.getDescription() ); 211 } 212 w.endElement(); 213 } 214 215 // ---------------------------------------------------------------------- 216 // 217 // ---------------------------------------------------------------------- 218 219 if ( StringUtils.isNotEmpty( mojoDescriptor.isDependencyResolutionRequired() ) ) 220 { 221 GeneratorUtils.element( w, "requiresDependencyResolution", 222 mojoDescriptor.isDependencyResolutionRequired() ); 223 } 224 225 // ---------------------------------------------------------------------- 226 // 227 // ---------------------------------------------------------------------- 228 229 GeneratorUtils.element( w, "requiresDirectInvocation", 230 String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) ); 231 232 // ---------------------------------------------------------------------- 233 // 234 // ---------------------------------------------------------------------- 235 236 GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) ); 237 238 // ---------------------------------------------------------------------- 239 // 240 // ---------------------------------------------------------------------- 241 242 GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) ); 243 244 // ---------------------------------------------------------------------- 245 // 246 // ---------------------------------------------------------------------- 247 248 GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) ); 249 250 // ---------------------------------------------------------------------- 251 // 252 // ---------------------------------------------------------------------- 253 254 GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) ); 255 256 // ---------------------------------------------------------------------- 257 // 258 // ---------------------------------------------------------------------- 259 260 GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) ); 261 262 // ---------------------------------------------------------------------- 263 // 264 // ---------------------------------------------------------------------- 265 266 if ( StringUtils.isNotEmpty( mojoDescriptor.getPhase() ) ) 267 { 268 GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() ); 269 } 270 271 // ---------------------------------------------------------------------- 272 // 273 // ---------------------------------------------------------------------- 274 275 if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) ) 276 { 277 GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() ); 278 } 279 280 if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteGoal() ) ) 281 { 282 GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() ); 283 } 284 285 if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteLifecycle() ) ) 286 { 287 GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() ); 288 } 289 290 // ---------------------------------------------------------------------- 291 // 292 // ---------------------------------------------------------------------- 293 294 w.startElement( "implementation" ); 295 w.writeText( mojoDescriptor.getImplementation() ); 296 w.endElement(); 297 298 // ---------------------------------------------------------------------- 299 // 300 // ---------------------------------------------------------------------- 301 302 w.startElement( "language" ); 303 w.writeText( mojoDescriptor.getLanguage() ); 304 w.endElement(); 305 306 // ---------------------------------------------------------------------- 307 // 308 // ---------------------------------------------------------------------- 309 310 if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentConfigurator() ) ) 311 { 312 w.startElement( "configurator" ); 313 w.writeText( mojoDescriptor.getComponentConfigurator() ); 314 w.endElement(); 315 } 316 317 // ---------------------------------------------------------------------- 318 // 319 // ---------------------------------------------------------------------- 320 321 if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentComposer() ) ) 322 { 323 w.startElement( "composer" ); 324 w.writeText( mojoDescriptor.getComponentComposer() ); 325 w.endElement(); 326 } 327 328 // ---------------------------------------------------------------------- 329 // 330 // ---------------------------------------------------------------------- 331 332 w.startElement( "instantiationStrategy" ); 333 w.writeText( mojoDescriptor.getInstantiationStrategy() ); 334 w.endElement(); 335 336 // ---------------------------------------------------------------------- 337 // Strategy for handling repeated reference to mojo in 338 // the calculated (decorated, resolved) execution stack 339 // ---------------------------------------------------------------------- 340 w.startElement( "executionStrategy" ); 341 w.writeText( mojoDescriptor.getExecutionStrategy() ); 342 w.endElement(); 343 344 // ---------------------------------------------------------------------- 345 // 346 // ---------------------------------------------------------------------- 347 348 if ( mojoDescriptor.getSince() != null ) 349 { 350 w.startElement( "since" ); 351 352 if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) ) 353 { 354 w.writeText( "No version given" ); 355 } 356 else 357 { 358 w.writeText( mojoDescriptor.getSince() ); 359 } 360 361 w.endElement(); 362 } 363 364 // ---------------------------------------------------------------------- 365 // 366 // ---------------------------------------------------------------------- 367 368 if ( mojoDescriptor.getDeprecated() != null ) 369 { 370 w.startElement( "deprecated" ); 371 372 if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) ) 373 { 374 w.writeText( "No reason given" ); 375 } 376 else 377 { 378 w.writeText( mojoDescriptor.getDeprecated() ); 379 } 380 381 w.endElement(); 382 } 383 384 // ---------------------------------------------------------------------- 385 // Extended (3.0) descriptor 386 // ---------------------------------------------------------------------- 387 388 if ( mojoDescriptor instanceof ExtendedMojoDescriptor ) 389 { 390 ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor; 391 if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null ) 392 { 393 GeneratorUtils.element( w, "requiresDependencyCollection", 394 extendedMojoDescriptor.getDependencyCollectionRequired() ); 395 } 396 397 GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) ); 398 } 399 400 // ---------------------------------------------------------------------- 401 // Parameters 402 // ---------------------------------------------------------------------- 403 404 List<Parameter> parameters = mojoDescriptor.getParameters(); 405 406 w.startElement( "parameters" ); 407 408 Map<String, Requirement> requirements = new LinkedHashMap<>(); 409 410 Set<Parameter> configuration = new LinkedHashSet<>(); 411 412 if ( parameters != null ) 413 { 414 if ( helpDescriptor ) 415 { 416 PluginUtils.sortMojoParameters( parameters ); 417 } 418 419 for ( Parameter parameter : parameters ) 420 { 421 String expression = getExpression( parameter ); 422 423 if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) ) 424 { 425 // treat it as a component...a requirement, in other words. 426 427 // remove "component." plus expression delimiters 428 String role = expression.substring( "${component.".length(), expression.length() - 1 ); 429 430 String roleHint = null; 431 432 int posRoleHintSeparator = role.indexOf( '#' ); 433 if ( posRoleHintSeparator > 0 ) 434 { 435 roleHint = role.substring( posRoleHintSeparator + 1 ); 436 437 role = role.substring( 0, posRoleHintSeparator ); 438 } 439 440 // TODO: remove deprecated expression 441 requirements.put( parameter.getName(), new Requirement( role, roleHint ) ); 442 } 443 else if ( parameter.getRequirement() != null ) 444 { 445 requirements.put( parameter.getName(), parameter.getRequirement() ); 446 } 447 else if ( !helpDescriptor || parameter.isEditable() ) // don't show readonly parameters in help 448 { 449 // treat it as a normal parameter. 450 451 w.startElement( "parameter" ); 452 453 GeneratorUtils.element( w, "name", parameter.getName() ); 454 455 if ( parameter.getAlias() != null ) 456 { 457 GeneratorUtils.element( w, "alias", parameter.getAlias() ); 458 } 459 460 GeneratorUtils.element( w, "type", parameter.getType() ); 461 462 if ( parameter.getSince() != null ) 463 { 464 w.startElement( "since" ); 465 466 if ( StringUtils.isEmpty( parameter.getSince() ) ) 467 { 468 w.writeText( "No version given" ); 469 } 470 else 471 { 472 w.writeText( parameter.getSince() ); 473 } 474 475 w.endElement(); 476 } 477 478 if ( parameter.getDeprecated() != null ) 479 { 480 if ( StringUtils.isEmpty( parameter.getDeprecated() ) ) 481 { 482 GeneratorUtils.element( w, "deprecated", "No reason given" ); 483 } 484 else 485 { 486 GeneratorUtils.element( w, "deprecated", parameter.getDeprecated() ); 487 } 488 } 489 490 if ( parameter.getImplementation() != null ) 491 { 492 GeneratorUtils.element( w, "implementation", parameter.getImplementation() ); 493 } 494 495 GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) ); 496 497 GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) ); 498 499 GeneratorUtils.element( w, "description", parameter.getDescription(), helpDescriptor ); 500 501 if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) || StringUtils.isNotEmpty( 502 parameter.getExpression() ) ) 503 { 504 configuration.add( parameter ); 505 } 506 507 w.endElement(); 508 } 509 510 } 511 } 512 513 w.endElement(); 514 515 // ---------------------------------------------------------------------- 516 // Configuration 517 // ---------------------------------------------------------------------- 518 519 if ( !configuration.isEmpty() ) 520 { 521 w.startElement( "configuration" ); 522 523 for ( Parameter parameter : configuration ) 524 { 525 if ( helpDescriptor && !parameter.isEditable() ) 526 { 527 // don't show readonly parameters in help 528 continue; 529 } 530 531 w.startElement( parameter.getName() ); 532 533 String type = parameter.getType(); 534 if ( StringUtils.isNotEmpty( type ) ) 535 { 536 w.addAttribute( "implementation", type ); 537 } 538 539 if ( parameter.getDefaultValue() != null ) 540 { 541 w.addAttribute( "default-value", parameter.getDefaultValue() ); 542 } 543 544 if ( StringUtils.isNotEmpty( parameter.getExpression() ) ) 545 { 546 w.writeText( parameter.getExpression() ); 547 } 548 549 w.endElement(); 550 } 551 552 w.endElement(); 553 } 554 555 // ---------------------------------------------------------------------- 556 // Requirements 557 // ---------------------------------------------------------------------- 558 559 if ( !requirements.isEmpty() && !helpDescriptor ) 560 { 561 w.startElement( "requirements" ); 562 563 for ( Map.Entry<String, Requirement> entry : requirements.entrySet() ) 564 { 565 String key = entry.getKey(); 566 Requirement requirement = entry.getValue(); 567 568 w.startElement( "requirement" ); 569 570 GeneratorUtils.element( w, "role", requirement.getRole() ); 571 572 if ( StringUtils.isNotEmpty( requirement.getRoleHint() ) ) 573 { 574 GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() ); 575 } 576 577 GeneratorUtils.element( w, "field-name", key ); 578 579 w.endElement(); 580 } 581 582 w.endElement(); 583 } 584 585 w.endElement(); 586 } 587 588 /** 589 * Get the expression value, eventually surrounding it with <code>${ }</code>. 590 * 591 * @param parameter the parameter 592 * @return the expression value 593 */ 594 private String getExpression( Parameter parameter ) 595 { 596 String expression = parameter.getExpression(); 597 if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) ) 598 { 599 expression = "${" + expression.trim() + "}"; 600 parameter.setExpression( expression ); 601 } 602 return expression; 603 } 604}