View Javadoc
1   package org.apache.maven.plugins.site.render;
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 static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
23  
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.io.Writer;
28  import java.io.File;
29  import java.lang.reflect.InvocationTargetException;
30  import java.lang.reflect.Method;
31  import java.util.ArrayList;
32  import java.util.Locale;
33  import java.util.List;
34  
35  import org.apache.maven.doxia.sink.Sink;
36  import org.apache.maven.doxia.sink.SinkFactory;
37  import org.apache.maven.doxia.siterenderer.DocumentRenderer;
38  import org.apache.maven.doxia.siterenderer.Renderer;
39  import org.apache.maven.doxia.siterenderer.RendererException;
40  import org.apache.maven.doxia.siterenderer.RenderingContext;
41  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
42  import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
43  import org.apache.maven.doxia.tools.MojoLogWrapper;
44  import org.apache.maven.plugin.logging.Log;
45  import org.apache.maven.reporting.MavenMultiPageReport;
46  import org.apache.maven.reporting.MavenReport;
47  import org.apache.maven.reporting.MavenReportException;
48  import org.apache.maven.reporting.exec.MavenReportExecution;
49  import org.codehaus.plexus.util.PathTool;
50  import org.codehaus.plexus.util.StringUtils;
51  import org.codehaus.plexus.util.WriterFactory;
52  
53  /**
54   * Renders a Maven report in a Doxia site.
55   *
56   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
57   * @see org.apache.maven.doxia.siterenderer.DoxiaDocumentRenderer
58   */
59  public class ReportDocumentRenderer
60      implements DocumentRenderer
61  {
62      private final MavenReport report;
63  
64      private final RenderingContext renderingContext;
65  
66      private final String reportMojoInfo;
67  
68      private final ClassLoader classLoader;
69  
70      private final Log log;
71  
72      public ReportDocumentRenderer( MavenReportExecution mavenReportExecution, RenderingContext renderingContext,
73                                     Log log )
74      {
75          this.report = mavenReportExecution.getMavenReport();
76  
77          this.renderingContext = renderingContext;
78  
79          // full MavenReportExecution prepared by maven-reporting-impl
80          this.reportMojoInfo =
81              mavenReportExecution.getPlugin().getArtifactId() + ':' + mavenReportExecution.getPlugin().getVersion()
82                  + ':' + mavenReportExecution.getGoal();
83  
84          this.classLoader = mavenReportExecution.getClassLoader();
85  
86          this.log = log;
87      }
88  
89      private static class MultiPageSubSink
90          extends SiteRendererSink
91      {
92          private File outputDir;
93  
94          private String outputName;
95  
96          MultiPageSubSink( File outputDir, String outputName, RenderingContext context )
97          {
98              super( context );
99              this.outputName = outputName;
100             this.outputDir = outputDir;
101         }
102 
103         public String getOutputName()
104         {
105             return outputName;
106         }
107 
108         public File getOutputDir()
109         {
110             return outputDir;
111         }
112 
113     }
114 
115     private static class MultiPageSinkFactory
116         implements SinkFactory
117     {
118         /**
119          * The report that is (maybe) generating multiple pages
120          */
121         private MavenReport report;
122 
123         /**
124          * The main RenderingContext, which is the base for the RenderingContext of subpages
125          */
126         private RenderingContext context;
127 
128         /**
129          * List of sinks (subpages) associated to this report
130          */
131         private List<MultiPageSubSink> sinks = new ArrayList<MultiPageSubSink>();
132 
133         MultiPageSinkFactory( MavenReport report, RenderingContext context )
134         {
135             this.report = report;
136             this.context = context;
137         }
138 
139         @Override
140         public Sink createSink( File outputDir, String outputName )
141         {
142             // Create a new context, similar to the main one, but with a different output name
143             String outputRelativeToTargetSite = PathTool.getRelativeFilePath(
144                 report.getReportOutputDirectory().getPath(),
145                 new File( outputDir, outputName ).getPath()
146             );
147 
148             RenderingContext subSinkContext = new RenderingContext(
149                 context.getBasedir(),
150                 context.getBasedirRelativePath(),
151                 outputRelativeToTargetSite,
152                 context.getParserId(),
153                 context.getExtension(),
154                 context.isEditable(),
155                 context.getGenerator()
156             );
157 
158             // Create a sink for this subpage, based on this new context
159             MultiPageSubSink sink = new MultiPageSubSink( outputDir, outputName, subSinkContext );
160 
161             // Add it to the list of sinks associated to this report
162             sinks.add( sink );
163 
164             return sink;
165         }
166 
167         @Override
168         public Sink createSink( File arg0, String arg1, String arg2 )
169             throws IOException
170         {
171             // Not used
172             return null;
173         }
174 
175         @Override
176         public Sink createSink( OutputStream arg0 )
177             throws IOException
178         {
179             // Not used
180             return null;
181         }
182 
183         @Override
184         public Sink createSink( OutputStream arg0, String arg1 )
185             throws IOException
186         {
187             // Not used
188             return null;
189         }
190 
191         public List<MultiPageSubSink> sinks()
192         {
193             return sinks;
194         }
195     }
196 
197     @Override
198     public void renderDocument( Writer writer, Renderer renderer, SiteRenderingContext siteRenderingContext )
199         throws RendererException, FileNotFoundException
200     {
201         Locale locale = siteRenderingContext.getLocale();
202         String localReportName = report.getName( locale );
203 
204         String msg = "Generating \"" + buffer().strong( localReportName ) + "\" report";
205         // CHECKSTYLE_OFF: MagicNumber
206         log.info( reportMojoInfo == null ? ( msg + '.' )
207                         : ( StringUtils.rightPad( msg, 40 ) + buffer().strong( " --- " ).mojo( reportMojoInfo ) ) );
208         // CHECKSTYLE_ON: MagicNumber
209 
210         // main sink
211         SiteRendererSink mainSink = new SiteRendererSink( renderingContext );
212         // sink factory, for multi-page reports that need sub-sinks
213         MultiPageSinkFactory multiPageSinkFactory = new MultiPageSinkFactory( report, renderingContext );
214 
215         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
216         try
217         {
218             if ( classLoader != null )
219             {
220                 Thread.currentThread().setContextClassLoader( classLoader );
221             }
222 
223             if ( report instanceof MavenMultiPageReport )
224             {
225                 // extended multi-page API
226                 ( (MavenMultiPageReport) report ).generate( mainSink, multiPageSinkFactory, locale );
227             }
228             else if ( generateMultiPage( locale, multiPageSinkFactory, mainSink ) )
229             {
230                 // extended multi-page API for Maven 2.2, only accessible by reflection API
231             }
232             else
233             {
234                 // old single-page-only API
235                 report.generate( mainSink, locale );
236             }
237         }
238         catch ( MavenReportException e )
239         {
240             String report = ( reportMojoInfo == null ) ? ( '"' + localReportName + '"' ) : reportMojoInfo;
241             throw new RendererException( "Error generating " + report + " report", e );
242         }
243         catch ( RuntimeException re )
244         {
245             // MSITE-836: if report generation throws a RuntimeException, transform to RendererException
246             String report = ( reportMojoInfo == null ) ? ( '"' + localReportName + '"' ) : reportMojoInfo;
247             throw new RendererException( "Error generating " + report + " report", re );
248         }
249         catch ( LinkageError e )
250         {
251             String report = ( reportMojoInfo == null ) ? ( '"' + localReportName + '"' ) : reportMojoInfo;
252             log.warn( "An issue has occurred with " + report + " report, skipping LinkageError "
253                           + e.getMessage() + ", please report an issue to Maven dev team.", e );
254         }
255         finally
256         {
257             if ( classLoader != null )
258             {
259                 Thread.currentThread().setContextClassLoader( originalClassLoader );
260             }
261             mainSink.close();
262         }
263 
264         if ( report.isExternalReport() )
265         {
266             // external reports are rendered from their own: no Doxia site rendering needed
267             return;
268         }
269 
270         // render main sink document content
271         renderer.mergeDocumentIntoSite( writer, mainSink, siteRenderingContext );
272 
273         // render sub-sinks, eventually created by multi-page reports
274         String outputName = "";
275         try
276         {
277             List<MultiPageSubSink> sinks = multiPageSinkFactory.sinks();
278 
279             log.debug( "Multipage report: " + sinks.size() + " subreports" );
280 
281             for ( MultiPageSubSink mySink : sinks )
282             {
283                 mySink.enableLogging( new MojoLogWrapper( log ) );
284 
285                 outputName = mySink.getOutputName();
286                 log.debug( "  Rendering " + outputName );
287 
288                 // Create directories if necessary
289                 if ( !mySink.getOutputDir().exists() )
290                 {
291                     mySink.getOutputDir().mkdirs();
292                 }
293 
294                 File outputFile = new File( mySink.getOutputDir(), outputName );
295 
296                 try ( Writer out = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() ) )
297                 {
298                     renderer.mergeDocumentIntoSite( out, mySink, siteRenderingContext );
299                     mySink.close();
300                     mySink = null;
301                 }
302                 finally
303                 {
304                     if ( mySink != null )
305                     {
306                         mySink.close();
307                     }
308                 }
309             }
310         }
311         catch ( IOException e )
312         {
313             throw new RendererException( "Cannot create writer to " + outputName, e );
314         }
315     }
316 
317     /**
318      * Try to generate report with extended multi-page API.
319      *
320      * @return <code>true</code> if the report was compatible with the extended API
321      */
322     private boolean generateMultiPage( Locale locale, SinkFactory sf, Sink sink )
323         throws MavenReportException
324     {
325         try
326         {
327             // MavenMultiPageReport is not in Maven Core, then the class is different in site plugin and in each report
328             // plugin: only reflection can let us invoke its method
329             Method generate =
330                 report.getClass().getMethod( "generate", Sink.class, SinkFactory.class, Locale.class );
331 
332             generate.invoke( report, sink, sf, locale );
333 
334             return true;
335         }
336         catch ( SecurityException se )
337         {
338             return false;
339         }
340         catch ( NoSuchMethodException nsme )
341         {
342             return false;
343         }
344         catch ( IllegalArgumentException | IllegalAccessException | InvocationTargetException ite )
345         {
346             throw new MavenReportException( "error while invoking generate on " + report.getClass(), ite );
347         }
348     }
349 
350     @Override
351     public String getOutputName()
352     {
353         return renderingContext.getOutputName();
354     }
355 
356     @Override
357     public RenderingContext getRenderingContext()
358     {
359         return renderingContext;
360     }
361 
362     @Override
363     public boolean isOverwrite()
364     {
365         // TODO: would be nice to query the report to see if it is modified
366         return true;
367     }
368 
369     /**
370      * @return true if the current report is external, false otherwise
371      */
372     @Override
373     public boolean isExternalReport()
374     {
375         return report.isExternalReport();
376     }
377 
378     public String getReportMojoInfo()
379     {
380         return reportMojoInfo;
381     }
382 }