View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.site.render;
20  
21  import java.io.File;
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.io.Writer;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Locale;
29  
30  import org.apache.maven.doxia.sink.Sink;
31  import org.apache.maven.doxia.sink.SinkFactory;
32  import org.apache.maven.doxia.siterenderer.DocumentRenderer;
33  import org.apache.maven.doxia.siterenderer.Renderer;
34  import org.apache.maven.doxia.siterenderer.RendererException;
35  import org.apache.maven.doxia.siterenderer.RenderingContext;
36  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
37  import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
38  import org.apache.maven.plugin.logging.Log;
39  import org.apache.maven.reporting.MavenMultiPageReport;
40  import org.apache.maven.reporting.MavenReport;
41  import org.apache.maven.reporting.MavenReportException;
42  import org.apache.maven.reporting.exec.MavenReportExecution;
43  import org.codehaus.plexus.util.PathTool;
44  import org.codehaus.plexus.util.StringUtils;
45  import org.codehaus.plexus.util.WriterFactory;
46  
47  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
48  
49  /**
50   * Renders a Maven report in a Doxia site.
51   *
52   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
53   * @see org.apache.maven.doxia.siterenderer.DoxiaDocumentRenderer
54   */
55  public class ReportDocumentRenderer implements DocumentRenderer {
56      private final MavenReport report;
57  
58      private final RenderingContext renderingContext;
59  
60      private final String reportMojoInfo;
61  
62      private final ClassLoader classLoader;
63  
64      private final Log log;
65  
66      public ReportDocumentRenderer(
67              MavenReportExecution mavenReportExecution, RenderingContext renderingContext, Log log) {
68          this.report = mavenReportExecution.getMavenReport();
69  
70          this.renderingContext = renderingContext;
71  
72          // full MavenReportExecution prepared by maven-reporting-impl
73          this.reportMojoInfo = mavenReportExecution.getPlugin().getArtifactId()
74                  + ':'
75                  + mavenReportExecution.getPlugin().getVersion()
76                  + ':'
77                  + mavenReportExecution.getGoal();
78  
79          this.classLoader = mavenReportExecution.getClassLoader();
80  
81          this.log = log;
82      }
83  
84      private static class MultiPageSubSink extends SiteRendererSink {
85          private File outputDir;
86  
87          private String outputName;
88  
89          MultiPageSubSink(File outputDir, String outputName, RenderingContext context) {
90              super(context);
91              this.outputName = outputName;
92              this.outputDir = outputDir;
93          }
94  
95          public String getOutputName() {
96              return outputName;
97          }
98  
99          public File getOutputDir() {
100             return outputDir;
101         }
102     }
103 
104     private static class MultiPageSinkFactory implements SinkFactory {
105         /**
106          * The report that is (maybe) generating multiple pages
107          */
108         private MavenReport report;
109 
110         /**
111          * The main RenderingContext, which is the base for the RenderingContext of subpages
112          */
113         private RenderingContext context;
114 
115         /**
116          * List of sinks (subpages) associated to this report
117          */
118         private List<MultiPageSubSink> sinks = new ArrayList<MultiPageSubSink>();
119 
120         MultiPageSinkFactory(MavenReport report, RenderingContext context) {
121             this.report = report;
122             this.context = context;
123         }
124 
125         @Override
126         public Sink createSink(File outputDir, String outputName) {
127             // Create a new context, similar to the main one, but with a different output name
128             String outputRelativeToTargetSite = PathTool.getRelativeFilePath(
129                     report.getReportOutputDirectory().getPath(), new File(outputDir, outputName).getPath());
130 
131             RenderingContext subSinkContext = new RenderingContext(
132                     context.getBasedir(),
133                     context.getBasedirRelativePath(),
134                     outputRelativeToTargetSite,
135                     context.getParserId(),
136                     context.getExtension(),
137                     context.isEditable(),
138                     context.getGenerator());
139 
140             // Create a sink for this subpage, based on this new context
141             MultiPageSubSink sink = new MultiPageSubSink(outputDir, outputName, subSinkContext);
142 
143             // Add it to the list of sinks associated to this report
144             sinks.add(sink);
145 
146             return sink;
147         }
148 
149         @Override
150         public Sink createSink(File arg0, String arg1, String arg2) throws IOException {
151             // Not used
152             return null;
153         }
154 
155         @Override
156         public Sink createSink(OutputStream arg0) throws IOException {
157             // Not used
158             return null;
159         }
160 
161         @Override
162         public Sink createSink(OutputStream arg0, String arg1) throws IOException {
163             // Not used
164             return null;
165         }
166 
167         public List<MultiPageSubSink> sinks() {
168             return sinks;
169         }
170     }
171 
172     @Override
173     public void renderDocument(Writer writer, Renderer renderer, SiteRenderingContext siteRenderingContext)
174             throws RendererException, FileNotFoundException {
175         Locale locale = siteRenderingContext.getLocale();
176         String localReportName = report.getName(locale);
177 
178         String msg = "Generating \"" + buffer().strong(localReportName) + "\" report";
179         // CHECKSTYLE_OFF: MagicNumber
180         log.info(
181                 reportMojoInfo == null
182                         ? (msg + '.')
183                         : (StringUtils.rightPad(msg, 40)
184                                 + buffer().strong(" --- ").mojo(reportMojoInfo)));
185         // CHECKSTYLE_ON: MagicNumber
186 
187         // main sink
188         SiteRendererSink mainSink = new SiteRendererSink(renderingContext);
189         // sink factory, for multi-page reports that need sub-sinks
190         MultiPageSinkFactory multiPageSinkFactory = new MultiPageSinkFactory(report, renderingContext);
191 
192         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
193         try {
194             if (classLoader != null) {
195                 Thread.currentThread().setContextClassLoader(classLoader);
196             }
197 
198             if (report instanceof MavenMultiPageReport) {
199                 // extended multi-page API
200                 ((MavenMultiPageReport) report).generate(mainSink, multiPageSinkFactory, locale);
201             } else {
202                 // old single-page-only API
203                 report.generate(mainSink, locale);
204             }
205         } catch (MavenReportException e) {
206             String report = (reportMojoInfo == null) ? ('"' + localReportName + '"') : reportMojoInfo;
207             throw new RendererException("Error generating " + report + " report", e);
208         } catch (RuntimeException re) {
209             // MSITE-836: if report generation throws a RuntimeException, transform to RendererException
210             String report = (reportMojoInfo == null) ? ('"' + localReportName + '"') : reportMojoInfo;
211             throw new RendererException("Error generating " + report + " report", re);
212         } catch (LinkageError e) {
213             String report = (reportMojoInfo == null) ? ('"' + localReportName + '"') : reportMojoInfo;
214             log.warn(
215                     "An issue has occurred with " + report + " report, skipping LinkageError " + e.getMessage()
216                             + ", please report an issue to Maven dev team.",
217                     e);
218         } finally {
219             if (classLoader != null) {
220                 Thread.currentThread().setContextClassLoader(originalClassLoader);
221             }
222             mainSink.close();
223         }
224 
225         if (report.isExternalReport()) {
226             // external reports are rendered from their own: no Doxia site rendering needed
227             return;
228         }
229 
230         // render main sink document content
231         renderer.mergeDocumentIntoSite(writer, mainSink, siteRenderingContext);
232 
233         // render sub-sinks, eventually created by multi-page reports
234         String outputName = "";
235         try {
236             List<MultiPageSubSink> sinks = multiPageSinkFactory.sinks();
237 
238             log.debug("Multipage report: " + sinks.size() + " subreports");
239 
240             for (MultiPageSubSink mySink : sinks) {
241                 outputName = mySink.getOutputName();
242                 log.debug("  Rendering " + outputName);
243 
244                 // Create directories if necessary
245                 if (!mySink.getOutputDir().exists()) {
246                     mySink.getOutputDir().mkdirs();
247                 }
248 
249                 File outputFile = new File(mySink.getOutputDir(), outputName);
250 
251                 try (Writer out = WriterFactory.newWriter(outputFile, siteRenderingContext.getOutputEncoding())) {
252                     renderer.mergeDocumentIntoSite(out, mySink, siteRenderingContext);
253                     mySink.close();
254                     mySink = null;
255                 } finally {
256                     if (mySink != null) {
257                         mySink.close();
258                     }
259                 }
260             }
261         } catch (IOException e) {
262             throw new RendererException("Cannot create writer to " + outputName, e);
263         }
264     }
265 
266     @Override
267     public String getOutputName() {
268         return renderingContext.getOutputName();
269     }
270 
271     @Override
272     public RenderingContext getRenderingContext() {
273         return renderingContext;
274     }
275 
276     @Override
277     public boolean isOverwrite() {
278         // TODO: would be nice to query the report to see if it is modified
279         return true;
280     }
281 
282     /**
283      * @return true if the current report is external, false otherwise
284      */
285     @Override
286     public boolean isExternalReport() {
287         return report.isExternalReport();
288     }
289 
290     public String getReportMojoInfo() {
291         return reportMojoInfo;
292     }
293 }