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      * SiteTool.
132      */
133     @Component
134     protected SiteTool siteTool;
135 
136     /**
137      * Doxia Site Renderer component.
138      */
139     @Component
140     protected Renderer siteRenderer;
141 
142     /** The current sink to use */
143     private Sink sink;
144 
145     /** The sink factory to use */
146     private SinkFactory sinkFactory;
147 
148     /** The current report output directory to use */
149     private File reportOutputDirectory;
150 
151     /**
152      * This method is called when the report generation is invoked directly as a standalone Mojo.
153      *
154      * @throws MojoExecutionException if an error occurs when generating the report
155      * @see org.apache.maven.plugin.Mojo#execute()
156      */
157     @Override
158     public void execute()
159         throws MojoExecutionException
160     {
161         if ( !canGenerateReport() )
162         {
163             return;
164         }
165 
166         File outputDirectory = new File( getOutputDirectory() );
167 
168         String filename = getOutputName() + ".html";
169 
170         Locale locale = Locale.getDefault();
171 
172         try
173         {
174             SiteRenderingContext siteContext = createSiteRenderingContext( locale );
175 
176             // copy resources
177             siteRenderer.copyResources( siteContext, outputDirectory );
178 
179             // TODO Replace null with real value
180             RenderingContext docRenderingContext = new RenderingContext( outputDirectory, filename, null );
181 
182             SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
183 
184             generate( sink, null, locale );
185 
186             if ( !isExternalReport() ) // MSHARED-204: only render Doxia sink if not an external report
187             {
188                 outputDirectory.mkdirs();
189 
190                 try ( Writer writer =
191                       new OutputStreamWriter( new FileOutputStream( new File( outputDirectory, filename ) ),
192                                               getOutputEncoding() ) )
193                 {
194                     // render report
195                     getSiteRenderer().mergeDocumentIntoSite( writer, sink, siteContext );
196                 }
197             }
198 
199             // copy generated resources also
200             siteRenderer.copyResources( siteContext, outputDirectory );
201         }
202         catch ( RendererException | IOException | MavenReportException e )
203         {
204             throw new MojoExecutionException(
205                 "An error has occurred in " + getName( Locale.ENGLISH ) + " report generation.", e );
206         }
207     }
208 
209     private SiteRenderingContext createSiteRenderingContext( Locale locale )
210         throws MavenReportException, IOException
211     {
212         DecorationModel decorationModel = new DecorationModel();
213         decorationModel.setSkin( getSkin() );
214 
215         Map<String, Object> templateProperties = new HashMap<>();
216         // We tell the skin that we are rendering in standalone mode
217         templateProperties.put( "standalone", Boolean.TRUE );
218         templateProperties.put( "project", getProject() );
219         templateProperties.put( "inputEncoding", getInputEncoding() );
220         templateProperties.put( "outputEncoding", getOutputEncoding() );
221         // Put any of the properties in directly into the Velocity context
222         for ( Map.Entry<Object, Object> entry : getProject().getProperties().entrySet() )
223         {
224             templateProperties.put( (String) entry.getKey(), entry.getValue() );
225         }
226 
227         SiteRenderingContext context;
228         try
229         {
230            Artifact skinArtifact =
231                siteTool.getSkinArtifactFromRepository( localRepository, remoteRepositories, decorationModel );
232 
233            getLog().info( buffer().a( "Rendering content with " ).strong( skinArtifact.getId()
234                + " skin" ).a( '.' ).toString() );
235 
236             context = siteRenderer.createContextForSkin( skinArtifact, templateProperties, decorationModel,
237                                                          project.getName(), locale );
238         }
239         catch ( SiteToolException e )
240         {
241             throw new MavenReportException( "Failed to retrieve skin artifact", e );
242         }
243         catch ( RendererException e )
244         {
245             throw new MavenReportException( "Failed to create context for skin", e );
246         }
247 
248         // Generate static site
249         context.setRootDirectory( project.getBasedir() );
250 
251         return context;
252     }
253 
254     /**
255      * Generate a report.
256      *
257      * @param sink
258      * @param locale
259      * @throws MavenReportException
260      * @deprecated use {@link #generate(Sink, SinkFactory, Locale)} instead.
261      */
262     @Deprecated
263     @Override
264     public void generate( Sink sink, Locale locale )
265         throws MavenReportException
266     {
267         generate( sink, null, locale );
268     }
269 
270     /**
271      * This method is called when the report generation is invoked by maven-site-plugin.
272      *
273      * @param sink
274      * @param sinkFactory
275      * @param locale
276      * @throws MavenReportException
277      */
278     @Override
279     public void generate( Sink sink, SinkFactory sinkFactory, Locale locale )
280         throws MavenReportException
281     {
282         if ( !canGenerateReport() )
283         {
284             getLog().info( "This report cannot be generated as part of the current build. "
285                            + "The report name should be referenced in this line of output." );
286             return;
287         }
288 
289         this.sink = sink;
290 
291         this.sinkFactory = sinkFactory;
292 
293         executeReport( locale );
294 
295         closeReport();
296     }
297 
298     /**
299      * @return CATEGORY_PROJECT_REPORTS
300      */
301     @Override
302     public String getCategoryName()
303     {
304         return CATEGORY_PROJECT_REPORTS;
305     }
306 
307     @Override
308     public File getReportOutputDirectory()
309     {
310         if ( reportOutputDirectory == null )
311         {
312             reportOutputDirectory = new File( getOutputDirectory() );
313         }
314 
315         return reportOutputDirectory;
316     }
317 
318     @Override
319     public void setReportOutputDirectory( File reportOutputDirectory )
320     {
321         this.reportOutputDirectory = reportOutputDirectory;
322         this.outputDirectory = reportOutputDirectory;
323     }
324 
325     protected String getOutputDirectory()
326     {
327         return outputDirectory.getAbsolutePath();
328     }
329 
330     protected MavenProject getProject()
331     {
332         return project;
333     }
334 
335     protected Renderer getSiteRenderer()
336     {
337         return siteRenderer;
338     }
339 
340     /**
341      * Gets the input files encoding.
342      *
343      * @return The input files encoding, never <code>null</code>.
344      */
345     protected String getInputEncoding()
346     {
347         return ( inputEncoding == null ) ? ReaderFactory.FILE_ENCODING : inputEncoding;
348     }
349 
350     /**
351      * Gets the effective reporting output files encoding.
352      *
353      * @return The effective reporting output file encoding, never <code>null</code>.
354      */
355     protected String getOutputEncoding()
356     {
357         return ( outputEncoding == null ) ? WriterFactory.UTF_8 : outputEncoding;
358     }
359 
360     /**
361      * Gets the skin
362      *
363      * @return the skin for this standalone report
364      */
365     protected Skin getSkin()
366     {
367         if ( skin == null )
368         {
369             Skin skin = new Skin();
370             skin.setGroupId( "org.apache.maven.skins" );
371             skin.setArtifactId( "maven-fluido-skin" );
372             skin.setVersion( "1.11.1" );
373             return skin;
374         }
375         else
376         {
377             return skin;
378         }
379     }
380 
381     /**
382      * Actions when closing the report.
383      */
384     protected void closeReport()
385     {
386         getSink().close();
387     }
388 
389     /**
390      * @return the sink used
391      */
392     public Sink getSink()
393     {
394         return sink;
395     }
396 
397     /**
398      * @return the sink factory used
399      */
400     public SinkFactory getSinkFactory()
401     {
402         return sinkFactory;
403     }
404 
405     /**
406      * @see org.apache.maven.reporting.MavenReport#isExternalReport()
407      * @return {@code false} by default.
408      */
409     @Override
410     public boolean isExternalReport()
411     {
412         return false;
413     }
414 
415     @Override
416     public boolean canGenerateReport()
417     {
418         return true;
419     }
420 
421     /**
422      * Execute the generation of the report.
423      *
424      * @param locale the wanted locale to return the report's description, could be <code>null</code>.
425      * @throws MavenReportException if any
426      */
427     protected abstract void executeReport( Locale locale )
428         throws MavenReportException;
429 }