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.decoration.DecorationModel;
37  import org.apache.maven.doxia.siterenderer.Renderer;
38  import org.apache.maven.doxia.siterenderer.RendererException;
39  import org.apache.maven.doxia.siterenderer.RenderingContext;
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             RenderingContext docRenderingContext = new RenderingContext(outputDirectory, filename, null);
228 
229             SiteRendererSink sink = new SiteRendererSink(docRenderingContext);
230 
231             generate(sink, null, locale);
232 
233             if (!isExternalReport()) // MSHARED-204: only render Doxia sink if not an external report
234             {
235                 outputDirectory.mkdirs();
236 
237                 try (Writer writer = new OutputStreamWriter(
238                         new FileOutputStream(new File(outputDirectory, filename)), getOutputEncoding())) {
239                     // render report
240                     getSiteRenderer().mergeDocumentIntoSite(writer, sink, siteContext);
241                 }
242             }
243 
244             // copy generated resources also
245             getSiteRenderer().copyResources(siteContext, outputDirectory);
246         } catch (RendererException | IOException | MavenReportException | SiteToolException e) {
247             throw new MojoExecutionException(
248                     "An error has occurred in " + getName(Locale.ENGLISH) + " report generation.", e);
249         }
250     }
251 
252     private SiteRenderingContext createSiteRenderingContext(Locale locale)
253             throws MavenReportException, IOException, SiteToolException {
254         DecorationModel decorationModel = siteTool.getDecorationModel(
255                 siteDirectory, locale, project, reactorProjects, repoSession, remoteProjectRepositories);
256 
257         Map<String, Object> templateProperties = new HashMap<>();
258         // We tell the skin that we are rendering in standalone mode
259         templateProperties.put("standalone", Boolean.TRUE);
260         templateProperties.put("project", getProject());
261         templateProperties.put("inputEncoding", getInputEncoding());
262         templateProperties.put("outputEncoding", getOutputEncoding());
263         // Put any of the properties in directly into the Velocity context
264         for (Map.Entry<Object, Object> entry : getProject().getProperties().entrySet()) {
265             templateProperties.put((String) entry.getKey(), entry.getValue());
266         }
267 
268         SiteRenderingContext context;
269         try {
270             Artifact skinArtifact = siteTool.getSkinArtifactFromRepository(
271                     repoSession, remoteProjectRepositories, decorationModel.getSkin());
272 
273             getLog().info(buffer().a("Rendering content with ")
274                     .strong(skinArtifact.getId() + " skin")
275                     .a('.')
276                     .toString());
277 
278             context = siteRenderer.createContextForSkin(
279                     skinArtifact, templateProperties, decorationModel, project.getName(), locale);
280         } catch (SiteToolException e) {
281             throw new MavenReportException("Failed to retrieve skin artifact", e);
282         } catch (RendererException e) {
283             throw new MavenReportException("Failed to create context for skin", e);
284         }
285 
286         // Add publish date
287         String outputTimestamp = getProject().getProperties().getProperty("project.build.outputTimestamp");
288         MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).ifPresent(v -> {
289             context.setPublishDate(Date.from(v));
290         });
291 
292         // Generate static site
293         context.setRootDirectory(project.getBasedir());
294 
295         return context;
296     }
297 
298     /**
299      * Generate a report.
300      *
301      * @param sink the sink to use for the generation.
302      * @param locale the wanted locale to generate the report, could be null.
303      * @throws MavenReportException if any
304      * @deprecated use {@link #generate(Sink, SinkFactory, Locale)} instead.
305      */
306     @Deprecated
307     @Override
308     public void generate(Sink sink, Locale locale) throws MavenReportException {
309         generate(sink, null, locale);
310     }
311 
312     /**
313      * This method is called when the report generation is invoked by maven-site-plugin.
314      *
315      * @param sink
316      * @param sinkFactory
317      * @param locale
318      * @throws MavenReportException
319      */
320     @Override
321     public void generate(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException {
322         if (!canGenerateReport()) {
323             getLog().info("This report cannot be generated as part of the current build. "
324                     + "The report name should be referenced in this line of output.");
325             return;
326         }
327 
328         this.sink = sink;
329 
330         this.sinkFactory = sinkFactory;
331 
332         executeReport(locale);
333 
334         closeReport();
335     }
336 
337     /**
338      * @return CATEGORY_PROJECT_REPORTS
339      */
340     @Override
341     public String getCategoryName() {
342         return CATEGORY_PROJECT_REPORTS;
343     }
344 
345     @Override
346     public File getReportOutputDirectory() {
347         if (reportOutputDirectory == null) {
348             reportOutputDirectory = new File(getOutputDirectory());
349         }
350 
351         return reportOutputDirectory;
352     }
353 
354     @Override
355     public void setReportOutputDirectory(File reportOutputDirectory) {
356         this.reportOutputDirectory = reportOutputDirectory;
357         this.outputDirectory = reportOutputDirectory;
358     }
359 
360     protected String getOutputDirectory() {
361         return outputDirectory.getAbsolutePath();
362     }
363 
364     protected MavenProject getProject() {
365         return project;
366     }
367 
368     protected Renderer getSiteRenderer() {
369         return siteRenderer;
370     }
371 
372     /**
373      * Gets the input files encoding.
374      *
375      * @return The input files encoding, never <code>null</code>.
376      */
377     protected String getInputEncoding() {
378         return (inputEncoding == null) ? ReaderFactory.FILE_ENCODING : inputEncoding;
379     }
380 
381     /**
382      * Gets the effective reporting output files encoding.
383      *
384      * @return The effective reporting output file encoding, never <code>null</code>.
385      */
386     protected String getOutputEncoding() {
387         return (outputEncoding == null) ? WriterFactory.UTF_8 : outputEncoding;
388     }
389 
390     /**
391      * Gets the locale
392      *
393      * @return the locale for this standalone report
394      */
395     protected Locale getLocale() {
396         return siteTool.getSiteLocales(locale).get(0);
397     }
398 
399     /**
400      * Actions when closing the report.
401      */
402     protected void closeReport() {
403         if (getSink() != null) {
404             getSink().close();
405         }
406     }
407 
408     /**
409      * @return the sink used
410      */
411     public Sink getSink() {
412         return sink;
413     }
414 
415     /**
416      * @return the sink factory used
417      */
418     public SinkFactory getSinkFactory() {
419         return sinkFactory;
420     }
421 
422     /**
423      * @see org.apache.maven.reporting.MavenReport#isExternalReport()
424      * @return {@code false} by default.
425      */
426     @Override
427     public boolean isExternalReport() {
428         return false;
429     }
430 
431     @Override
432     public boolean canGenerateReport() {
433         return true;
434     }
435 
436     /**
437      * Execute the generation of the report.
438      *
439      * @param locale the wanted locale to return the report's description, could be <code>null</code>.
440      * @throws MavenReportException if any
441      */
442     protected abstract void executeReport(Locale locale) throws MavenReportException;
443 }