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