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 protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, boolean helpDescriptor ) 183 { 184 w.startElement( "mojo" ); 185 186 // ---------------------------------------------------------------------- 187 // 188 // ---------------------------------------------------------------------- 189 190 w.startElement( "goal" ); 191 w.writeText( mojoDescriptor.getGoal() ); 192 w.endElement(); 193 194 // ---------------------------------------------------------------------- 195 // 196 // ---------------------------------------------------------------------- 197 198 String description = mojoDescriptor.getDescription(); 199 200 if ( StringUtils.isNotEmpty( description ) ) 201 { 202 w.startElement( "description" ); 203 if ( helpDescriptor ) 204 { 205 w.writeText( GeneratorUtils.toText( mojoDescriptor.getDescription() ) ); 206 } 207 else 208 { 209 w.writeText( mojoDescriptor.getDescription() ); 210 } 211 w.endElement(); 212 } 213 214 // ---------------------------------------------------------------------- 215 // 216 // ---------------------------------------------------------------------- 217 218 if ( StringUtils.isNotEmpty( mojoDescriptor.isDependencyResolutionRequired() ) ) 219 { 220 GeneratorUtils.element( w, "requiresDependencyResolution", 221 mojoDescriptor.isDependencyResolutionRequired() ); 222 } 223 224 // ---------------------------------------------------------------------- 225 // 226 // ---------------------------------------------------------------------- 227 228 GeneratorUtils.element( w, "requiresDirectInvocation", 229 String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) ); 230 231 // ---------------------------------------------------------------------- 232 // 233 // ---------------------------------------------------------------------- 234 235 GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) ); 236 237 // ---------------------------------------------------------------------- 238 // 239 // ---------------------------------------------------------------------- 240 241 GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) ); 242 243 // ---------------------------------------------------------------------- 244 // 245 // ---------------------------------------------------------------------- 246 247 GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) ); 248 249 // ---------------------------------------------------------------------- 250 // 251 // ---------------------------------------------------------------------- 252 253 GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) ); 254 255 // ---------------------------------------------------------------------- 256 // 257 // ---------------------------------------------------------------------- 258 259 GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) ); 260 261 // ---------------------------------------------------------------------- 262 // 263 // ---------------------------------------------------------------------- 264 265 if ( StringUtils.isNotEmpty( mojoDescriptor.getPhase() ) ) 266 { 267 GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() ); 268 } 269 270 // ---------------------------------------------------------------------- 271 // 272 // ---------------------------------------------------------------------- 273 274 if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) ) 275 { 276 GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() ); 277 } 278 279 if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteGoal() ) ) 280 { 281 GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() ); 282 } 283 284 if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteLifecycle() ) ) 285 { 286 GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() ); 287 } 288 289 // ---------------------------------------------------------------------- 290 // 291 // ---------------------------------------------------------------------- 292 293 w.startElement( "implementation" ); 294 w.writeText( mojoDescriptor.getImplementation() ); 295 w.endElement(); 296 297 // ---------------------------------------------------------------------- 298 // 299 // ---------------------------------------------------------------------- 300 301 w.startElement( "language" ); 302 w.writeText( mojoDescriptor.getLanguage() ); 303 w.endElement(); 304 305 // ---------------------------------------------------------------------- 306 // 307 // ---------------------------------------------------------------------- 308 309 if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentConfigurator() ) ) 310 { 311 w.startElement( "configurator" ); 312 w.writeText( mojoDescriptor.getComponentConfigurator() ); 313 w.endElement(); 314 } 315 316 // ---------------------------------------------------------------------- 317 // 318 // ---------------------------------------------------------------------- 319 320 if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentComposer() ) ) 321 { 322 w.startElement( "composer" ); 323 w.writeText( mojoDescriptor.getComponentComposer() ); 324 w.endElement(); 325 } 326 327 // ---------------------------------------------------------------------- 328 // 329 // ---------------------------------------------------------------------- 330 331 w.startElement( "instantiationStrategy" ); 332 w.writeText( mojoDescriptor.getInstantiationStrategy() ); 333 w.endElement(); 334 335 // ---------------------------------------------------------------------- 336 // Strategy for handling repeated reference to mojo in 337 // the calculated (decorated, resolved) execution stack 338 // ---------------------------------------------------------------------- 339 w.startElement( "executionStrategy" ); 340 w.writeText( mojoDescriptor.getExecutionStrategy() ); 341 w.endElement(); 342 343 // ---------------------------------------------------------------------- 344 // 345 // ---------------------------------------------------------------------- 346 347 if ( mojoDescriptor.getSince() != null ) 348 { 349 w.startElement( "since" ); 350 351 if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) ) 352 { 353 w.writeText( "No version given" ); 354 } 355 else 356 { 357 w.writeText( mojoDescriptor.getSince() ); 358 } 359 360 w.endElement(); 361 } 362 363 // ---------------------------------------------------------------------- 364 // 365 // ---------------------------------------------------------------------- 366 367 if ( mojoDescriptor.getDeprecated() != null ) 368 { 369 w.startElement( "deprecated" ); 370 371 if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) ) 372 { 373 w.writeText( "No reason given" ); 374 } 375 else 376 { 377 w.writeText( mojoDescriptor.getDeprecated() ); 378 } 379 380 w.endElement(); 381 } 382 383 // ---------------------------------------------------------------------- 384 // Extended (3.0) descriptor 385 // ---------------------------------------------------------------------- 386 387 if ( mojoDescriptor instanceof ExtendedMojoDescriptor ) 388 { 389 ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor; 390 if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null ) 391 { 392 GeneratorUtils.element( w, "requiresDependencyCollection", 393 extendedMojoDescriptor.getDependencyCollectionRequired() ); 394 } 395 396 GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) ); 397 } 398 399 // ---------------------------------------------------------------------- 400 // Parameters 401 // ---------------------------------------------------------------------- 402 403 List<Parameter> parameters = mojoDescriptor.getParameters(); 404 405 w.startElement( "parameters" ); 406 407 Map<String, Requirement> requirements = new LinkedHashMap<>(); 408 409 Set<Parameter> configuration = new LinkedHashSet<>(); 410 411 if ( parameters != null ) 412 { 413 if ( helpDescriptor ) 414 { 415 PluginUtils.sortMojoParameters( parameters ); 416 } 417 418 for ( Parameter parameter : parameters ) 419 { 420 String expression = getExpression( parameter ); 421 422 if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) ) 423 { 424 // treat it as a component...a requirement, in other words. 425 426 // remove "component." plus expression delimiters 427 String role = expression.substring( "${component.".length(), expression.length() - 1 ); 428 429 String roleHint = null; 430 431 int posRoleHintSeparator = role.indexOf( '#' ); 432 if ( posRoleHintSeparator > 0 ) 433 { 434 roleHint = role.substring( posRoleHintSeparator + 1 ); 435 436 role = role.substring( 0, posRoleHintSeparator ); 437 } 438 439 // TODO: remove deprecated expression 440 requirements.put( parameter.getName(), new Requirement( role, roleHint ) ); 441 } 442 else if ( parameter.getRequirement() != null ) 443 { 444 requirements.put( parameter.getName(), parameter.getRequirement() ); 445 } 446 else if ( !helpDescriptor || parameter.isEditable() ) // don't show readonly parameters in help 447 { 448 // treat it as a normal parameter. 449 450 w.startElement( "parameter" ); 451 452 GeneratorUtils.element( w, "name", parameter.getName() ); 453 454 if ( parameter.getAlias() != null ) 455 { 456 GeneratorUtils.element( w, "alias", parameter.getAlias() ); 457 } 458 459 GeneratorUtils.element( w, "type", parameter.getType() ); 460 461 if ( parameter.getSince() != null ) 462 { 463 w.startElement( "since" ); 464 465 if ( StringUtils.isEmpty( parameter.getSince() ) ) 466 { 467 w.writeText( "No version given" ); 468 } 469 else 470 { 471 w.writeText( parameter.getSince() ); 472 } 473 474 w.endElement(); 475 } 476 477 if ( parameter.getDeprecated() != null ) 478 { 479 if ( StringUtils.isEmpty( parameter.getDeprecated() ) ) 480 { 481 GeneratorUtils.element( w, "deprecated", "No reason given" ); 482 } 483 else 484 { 485 GeneratorUtils.element( w, "deprecated", parameter.getDeprecated() ); 486 } 487 } 488 489 if ( parameter.getImplementation() != null ) 490 { 491 GeneratorUtils.element( w, "implementation", parameter.getImplementation() ); 492 } 493 494 GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) ); 495 496 GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) ); 497 498 GeneratorUtils.element( w, "description", parameter.getDescription(), helpDescriptor ); 499 500 if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) || StringUtils.isNotEmpty( 501 parameter.getExpression() ) ) 502 { 503 configuration.add( parameter ); 504 } 505 506 w.endElement(); 507 } 508 509 } 510 } 511 512 w.endElement(); 513 514 // ---------------------------------------------------------------------- 515 // Configuration 516 // ---------------------------------------------------------------------- 517 518 if ( !configuration.isEmpty() ) 519 { 520 w.startElement( "configuration" ); 521 522 for ( Parameter parameter : configuration ) 523 { 524 if ( helpDescriptor && !parameter.isEditable() ) 525 { 526 // don't show readonly parameters in help 527 continue; 528 } 529 530 w.startElement( parameter.getName() ); 531 532 String type = parameter.getType(); 533 if ( StringUtils.isNotEmpty( type ) ) 534 { 535 w.addAttribute( "implementation", type ); 536 } 537 538 if ( parameter.getDefaultValue() != null ) 539 { 540 w.addAttribute( "default-value", parameter.getDefaultValue() ); 541 } 542 543 if ( StringUtils.isNotEmpty( parameter.getExpression() ) ) 544 { 545 w.writeText( parameter.getExpression() ); 546 } 547 548 w.endElement(); 549 } 550 551 w.endElement(); 552 } 553 554 // ---------------------------------------------------------------------- 555 // Requirements 556 // ---------------------------------------------------------------------- 557 558 if ( !requirements.isEmpty() && !helpDescriptor ) 559 { 560 w.startElement( "requirements" ); 561 562 for ( Map.Entry<String, Requirement> entry : requirements.entrySet() ) 563 { 564 String key = entry.getKey(); 565 Requirement requirement = entry.getValue(); 566 567 w.startElement( "requirement" ); 568 569 GeneratorUtils.element( w, "role", requirement.getRole() ); 570 571 if ( StringUtils.isNotEmpty( requirement.getRoleHint() ) ) 572 { 573 GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() ); 574 } 575 576 GeneratorUtils.element( w, "field-name", key ); 577 578 w.endElement(); 579 } 580 581 w.endElement(); 582 } 583 584 w.endElement(); 585 } 586 587 /** 588 * Get the expression value, eventually surrounding it with <code>${ }</code>. 589 * 590 * @param parameter the parameter 591 * @return the expression value 592 */ 593 private String getExpression( Parameter parameter ) 594 { 595 String expression = parameter.getExpression(); 596 if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) ) 597 { 598 expression = "${" + expression.trim() + "}"; 599 parameter.setExpression( expression ); 600 } 601 return expression; 602 } 603}