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