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