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