View Javadoc

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