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.IOException;
23  import java.io.OutputStream;
24  import java.io.Writer;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Locale;
28  
29  import org.apache.commons.lang3.StringUtils;
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.DocumentRenderingContext;
34  import org.apache.maven.doxia.siterenderer.RendererException;
35  import org.apache.maven.doxia.siterenderer.SiteRenderer;
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.WriterFactory;
45  
46  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
47  
48  /**
49   * Renders a Maven report in a Doxia site.
50   *
51   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
52   * @see org.apache.maven.doxia.siterenderer.DoxiaDocumentRenderer
53   */
54  public class ReportDocumentRenderer implements DocumentRenderer {
55      private final MavenReport report;
56  
57      private final DocumentRenderingContext docRenderingContext;
58  
59      private final String reportMojoInfo;
60  
61      private final ClassLoader classLoader;
62  
63      private final Log log;
64  
65      public ReportDocumentRenderer(
66              MavenReportExecution mavenReportExecution, DocumentRenderingContext docRenderingContext, Log log) {
67          this.report = mavenReportExecution.getMavenReport();
68          this.docRenderingContext = docRenderingContext;
69          this.reportMojoInfo = mavenReportExecution.getGoal() == null
70                  ? null
71                  : mavenReportExecution.getPlugin().getArtifactId()
72                          + ':'
73                          + mavenReportExecution.getPlugin().getVersion()
74                          + ':'
75                          + mavenReportExecution.getGoal();
76          this.classLoader = mavenReportExecution.getClassLoader();
77          this.log = log;
78      }
79  
80      private static class MultiPageSubSink extends SiteRendererSink {
81          private File outputDirectory;
82  
83          private String outputName;
84  
85          MultiPageSubSink(File outputDirectory, String outputName, DocumentRenderingContext docRenderingContext) {
86              super(docRenderingContext);
87              this.outputName = outputName;
88              this.outputDirectory = outputDirectory;
89          }
90  
91          public String getOutputName() {
92              return outputName;
93          }
94  
95          public File getOutputDirectory() {
96              return outputDirectory;
97          }
98      }
99  
100     private static class MultiPageSinkFactory implements SinkFactory {
101         /**
102          * The report that is (maybe) generating multiple pages
103          */
104         private MavenReport report;
105 
106         /**
107          * The main DocumentRenderingContext, which is the base for the DocumentRenderingContext of subpages
108          */
109         private DocumentRenderingContext docRenderingContext;
110 
111         /**
112          * List of sinks (subpages) associated to this report
113          */
114         private List<MultiPageSubSink> sinks = new ArrayList<>();
115 
116         MultiPageSinkFactory(MavenReport report, DocumentRenderingContext docRenderingContext) {
117             this.report = report;
118             this.docRenderingContext = docRenderingContext;
119         }
120 
121         @Override
122         public Sink createSink(File outputDirectory, String outputName) {
123             // Create a new document rendering context, similar to the main one, but with a different output name
124             String document = PathTool.getRelativeFilePath(
125                     report.getReportOutputDirectory().getPath(), new File(outputDirectory, outputName).getPath());
126             // Remove .html suffix since we know that we are in Site Renderer context
127             document = document.substring(0, document.lastIndexOf('.'));
128 
129             DocumentRenderingContext subSinkContext = new DocumentRenderingContext(
130                     docRenderingContext.getBasedir(),
131                     docRenderingContext.getBasedirRelativePath(),
132                     document,
133                     docRenderingContext.getParserId(),
134                     docRenderingContext.getExtension(),
135                     docRenderingContext.isEditable(),
136                     docRenderingContext.getGenerator());
137 
138             // Create a sink for this subpage, based on this new document rendering context
139             MultiPageSubSink sink = new MultiPageSubSink(outputDirectory, outputName, subSinkContext);
140 
141             // Add it to the list of sinks associated to this report
142             sinks.add(sink);
143 
144             return sink;
145         }
146 
147         @Override
148         public Sink createSink(File arg0, String arg1, String arg2) throws IOException {
149             // Not used
150             return null;
151         }
152 
153         @Override
154         public Sink createSink(OutputStream arg0) throws IOException {
155             // Not used
156             return null;
157         }
158 
159         @Override
160         public Sink createSink(OutputStream arg0, String arg1) throws IOException {
161             // Not used
162             return null;
163         }
164 
165         public List<MultiPageSubSink> sinks() {
166             return sinks;
167         }
168     }
169 
170     @Override
171     public void renderDocument(Writer writer, SiteRenderer siteRenderer, SiteRenderingContext siteRenderingContext)
172             throws RendererException, IOException {
173         Locale locale = siteRenderingContext.getLocale();
174         String localReportName = report.getName(locale);
175 
176         String msg = "Generating \"" + buffer().strong(localReportName) + "\" report";
177         // CHECKSTYLE_OFF: MagicNumber
178         log.info(
179                 reportMojoInfo == null
180                         ? msg
181                         : (StringUtils.rightPad(msg, 40)
182                                 + buffer().strong(" --- ").mojo(reportMojoInfo)));
183         // CHECKSTYLE_ON: MagicNumber
184 
185         // main sink
186         SiteRendererSink mainSink = new SiteRendererSink(docRenderingContext);
187         // sink factory, for multi-page reports that need sub-sinks
188         MultiPageSinkFactory multiPageSinkFactory = new MultiPageSinkFactory(report, docRenderingContext);
189 
190         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
191         try {
192             if (classLoader != null) {
193                 Thread.currentThread().setContextClassLoader(classLoader);
194             }
195 
196             if (report instanceof MavenMultiPageReport) {
197                 // extended multi-page API
198                 ((MavenMultiPageReport) report).generate(mainSink, multiPageSinkFactory, locale);
199             } else {
200                 // old single-page-only API
201                 report.generate(mainSink, locale);
202             }
203         } catch (MavenReportException e) {
204             String report = (reportMojoInfo == null) ? ('"' + localReportName + '"') : reportMojoInfo;
205             throw new RendererException("Error generating " + report + " report", e);
206         } catch (RuntimeException re) {
207             // MSITE-836: if report generation throws a RuntimeException, transform to RendererException
208             String report = (reportMojoInfo == null) ? ('"' + localReportName + '"') : reportMojoInfo;
209             throw new RendererException("Error generating " + report + " report", re);
210         } catch (LinkageError e) {
211             String report = (reportMojoInfo == null) ? ('"' + localReportName + '"') : reportMojoInfo;
212             log.warn(
213                     "An issue has occurred with " + report + " report, skipping LinkageError " + e.getMessage()
214                             + ", please report an issue to Maven dev team.",
215                     e);
216         } finally {
217             if (classLoader != null) {
218                 Thread.currentThread().setContextClassLoader(originalClassLoader);
219             }
220             mainSink.close();
221         }
222 
223         if (report.isExternalReport()) {
224             // external reports are rendered from their own: no Doxia site rendering needed
225             return;
226         }
227 
228         // render main sink document content
229         siteRenderer.mergeDocumentIntoSite(writer, mainSink, siteRenderingContext);
230 
231         // render sub-sinks, eventually created by multi-page reports
232         String outputName = "";
233         List<MultiPageSubSink> sinks = multiPageSinkFactory.sinks();
234 
235         log.debug("Multipage report: " + sinks.size() + " subreports");
236 
237         for (MultiPageSubSink mySink : sinks) {
238             outputName = mySink.getOutputName();
239             log.debug("  Rendering " + outputName);
240 
241             // Create directories if necessary
242             if (!mySink.getOutputDirectory().exists()) {
243                 mySink.getOutputDirectory().mkdirs();
244             }
245 
246             File outputFile = new File(mySink.getOutputDirectory(), outputName);
247 
248             try (Writer out = WriterFactory.newWriter(outputFile, siteRenderingContext.getOutputEncoding())) {
249                 siteRenderer.mergeDocumentIntoSite(out, mySink, siteRenderingContext);
250                 mySink.close();
251                 mySink = null;
252             } finally {
253                 if (mySink != null) {
254                     mySink.close();
255                 }
256             }
257         }
258     }
259 
260     @Override
261     public String getOutputName() {
262         return docRenderingContext.getOutputName();
263     }
264 
265     @Override
266     public DocumentRenderingContext getRenderingContext() {
267         return docRenderingContext;
268     }
269 
270     @Override
271     public boolean isOverwrite() {
272         // TODO: would be nice to query the report to see if it is modified
273         return true;
274     }
275 
276     /**
277      * @return true if the current report is external, false otherwise
278      */
279     @Override
280     public boolean isExternalReport() {
281         return report.isExternalReport();
282     }
283 
284     public String getReportMojoInfo() {
285         return reportMojoInfo;
286     }
287 }