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