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.rat.mp;
20  
21  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
22  
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.OutputStreamWriter;
27  import java.io.Writer;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Files;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Locale;
33  import java.util.Map;
34  import java.util.ResourceBundle;
35  
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.artifact.repository.ArtifactRepository;
38  import org.apache.maven.doxia.sink.Sink;
39  import org.apache.maven.doxia.sink.SinkFactory;
40  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
41  import org.apache.maven.doxia.site.decoration.DecorationModel;
42  import org.apache.maven.doxia.siterenderer.Renderer;
43  import org.apache.maven.doxia.siterenderer.RendererException;
44  import org.apache.maven.doxia.siterenderer.RenderingContext;
45  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
46  import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
47  import org.apache.maven.doxia.tools.SiteTool;
48  import org.apache.maven.doxia.tools.SiteToolException;
49  import org.apache.maven.execution.MavenSession;
50  import org.apache.maven.plugin.MojoExecutionException;
51  import org.apache.maven.plugins.annotations.Component;
52  import org.apache.maven.plugins.annotations.Mojo;
53  import org.apache.maven.plugins.annotations.Parameter;
54  import org.apache.maven.plugins.annotations.ResolutionScope;
55  import org.apache.maven.reporting.MavenMultiPageReport;
56  import org.apache.maven.reporting.MavenReportException;
57  import org.apache.rat.ReportConfiguration;
58  import org.apache.rat.Reporter;
59  import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
60  import org.codehaus.plexus.util.ReaderFactory;
61  
62  /**
63   * Generates a report with Rat's output.
64   */
65  @Mojo(name = "rat", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
66  public class RatReportMojo extends AbstractRatMojo implements MavenMultiPageReport {
67  
68      /**
69       * The output directory for the report. Note that this parameter is only
70       * evaluated if the goal is run directly from the command line. If the goal is
71       * run indirectly as part of a site generation, the output directory configured
72       * in the Maven Site Plugin is used instead.
73       */
74      @Parameter(defaultValue = "${project.reporting.outputDirectory}", readonly = true, required = true)
75      protected File outputDirectory;
76  
77      /**
78       * Specifies the input encoding.
79       */
80      @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}", readonly = true)
81      private String inputEncoding;
82  
83      /**
84       * Specifies the output encoding.
85       */
86      @Parameter(property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}", readonly = true)
87      private String outputEncoding;
88  
89      /**
90       * The local repository.
91       */
92      @Parameter(defaultValue = "${session}", readonly = true, required = true)
93      protected MavenSession session;
94  
95      /**
96       * Remote repositories used for the project.
97       */
98      @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true)
99      protected List<ArtifactRepository> remoteRepositories;
100 
101     /**
102      * SiteTool.
103      */
104     @Component
105     protected SiteTool siteTool;
106 
107     /**
108      * Doxia Site Renderer component.
109      */
110     @Component
111     protected Renderer siteRenderer;
112 
113     /**
114      * The current sink to use
115      */
116     private Sink sink;
117 
118     /**
119      * The sink factory to use
120      */
121     private SinkFactory sinkFactory;
122 
123     /**
124      * The current report output directory to use
125      */
126     private File reportOutputDirectory;
127 
128     /**
129      * This method is called when the report generation is invoked directly as a
130      * standalone Mojo.
131      *
132      * @throws MojoExecutionException if an error occurs when generating the report
133      * @see org.apache.maven.plugin.Mojo#execute()
134      */
135     @Override
136     public void execute() throws MojoExecutionException {
137         if (!canGenerateReport()) {
138             return;
139         }
140 
141         File outputDirectory = new File(getOutputDirectory());
142 
143         String filename = getOutputName() + ".html";
144 
145         Locale locale = Locale.getDefault();
146 
147         try {
148             SiteRenderingContext siteContext = createSiteRenderingContext(locale);
149 
150             // copy resources
151             getSiteRenderer().copyResources(siteContext, outputDirectory);
152 
153             // TODO Replace null with real value
154             RenderingContext docRenderingContext = new RenderingContext(outputDirectory, filename, null);
155 
156             SiteRendererSink sink = new SiteRendererSink(docRenderingContext);
157 
158             generate(sink, null, locale);
159 
160             // MSHARED-204: only render Doxia sink if not an external report
161             if (!isExternalReport()) {
162                 outputDirectory.mkdirs();
163 
164                 try (Writer writer = new OutputStreamWriter(
165                         Files.newOutputStream(new File(outputDirectory, filename).toPath()), getOutputEncoding())) {
166                     // render report
167                     getSiteRenderer().mergeDocumentIntoSite(writer, sink, siteContext);
168                 }
169             }
170 
171             // copy generated resources also
172             getSiteRenderer().copyResources(siteContext, outputDirectory);
173         } catch (RendererException | IOException | MavenReportException e) {
174             throw new MojoExecutionException(
175                     "An error has occurred in " + getName(Locale.ENGLISH) + " report generation.", e);
176         }
177     }
178 
179     private SiteRenderingContext createSiteRenderingContext(Locale locale) throws MavenReportException, IOException {
180         DecorationModel decorationModel = new DecorationModel();
181 
182         Map<String, Object> templateProperties = new HashMap<>();
183         // We tell the skin that we are rendering in standalone mode
184         templateProperties.put("standalone", Boolean.TRUE);
185         templateProperties.put("project", getProject());
186         templateProperties.put("inputEncoding", getInputEncoding());
187         templateProperties.put("outputEncoding", getOutputEncoding());
188         // Put any of the properties in directly into the Velocity context
189         for (Map.Entry<Object, Object> entry : getProject().getProperties().entrySet()) {
190             templateProperties.put((String) entry.getKey(), entry.getValue());
191         }
192 
193         SiteRenderingContext context;
194         try {
195             Artifact skinArtifact = siteTool.getSkinArtifactFromRepository(session.getLocalRepository(),
196                     remoteRepositories, decorationModel);
197 
198             getLog().debug(
199                     buffer().a("Rendering content with ").strong(skinArtifact.getId() + " skin").a('.').build());
200 
201             context = siteRenderer.createContextForSkin(skinArtifact, templateProperties, decorationModel,
202                     project.getName(), locale);
203         } catch (SiteToolException e) {
204             throw new MavenReportException("Failed to retrieve skin artifact", e);
205         } catch (RendererException e) {
206             throw new MavenReportException("Failed to create context for skin", e);
207         }
208 
209         // Generate static site
210         context.setRootDirectory(project.getBasedir());
211 
212         return context;
213     }
214 
215     /**
216      * Generate a report.
217      *
218      * @param sink the sink to use for the generation.
219      * @param locale the wanted locale to generate the report, could be null.
220      * @throws MavenReportException if any
221      * @deprecated use {@link #generate(Sink, SinkFactory, Locale)} instead.
222      */
223     @Deprecated
224     @Override
225     public void generate(org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException {
226         generate(sink, null, locale);
227     }
228 
229     /**
230      * Generate a report.
231      *
232      * @param sink the sink to use for the generation.
233      * @param locale the wanted locale to generate the report, could be null.
234      * @throws MavenReportException if any
235      * @deprecated use {@link #generate(Sink, SinkFactory, Locale)} instead.
236      */
237     @Deprecated
238     public void generate(Sink sink, Locale locale) throws MavenReportException {
239         generate(sink, null, locale);
240     }
241 
242     /**
243      * This method is called when the report generation is invoked by
244      * maven-site-plugin.
245      *
246      * @param sink the sink to use for the generation.
247      * @param sinkFactory the sink factory to use for the generation.
248      * @param locale the wanted locale to generate the report, could be null.
249      * @throws MavenReportException if any
250      */
251     @Override
252     public void generate(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException {
253         if (!canGenerateReport()) {
254             getLog().info("This report cannot be generated as part of the current build. "
255                     + "The report name should be referenced in this line of output.");
256             return;
257         }
258 
259         this.sink = sink;
260 
261         this.sinkFactory = sinkFactory;
262 
263         executeReport(locale);
264 
265         closeReport();
266     }
267 
268     /**
269      * @return CATEGORY_PROJECT_REPORTS
270      */
271     @Override
272     public String getCategoryName() {
273         return CATEGORY_PROJECT_REPORTS;
274     }
275 
276     @Override
277     public File getReportOutputDirectory() {
278         if (reportOutputDirectory == null) {
279             reportOutputDirectory = new File(getOutputDirectory());
280         }
281 
282         return reportOutputDirectory;
283     }
284 
285     @Override
286     public void setReportOutputDirectory(File reportOutputDirectory) {
287         this.reportOutputDirectory = reportOutputDirectory;
288         this.outputDirectory = reportOutputDirectory;
289     }
290 
291     protected String getOutputDirectory() {
292         return outputDirectory.getAbsolutePath();
293     }
294 
295     protected Renderer getSiteRenderer() {
296         return siteRenderer;
297     }
298 
299     /**
300      * Gets the input files encoding.
301      *
302      * @return The input files encoding, never <code>null</code>.
303      */
304     protected String getInputEncoding() {
305         return (inputEncoding == null) ? ReaderFactory.FILE_ENCODING : inputEncoding;
306     }
307 
308     /**
309      * Gets the effective reporting output files encoding.
310      *
311      * @return The effective reporting output file encoding, never
312      * <code>null</code>.
313      */
314     protected String getOutputEncoding() {
315         return (outputEncoding == null) ? StandardCharsets.UTF_8.toString() : outputEncoding;
316     }
317 
318     /**
319      * Actions when closing the report.
320      */
321     protected void closeReport() {
322         getSink().close();
323     }
324 
325     /**
326      * @return the sink used
327      */
328     public Sink getSink() {
329         return sink;
330     }
331 
332     /**
333      * @return the sink factory used
334      */
335     public SinkFactory getSinkFactory() {
336         return sinkFactory;
337     }
338 
339     /**
340      * @return {@code false} by default.
341      * @see org.apache.maven.reporting.MavenReport#isExternalReport()
342      */
343     @Override
344     public boolean isExternalReport() {
345         return false;
346     }
347 
348     @Override
349     public boolean canGenerateReport() {
350         return !skip;
351     }
352 
353     /**
354      * Searches for a Rat artifact in the dependency list and returns its version.
355      *
356      * @return Version number, if found, or null.
357      */
358     // TODO The canonical way is to read the pom.properties for the artifact desired
359     // in META-INF/maven
360     private String getRatVersion() {
361         // noinspection unchecked
362         for (Artifact a : getProject().getDependencyArtifacts()) {
363             if ("rat-lib".equals(a.getArtifactId())) {
364                 return a.getVersion();
365             }
366         }
367         return null;
368     }
369 
370     /**
371      * Writes the report to the Doxia sink.
372      *
373      * @param locale The locale to use for writing the report.
374      * @throws MavenReportException Writing the report failed.
375      */
376     protected void executeReport(Locale locale) throws MavenReportException {
377         ResourceBundle bundle = getBundle(locale);
378         final String title = bundle.getString("report.rat.title");
379         sink.head();
380         sink.title();
381         sink.text(title);
382         sink.title_();
383         sink.head_();
384 
385         sink.body();
386 
387         sink.section1();
388         sink.sectionTitle1();
389         sink.text(title);
390         sink.sectionTitle1_();
391 
392         sink.paragraph();
393         sink.text(bundle.getString("report.rat.link") + " ");
394         sink.link(bundle.getString("report.rat.url"));
395         sink.text(bundle.getString("report.rat.fullName"));
396         sink.link_();
397         final String ratVersion = getRatVersion();
398         if (ratVersion != null) {
399             sink.text(" " + ratVersion);
400         }
401         sink.text(".");
402         sink.paragraph_();
403 
404         sink.paragraph();
405         sink.verbatim(SinkEventAttributeSet.BOXED);
406         try {
407             ReportConfiguration config = getConfiguration();
408             config.setFrom(getDefaultsBuilder().build());
409             //config.setStyleSheet(Defaults.getUnapprovedLicensesStyleSheet());
410             logLicenses(config.getLicenses(LicenseFilter.all));
411             ByteArrayOutputStream baos = new ByteArrayOutputStream();
412             config.setOut(()->baos);
413             Reporter.report(config);
414             sink.text(baos.toString());
415         } catch (Exception e) {
416             throw new MavenReportException(e.getMessage(), e);
417         }
418         sink.verbatim_();
419         sink.paragraph_();
420         sink.section1_();
421         sink.body_();
422     }
423 
424     /**
425      * Returns the reports bundle
426      *
427      * @param locale Requested locale of the bundle
428      * @return The bundle, which is used to read localized strings.
429      */
430     private ResourceBundle getBundle(Locale locale) {
431         return ResourceBundle.getBundle("org/apache/rat/mp/rat-report", locale, getClass().getClassLoader());
432     }
433 
434     /**
435      * Returns the reports description.
436      *
437      * @param locale Requested locale of the bundle
438      * @return Report description, as given by the key "report.rat.description" in
439      * the bundle.
440      */
441     @Override
442     public String getDescription(Locale locale) {
443         return getBundle(locale).getString("report.rat.description");
444     }
445 
446     /**
447      * Returns the reports name.
448      *
449      * @param locale Requested locale of the bundle
450      * @return Report name, as given by the key "report.rat.name" in the bundle.
451      */
452     @Override
453     public String getName(Locale locale) {
454         return getBundle(locale).getString("report.rat.name");
455     }
456 
457     /**
458      * Returns the reports file name.
459      *
460      * @return "rat-report"
461      */
462     @Override
463     public String getOutputName() {
464         return "rat-report";
465     }
466 }