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