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