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             return false;
167         }
168 
169         if (!(enhancedPluginXmlFile != null && enhancedPluginXmlFile.isFile() && enhancedPluginXmlFile.canRead())) {
170             return false;
171         }
172 
173         return true;
174     }
175 
176     /**
177      * {@inheritDoc}
178      */
179     @Override
180     protected void executeReport(Locale locale) throws MavenReportException {
181         PluginDescriptor pluginDescriptor = extractPluginDescriptor();
182 
183         // Generate the mojos' documentation
184         generateMojosDocumentation(pluginDescriptor, locale);
185 
186         if (requirementsHistories.isEmpty()) {
187             // detect requirements history
188             String v = null;
189             try {
190                 List<Version> versions = discoverVersions(requirementsHistoryDetectionRange);
191                 if (versions.isEmpty()) {
192                     getLog().info("No plugin history found for range " + requirementsHistoryDetectionRange);
193                 } else {
194                     getLog().info("Detecting plugin requirements history for range "
195                             + requirementsHistoryDetectionRange + ": "
196                             + versions.size() + " releases, from " + versions.get(0) + " to "
197                             + versions.get(versions.size() - 1));
198                 }
199 
200                 Collections.reverse(versions);
201                 for (Version version : versions) {
202                     v = version.toString();
203                     MavenProject versionProject = buildMavenProject(v);
204                     RequirementsHistory requirements = RequirementsHistory.discoverRequirements(versionProject);
205                     requirementsHistories.add(requirements);
206                     getLog().debug("  - " + requirements);
207                 }
208             } catch (VersionRangeResolutionException vrre) {
209                 throw new MavenReportException(
210                         "Cannot resolve past versions " + requirementsHistoryDetectionRange, vrre);
211             } catch (ProjectBuildingException pbe) {
212                 throw new MavenReportException("Cannot resolve MavenProject for version " + v, pbe);
213             }
214         }
215 
216         // Write the overview
217         PluginOverviewRenderer r = new PluginOverviewRenderer(
218                 getSink(), i18n, locale, getProject(), requirementsHistories, pluginDescriptor, hasExtensionsToLoad);
219         r.render();
220     }
221 
222     private PluginDescriptor extractPluginDescriptor() throws MavenReportException {
223         PluginDescriptorBuilder builder = new EnhancedPluginDescriptorBuilder(rtInfo);
224 
225         try (Reader input = new XmlStreamReader(Files.newInputStream(enhancedPluginXmlFile.toPath()))) {
226             return builder.build(input);
227         } catch (IOException | PlexusConfigurationException e) {
228             throw new MavenReportException("Error extracting plugin descriptor from " + enhancedPluginXmlFile, e);
229         }
230     }
231 
232     /**
233      * @param locale The locale
234      * @param key The key to search for
235      * @return The text appropriate for the locale.
236      */
237     private String getI18nString(Locale locale, String key) {
238         return i18n.getString("plugin-report", locale, "report.plugin." + key);
239     }
240 
241     /**
242      * {@inheritDoc}
243      */
244     @Override
245     public String getName(Locale locale) {
246         return getI18nString(locale, "name");
247     }
248 
249     /**
250      * {@inheritDoc}
251      */
252     @Override
253     public String getDescription(Locale locale) {
254         return getI18nString(locale, "description");
255     }
256 
257     /**
258      * {@inheritDoc}
259      */
260     @Override
261     public String getOutputName() {
262         return "plugin-info";
263     }
264 
265     /**
266      * Generate the mojos' documentation with the {@link #getSinkFactory()}
267      *
268      * @param pluginDescriptor not null
269      * @param locale           not null
270      * @throws MavenReportException if any
271      * @throws IOException
272      */
273     private void generateMojosDocumentation(PluginDescriptor pluginDescriptor, Locale locale)
274             throws MavenReportException {
275         if (pluginDescriptor.getMojos() != null) {
276             for (MojoDescriptor descriptor : pluginDescriptor.getMojos()) {
277                 GoalRenderer renderer;
278                 try {
279                     String filename = descriptor.getGoal() + "-mojo.html";
280                     Sink sink = getSinkFactory().createSink(getReportOutputDirectory(), filename);
281                     renderer = new GoalRenderer(
282                             sink,
283                             i18n,
284                             locale,
285                             project,
286                             descriptor,
287                             getReportOutputDirectory(),
288                             disableInternalJavadocLinkValidation,
289                             getLog());
290                 } catch (IOException e) {
291                     throw new MavenReportException("Cannot generate sink for mojo " + descriptor.getGoal(), e);
292                 }
293                 renderer.render();
294             }
295         }
296     }
297 
298     private List<Version> discoverVersions(String range) throws VersionRangeResolutionException {
299         MavenProject currentProject = mavenSession.getCurrentProject();
300         VersionRangeRequest rangeRequest = new VersionRangeRequest();
301         rangeRequest.setArtifact(
302                 new DefaultArtifact(currentProject.getGroupId() + ":" + currentProject.getArtifactId() + ":" + range));
303         rangeRequest.setRepositories(
304                 RepositoryUtils.toRepos(mavenSession.getCurrentProject().getRemoteArtifactRepositories()));
305         VersionRangeResult rangeResult =
306                 repositorySystem.resolveVersionRange(mavenSession.getRepositorySession(), rangeRequest);
307         return rangeResult.getVersions().stream()
308                 .filter(version -> !ArtifactUtils.isSnapshot(version.toString()))
309                 .collect(Collectors.toList());
310     }
311 
312     private MavenProject buildMavenProject(String version) throws ProjectBuildingException {
313         MavenProject currentProject = mavenSession.getCurrentProject();
314         ProjectBuildingRequest buildRequest = new DefaultProjectBuildingRequest();
315         buildRequest.setLocalRepository(mavenSession.getLocalRepository());
316         buildRequest.setRemoteRepositories(mavenSession.getCurrentProject().getRemoteArtifactRepositories());
317         buildRequest.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
318         buildRequest.setProcessPlugins(false);
319         buildRequest.setRepositoryMerging(ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT);
320         buildRequest.setSystemProperties(mavenSession.getSystemProperties());
321         buildRequest.setUserProperties(mavenSession.getUserProperties());
322         buildRequest.setRepositorySession(mavenSession.getRepositorySession());
323         return projectBuilder
324                 .build(
325                         RepositoryUtils.toArtifact(new DefaultArtifact(currentProject.getGroupId() + ":"
326                                 + currentProject.getArtifactId() + ":pom:" + version)),
327                         buildRequest)
328                 .getProject();
329     }
330 }