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.reporting;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.OutputStreamWriter;
25  import java.io.Writer;
26  import java.util.Date;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  
32  import org.apache.maven.archiver.MavenArchiver;
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.doxia.sink.Sink;
35  import org.apache.maven.doxia.sink.SinkFactory;
36  import org.apache.maven.doxia.site.SiteModel;
37  import org.apache.maven.doxia.siterenderer.DocumentRenderingContext;
38  import org.apache.maven.doxia.siterenderer.Renderer;
39  import org.apache.maven.doxia.siterenderer.RendererException;
40  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
41  import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
42  import org.apache.maven.doxia.tools.SiteTool;
43  import org.apache.maven.doxia.tools.SiteToolException;
44  import org.apache.maven.plugin.AbstractMojo;
45  import org.apache.maven.plugin.MojoExecutionException;
46  import org.apache.maven.plugins.annotations.Component;
47  import org.apache.maven.plugins.annotations.Parameter;
48  import org.apache.maven.project.MavenProject;
49  import org.apache.maven.shared.utils.WriterFactory;
50  import org.codehaus.plexus.PlexusContainer;
51  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
52  import org.codehaus.plexus.util.ReaderFactory;
53  import org.eclipse.aether.RepositorySystemSession;
54  import org.eclipse.aether.repository.RemoteRepository;
55  
56  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
57  
58  /**
59   * The basis for a Maven report which can be generated both as part of a site generation or
60   * as a direct standalone goal invocation.
61   * Both invocations are delegated to <code>abstract executeReport( Locale )</code> from:
62   * <ul>
63   * <li>Mojo's <code>execute()</code> method, see maven-plugin-api</li>
64   * <li>MavenMultiPageReport's <code>generate( Sink, SinkFactory, Locale )</code>, see maven-reporting-api</li>
65   * </ul>
66   *
67   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
68   * @since 2.0
69   * @see #execute() <code>Mojo.execute()</code>, from maven-plugin-api
70   * @see #generate(Sink, SinkFactory, Locale) <code>MavenMultiPageReport.generate( Sink, SinkFactory, Locale )</code>,
71   *  from maven-reporting-api
72   * @see #executeReport(Locale) <code>abstract executeReport( Locale )</code>
73   */
74  public abstract class AbstractMavenReport extends AbstractMojo implements MavenMultiPageReport {
75      /**
76       * The output directory for the report. Note that this parameter is only evaluated if the goal is run directly from
77       * the command line. If the goal is run indirectly as part of a site generation, the output directory configured in
78       * the Maven Site Plugin is used instead.
79       */
80      @Parameter(defaultValue = "${project.reporting.outputDirectory}", readonly = true, required = true)
81      protected File outputDirectory;
82  
83      /**
84       * The Maven Project.
85       */
86      @Parameter(defaultValue = "${project}", readonly = true, required = true)
87      protected MavenProject project;
88  
89      /**
90       * The reactor projects.
91       */
92      @Parameter(defaultValue = "${reactorProjects}", required = true, readonly = true)
93      protected List<MavenProject> reactorProjects;
94  
95      /**
96       * Specifies the input encoding.
97       */
98      @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}", readonly = true)
99      private String inputEncoding;
100 
101     /**
102      * Specifies the output encoding.
103      */
104     @Parameter(property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}", readonly = true)
105     private String outputEncoding;
106 
107     /**
108      * The repository system session.
109      */
110     @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true)
111     protected RepositorySystemSession repoSession;
112 
113     /**
114      * Remote project repositories used for the project.
115      */
116     @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true)
117     protected List<RemoteRepository> remoteProjectRepositories;
118 
119     /**
120      * Directory containing the <code>site.xml</code> file.
121      */
122     @Parameter(defaultValue = "${basedir}/src/site")
123     protected File siteDirectory;
124 
125     /**
126      * The locale to use  when the report generation is invoked directly as a standalone Mojo.
127      * <p>
128      * <b>Default value is</b>: {@link SiteTool#DEFAULT_LOCALE}
129      *
130      * @see SiteTool#getSiteLocales(String)
131      */
132     @Parameter(defaultValue = "default")
133     protected String locale;
134 
135     /**
136      * SiteTool.
137      */
138     @Component
139     protected SiteTool siteTool;
140 
141     /**
142      * Doxia Site Renderer component.
143      */
144     @Component
145     protected Renderer siteRenderer;
146 
147     /** The current sink to use */
148     private Sink sink;
149 
150     /** The sink factory to use */
151     private SinkFactory sinkFactory;
152 
153     /** The current report output directory to use */
154     private File reportOutputDirectory;
155 
156     /**
157      * The report output format: null by default, to represent a site, but can be configured to a Doxia Sink id.
158      */
159     @Parameter(property = "output.format")
160     protected String outputFormat;
161 
162     @Component
163     private PlexusContainer container;
164 
165     /**
166      * This method is called when the report generation is invoked directly as a standalone Mojo.
167      *
168      * @throws MojoExecutionException if an error occurs when generating the report
169      * @see org.apache.maven.plugin.Mojo#execute()
170      */
171     @Override
172     public void execute() throws MojoExecutionException {
173         if (!canGenerateReport()) {
174             return;
175         }
176 
177         if (outputFormat != null) {
178             reportToMarkup();
179         } else {
180             reportToSite();
181         }
182     }
183 
184     private void reportToMarkup() throws MojoExecutionException {
185         getLog().info("Rendering to " + outputFormat + " markup");
186 
187         if (!isExternalReport()) {
188             String filename = getOutputName() + '.' + outputFormat;
189             try {
190                 sinkFactory = container.lookup(SinkFactory.class, outputFormat);
191                 sink = sinkFactory.createSink(outputDirectory, filename);
192             } catch (ComponentLookupException cle) {
193                 throw new MojoExecutionException(
194                         "Cannot find SinkFactory for Doxia output format: " + outputFormat, cle);
195             } catch (IOException ioe) {
196                 throw new MojoExecutionException("Cannot create sink to " + new File(outputDirectory, filename), ioe);
197             }
198         }
199 
200         try {
201             Locale locale = getLocale();
202             generate(sink, sinkFactory, locale);
203         } catch (MavenReportException e) {
204             throw new MojoExecutionException(
205                     "An error has occurred in " + getName(Locale.ENGLISH) + " report generation.", e);
206         } finally {
207             if (sink != null) {
208                 sink.close();
209             }
210         }
211     }
212 
213     private void reportToSite() throws MojoExecutionException {
214         File outputDirectory = new File(getOutputDirectory());
215 
216         String filename = getOutputName() + ".html";
217 
218         Locale locale = getLocale();
219 
220         try {
221             SiteRenderingContext siteContext = createSiteRenderingContext(locale);
222 
223             // copy resources
224             getSiteRenderer().copyResources(siteContext, outputDirectory);
225 
226             // TODO Replace null with real value
227             DocumentRenderingContext docRenderingContext =
228                     new DocumentRenderingContext(outputDirectory, filename, null);
229 
230             SiteRendererSink sink = new SiteRendererSink(docRenderingContext);
231 
232             generate(sink, null, locale);
233 
234             if (!isExternalReport()) // MSHARED-204: only render Doxia sink if not an external report
235             {
236                 outputDirectory.mkdirs();
237 
238                 try (Writer writer = new OutputStreamWriter(
239                         new FileOutputStream(new File(outputDirectory, filename)), getOutputEncoding())) {
240                     // render report
241                     getSiteRenderer().mergeDocumentIntoSite(writer, sink, siteContext);
242                 }
243             }
244 
245             // copy generated resources also
246             getSiteRenderer().copyResources(siteContext, outputDirectory);
247         } catch (RendererException | IOException | MavenReportException | SiteToolException e) {
248             throw new MojoExecutionException(
249                     "An error has occurred in " + getName(Locale.ENGLISH) + " report generation.", e);
250         }
251     }
252 
253     private SiteRenderingContext createSiteRenderingContext(Locale locale)
254             throws MavenReportException, IOException, SiteToolException {
255         SiteModel siteModel = siteTool.getSiteModel(
256                 siteDirectory, locale, project, reactorProjects, repoSession, remoteProjectRepositories);
257 
258         Map<String, Object> templateProperties = new HashMap<>();
259         // We tell the skin that we are rendering in standalone mode
260         templateProperties.put("standalone", Boolean.TRUE);
261         templateProperties.put("project", getProject());
262         templateProperties.put("inputEncoding", getInputEncoding());
263         templateProperties.put("outputEncoding", getOutputEncoding());
264         // Put any of the properties in directly into the Velocity context
265         for (Map.Entry<Object, Object> entry : getProject().getProperties().entrySet()) {
266             templateProperties.put((String) entry.getKey(), entry.getValue());
267         }
268 
269         SiteRenderingContext context;
270         try {
271             Artifact skinArtifact =
272                     siteTool.getSkinArtifactFromRepository(repoSession, remoteProjectRepositories, siteModel.getSkin());
273 
274             getLog().info(buffer().a("Rendering content with ")
275                     .strong(skinArtifact.getId() + " skin")
276                     .a('.')
277                     .toString());
278 
279             context = siteRenderer.createContextForSkin(
280                     skinArtifact, templateProperties, siteModel, project.getName(), locale);
281         } catch (SiteToolException e) {
282             throw new MavenReportException("Failed to retrieve skin artifact", e);
283         } catch (RendererException e) {
284             throw new MavenReportException("Failed to create context for skin", e);
285         }
286 
287         // Add publish date
288         String outputTimestamp = getProject().getProperties().getProperty("project.build.outputTimestamp");
289         MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).ifPresent(v -> {
290             context.setPublishDate(Date.from(v));
291         });
292 
293         // Generate static site
294         context.setRootDirectory(project.getBasedir());
295 
296         return context;
297     }
298 
299     /**
300      * Generate a report.
301      *
302      * @param sink the sink to use for the generation.
303      * @param locale the wanted locale to generate the report, could be null.
304      * @throws MavenReportException if any
305      * @deprecated use {@link #generate(Sink, SinkFactory, Locale)} instead.
306      */
307     @Deprecated
308     @Override
309     public void generate(Sink sink, Locale locale) throws MavenReportException {
310         generate(sink, null, locale);
311     }
312 
313     /**
314      * This method is called when the report generation is invoked by maven-site-plugin.
315      *
316      * @param sink
317      * @param sinkFactory
318      * @param locale
319      * @throws MavenReportException
320      */
321     @Override
322     public void generate(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException {
323         if (!canGenerateReport()) {
324             getLog().info("This report cannot be generated as part of the current build. "
325                     + "The report name should be referenced in this line of output.");
326             return;
327         }
328 
329         this.sink = sink;
330 
331         this.sinkFactory = sinkFactory;
332 
333         executeReport(locale);
334 
335         closeReport();
336     }
337 
338     /**
339      * @return CATEGORY_PROJECT_REPORTS
340      */
341     @Override
342     public String getCategoryName() {
343         return CATEGORY_PROJECT_REPORTS;
344     }
345 
346     @Override
347     public File getReportOutputDirectory() {
348         if (reportOutputDirectory == null) {
349             reportOutputDirectory = new File(getOutputDirectory());
350         }
351 
352         return reportOutputDirectory;
353     }
354 
355     @Override
356     public void setReportOutputDirectory(File reportOutputDirectory) {
357         this.reportOutputDirectory = reportOutputDirectory;
358         this.outputDirectory = reportOutputDirectory;
359     }
360 
361     protected String getOutputDirectory() {
362         return outputDirectory.getAbsolutePath();
363     }
364 
365     protected MavenProject getProject() {
366         return project;
367     }
368 
369     protected Renderer getSiteRenderer() {
370         return siteRenderer;
371     }
372 
373     /**
374      * Gets the input files encoding.
375      *
376      * @return The input files encoding, never <code>null</code>.
377      */
378     protected String getInputEncoding() {
379         return (inputEncoding == null) ? ReaderFactory.FILE_ENCODING : inputEncoding;
380     }
381 
382     /**
383      * Gets the effective reporting output files encoding.
384      *
385      * @return The effective reporting output file encoding, never <code>null</code>.
386      */
387     protected String getOutputEncoding() {
388         return (outputEncoding == null) ? WriterFactory.UTF_8 : outputEncoding;
389     }
390 
391     /**
392      * Gets the locale
393      *
394      * @return the locale for this standalone report
395      */
396     protected Locale getLocale() {
397         return siteTool.getSiteLocales(locale).get(0);
398     }
399 
400     /**
401      * Actions when closing the report.
402      */
403     protected void closeReport() {
404         if (getSink() != null) {
405             getSink().close();
406         }
407     }
408 
409     /**
410      * @return the sink used
411      */
412     public Sink getSink() {
413         return sink;
414     }
415 
416     /**
417      * @return the sink factory used
418      */
419     public SinkFactory getSinkFactory() {
420         return sinkFactory;
421     }
422 
423     /**
424      * @see org.apache.maven.reporting.MavenReport#isExternalReport()
425      * @return {@code false} by default.
426      */
427     @Override
428     public boolean isExternalReport() {
429         return false;
430     }
431 
432     @Override
433     public boolean canGenerateReport() {
434         return true;
435     }
436 
437     /**
438      * Execute the generation of the report.
439      *
440      * @param locale the wanted locale to return the report's description, could be <code>null</code>.
441      * @throws MavenReportException if any
442      */
443     protected abstract void executeReport(Locale locale) throws MavenReportException;
444 }