View Javadoc
1   package org.apache.maven.reporting;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.repository.ArtifactRepository;
24  import org.apache.maven.doxia.sink.Sink;
25  import org.apache.maven.doxia.sink.SinkFactory;
26  import org.apache.maven.doxia.site.decoration.DecorationModel;
27  import org.apache.maven.doxia.site.decoration.Skin;
28  import org.apache.maven.doxia.siterenderer.Renderer;
29  import org.apache.maven.doxia.siterenderer.RendererException;
30  import org.apache.maven.doxia.siterenderer.RenderingContext;
31  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
32  import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
33  import org.apache.maven.doxia.tools.SiteTool;
34  import org.apache.maven.doxia.tools.SiteToolException;
35  import org.apache.maven.plugin.AbstractMojo;
36  import org.apache.maven.plugin.MojoExecutionException;
37  import org.apache.maven.plugins.annotations.Component;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.shared.utils.WriterFactory;
41  import org.codehaus.plexus.util.ReaderFactory;
42  
43  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
44  
45  import java.io.File;
46  import java.io.FileOutputStream;
47  import java.io.IOException;
48  import java.io.OutputStreamWriter;
49  import java.io.Writer;
50  import java.util.HashMap;
51  import java.util.List;
52  import java.util.Locale;
53  import java.util.Map;
54  
55  /**
56   * The basis for a Maven report which can be generated both as part of a site generation or
57   * as a direct standalone goal invocation.
58   * Both invocations are delegated to <code>abstract executeReport( Locale )</code> from:
59   * <ul>
60   * <li>Mojo's <code>execute()</code> method, see maven-plugin-api</li>
61   * <li>MavenMultiPageReport's <code>generate( Sink, SinkFactory, Locale )</code>, see maven-reporting-api</li>
62   * </ul>
63   *
64   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
65   * @since 2.0
66   * @see #execute() <code>Mojo.execute()</code>, from maven-plugin-api
67   * @see #generate(Sink, SinkFactory, Locale) <code>MavenMultiPageReport.generate( Sink, SinkFactory, Locale )</code>,
68   *  from maven-reporting-api
69   * @see #executeReport(Locale) <code>abstract executeReport( Locale )</code>
70   */
71  public abstract class AbstractMavenReport
72      extends AbstractMojo
73      implements MavenMultiPageReport
74  {
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       * Specifies the input encoding.
91       */
92      @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}", readonly = true )
93      private String inputEncoding;
94  
95      /**
96       * Specifies the output encoding.
97       */
98      @Parameter( property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}", readonly = true )
99      private String outputEncoding;
100 
101     /**
102      * The local repository.
103      */
104     @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
105     protected ArtifactRepository localRepository;
106 
107     /**
108      * Remote repositories used for the project.
109      */
110     @Parameter( defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true )
111     protected List<ArtifactRepository> remoteRepositories;
112 
113     /**
114      * The skin to use when the report generation is invoked directly as a standalone Mojo.
115      * <p>
116      * <b>Default value is</b>:
117      * <pre>
118      * &lt;skin&gt;
119      *   &lt;groupId&gt;org.apache.maven.skins&lt;/groupId&gt;
120      *   &lt;artifactId&gt;maven-fluido-skin&lt;/artifactId&gt;
121      *   &lt;version&gt;1.11.1&lt;/version&gt;
122      * &lt;/skin&gt;
123      * </pre>
124      *
125      * @see Skin
126      */
127     @Parameter
128     protected Skin skin;
129 
130     /**
131      * The locale to use  when the report generation is invoked directly as a standalone Mojo.
132      * <p>
133      * <b>Default value is</b>: {@link SiteTool#DEFAULT_LOCALE}
134      *
135      * @see SiteTool#getSiteLocales(String)
136      */
137 
138     @Parameter( defaultValue = "default" )
139     protected String locale;
140 
141     /**
142      * SiteTool.
143      */
144     @Component
145     protected SiteTool siteTool;
146 
147     /**
148      * Doxia Site Renderer component.
149      */
150     @Component
151     protected Renderer siteRenderer;
152 
153     /** The current sink to use */
154     private Sink sink;
155 
156     /** The sink factory to use */
157     private SinkFactory sinkFactory;
158 
159     /** The current report output directory to use */
160     private File reportOutputDirectory;
161 
162     /**
163      * This method is called when the report generation is invoked directly as a standalone Mojo.
164      *
165      * @throws MojoExecutionException if an error occurs when generating the report
166      * @see org.apache.maven.plugin.Mojo#execute()
167      */
168     @Override
169     public void execute()
170         throws MojoExecutionException
171     {
172         if ( !canGenerateReport() )
173         {
174             return;
175         }
176 
177         File outputDirectory = new File( getOutputDirectory() );
178 
179         String filename = getOutputName() + ".html";
180 
181         Locale locale = getLocale();
182 
183         try
184         {
185             SiteRenderingContext siteContext = createSiteRenderingContext( locale );
186 
187             // copy resources
188             getSiteRenderer().copyResources( siteContext, outputDirectory );
189 
190             // TODO Replace null with real value
191             RenderingContext docRenderingContext = new RenderingContext( outputDirectory, filename, null );
192 
193             SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
194 
195             generate( sink, null, locale );
196 
197             if ( !isExternalReport() ) // MSHARED-204: only render Doxia sink if not an external report
198             {
199                 outputDirectory.mkdirs();
200 
201                 try ( Writer writer =
202                       new OutputStreamWriter( new FileOutputStream( new File( outputDirectory, filename ) ),
203                                               getOutputEncoding() ) )
204                 {
205                     // render report
206                     getSiteRenderer().mergeDocumentIntoSite( writer, sink, siteContext );
207                 }
208             }
209 
210             // copy generated resources also
211             getSiteRenderer().copyResources( siteContext, outputDirectory );
212         }
213         catch ( RendererException | IOException | MavenReportException e )
214         {
215             throw new MojoExecutionException(
216                 "An error has occurred in " + getName( Locale.ENGLISH ) + " report generation.", e );
217         }
218     }
219 
220     private SiteRenderingContext createSiteRenderingContext( Locale locale )
221         throws MavenReportException, IOException
222     {
223         DecorationModel decorationModel = new DecorationModel();
224         decorationModel.setSkin( getSkin() );
225 
226         Map<String, Object> templateProperties = new HashMap<>();
227         // We tell the skin that we are rendering in standalone mode
228         templateProperties.put( "standalone", Boolean.TRUE );
229         templateProperties.put( "project", getProject() );
230         templateProperties.put( "inputEncoding", getInputEncoding() );
231         templateProperties.put( "outputEncoding", getOutputEncoding() );
232         // Put any of the properties in directly into the Velocity context
233         for ( Map.Entry<Object, Object> entry : getProject().getProperties().entrySet() )
234         {
235             templateProperties.put( (String) entry.getKey(), entry.getValue() );
236         }
237 
238         SiteRenderingContext context;
239         try
240         {
241             Artifact skinArtifact =
242                 siteTool.getSkinArtifactFromRepository( localRepository, remoteRepositories, decorationModel );
243 
244             getLog().info( buffer().a( "Rendering content with " ).strong( skinArtifact.getId()
245                 + " skin" ).a( '.' ).toString() );
246 
247             context = siteRenderer.createContextForSkin( skinArtifact, templateProperties, decorationModel,
248                                                          project.getName(), locale );
249         }
250         catch ( SiteToolException e )
251         {
252             throw new MavenReportException( "Failed to retrieve skin artifact", e );
253         }
254         catch ( RendererException e )
255         {
256             throw new MavenReportException( "Failed to create context for skin", e );
257         }
258 
259         // Generate static site
260         context.setRootDirectory( project.getBasedir() );
261 
262         return context;
263     }
264 
265     /**
266      * Generate a report.
267      *
268      * @param sink the sink to use for the generation.
269      * @param locale the wanted locale to generate the report, could be null.
270      * @throws MavenReportException if any
271      * @deprecated use {@link #generate(Sink, SinkFactory, Locale)} instead.
272      */
273     @Deprecated
274     @Override
275     public void generate( Sink sink, Locale locale )
276         throws MavenReportException
277     {
278         generate( sink, null, locale );
279     }
280 
281     /**
282      * This method is called when the report generation is invoked by maven-site-plugin.
283      *
284      * @param sink
285      * @param sinkFactory
286      * @param locale
287      * @throws MavenReportException
288      */
289     @Override
290     public void generate( Sink sink, SinkFactory sinkFactory, Locale locale )
291         throws MavenReportException
292     {
293         if ( !canGenerateReport() )
294         {
295             getLog().info( "This report cannot be generated as part of the current build. "
296                            + "The report name should be referenced in this line of output." );
297             return;
298         }
299 
300         this.sink = sink;
301 
302         this.sinkFactory = sinkFactory;
303 
304         executeReport( locale );
305 
306         closeReport();
307     }
308 
309     /**
310      * @return CATEGORY_PROJECT_REPORTS
311      */
312     @Override
313     public String getCategoryName()
314     {
315         return CATEGORY_PROJECT_REPORTS;
316     }
317 
318     @Override
319     public File getReportOutputDirectory()
320     {
321         if ( reportOutputDirectory == null )
322         {
323             reportOutputDirectory = new File( getOutputDirectory() );
324         }
325 
326         return reportOutputDirectory;
327     }
328 
329     @Override
330     public void setReportOutputDirectory( File reportOutputDirectory )
331     {
332         this.reportOutputDirectory = reportOutputDirectory;
333         this.outputDirectory = reportOutputDirectory;
334     }
335 
336     protected String getOutputDirectory()
337     {
338         return outputDirectory.getAbsolutePath();
339     }
340 
341     protected MavenProject getProject()
342     {
343         return project;
344     }
345 
346     protected Renderer getSiteRenderer()
347     {
348         return siteRenderer;
349     }
350 
351     /**
352      * Gets the input files encoding.
353      *
354      * @return The input files encoding, never <code>null</code>.
355      */
356     protected String getInputEncoding()
357     {
358         return ( inputEncoding == null ) ? ReaderFactory.FILE_ENCODING : inputEncoding;
359     }
360 
361     /**
362      * Gets the effective reporting output files encoding.
363      *
364      * @return The effective reporting output file encoding, never <code>null</code>.
365      */
366     protected String getOutputEncoding()
367     {
368         return ( outputEncoding == null ) ? WriterFactory.UTF_8 : outputEncoding;
369     }
370 
371     /**
372      * Gets the skin
373      *
374      * @return the skin for this standalone report
375      */
376     protected Skin getSkin()
377     {
378         if ( skin == null )
379         {
380             Skin skin = new Skin();
381             skin.setGroupId( "org.apache.maven.skins" );
382             skin.setArtifactId( "maven-fluido-skin" );
383             skin.setVersion( "1.11.1" );
384             return skin;
385         }
386         else
387         {
388             return skin;
389         }
390     }
391 
392     /**
393      * Gets the locale
394      *
395      * @return the locale for this standalone report
396      */
397     protected Locale getLocale()
398     {
399         return siteTool.getSiteLocales( locale ).get( 0 );
400     }
401 
402     /**
403      * Actions when closing the report.
404      */
405     protected void closeReport()
406     {
407         getSink().close();
408     }
409 
410     /**
411      * @return the sink used
412      */
413     public Sink getSink()
414     {
415         return sink;
416     }
417 
418     /**
419      * @return the sink factory used
420      */
421     public SinkFactory getSinkFactory()
422     {
423         return sinkFactory;
424     }
425 
426     /**
427      * @see org.apache.maven.reporting.MavenReport#isExternalReport()
428      * @return {@code false} by default.
429      */
430     @Override
431     public boolean isExternalReport()
432     {
433         return false;
434     }
435 
436     @Override
437     public boolean canGenerateReport()
438     {
439         return true;
440     }
441 
442     /**
443      * Execute the generation of the report.
444      *
445      * @param locale the wanted locale to return the report's description, could be <code>null</code>.
446      * @throws MavenReportException if any
447      */
448     protected abstract void executeReport( Locale locale )
449         throws MavenReportException;
450 }