PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/amps-maven-plugin/src/main/java/com/atlassian/maven/plugins/amps/wadl/RestDocsGenerator.java

https://bitbucket.org/atlassian/amps
Java | 262 lines | 203 code | 25 blank | 34 comment | 9 complexity | 341f64866df6b08cb7e6abeb74eaef5c MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. package com.atlassian.maven.plugins.amps.wadl;
  2. import com.atlassian.maven.plugins.amps.MavenContext;
  3. import com.atlassian.maven.plugins.amps.util.MojoExecutorWrapper;
  4. import com.atlassian.maven.plugins.amps.util.PluginXmlUtils;
  5. import com.atlassian.maven.plugins.amps.util.PluginXmlUtils.RESTModuleInfo;
  6. import com.google.common.annotations.VisibleForTesting;
  7. import org.apache.maven.artifact.DependencyResolutionRequiredException;
  8. import org.apache.maven.model.Plugin;
  9. import org.apache.maven.plugin.MojoExecutionException;
  10. import org.apache.maven.plugin.logging.Log;
  11. import org.apache.maven.project.MavenProject;
  12. import org.twdata.maven.mojoexecutor.MojoExecutor.Element;
  13. import javax.annotation.Nullable;
  14. import javax.annotation.ParametersAreNonnullByDefault;
  15. import java.io.File;
  16. import java.io.IOException;
  17. import java.net.URL;
  18. import java.net.URLClassLoader;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.LinkedHashSet;
  22. import java.util.List;
  23. import java.util.Set;
  24. import java.util.function.Function;
  25. import static com.atlassian.maven.plugins.amps.util.FileUtils.fixWindowsSlashes;
  26. import static java.lang.String.format;
  27. import static java.lang.String.join;
  28. import static java.lang.Thread.currentThread;
  29. import static java.nio.charset.StandardCharsets.UTF_8;
  30. import static java.util.Arrays.stream;
  31. import static java.util.Objects.requireNonNull;
  32. import static java.util.function.Function.identity;
  33. import static java.util.regex.Matcher.quoteReplacement;
  34. import static java.util.stream.Collectors.joining;
  35. import static java.util.stream.Collectors.toList;
  36. import static org.apache.commons.io.FileUtils.writeStringToFile;
  37. import static org.apache.commons.io.IOUtils.resourceToString;
  38. import static org.apache.commons.lang3.StringUtils.replace;
  39. import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
  40. import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
  41. import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
  42. import static org.twdata.maven.mojoexecutor.MojoExecutor.name;
  43. /**
  44. * Generates WADL documentation for P2 plugin REST modules.
  45. *
  46. * @since 8.1.2 was previously in the {@code MavenGoals} class
  47. */
  48. @ParametersAreNonnullByDefault
  49. public class RestDocsGenerator {
  50. private final MavenContext mavenContext;
  51. private final MojoExecutorWrapper mojoExecutorWrapper;
  52. /**
  53. * Constructor.
  54. *
  55. * @param mavenContext the Maven context
  56. * @param mojoExecutorWrapper executes Mojos
  57. */
  58. public RestDocsGenerator(final MavenContext mavenContext, final MojoExecutorWrapper mojoExecutorWrapper) {
  59. this.mavenContext = requireNonNull(mavenContext);
  60. this.mojoExecutorWrapper = requireNonNull(mojoExecutorWrapper);
  61. }
  62. private Log log() {
  63. return mavenContext.getLog();
  64. }
  65. private MavenProject mavenProject() {
  66. return mavenContext.getProject();
  67. }
  68. /**
  69. * Generates <a href="https://en.wikipedia.org/wiki/Web_Application_Description_Language">WADL</a> documentation for
  70. * the REST modules in the current plugin project.
  71. * <p>
  72. * Works by invoking the Maven JavaDoc plugin, using the custom doclet
  73. * {@code com.sun.jersey.wadl.resourcedoc.ResourceDocletJSON}, the source for which can be found in
  74. * <a href="https://bitbucket.org/atlassian/atlassian-jersey-restdoc/>atlassian/atlassian-jersey-restdoc</a>
  75. *
  76. * @param jacksonModules any custom Jackson serializer modules to be used; a colon-separated list of their fully
  77. * qualified class names, each implementing {@code org.codehaus.jackson.map.Module}; see REST-267
  78. * @throws MojoExecutionException if the operation fails
  79. */
  80. public void generateRestDocs(@Nullable final String jacksonModules) throws MojoExecutionException {
  81. final List<RESTModuleInfo> restModules = PluginXmlUtils.getRestModules(mavenContext);
  82. if (restModules.isEmpty()) {
  83. log().info("No REST modules found, skipping WADL doc generation");
  84. return;
  85. }
  86. withoutGlobalJavadocPlugin(() -> invokeJavadocPluginWithCustomDoclet(restModules, jacksonModules));
  87. generateApplicationXmlFiles();
  88. }
  89. private void invokeJavadocPluginWithCustomDoclet(
  90. final List<RESTModuleInfo> restModules, @Nullable final String jacksonModules) {
  91. try {
  92. mojoExecutorWrapper.executeWithMergedConfig(
  93. mavenContext.getPlugin("org.apache.maven.plugins", "maven-javadoc-plugin"),
  94. goal("javadoc"),
  95. configuration(
  96. element(name("maxmemory"), "1024m"),
  97. element(name("sourcepath"), getPackagesPath(restModules)),
  98. element(name("doclet"), "com.atlassian.jersey.wadl.doclet.ResourceDocletJSON"),
  99. element(name("docletPath"), join(File.pathSeparator, getDocletPathElements())),
  100. element(name("docletArtifacts"),
  101. element(name("docletArtifact"),
  102. element(name("groupId"), "com.atlassian.plugins.rest"),
  103. element(name("artifactId"), "atlassian-rest-doclet"),
  104. element(name("version"), "6.1.9")
  105. ),
  106. element(name("docletArtifact"),
  107. // Contains the class named in the "doclet" element above
  108. element(name("groupId"), "com.atlassian.jersey"),
  109. element(name("artifactId"), "atlassian-jersey-restdoc"),
  110. element(name("version"), "2.0.0")
  111. ),
  112. element(name("docletArtifact"),
  113. // The Xerces version used by the above doclet
  114. element(name("groupId"), "xerces"),
  115. element(name("artifactId"), "xercesImpl"),
  116. element(name("version"), "2.12.1")
  117. ),
  118. element(name("docletArtifact"),
  119. element(name("groupId"), "commons-lang"),
  120. element(name("artifactId"), "commons-lang"),
  121. element(name("version"), "2.6")
  122. ),
  123. element(name("docletArtifact"),
  124. element(name("groupId"), "com.sun.istack"),
  125. element(name("artifactId"), "istack-commons-runtime"),
  126. element(name("version"), "3.0.11")
  127. )
  128. ),
  129. element(name("outputDirectory")),
  130. element(name("additionalOptions"), getAdditionalOptions(jacksonModules)),
  131. element(name("useStandardDocletOptions"), "false")
  132. ),
  133. mavenContext.getExecutionEnvironment()
  134. );
  135. } catch (MojoExecutionException e) {
  136. log().warn("Could not generate REST documentation. Please verify that Atlassian REST is a " +
  137. "'provided' scope dependency of the plugin.", e);
  138. }
  139. }
  140. private Element[] getAdditionalOptions(@Nullable final String jacksonModules) {
  141. final String resourcedocPath = fixWindowsSlashes(
  142. mavenProject().getBuild().getOutputDirectory() + File.separator + "resourcedoc.xml");
  143. final List<Element> additionalOptions = new ArrayList<>();
  144. additionalOptions.add(element(name("additionalOption"), "-output \"" + resourcedocPath + "\""));
  145. if (jacksonModules != null) {
  146. additionalOptions.add(element(name("additionalOption"), " -modules \"" + jacksonModules + "\""));
  147. }
  148. return additionalOptions.toArray(new Element[0]);
  149. }
  150. private Set<String> getDocletPathElements() throws MojoExecutionException {
  151. try {
  152. final Set<String> docletPathElements = new LinkedHashSet<>();
  153. docletPathElements.add(mavenProject().getBuild().getOutputDirectory());
  154. docletPathElements.addAll(mavenProject().getCompileClasspathElements());
  155. docletPathElements.addAll(mavenProject().getRuntimeClasspathElements());
  156. //noinspection deprecation -- no suggested replacement
  157. docletPathElements.addAll(mavenProject().getSystemClasspathElements());
  158. // AMPS-663: add plugin execution classes to doclet path
  159. docletPathElements.addAll(getPluginClasspathElements());
  160. return docletPathElements;
  161. } catch (final DependencyResolutionRequiredException e) {
  162. throw new MojoExecutionException("Dependencies must be resolved", e);
  163. }
  164. }
  165. @VisibleForTesting
  166. static List<String> getPluginClasspathElements() {
  167. final URL[] pluginUrls = ((URLClassLoader) currentThread().getContextClassLoader()).getURLs();
  168. return stream(pluginUrls)
  169. .map(URL::getFile)
  170. .map(File::new)
  171. .map(File::getPath)
  172. .collect(toList());
  173. }
  174. private String getPackagesPath(final Collection<RESTModuleInfo> restModules) {
  175. if (anyRestModuleHasNoExplicitPackages(restModules)) {
  176. final String sourceDirectory = mavenProject().getBuild().getSourceDirectory();
  177. log().info(format("Scanning all of %s for REST resources", sourceDirectory)); // fixes AMPS-1152
  178. return sourceDirectory;
  179. }
  180. return restModules.stream()
  181. .flatMap(module -> module.getPackagesToScan().stream())
  182. .map(this::getAbsoluteDirectory)
  183. .distinct()
  184. .collect(joining(File.pathSeparator));
  185. }
  186. private boolean anyRestModuleHasNoExplicitPackages(final Collection<RESTModuleInfo> restModules) {
  187. return restModules.stream()
  188. .map(RESTModuleInfo::getPackagesToScan)
  189. .anyMatch(List::isEmpty);
  190. }
  191. private String getAbsoluteDirectory(final String packageName) {
  192. final String relativePackageDirectory = packageName.replaceAll("\\.", quoteReplacement(File.separator));
  193. return mavenProject().getBuild().getSourceDirectory() + File.separator + relativePackageDirectory;
  194. }
  195. private void generateApplicationXmlFiles() throws MojoExecutionException {
  196. try {
  197. // application-doc.xml
  198. final PluginXmlUtils.PluginInfo pluginInfo = PluginXmlUtils.getPluginInfo(mavenContext);
  199. generateApplicationXmlFile("application-doc.xml", template -> {
  200. final String intermediate = replace(template, "${rest.doc.title}", pluginInfo.getName());
  201. return replace(intermediate, "${rest.doc.description}", pluginInfo.getDescription());
  202. });
  203. // application-grammars.xml
  204. generateApplicationXmlFile("application-grammars.xml", identity());
  205. } catch (Exception e) {
  206. throw new MojoExecutionException("Error writing REST application XML files", e);
  207. }
  208. }
  209. private void generateApplicationXmlFile(final String filename, final Function<String, String> fileContentTransformer)
  210. throws IOException {
  211. final File outputFile = new File(mavenProject().getBuild().getOutputDirectory(), filename);
  212. if (!outputFile.exists()) {
  213. final String defaultFileContent = resourceToString(filename, UTF_8, getClass().getClassLoader());
  214. final String transformedFileContent = fileContentTransformer.apply(defaultFileContent);
  215. writeStringToFile(outputFile, transformedFileContent, UTF_8);
  216. log().info("Wrote " + outputFile.getAbsolutePath());
  217. }
  218. }
  219. private interface CustomJavadocAction {
  220. void invoke() throws MojoExecutionException;
  221. }
  222. /**
  223. * AMPSDEV-127: 'generate-rest-docs' fails with JDK8 - invalid flag: -Xdoclint:all
  224. * Root cause: ResourceDocletJSON doclet does not support option doclint
  225. * Solution: Temporarily remove global javadoc configuration (remove doclint)
  226. */
  227. private void withoutGlobalJavadocPlugin(final CustomJavadocAction javadocAction) throws MojoExecutionException {
  228. final Plugin globalJavadoc = mavenProject().getPlugin("org.apache.maven.plugins:maven-javadoc-plugin");
  229. if (globalJavadoc != null) {
  230. mavenProject().getBuild().removePlugin(globalJavadoc);
  231. }
  232. javadocAction.invoke();
  233. if (globalJavadoc != null) {
  234. mavenProject().getBuild().addPlugin(globalJavadoc);
  235. }
  236. }
  237. }