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