001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.tools.plugin.generator; 020 021import java.io.File; 022import java.io.IOException; 023import java.io.OutputStreamWriter; 024import java.io.Writer; 025import java.net.URI; 026import java.util.LinkedHashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032import org.apache.maven.plugin.descriptor.MojoDescriptor; 033import org.apache.maven.plugin.descriptor.Parameter; 034import org.apache.maven.plugin.descriptor.PluginDescriptor; 035import org.apache.maven.plugin.descriptor.Requirement; 036import org.apache.maven.project.MavenProject; 037import org.apache.maven.tools.plugin.ExtendedMojoDescriptor; 038import org.apache.maven.tools.plugin.PluginDescriptorHelper; 039import org.apache.maven.tools.plugin.PluginToolsRequest; 040import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator; 041import org.apache.maven.tools.plugin.util.PluginUtils; 042import org.codehaus.plexus.util.StringUtils; 043import org.codehaus.plexus.util.io.CachingOutputStream; 044import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; 045import org.codehaus.plexus.util.xml.XMLWriter; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048 049import static java.nio.charset.StandardCharsets.UTF_8; 050 051/** 052 * Serializes 053 * <ol> 054 * <li>a standard <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a></li> 055 * <li>a descriptor containing a limited set of elements for {@link PluginHelpGenerator}</li> 056 * <li>an enhanced descriptor containing HTML values for some elements (instead of plain text as for the other two) 057 * for {@code org.apache.maven.plugin.plugin.report.GoalRenderer}</li> 058 * </ol> 059 * from a given in-memory descriptor. The in-memory descriptor acting as source is supposed to contain XHTML values 060 * for description elements. 061 */ 062public class PluginDescriptorFilesGenerator implements Generator { 063 private static final Logger LOG = LoggerFactory.getLogger(PluginDescriptorFilesGenerator.class); 064 065 /** 066 * The type of the plugin descriptor file 067 */ 068 enum DescriptorType { 069 STANDARD, 070 LIMITED_FOR_HELP_MOJO, 071 XHTML 072 } 073 074 @Override 075 public void execute(File destinationDirectory, PluginToolsRequest request) throws GeneratorException { 076 try { 077 // write standard plugin.xml descriptor 078 File f = new File(destinationDirectory, "plugin.xml"); 079 writeDescriptor(f, request, DescriptorType.STANDARD); 080 081 // write plugin-help.xml help-descriptor (containing only a limited set of attributes) 082 MavenProject mavenProject = request.getProject(); 083 f = new File(destinationDirectory, PluginHelpGenerator.getPluginHelpPath(mavenProject)); 084 writeDescriptor(f, request, DescriptorType.LIMITED_FOR_HELP_MOJO); 085 086 // write enhanced plugin-enhanced.xml descriptor (containing some XHTML values) 087 f = getEnhancedDescriptorFilePath(mavenProject); 088 writeDescriptor(f, request, DescriptorType.XHTML); 089 } catch (IOException e) { 090 throw new GeneratorException(e.getMessage(), e); 091 } 092 } 093 094 public static File getEnhancedDescriptorFilePath(MavenProject project) { 095 return new File(project.getBuild().getDirectory(), "plugin-enhanced.xml"); 096 } 097 098 private String getVersion() { 099 Package p = this.getClass().getPackage(); 100 String version = (p == null) ? null : p.getSpecificationVersion(); 101 return (version == null) ? "SNAPSHOT" : version; 102 } 103 104 public void writeDescriptor(File destinationFile, PluginToolsRequest request, DescriptorType type) 105 throws IOException { 106 PluginDescriptor pluginDescriptor = request.getPluginDescriptor(); 107 108 if (!destinationFile.getParentFile().exists()) { 109 destinationFile.getParentFile().mkdirs(); 110 } 111 112 try (Writer writer = new OutputStreamWriter(new CachingOutputStream(destinationFile), UTF_8)) { 113 XMLWriter w = new PrettyPrintXMLWriter(writer, UTF_8.name(), null); 114 115 final String additionalInfo; 116 switch (type) { 117 case LIMITED_FOR_HELP_MOJO: 118 additionalInfo = " (for help mojo with limited elements)"; 119 break; 120 case XHTML: 121 additionalInfo = " (enhanced XHTML version (used for plugin:report))"; 122 break; 123 default: 124 additionalInfo = ""; 125 break; 126 } 127 w.writeMarkup("\n<!-- Generated by maven-plugin-tools " + getVersion() + additionalInfo + "-->\n\n"); 128 129 w.startElement("plugin"); 130 131 GeneratorUtils.element(w, "name", pluginDescriptor.getName()); 132 133 GeneratorUtils.element(w, "description", pluginDescriptor.getDescription()); 134 135 GeneratorUtils.element(w, "groupId", pluginDescriptor.getGroupId()); 136 137 GeneratorUtils.element(w, "artifactId", pluginDescriptor.getArtifactId()); 138 139 GeneratorUtils.element(w, "version", pluginDescriptor.getVersion()); 140 141 GeneratorUtils.element(w, "goalPrefix", pluginDescriptor.getGoalPrefix()); 142 143 if (type != DescriptorType.LIMITED_FOR_HELP_MOJO) { 144 GeneratorUtils.element(w, "isolatedRealm", String.valueOf(pluginDescriptor.isIsolatedRealm())); 145 146 GeneratorUtils.element( 147 w, "inheritedByDefault", String.valueOf(pluginDescriptor.isInheritedByDefault())); 148 149 if (StringUtils.isNotBlank(PluginDescriptorHelper.getRequiredJavaVersion(pluginDescriptor))) { 150 GeneratorUtils.element( 151 w, "requiredJavaVersion", PluginDescriptorHelper.getRequiredJavaVersion(pluginDescriptor)); 152 } 153 if (StringUtils.isNotBlank(pluginDescriptor.getRequiredMavenVersion())) { 154 GeneratorUtils.element(w, "requiredMavenVersion", pluginDescriptor.getRequiredMavenVersion()); 155 } 156 } 157 158 w.startElement("mojos"); 159 160 final JavadocLinkGenerator javadocLinkGenerator; 161 if (request.getInternalJavadocBaseUrl() != null 162 || (request.getExternalJavadocBaseUrls() != null 163 && !request.getExternalJavadocBaseUrls().isEmpty())) { 164 javadocLinkGenerator = new JavadocLinkGenerator( 165 request.getInternalJavadocBaseUrl(), 166 request.getInternalJavadocVersion(), 167 request.getExternalJavadocBaseUrls(), 168 request.getSettings()); 169 } else { 170 javadocLinkGenerator = null; 171 } 172 if (pluginDescriptor.getMojos() != null) { 173 List<MojoDescriptor> descriptors = pluginDescriptor.getMojos(); 174 175 PluginUtils.sortMojos(descriptors); 176 177 for (MojoDescriptor descriptor : descriptors) { 178 processMojoDescriptor(descriptor, w, type, javadocLinkGenerator); 179 } 180 } 181 182 w.endElement(); 183 184 if (type != DescriptorType.LIMITED_FOR_HELP_MOJO) { 185 GeneratorUtils.writeDependencies(w, pluginDescriptor); 186 } 187 188 w.endElement(); 189 190 writer.flush(); 191 } 192 } 193 194 /** 195 * @param type 196 * @param containsXhtmlValue 197 * @param text 198 * @return the normalized text value (i.e. potentially converted to XHTML) 199 */ 200 private static String getTextValue(DescriptorType type, boolean containsXhtmlValue, String text) { 201 final String xhtmlText; 202 if (!containsXhtmlValue) // text comes from legacy extractor 203 { 204 xhtmlText = GeneratorUtils.makeHtmlValid(text); 205 } else { 206 xhtmlText = text; 207 } 208 if (type != DescriptorType.XHTML) { 209 return new HtmlToPlainTextConverter().convert(text); 210 } else { 211 return xhtmlText; 212 } 213 } 214 215 @SuppressWarnings("deprecation") 216 protected void processMojoDescriptor( 217 MojoDescriptor mojoDescriptor, 218 XMLWriter w, 219 DescriptorType type, 220 JavadocLinkGenerator javadocLinkGenerator) { 221 boolean containsXhtmlTextValues = mojoDescriptor instanceof ExtendedMojoDescriptor 222 && ((ExtendedMojoDescriptor) mojoDescriptor).containsXhtmlTextValues(); 223 224 w.startElement("mojo"); 225 226 // ---------------------------------------------------------------------- 227 // 228 // ---------------------------------------------------------------------- 229 230 w.startElement("goal"); 231 w.writeText(mojoDescriptor.getGoal()); 232 w.endElement(); 233 234 // ---------------------------------------------------------------------- 235 // 236 // ---------------------------------------------------------------------- 237 238 String description = mojoDescriptor.getDescription(); 239 240 if (description != null && !description.isEmpty()) { 241 w.startElement("description"); 242 w.writeText(getTextValue(type, containsXhtmlTextValues, mojoDescriptor.getDescription())); 243 w.endElement(); 244 } 245 246 // ---------------------------------------------------------------------- 247 // 248 // ---------------------------------------------------------------------- 249 250 if (StringUtils.isNotEmpty(mojoDescriptor.isDependencyResolutionRequired())) { 251 GeneratorUtils.element(w, "requiresDependencyResolution", mojoDescriptor.isDependencyResolutionRequired()); 252 } 253 254 // ---------------------------------------------------------------------- 255 // 256 // ---------------------------------------------------------------------- 257 258 GeneratorUtils.element(w, "requiresDirectInvocation", String.valueOf(mojoDescriptor.isDirectInvocationOnly())); 259 260 // ---------------------------------------------------------------------- 261 // 262 // ---------------------------------------------------------------------- 263 264 GeneratorUtils.element(w, "requiresProject", String.valueOf(mojoDescriptor.isProjectRequired())); 265 266 // ---------------------------------------------------------------------- 267 // 268 // ---------------------------------------------------------------------- 269 270 GeneratorUtils.element(w, "requiresReports", String.valueOf(mojoDescriptor.isRequiresReports())); 271 272 // ---------------------------------------------------------------------- 273 // 274 // ---------------------------------------------------------------------- 275 276 GeneratorUtils.element(w, "aggregator", String.valueOf(mojoDescriptor.isAggregator())); 277 278 // ---------------------------------------------------------------------- 279 // 280 // ---------------------------------------------------------------------- 281 282 GeneratorUtils.element(w, "requiresOnline", String.valueOf(mojoDescriptor.isOnlineRequired())); 283 284 // ---------------------------------------------------------------------- 285 // 286 // ---------------------------------------------------------------------- 287 288 GeneratorUtils.element(w, "inheritedByDefault", String.valueOf(mojoDescriptor.isInheritedByDefault())); 289 290 // ---------------------------------------------------------------------- 291 // 292 // ---------------------------------------------------------------------- 293 294 if (StringUtils.isNotEmpty(mojoDescriptor.getPhase())) { 295 GeneratorUtils.element(w, "phase", mojoDescriptor.getPhase()); 296 } 297 298 // ---------------------------------------------------------------------- 299 // 300 // ---------------------------------------------------------------------- 301 302 if (StringUtils.isNotEmpty(mojoDescriptor.getExecutePhase())) { 303 GeneratorUtils.element(w, "executePhase", mojoDescriptor.getExecutePhase()); 304 } 305 306 if (StringUtils.isNotEmpty(mojoDescriptor.getExecuteGoal())) { 307 GeneratorUtils.element(w, "executeGoal", mojoDescriptor.getExecuteGoal()); 308 } 309 310 if (StringUtils.isNotEmpty(mojoDescriptor.getExecuteLifecycle())) { 311 GeneratorUtils.element(w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle()); 312 } 313 314 // ---------------------------------------------------------------------- 315 // 316 // ---------------------------------------------------------------------- 317 318 w.startElement("implementation"); 319 w.writeText(mojoDescriptor.getImplementation()); 320 w.endElement(); 321 322 // ---------------------------------------------------------------------- 323 // 324 // ---------------------------------------------------------------------- 325 326 w.startElement("language"); 327 w.writeText(mojoDescriptor.getLanguage()); 328 w.endElement(); 329 330 // ---------------------------------------------------------------------- 331 // 332 // ---------------------------------------------------------------------- 333 334 if (StringUtils.isNotEmpty(mojoDescriptor.getComponentConfigurator())) { 335 w.startElement("configurator"); 336 w.writeText(mojoDescriptor.getComponentConfigurator()); 337 w.endElement(); 338 } 339 340 // ---------------------------------------------------------------------- 341 // 342 // ---------------------------------------------------------------------- 343 344 if (StringUtils.isNotEmpty(mojoDescriptor.getComponentComposer())) { 345 w.startElement("composer"); 346 w.writeText(mojoDescriptor.getComponentComposer()); 347 w.endElement(); 348 } 349 350 // ---------------------------------------------------------------------- 351 // 352 // ---------------------------------------------------------------------- 353 354 w.startElement("instantiationStrategy"); 355 w.writeText(mojoDescriptor.getInstantiationStrategy()); 356 w.endElement(); 357 358 // ---------------------------------------------------------------------- 359 // Strategy for handling repeated reference to mojo in 360 // the calculated (decorated, resolved) execution stack 361 // ---------------------------------------------------------------------- 362 w.startElement("executionStrategy"); 363 w.writeText(mojoDescriptor.getExecutionStrategy()); 364 w.endElement(); 365 366 // ---------------------------------------------------------------------- 367 // 368 // ---------------------------------------------------------------------- 369 370 if (mojoDescriptor.getSince() != null) { 371 w.startElement("since"); 372 373 if (StringUtils.isEmpty(mojoDescriptor.getSince())) { 374 w.writeText("No version given"); 375 } else { 376 w.writeText(mojoDescriptor.getSince()); 377 } 378 379 w.endElement(); 380 } 381 382 // ---------------------------------------------------------------------- 383 // 384 // ---------------------------------------------------------------------- 385 386 if (mojoDescriptor.getDeprecated() != null) { 387 w.startElement("deprecated"); 388 389 if (StringUtils.isEmpty(mojoDescriptor.getDeprecated())) { 390 w.writeText("No reason given"); 391 } else { 392 w.writeText(getTextValue(type, containsXhtmlTextValues, mojoDescriptor.getDeprecated())); 393 } 394 395 w.endElement(); 396 } 397 398 // ---------------------------------------------------------------------- 399 // Extended (3.0) descriptor 400 // ---------------------------------------------------------------------- 401 402 if (mojoDescriptor instanceof ExtendedMojoDescriptor) { 403 ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor; 404 if (extendedMojoDescriptor.getDependencyCollectionRequired() != null) { 405 GeneratorUtils.element( 406 w, "requiresDependencyCollection", extendedMojoDescriptor.getDependencyCollectionRequired()); 407 } 408 409 GeneratorUtils.element(w, "threadSafe", String.valueOf(extendedMojoDescriptor.isThreadSafe())); 410 411 boolean v4Api = extendedMojoDescriptor.isV4Api(); 412 if (v4Api) { 413 GeneratorUtils.element(w, "v4Api", String.valueOf(v4Api)); 414 } 415 } 416 417 // ---------------------------------------------------------------------- 418 // Parameters 419 // ---------------------------------------------------------------------- 420 421 List<Parameter> parameters = mojoDescriptor.getParameters(); 422 423 w.startElement("parameters"); 424 425 Map<String, Requirement> requirements = new LinkedHashMap<>(); 426 427 Set<Parameter> configuration = new LinkedHashSet<>(); 428 429 if (parameters != null) { 430 if (type == DescriptorType.LIMITED_FOR_HELP_MOJO) { 431 PluginUtils.sortMojoParameters(parameters); 432 } 433 434 for (Parameter parameter : parameters) { 435 String expression = getExpression(parameter); 436 437 if ((expression != null && !expression.isEmpty()) && expression.startsWith("${component.")) { 438 // treat it as a component...a requirement, in other words. 439 440 // remove "component." plus expression delimiters 441 String role = expression.substring("${component.".length(), expression.length() - 1); 442 443 String roleHint = null; 444 445 int posRoleHintSeparator = role.indexOf('#'); 446 if (posRoleHintSeparator > 0) { 447 roleHint = role.substring(posRoleHintSeparator + 1); 448 449 role = role.substring(0, posRoleHintSeparator); 450 } 451 452 // TODO: remove deprecated expression 453 requirements.put(parameter.getName(), new Requirement(role, roleHint)); 454 } else if (parameter.getRequirement() != null) { 455 requirements.put(parameter.getName(), parameter.getRequirement()); 456 } 457 // don't show readonly parameters in help 458 else if (type != DescriptorType.LIMITED_FOR_HELP_MOJO || parameter.isEditable()) { 459 // treat it as a normal parameter. 460 461 w.startElement("parameter"); 462 463 GeneratorUtils.element(w, "name", parameter.getName()); 464 465 if (parameter.getAlias() != null) { 466 GeneratorUtils.element(w, "alias", parameter.getAlias()); 467 } 468 469 writeParameterType(w, type, javadocLinkGenerator, parameter, mojoDescriptor.getGoal()); 470 471 if (parameter.getSince() != null) { 472 w.startElement("since"); 473 474 if (StringUtils.isEmpty(parameter.getSince())) { 475 w.writeText("No version given"); 476 } else { 477 w.writeText(parameter.getSince()); 478 } 479 480 w.endElement(); 481 } 482 483 if (parameter.getDeprecated() != null) { 484 if (StringUtils.isEmpty(parameter.getDeprecated())) { 485 GeneratorUtils.element(w, "deprecated", "No reason given"); 486 } else { 487 GeneratorUtils.element( 488 w, 489 "deprecated", 490 getTextValue(type, containsXhtmlTextValues, parameter.getDeprecated())); 491 } 492 } 493 494 if (parameter.getImplementation() != null) { 495 GeneratorUtils.element(w, "implementation", parameter.getImplementation()); 496 } 497 498 GeneratorUtils.element(w, "required", Boolean.toString(parameter.isRequired())); 499 500 GeneratorUtils.element(w, "editable", Boolean.toString(parameter.isEditable())); 501 502 GeneratorUtils.element( 503 w, "description", getTextValue(type, containsXhtmlTextValues, parameter.getDescription())); 504 505 if (StringUtils.isNotEmpty(parameter.getDefaultValue()) 506 || StringUtils.isNotEmpty(parameter.getExpression())) { 507 configuration.add(parameter); 508 } 509 510 w.endElement(); 511 } 512 } 513 } 514 515 w.endElement(); 516 517 // ---------------------------------------------------------------------- 518 // Configuration 519 // ---------------------------------------------------------------------- 520 521 if (!configuration.isEmpty()) { 522 w.startElement("configuration"); 523 524 for (Parameter parameter : configuration) { 525 if (type == DescriptorType.LIMITED_FOR_HELP_MOJO && !parameter.isEditable()) { 526 // don't show readonly parameters in help 527 continue; 528 } 529 530 w.startElement(parameter.getName()); 531 532 // strip type by parameter type (generics) information 533 String parameterType = StringUtils.chomp(parameter.getType(), "<"); 534 if (parameterType != null && !parameterType.isEmpty()) { 535 w.addAttribute("implementation", parameterType); 536 } 537 538 if (parameter.getDefaultValue() != null) { 539 w.addAttribute("default-value", parameter.getDefaultValue()); 540 } 541 542 if (StringUtils.isNotEmpty(parameter.getExpression())) { 543 w.writeText(parameter.getExpression()); 544 } 545 546 w.endElement(); 547 } 548 549 w.endElement(); 550 } 551 552 // ---------------------------------------------------------------------- 553 // Requirements 554 // ---------------------------------------------------------------------- 555 556 if (!requirements.isEmpty() && type != DescriptorType.LIMITED_FOR_HELP_MOJO) { 557 w.startElement("requirements"); 558 559 for (Map.Entry<String, Requirement> entry : requirements.entrySet()) { 560 String key = entry.getKey(); 561 Requirement requirement = entry.getValue(); 562 563 w.startElement("requirement"); 564 565 GeneratorUtils.element(w, "role", requirement.getRole()); 566 567 if (StringUtils.isNotEmpty(requirement.getRoleHint())) { 568 GeneratorUtils.element(w, "role-hint", requirement.getRoleHint()); 569 } 570 571 GeneratorUtils.element(w, "field-name", key); 572 573 w.endElement(); 574 } 575 576 w.endElement(); 577 } 578 579 w.endElement(); 580 } 581 582 /** 583 * Writes parameter type information and potentially also the related javadoc URL. 584 * 585 * @param w 586 * @param type 587 * @param javadocLinkGenerator 588 * @param parameter 589 * @param goal 590 */ 591 protected void writeParameterType( 592 XMLWriter w, 593 DescriptorType type, 594 JavadocLinkGenerator javadocLinkGenerator, 595 Parameter parameter, 596 String goal) { 597 String parameterType = parameter.getType(); 598 599 if (type == DescriptorType.STANDARD) { 600 // strip type by parameter type (generics) information for standard plugin descriptor 601 parameterType = StringUtils.chomp(parameterType, "<"); 602 } 603 GeneratorUtils.element(w, "type", parameterType); 604 605 if (type == DescriptorType.XHTML && javadocLinkGenerator != null) { 606 // skip primitives which never has javadoc 607 if (parameter.getType().indexOf('.') == -1) { 608 LOG.debug("Javadoc URLs are not available for primitive types like {}", parameter.getType()); 609 } else { 610 try { 611 URI javadocUrl = getJavadocUrlForType(javadocLinkGenerator, parameterType); 612 GeneratorUtils.element(w, "typeJavadocUrl", javadocUrl.toString()); 613 } catch (IllegalArgumentException e) { 614 LOG.warn( 615 "Could not get javadoc URL for type {} of parameter {} from goal {}: {}", 616 parameter.getType(), 617 parameter.getName(), 618 goal, 619 e.getMessage()); 620 } 621 } 622 } 623 } 624 625 private static String extractBinaryNameForJavadoc(String type) { 626 final String binaryName; 627 int startOfParameterType = type.indexOf("<"); 628 if (startOfParameterType != -1) { 629 // parse parameter type 630 String mainType = type.substring(0, startOfParameterType); 631 632 // some heuristics here 633 String[] parameterTypes = type.substring(startOfParameterType + 1, type.lastIndexOf(">")) 634 .split(",\\s*"); 635 switch (parameterTypes.length) { 636 case 1: // if only one parameter type, assume collection, first parameter type is most interesting 637 binaryName = extractBinaryNameForJavadoc(parameterTypes[0]); 638 break; 639 case 2: // if two parameter types assume map, second parameter type is most interesting 640 binaryName = extractBinaryNameForJavadoc(parameterTypes[1]); 641 break; 642 default: 643 // all other cases link to main type 644 binaryName = mainType; 645 } 646 } else { 647 binaryName = type; 648 } 649 return binaryName; 650 } 651 652 static URI getJavadocUrlForType(JavadocLinkGenerator javadocLinkGenerator, String type) { 653 return javadocLinkGenerator.createLink(extractBinaryNameForJavadoc(type)); 654 } 655 656 /** 657 * Get the expression value, eventually surrounding it with <code>${ }</code>. 658 * 659 * @param parameter the parameter 660 * @return the expression value 661 */ 662 private String getExpression(Parameter parameter) { 663 String expression = parameter.getExpression(); 664 if (StringUtils.isNotBlank(expression) && !expression.contains("${")) { 665 expression = "${" + expression.trim() + "}"; 666 parameter.setExpression(expression); 667 } 668 return expression; 669 } 670}