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.pdf;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayDeque;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.Deque;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  
32  import org.apache.maven.doxia.document.DocumentModel;
33  import org.apache.maven.doxia.document.DocumentTOC;
34  import org.apache.maven.doxia.document.DocumentTOCItem;
35  import org.apache.maven.model.Reporting;
36  import org.apache.maven.plugins.annotations.Execute;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.annotations.ResolutionScope;
40  import org.apache.maven.project.MavenProject;
41  import org.codehaus.plexus.util.FileUtils;
42  
43  /**
44   * Forks {@code pdf} goal then aggregates PDF content from all modules in the reactor.
45   *
46   * @author anthony-beurive
47   * @since 1.5
48   */
49  @Mojo(name = "aggregate", aggregator = true, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
50  @Execute(goal = "pdf")
51  // TODO should extend AbstractPdfMojo, but requires extensive refactoring
52  public class PdfAggregateMojo extends PdfMojo {
53      /**
54       * The reactor projects.
55       */
56      @Parameter(defaultValue = "${reactorProjects}", required = true, readonly = true)
57      private List<MavenProject> reactorProjects;
58  
59      /**
60       * Output directory where aggregated PDF files should be created.
61       */
62      @Parameter(defaultValue = "${project.build.directory}/pdf-aggregate", required = true)
63      private File aggregatedOutputDirectory;
64  
65      /**
66       * Working directory for aggregated working files like temp files/resources.
67       */
68      @Parameter(defaultValue = "${project.build.directory}/pdf-aggregate", required = true)
69      private File aggregatedWorkingDirectory;
70  
71      protected File getOutputDirectory() {
72          return aggregatedOutputDirectory;
73      }
74  
75      protected File getWorkingDirectory() {
76          return aggregatedWorkingDirectory;
77      }
78  
79      protected boolean isIncludeReports() {
80          return false; // reports were generate (or not) during pdf:pdf: here, we only aggregate
81      }
82  
83      protected void prepareTempSiteDirectory(final File tmpSiteDir) {
84          tmpSiteDir.mkdirs();
85      }
86  
87      @Override
88      protected void appendGeneratedReports(DocumentModel model, Locale locale) {
89          super.appendGeneratedReports(model, locale);
90  
91          getLog().info("Appending staged reports.");
92  
93          DocumentTOC toc = model.getToc();
94  
95          File dstSiteTmp = null;
96          try {
97              dstSiteTmp = getSiteDirectoryTmp();
98          } catch (IOException ioe) {
99              getLog().error("unexpected IOException while getting aggregator root tmp site dir", ioe);
100         }
101         if (!dstSiteTmp.exists()) {
102             getLog().error("Top-level project does not have src.tmp directory");
103             return;
104         }
105 
106         for (MavenProject reactorProject : reactorProjects) {
107             getLog().info("Appending " + reactorProject.getArtifactId() + " reports.");
108 
109             copySiteDirectoryTmp(reactorProject, dstSiteTmp);
110 
111             addTOCItems(toc, reactorProject);
112         }
113     }
114 
115     private void copySiteDirectoryTmp(MavenProject project, File dstSiteTmp) {
116         Reporting reporting = project.getReporting();
117         if (reporting == null) {
118             getLog().info("Skipping reactor project " + project + ": no reporting");
119             return;
120         }
121 
122         File srcSiteTmp = getModuleSiteDirectoryTmp(project);
123         if (!srcSiteTmp.exists()) {
124             getLog().info("Skipping reactor project " + project + ": no site.tmp directory");
125             return;
126         }
127 
128         String stagedId = getStagedId(project);
129 
130         try {
131             String defaultExcludes = FileUtils.getDefaultExcludesAsString();
132             List<String> srcDirNames = FileUtils.getDirectoryNames(srcSiteTmp, "*", defaultExcludes, false);
133             for (String srcDirName : srcDirNames) {
134                 File srcDir = new File(srcSiteTmp, srcDirName);
135                 File dstDir = new File(new File(dstSiteTmp, srcDirName), stagedId);
136                 if (!dstDir.exists() && !dstDir.mkdirs()) {
137                     getLog().error("Could not create directory: " + dstDir);
138                     return;
139                 }
140 
141                 FileUtils.copyDirectoryStructure(srcDir, dstDir);
142             }
143         } catch (IOException e) {
144             getLog().error(
145                             "Error while copying sub-project " + project.getArtifactId() + " site.tmp: "
146                                     + e.getMessage(),
147                             e);
148         }
149     }
150 
151     private void addTOCItems(DocumentTOC topLevelToc, MavenProject project) {
152         String stagedId = getStagedId(project);
153 
154         Map<String, Object> toc = loadToc(project);
155 
156         List<Map<String, Object>> items = (ArrayList) toc.get("items");
157 
158         DocumentTOCItem tocItem = new DocumentTOCItem();
159         tocItem.setName(project.getName());
160         tocItem.setRef(stagedId);
161 
162         if (items.size() == 1 && "project-info".equals(items.get(0).get("ref"))) {
163             // Special case where a sub-project only contains generated reports.
164             items = (List) items.get(0).get("items");
165         }
166 
167         for (Map<String, Object> item : items) {
168             addTOCItems(tocItem, item, stagedId);
169         }
170 
171         topLevelToc.addItem(tocItem);
172     }
173 
174     private Map<String, Object> loadToc(MavenProject project) {
175         try {
176             return TocFileHelper.loadToc(getModuleWorkingDirectory(project));
177         } catch (IOException e) {
178             getLog().error("Error while reading table of contents of module " + project.getArtifactId(), e);
179             return Collections.emptyMap();
180         }
181     }
182 
183     private void addTOCItems(DocumentTOCItem parent, Map<String, Object> item, String stagedId) {
184         DocumentTOCItem tocItem = new DocumentTOCItem();
185         tocItem.setName((String) item.get("name"));
186         tocItem.setRef(stagedId + "/" + item.get("ref"));
187 
188         List<Map<String, Object>> items = (List) item.get("items");
189 
190         for (Map<String, Object> it : items) {
191             addTOCItems(tocItem, it, stagedId);
192         }
193 
194         parent.addItem(tocItem);
195     }
196 
197     private String getStagedId(MavenProject p) {
198         Deque<String> projectPath = new ArrayDeque<>();
199         projectPath.addFirst(p.getArtifactId());
200         while (p.getParent() != null) {
201             p = p.getParent();
202             projectPath.addFirst(p.getArtifactId());
203         }
204 
205         StringBuilder stagedId = new StringBuilder();
206         Iterator<String> artifactIds = projectPath.iterator();
207         while (artifactIds.hasNext()) {
208             stagedId.append(artifactIds.next());
209             if (artifactIds.hasNext()) {
210                 stagedId.append('/');
211             }
212         }
213         return stagedId.toString();
214     }
215 
216     private File getModuleWorkingDirectory(MavenProject project) {
217         return new File(project.getBuild().getDirectory(), "pdf");
218     }
219 
220     private File getModuleSiteDirectoryTmp(MavenProject project) {
221         return new File(getModuleWorkingDirectory(project), "site.tmp");
222     }
223 }