View Javadoc

1   package org.apache.maven.plugins.site;
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 java.io.File;
23  import java.io.IOException;
24  
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  
34  import org.apache.maven.artifact.Artifact;
35  import org.apache.maven.artifact.repository.ArtifactRepository;
36  import org.apache.maven.doxia.sink.render.RenderingContext;
37  import org.apache.maven.doxia.site.decoration.DecorationModel;
38  import org.apache.maven.doxia.site.decoration.Menu;
39  import org.apache.maven.doxia.site.decoration.MenuItem;
40  import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler;
41  import org.apache.maven.doxia.siterenderer.DocumentRenderer;
42  import org.apache.maven.doxia.siterenderer.Renderer;
43  import org.apache.maven.doxia.siterenderer.RendererException;
44  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
45  import org.apache.maven.doxia.tools.SiteToolException;
46  import org.apache.maven.plugin.MojoExecutionException;
47  import org.apache.maven.plugin.MojoFailureException;
48  import org.apache.maven.reporting.MavenReport;
49  
50  
51  /**
52   * Base class for site rendering mojos.
53   *
54   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
55   * @version $Id: AbstractSiteRenderingMojo.html 816561 2012-05-08 12:02:24Z hboutemy $
56   */
57  public abstract class AbstractSiteRenderingMojo
58      extends AbstractSiteMojo
59  {
60      /**
61       * Module type exclusion mappings
62       * ex: <code>fml  -> **&#47;*-m1.fml</code>  (excludes fml files ending in '-m1.fml' recursively)
63       * <p/>
64       * The configuration looks like this:
65       * <pre>
66       *   &lt;moduleExcludes&gt;
67       *     &lt;moduleType&gt;filename1.ext,**&#47;*sample.ext&lt;/moduleType&gt;
68       *     &lt;!-- moduleType can be one of 'apt', 'fml' or 'xdoc'. --&gt;
69       *     &lt;!-- The value is a comma separated list of           --&gt;
70       *     &lt;!-- filenames or fileset patterns.                   --&gt;
71       *     &lt;!-- Here's an example:                               --&gt;
72       *     &lt;xdoc&gt;changes.xml,navigation.xml&lt;/xdoc&gt;
73       *   &lt;/moduleExcludes&gt;
74       * </pre>
75       *
76       * @parameter
77       */
78      private Map<String, String> moduleExcludes;
79  
80      /**
81       * The component for assembling inheritance.
82       *
83       * @component
84       */
85      private DecorationModelInheritanceAssembler assembler;
86  
87      /**
88       * Remote repositories used for the project.
89       *
90       * @todo this is used for site descriptor resolution - it should relate to the actual project but for some reason they are not always filled in
91       * @parameter default-value="${project.remoteArtifactRepositories}"
92       * @readonly
93       */
94      private List<ArtifactRepository> repositories;
95  
96      /**
97       * Directory containing the template page.
98       *
99       * @parameter expression="${templateDirectory}" default-value="src/site"
100      * @deprecated use templateFile or skinning instead
101      */
102     private File templateDirectory;
103 
104     /**
105      * Default template page.
106      *
107      * @parameter expression="${template}"
108      * @deprecated use templateFile or skinning instead
109      */
110     private String template;
111 
112     /**
113      * The location of a Velocity template file to use. When used, skins and the default templates, CSS and images
114      * are disabled. It is highly recommended that you package this as a skin instead.
115      *
116      * @parameter expression="${templateFile}"
117      * @since 2.0-beta-5
118      */
119     private File templateFile;
120 
121     /**
122      * The template properties for rendering the site.
123      *
124      * @parameter
125      */
126     private Map<String, Object> attributes;
127 
128     /**
129      * Site renderer.
130      *
131      * @component
132      */
133     protected Renderer siteRenderer;
134 
135     /**
136      * @parameter expression="${reports}"
137      * @required
138      * @readonly
139      */
140     protected List<MavenReport> reports;
141 
142     /**
143      * Alternative directory for xdoc source, useful for m1 to m2 migration
144      *
145      * @parameter default-value="${basedir}/xdocs"
146      * @deprecated use the standard m2 directory layout
147      */
148     private File xdocDirectory;
149 
150     /**
151      * Directory containing generated documentation.
152      * This is used to pick up other source docs that might have been generated at build time.
153      *
154      * @parameter alias="workingDirectory" default-value="${project.build.directory}/generated-site"
155      *
156      * @todo should we deprecate in favour of reports?
157      */
158     protected File generatedSiteDirectory;
159 
160     /**
161      * Make links in the site descriptor relative to the project URL.
162      * By default, any absolute links that appear in the site descriptor,
163      * e.g. banner hrefs, breadcrumbs, menu links, etc., will be made relative to project.url.
164      *
165      * Links will not be changed if this is set to false, or if the project has no URL defined.
166      *
167      * @parameter expression="${relativizeDecorationLinks}" default-value="true"
168      *
169      * @since 2.3
170      */
171     private boolean relativizeDecorationLinks;
172 
173     /**
174      * Whether to generate the summary page for project reports: project-info.html.
175      *
176      * @parameter expression="${generateProjectInfo}" default-value="true"
177      *
178      * @since 2.3
179      */
180     private boolean generateProjectInfo;
181 
182     protected List<MavenReport> filterReports( List<MavenReport> reports )
183     {
184         List<MavenReport> filteredReports = new ArrayList<MavenReport>( reports.size() );
185         for ( MavenReport report : reports )
186         {
187             if ( report.canGenerateReport() )
188             {
189                 filteredReports.add( report );
190             }
191         }
192         return filteredReports;
193     }
194 
195     protected SiteRenderingContext createSiteRenderingContext( Locale locale )
196         throws MojoExecutionException, IOException, MojoFailureException
197     {
198         if ( attributes == null )
199         {
200             attributes = new HashMap<String, Object>();
201         }
202 
203         if ( attributes.get( "project" ) == null )
204         {
205             attributes.put( "project", project );
206         }
207 
208         if ( attributes.get( "inputEncoding" ) == null )
209         {
210             attributes.put( "inputEncoding", getInputEncoding() );
211         }
212 
213         if ( attributes.get( "outputEncoding" ) == null )
214         {
215             attributes.put( "outputEncoding", getOutputEncoding() );
216         }
217 
218         // Put any of the properties in directly into the Velocity context
219         for ( Map.Entry<Object, Object> entry : project.getProperties().entrySet() )
220         {
221             attributes.put( (String) entry.getKey(), entry.getValue() );
222         }
223 
224         DecorationModel decorationModel;
225         try
226         {
227             decorationModel = siteTool.getDecorationModel( project, reactorProjects, localRepository, repositories,
228                                                            siteTool.getRelativePath( siteDirectory.getAbsolutePath(),
229                                                            project.getBasedir().getAbsolutePath() ),
230                                                            locale, getInputEncoding(), getOutputEncoding() );
231         }
232         catch ( SiteToolException e )
233         {
234             throw new MojoExecutionException( "SiteToolException: " + e.getMessage(), e );
235         }
236 
237         if ( relativizeDecorationLinks )
238         {
239             final String url = project.getUrl();
240 
241             if ( url == null )
242             {
243                 getLog().warn( "No project URL defined - decoration links will not be relativized!" );
244             }
245             else
246             {
247                 getLog().info( "Relativizing decoration links with respect to project URL: " + url );
248                 assembler.resolvePaths( decorationModel, url );
249             }
250         }
251 
252         if ( template != null )
253         {
254             if ( templateFile != null )
255             {
256                 getLog().warn( "'template' configuration is ignored when 'templateFile' is set" );
257             }
258             else
259             {
260                 templateFile = new File( templateDirectory, template );
261             }
262         }
263 
264         File skinFile;
265         try
266         {
267             Artifact skinArtifact =
268                 siteTool.getSkinArtifactFromRepository( localRepository, repositories, decorationModel );
269             getLog().info( "Rendering site with " + skinArtifact.getId() + " skin." );
270 
271             skinFile = skinArtifact.getFile();
272         }
273         catch ( SiteToolException e )
274         {
275             throw new MojoExecutionException( "SiteToolException: " + e.getMessage(), e );
276         }
277         SiteRenderingContext context;
278         if ( templateFile != null )
279         {
280             if ( !templateFile.exists() )
281             {
282                 throw new MojoFailureException( "Template file '" + templateFile + "' does not exist" );
283             }
284             context = siteRenderer.createContextForTemplate( templateFile, skinFile, attributes, decorationModel,
285                                                              project.getName(), locale );
286         }
287         else
288         {
289             context = siteRenderer.createContextForSkin( skinFile, attributes, decorationModel, project.getName(),
290                                                          locale );
291         }
292 
293         // Generate static site
294         if ( !locale.getLanguage().equals( Locale.getDefault().getLanguage() ) )
295         {
296             context.addSiteDirectory( new File( siteDirectory, locale.getLanguage() ) );
297             context.addModuleDirectory( new File( xdocDirectory, locale.getLanguage() ), "xdoc" );
298             context.addModuleDirectory( new File( xdocDirectory, locale.getLanguage() ), "fml" );
299         }
300         else
301         {
302             context.addSiteDirectory( siteDirectory );
303             context.addModuleDirectory( xdocDirectory, "xdoc" );
304             context.addModuleDirectory( xdocDirectory, "fml" );
305         }
306 
307         if ( moduleExcludes != null )
308         {
309             context.setModuleExcludes( moduleExcludes );
310         }
311 
312         return context;
313     }
314 
315     /**
316      * Go through the list of reports and process each one like this:
317      * <ul>
318      * <li>Add the report to a map of reports keyed by filename having the report itself as value
319      * <li>If the report is not yet in the map of documents, add it together with a suitable renderer
320      * </ul>
321      *
322      * @param reports A List of MavenReports
323      * @param documents A Map of documents, keyed by filename
324      * @param locale the Locale the reports are processed for.
325      * @return A map with all reports keyed by filename having the report itself as value.
326      * The map will be used to populate a menu.
327      */
328     protected Map<String, MavenReport> locateReports( List<MavenReport> reports,
329                                                       Map<String, DocumentRenderer> documents, Locale locale )
330     {
331         Map<String, MavenReport> reportsByOutputName = new LinkedHashMap<String, MavenReport>();
332         for ( Iterator<MavenReport> i = reports.iterator(); i.hasNext(); )
333         {
334             MavenReport report = i.next();
335 
336             String outputName = report.getOutputName() + ".html";
337 
338             // Always add the report to the menu, see MSITE-150
339             reportsByOutputName.put( report.getOutputName(), report );
340 
341             if ( documents.containsKey( outputName ) )
342             {
343                 String displayLanguage = locale.getDisplayLanguage( Locale.ENGLISH );
344 
345                 getLog().info( "Skipped \"" + report.getName( locale ) + "\" report, file \"" + outputName
346                                    + "\" already exists for the " + displayLanguage + " version." );
347                 i.remove();
348             }
349             else
350             {
351                 RenderingContext renderingContext = new RenderingContext( siteDirectory, outputName );
352                 ReportDocumentRenderer renderer = new ReportDocumentRenderer( report, renderingContext, getLog() );
353                 documents.put( outputName, renderer );
354             }
355         }
356         return reportsByOutputName;
357     }
358 
359     /**
360      * Go through the collection of reports and put each report into a list for the appropriate category. The list is
361      * put into a map keyed by the name of the category.
362      *
363      * @param reports A Collection of MavenReports
364      * @return A map keyed category having the report itself as value
365      */
366     protected Map<String, List<MavenReport>> categoriseReports( Collection<MavenReport> reports )
367     {
368         Map<String, List<MavenReport>> categories = new LinkedHashMap<String, List<MavenReport>>();
369         for ( MavenReport report : reports )
370         {
371             List<MavenReport> categoryReports = categories.get( report.getCategoryName() );
372             if ( categoryReports == null )
373             {
374                 categoryReports = new ArrayList<MavenReport>();
375                 categories.put( report.getCategoryName(), categoryReports );
376             }
377             categoryReports.add( report );
378         }
379         return categories;
380     }
381 
382     protected Map<String, DocumentRenderer> locateDocuments( SiteRenderingContext context, List<MavenReport> reports,
383                                                              Locale locale )
384         throws IOException, RendererException
385     {
386         Map<String, DocumentRenderer> documents = siteRenderer.locateDocumentFiles( context );
387 
388         Map<String, MavenReport> reportsByOutputName = locateReports( reports, documents, locale );
389 
390         // TODO: I want to get rid of categories eventually. There's no way to add your own in a fully i18n manner
391         Map<String, List<MavenReport>> categories = categoriseReports( reportsByOutputName.values() );
392 
393         siteTool.populateReportsMenu( context.getDecoration(), locale, categories );
394         populateReportItems( context.getDecoration(), locale, reportsByOutputName );
395 
396         if ( categories.containsKey( MavenReport.CATEGORY_PROJECT_INFORMATION ) && generateProjectInfo )
397         {
398             List<MavenReport> categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION );
399 
400             RenderingContext renderingContext = new RenderingContext( siteDirectory, "project-info.html" );
401             String title = i18n.getString( "site-plugin", locale, "report.information.title" );
402             String desc1 = i18n.getString( "site-plugin", locale, "report.information.description1" );
403             String desc2 = i18n.getString( "site-plugin", locale, "report.information.description2" );
404             DocumentRenderer renderer = new CategorySummaryDocumentRenderer( renderingContext, title, desc1, desc2,
405                                                                              i18n, categoryReports, getLog() );
406 
407             if ( !documents.containsKey( renderer.getOutputName() ) )
408             {
409                 documents.put( renderer.getOutputName(), renderer );
410             }
411             else
412             {
413                 getLog().info( "Category summary '" + renderer.getOutputName() + "' skipped; already exists" );
414             }
415         }
416 
417         if ( categories.containsKey( MavenReport.CATEGORY_PROJECT_REPORTS ) )
418         {
419             List<MavenReport> categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS );
420             RenderingContext renderingContext = new RenderingContext( siteDirectory, "project-reports.html" );
421             String title = i18n.getString( "site-plugin", locale, "report.project.title" );
422             String desc1 = i18n.getString( "site-plugin", locale, "report.project.description1" );
423             String desc2 = i18n.getString( "site-plugin", locale, "report.project.description2" );
424             DocumentRenderer renderer = new CategorySummaryDocumentRenderer( renderingContext, title, desc1, desc2,
425                                                                              i18n, categoryReports, getLog() );
426 
427             if ( !documents.containsKey( renderer.getOutputName() ) )
428             {
429                 documents.put( renderer.getOutputName(), renderer );
430             }
431             else
432             {
433                 getLog().info( "Category summary '" + renderer.getOutputName() + "' skipped; already exists" );
434             }
435         }
436         return documents;
437     }
438 
439     protected void populateReportItems( DecorationModel decorationModel, Locale locale,
440                                         Map<String, MavenReport> reportsByOutputName )
441     {
442         for ( Menu menu : decorationModel.getMenus() )
443         {
444             populateItemRefs( menu.getItems(), locale, reportsByOutputName );
445         }
446     }
447 
448     private void populateItemRefs( List<MenuItem> items, Locale locale, Map<String, MavenReport> reportsByOutputName )
449     {
450         for ( Iterator<MenuItem> i = items.iterator(); i.hasNext(); )
451         {
452             MenuItem item = i.next();
453 
454             if ( item.getRef() != null )
455             {
456                 MavenReport report = reportsByOutputName.get( item.getRef() );
457 
458                 if ( report != null )
459                 {
460                     if ( item.getName() == null )
461                     {
462                         item.setName( report.getName( locale ) );
463                     }
464 
465                     if ( item.getHref() == null || item.getHref().length() == 0 )
466                     {
467                         item.setHref( report.getOutputName() + ".html" );
468                     }
469                 }
470                 else
471                 {
472                     getLog().warn( "Unrecognised reference: '" + item.getRef() + "'" );
473                     i.remove();
474                 }
475             }
476 
477             populateItemRefs( item.getItems(), locale, reportsByOutputName );
478         }
479     }
480 }