View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.plugin.report;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.Reader;
24  import java.nio.file.Files;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.stream.Collectors;
30  
31  import org.apache.maven.RepositoryUtils;
32  import org.apache.maven.artifact.ArtifactUtils;
33  import org.apache.maven.doxia.sink.Sink;
34  import org.apache.maven.execution.MavenSession;
35  import org.apache.maven.model.building.ModelBuildingRequest;
36  import org.apache.maven.plugin.descriptor.MojoDescriptor;
37  import org.apache.maven.plugin.descriptor.PluginDescriptor;
38  import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
39  import org.apache.maven.plugins.annotations.Component;
40  import org.apache.maven.plugins.annotations.Execute;
41  import org.apache.maven.plugins.annotations.LifecyclePhase;
42  import org.apache.maven.plugins.annotations.Mojo;
43  import org.apache.maven.plugins.annotations.Parameter;
44  import org.apache.maven.plugins.plugin.descriptor.EnhancedPluginDescriptorBuilder;
45  import org.apache.maven.project.DefaultProjectBuildingRequest;
46  import org.apache.maven.project.MavenProject;
47  import org.apache.maven.project.ProjectBuilder;
48  import org.apache.maven.project.ProjectBuildingException;
49  import org.apache.maven.project.ProjectBuildingRequest;
50  import org.apache.maven.reporting.AbstractMavenReport;
51  import org.apache.maven.reporting.MavenReportException;
52  import org.apache.maven.rtinfo.RuntimeInformation;
53  import org.codehaus.plexus.configuration.PlexusConfigurationException;
54  import org.codehaus.plexus.i18n.I18N;
55  import org.codehaus.plexus.util.xml.XmlStreamReader;
56  import org.eclipse.aether.RepositorySystem;
57  import org.eclipse.aether.artifact.DefaultArtifact;
58  import org.eclipse.aether.resolution.VersionRangeRequest;
59  import org.eclipse.aether.resolution.VersionRangeResolutionException;
60  import org.eclipse.aether.resolution.VersionRangeResult;
61  import org.eclipse.aether.version.Version;
62  
63  /**
64   * Generates the Plugin's documentation report: <code>plugin-info.html</code> plugin overview page,
65   * and one <code><i>goal</i>-mojo.html</code> per goal.
66   * Relies on one output file from <a href="../maven-plugin-plugin/descriptor-mojo.html">plugin:descriptor</a>.
67   *
68   * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
69   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
70   * @since 3.7.0
71   */
72  @Mojo(name = "report", threadSafe = true)
73  @Execute(phase = LifecyclePhase.PROCESS_CLASSES)
74  public class PluginReport extends AbstractMavenReport {
75  
76      /**
77       * Set this to "true" to skip generating the report.
78       *
79       * @since 3.7.0
80       */
81      @Parameter(defaultValue = "false", property = "maven.plugin.report.skip")
82      private boolean skip;
83  
84      /**
85       * Set this to "true" to generate the usage section for "plugin-info.html" with
86       * {@code <extensions>true</extensions>}.
87       *
88       * @since 3.7.0
89       */
90      @Parameter(defaultValue = "false", property = "maven.plugin.report.hasExtensionsToLoad")
91      private boolean hasExtensionsToLoad;
92  
93      /**
94       * The Plugin requirements history list.
95       * <p>
96       * Can be specified as list of <code>requirementsHistory</code>:
97       *
98       * <pre>
99       * &lt;requirementsHistories&gt;
100      *   &lt;requirementsHistory&gt;
101      *     &lt;version&gt;plugin version&lt;/version&gt;
102      *     &lt;maven&gt;maven version&lt;/maven&gt;
103      *     &lt;jdk&gt;jdk version&lt;/jdk&gt;
104      *   &lt;/requirementsHistory&gt;
105      * &lt;/requirementsHistories&gt;
106      * </pre>
107      *
108      * @since 3.7.0
109      */
110     @Parameter
111     private List<RequirementsHistory> requirementsHistories = new ArrayList<>();
112 
113     /**
114      * Plugin's version range for automatic detection of requirements history.
115      *
116      * @since 3.12.0
117      */
118     @Parameter(defaultValue = "[0,)")
119     private String requirementsHistoryDetectionRange;
120 
121     @Component
122     private RuntimeInformation rtInfo;
123 
124     /**
125      * Internationalization component.
126      */
127     @Component
128     private I18N i18n;
129 
130     /**
131      * Path to enhanced plugin descriptor to generate the report from (must contain some XHTML values)
132      *
133      * @since 3.7.0
134      */
135     @Parameter(defaultValue = "${project.build.directory}/plugin-enhanced.xml", required = true, readonly = true)
136     private File enhancedPluginXmlFile;
137 
138     /**
139      * In case the internal javadoc site has not been generated when running this report goal
140      * (e.g. when using an aggregator javadoc report) link validation needs to be disabled by setting
141      * this value to {@code true}.
142      * This might have the drawback that some links being generated in the report might be broken
143      * in case not all parameter types and javadoc link references are resolvable through the sites being given to
144      * goal {@code plugin:descriptor}.
145      *
146      * @since 3.7.0
147      */
148     @Parameter(property = "maven.plugin.report.disableInternalJavadocLinkValidation")
149     private boolean disableInternalJavadocLinkValidation;
150 
151     @Component
152     private MavenSession mavenSession;
153 
154     @Component
155     private RepositorySystem repositorySystem;
156 
157     @Component
158     private ProjectBuilder projectBuilder;
159 
160     /**
161      * {@inheritDoc}
162      */
163     @Override
164     public boolean canGenerateReport() {
165         if (skip) {
166             getLog().info("Maven Plugin Plugin Report generation skipped.");
167             return false;
168         }
169 
170         if (!(enhancedPluginXmlFile != null && enhancedPluginXmlFile.isFile() && enhancedPluginXmlFile.canRead())) {
171             return false;
172         }
173 
174         return true;
175     }
176 
177     /**
178      * {@inheritDoc}
179      */
180     @Override
181     protected void executeReport(Locale locale) throws MavenReportException {
182         PluginDescriptor pluginDescriptor = extractPluginDescriptor();
183 
184         // Generate the mojos' documentation
185         generateMojosDocumentation(pluginDescriptor, locale);
186 
187         if (requirementsHistories.isEmpty()) {
188             // detect requirements history
189             String v = null;
190             try {
191                 List<Version> versions = discoverVersions(requirementsHistoryDetectionRange);
192                 if (versions.isEmpty()) {
193                     getLog().info("No plugin history found for range " + requirementsHistoryDetectionRange);
194                 } else {
195                     getLog().info("Detecting plugin requirements history for range "
196                             + requirementsHistoryDetectionRange + ": "
197                             + versions.size() + " releases, from " + versions.get(0) + " to "
198                             + versions.get(versions.size() - 1));
199                 }
200 
201                 Collections.reverse(versions);
202                 for (Version version : versions) {
203                     v = version.toString();
204                     MavenProject versionProject = buildMavenProject(v);
205                     RequirementsHistory requirements = RequirementsHistory.discoverRequirements(versionProject);
206                     requirementsHistories.add(requirements);
207                     getLog().info("  - " + requirements);
208                 }
209             } catch (VersionRangeResolutionException vrre) {
210                 throw new MavenReportException(
211                         "Cannot resolve past versions " + requirementsHistoryDetectionRange, vrre);
212             } catch (ProjectBuildingException pbe) {
213                 throw new MavenReportException("Cannot resolve MavenProject for version " + v, pbe);
214             }
215         }
216 
217         // Write the overview
218         PluginOverviewRenderer r = new PluginOverviewRenderer(
219                 getSink(), i18n, locale, getProject(), requirementsHistories, pluginDescriptor, hasExtensionsToLoad);
220         r.render();
221     }
222 
223     private PluginDescriptor extractPluginDescriptor() throws MavenReportException {
224         PluginDescriptorBuilder builder = new EnhancedPluginDescriptorBuilder(rtInfo);
225 
226         try (Reader input = new XmlStreamReader(Files.newInputStream(enhancedPluginXmlFile.toPath()))) {
227             return builder.build(input);
228         } catch (IOException | PlexusConfigurationException e) {
229             throw new MavenReportException("Error extracting plugin descriptor from " + enhancedPluginXmlFile, e);
230         }
231     }
232 
233     /**
234      * @param locale The locale
235      * @param key The key to search for
236      * @return The text appropriate for the locale.
237      */
238     private String getI18nString(Locale locale, String key) {
239         return i18n.getString("plugin-report", locale, "report.plugin." + key);
240     }
241 
242     /**
243      * {@inheritDoc}
244      */
245     @Override
246     public String getName(Locale locale) {
247         return getI18nString(locale, "name");
248     }
249 
250     /**
251      * {@inheritDoc}
252      */
253     @Override
254     public String getDescription(Locale locale) {
255         return getI18nString(locale, "description");
256     }
257 
258     /**
259      * {@inheritDoc}
260      */
261     @Override
262     public String getOutputName() {
263         return "plugin-info";
264     }
265 
266     /**
267      * Generate the mojos' documentation with the {@link #getSinkFactory()}
268      *
269      * @param pluginDescriptor not null
270      * @param locale           not null
271      * @throws MavenReportException if any
272      * @throws IOException
273      */
274     private void generateMojosDocumentation(PluginDescriptor pluginDescriptor, Locale locale)
275             throws MavenReportException {
276 
277         if (pluginDescriptor.getMojos() != null) {
278             for (MojoDescriptor descriptor : pluginDescriptor.getMojos()) {
279                 GoalRenderer renderer;
280                 try {
281                     String filename = descriptor.getGoal() + "-mojo.html";
282                     Sink sink = getSinkFactory().createSink(getReportOutputDirectory(), filename);
283                     renderer = new GoalRenderer(
284                             sink,
285                             i18n,
286                             locale,
287                             project,
288                             descriptor,
289                             getReportOutputDirectory(),
290                             disableInternalJavadocLinkValidation,
291                             getLog());
292                 } catch (IOException e) {
293                     throw new MavenReportException("Cannot generate sink for mojo " + descriptor.getGoal(), e);
294                 }
295                 renderer.render();
296             }
297         }
298     }
299 
300     private List<Version> discoverVersions(String range) throws VersionRangeResolutionException {
301         MavenProject currentProject = mavenSession.getCurrentProject();
302         VersionRangeRequest rangeRequest = new VersionRangeRequest();
303         rangeRequest.setArtifact(
304                 new DefaultArtifact(currentProject.getGroupId() + ":" + currentProject.getArtifactId() + ":" + range));
305         rangeRequest.setRepositories(
306                 RepositoryUtils.toRepos(mavenSession.getCurrentProject().getRemoteArtifactRepositories()));
307         VersionRangeResult rangeResult =
308                 repositorySystem.resolveVersionRange(mavenSession.getRepositorySession(), rangeRequest);
309         return rangeResult.getVersions().stream()
310                 .filter(version -> !ArtifactUtils.isSnapshot(version.toString()))
311                 .collect(Collectors.toList());
312     }
313 
314     private MavenProject buildMavenProject(String version) throws ProjectBuildingException {
315         MavenProject currentProject = mavenSession.getCurrentProject();
316         ProjectBuildingRequest buildRequest = new DefaultProjectBuildingRequest();
317         buildRequest.setLocalRepository(mavenSession.getLocalRepository());
318         buildRequest.setRemoteRepositories(mavenSession.getCurrentProject().getRemoteArtifactRepositories());
319         buildRequest.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
320         buildRequest.setProcessPlugins(false);
321         buildRequest.setRepositoryMerging(ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT);
322         buildRequest.setSystemProperties(mavenSession.getSystemProperties());
323         buildRequest.setUserProperties(mavenSession.getUserProperties());
324         buildRequest.setRepositorySession(mavenSession.getRepositorySession());
325         return projectBuilder
326                 .build(
327                         RepositoryUtils.toArtifact(new DefaultArtifact(currentProject.getGroupId() + ":"
328                                 + currentProject.getArtifactId() + ":pom:" + version)),
329                         buildRequest)
330                 .getProject();
331     }
332 }