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 java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.Reader;
26  import java.io.StringReader;
27  import java.io.StringWriter;
28  import java.io.Writer;
29  import java.lang.reflect.InvocationHandler;
30  import java.lang.reflect.Method;
31  import java.lang.reflect.Proxy;
32  import java.util.ArrayList;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Properties;
38  
39  import javax.swing.text.AttributeSet;
40  
41  import org.apache.commons.io.input.XmlStreamReader;
42  import org.apache.maven.artifact.Artifact;
43  import org.apache.maven.artifact.repository.ArtifactRepository;
44  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
45  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
46  import org.apache.maven.artifact.versioning.ArtifactVersion;
47  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
48  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
49  import org.apache.maven.artifact.versioning.VersionRange;
50  import org.apache.maven.doxia.Doxia;
51  import org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer;
52  import org.apache.maven.doxia.docrenderer.DocumentRenderer;
53  import org.apache.maven.doxia.docrenderer.DocumentRendererContext;
54  import org.apache.maven.doxia.docrenderer.DocumentRendererException;
55  import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer;
56  import org.apache.maven.doxia.document.DocumentMeta;
57  import org.apache.maven.doxia.document.DocumentModel;
58  import org.apache.maven.doxia.document.DocumentTOCItem;
59  import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Writer;
60  import org.apache.maven.doxia.index.IndexEntry;
61  import org.apache.maven.doxia.index.IndexingSink;
62  import org.apache.maven.doxia.module.xdoc.XdocSink;
63  import org.apache.maven.doxia.parser.ParseException;
64  import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
65  import org.apache.maven.doxia.sink.Sink;
66  import org.apache.maven.doxia.sink.SinkAdapter;
67  import org.apache.maven.doxia.sink.SinkEventAttributeSet;
68  import org.apache.maven.doxia.sink.SinkEventAttributes;
69  import org.apache.maven.doxia.site.decoration.DecorationModel;
70  import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader;
71  import org.apache.maven.doxia.siterenderer.Renderer;
72  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
73  import org.apache.maven.doxia.tools.SiteTool;
74  import org.apache.maven.doxia.tools.SiteToolException;
75  import org.apache.maven.execution.MavenSession;
76  import org.apache.maven.model.MailingList;
77  import org.apache.maven.model.ReportPlugin;
78  import org.apache.maven.model.ReportSet;
79  import org.apache.maven.plugin.AbstractMojo;
80  import org.apache.maven.plugin.InvalidPluginException;
81  import org.apache.maven.plugin.MojoExecution;
82  import org.apache.maven.plugin.MojoExecutionException;
83  import org.apache.maven.plugin.MojoFailureException;
84  import org.apache.maven.plugin.PluginConfigurationException;
85  import org.apache.maven.plugin.PluginManager;
86  import org.apache.maven.plugin.PluginManagerException;
87  import org.apache.maven.plugin.PluginNotFoundException;
88  import org.apache.maven.plugin.descriptor.MojoDescriptor;
89  import org.apache.maven.plugin.descriptor.PluginDescriptor;
90  import org.apache.maven.plugin.version.PluginVersionNotFoundException;
91  import org.apache.maven.plugin.version.PluginVersionResolutionException;
92  import org.apache.maven.plugins.annotations.Component;
93  import org.apache.maven.plugins.annotations.Mojo;
94  import org.apache.maven.plugins.annotations.Parameter;
95  import org.apache.maven.project.MavenProject;
96  import org.apache.maven.project.MavenProjectBuilder;
97  import org.apache.maven.project.ProjectBuildingException;
98  import org.apache.maven.reporting.AbstractMavenReportRenderer;
99  import org.apache.maven.reporting.MavenReport;
100 import org.apache.maven.reporting.MavenReportException;
101 import org.apache.maven.reporting.exec.MavenReportExecution;
102 import org.apache.maven.reporting.exec.MavenReportExecutor;
103 import org.apache.maven.reporting.exec.MavenReportExecutorRequest;
104 import org.apache.maven.settings.Settings;
105 import org.codehaus.plexus.PlexusConstants;
106 import org.codehaus.plexus.PlexusContainer;
107 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
108 import org.codehaus.plexus.context.Context;
109 import org.codehaus.plexus.context.ContextException;
110 import org.codehaus.plexus.i18n.I18N;
111 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
112 import org.codehaus.plexus.util.FileUtils;
113 import org.codehaus.plexus.util.IOUtil;
114 import org.codehaus.plexus.util.PathTool;
115 import org.codehaus.plexus.util.ReaderFactory;
116 import org.codehaus.plexus.util.StringUtils;
117 import org.codehaus.plexus.util.WriterFactory;
118 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
119 
120 /**
121  * Generates a PDF document for a project.
122  *
123  * @author ltheussl
124  * @version $Id: PdfMojo.java 1642369 2014-11-28 21:54:06Z hboutemy $
125  */
126 @Mojo( name = "pdf", threadSafe = true )
127 public class PdfMojo
128     extends AbstractMojo implements Contextualizable
129 {
130     /**
131      * The vm line separator
132      */
133     private static final String EOL = System.getProperty( "line.separator" );
134 
135     // ----------------------------------------------------------------------
136     // Mojo components
137     // ----------------------------------------------------------------------
138 
139     /**
140      * FO Document Renderer.
141      */
142     @Component( hint = "fo" )
143     private PdfRenderer foRenderer;
144 
145     /**
146      * Internationalization.
147      */
148     @Component
149     private I18N i18n;
150 
151     /**
152      * IText Document Renderer.
153      */
154     @Component( hint = "itext" )
155     private PdfRenderer itextRenderer;
156 
157     /**
158      * A comma separated list of locales supported by Maven.
159      * The first valid token will be the default Locale for this instance of the Java Virtual Machine.
160      */
161     @Parameter( property = "locales" )
162     private String locales;
163 
164     /**
165      * Site renderer.
166      */
167     @Component
168     private Renderer siteRenderer;
169 
170     /**
171      * SiteTool.
172      */
173     @Component
174     private SiteTool siteTool;
175 
176     /**
177      * The Plugin manager instance used to resolve Plugin descriptors.
178      *
179      * @since 1.1
180      */
181     @Component( role = PluginManager.class )
182     private PluginManager pluginManager;
183 
184     /**
185      * Doxia.
186      *
187      * @since 1.1
188      */
189     @Component
190     private Doxia doxia;
191 
192     /**
193      * Project builder.
194      *
195      * @since 1.1
196      */
197     @Component
198     private MavenProjectBuilder mavenProjectBuilder;
199 
200     // ----------------------------------------------------------------------
201     // Mojo Parameters
202     // ----------------------------------------------------------------------
203 
204     /**
205      * The Maven Project Object.
206      */
207     @Parameter( defaultValue = "${project}", readonly = true, required = true )
208     private MavenProject project;
209 
210     /**
211      * The Maven Settings.
212      *
213      * @since 1.1
214      */
215     @Parameter( defaultValue = "${settings}", readonly = true, required = true )
216     private Settings settings;
217 
218     /**
219      * The current build session instance.
220      *
221      * @since 1.1
222      */
223     @Parameter( defaultValue = "${session}", readonly = true, required = true )
224     private MavenSession session;
225 
226     /**
227      * Directory containing source for apt, fml and xdoc docs.
228      */
229     @Parameter( defaultValue = "${basedir}/src/site", required = true )
230     private File siteDirectory;
231 
232     /**
233      * Directory containing generated sources for apt, fml and xdoc docs.
234      *
235      * @since 1.1
236      */
237     @Parameter( defaultValue = "${project.build.directory}/generated-site", required = true )
238     private File generatedSiteDirectory;
239 
240     /**
241      * Output directory where PDF files should be created.
242      */
243     @Parameter( defaultValue = "${project.build.directory}/pdf", required = true )
244     private File outputDirectory;
245 
246     /**
247      * Working directory for working files like temp files/resources.
248      */
249     @Parameter( defaultValue = "${project.build.directory}/pdf", required = true )
250     private File workingDirectory;
251 
252     /**
253      * File that contains the DocumentModel of the PDF to generate.
254      */
255     @Parameter( defaultValue = "src/site/pdf.xml" )
256     private File docDescriptor;
257 
258     /**
259      * Identifies the framework to use for pdf generation: either "fo" (default) or "itext".
260      */
261     @Parameter( property = "implementation", defaultValue = "fo", required = true )
262     private String implementation;
263 
264     /**
265      * The local repository.
266      */
267     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
268     private ArtifactRepository localRepository;
269 
270     /**
271      * The remote repositories where artifacts are located.
272      *
273      * @since 1.1
274      */
275     @Parameter( defaultValue = "${project.remoteArtifactRepositories}"  )
276     private List<ArtifactRepository> remoteRepositories;
277 
278     /**
279      * If <code>true</false>, aggregate all source documents in one pdf, otherwise generate one pdf for each
280      * source document.
281      */
282     @Parameter( property = "aggregate", defaultValue = "true" )
283     private boolean aggregate;
284 
285     /**
286      * The current version of this plugin.
287      */
288     @Parameter( defaultValue = "${plugin.version}", readonly = true )
289     private String pluginVersion;
290 
291     /**
292      * If <code>true</false>, generate all Maven reports defined in <code>${project.reporting}</code> and append
293      * them as a new entry in the TOC (Table Of Contents).
294      * <b>Note</b>: Including the report generation could fail the PDF generation or increase the build time.
295      *
296      * @since 1.1
297      */
298     @Parameter( property = "includeReports", defaultValue = "true" )
299     private boolean includeReports;
300 
301     /**
302      * Generate a TOC (Table Of Content) for all items defined in the &lt;toc/&gt; element from the document descriptor.
303      * <br/>
304      * Possible values are: 'none', 'start' and 'end'.
305      *
306      * @since 1.1
307      */
308     @Parameter( property = "generateTOC", defaultValue = "start" )
309     private String generateTOC;
310 
311     /**
312      * Whether to validate xml input documents.
313      * If set to true, <strong>all</strong> input documents in xml format
314      * (in particular xdoc and fml) will be validated and any error will
315      * lead to a build failure.
316      *
317      * @since 1.2
318      */
319     @Parameter( property = "validate", defaultValue = "false" )
320     private boolean validate;
321     
322     /**
323      * Reports (Maven 2).
324      * @since 1.3
325      */
326     @Parameter( defaultValue = "${reports}", required = true, readonly = true )
327     private MavenReport[] reports;
328     
329     /**
330      * <p>Configuration section <b>used internally</b> by Maven 3.</p>
331      * <p>More details available here:
332      * <a href="http://maven.apache.org/plugins/maven-site-plugin/maven-3.html#Configuration_formats" target="_blank">
333      * http://maven.apache.org/plugins/maven-site-plugin/maven-3.html#Configuration_formats</a>
334      * </p>
335      * <p><b>Note:</b> using this field is not mandatory with Maven 3 as Maven core injects usual
336      * <code>&lt;reporting&gt;</code> section into this field.</p>
337      *
338      * @since 1.3
339      */
340     @Parameter( readonly = true )
341     private org.apache.maven.reporting.exec.ReportPlugin[] reportPlugins;
342 
343     // ----------------------------------------------------------------------
344     // Instance fields
345     // ----------------------------------------------------------------------
346 
347     /**
348      * The current document Renderer.
349      * @see #implementation
350      */
351     private DocumentRenderer docRenderer;
352 
353     /**
354      * The default locale.
355      */
356     private Locale defaultLocale;
357 
358     /**
359      * The available locales list.
360      */
361     private List<Locale> localesList;
362 
363     /**
364      * The default decoration model.
365      */
366     private DecorationModel defaultDecorationModel;
367 
368     /**
369      * The temp Site dir to have all site and generated-site files.
370      *
371      * @since 1.1
372      */
373     private File siteDirectoryTmp;
374 
375     /**
376      * The temp Generated Site dir to have generated reports by this plugin.
377      *
378      * @since 1.1
379      */
380     private File generatedSiteDirectoryTmp;
381 
382     /**
383      * A map of generated MavenReport list using locale as key.
384      *
385      * @since 1.1
386      */
387     private Map<Locale, List<MavenReport>> generatedMavenReports;
388     
389     /**
390      * @since 1.3
391      */
392     private PlexusContainer container;
393 
394     // ----------------------------------------------------------------------
395     // Public methods
396     // ----------------------------------------------------------------------
397 
398     /** {@inheritDoc} */
399     public void execute()
400         throws MojoExecutionException, MojoFailureException
401     {
402         init();
403 
404         try
405         {
406             generatePdf();
407         }
408         catch ( IOException e )
409         {
410             debugLogGeneratedModel( getDocumentModel( Locale.ENGLISH ) );
411 
412             throw new MojoExecutionException( "Error during document generation: " + e.getMessage(), e );
413         }
414 
415         try
416         {
417             copyGeneratedPdf();
418         }
419         catch ( IOException e )
420         {
421             throw new MojoExecutionException( "Error copying generated PDF: " + e.getMessage(), e );
422         }
423     }
424     
425     /** {@inheritDoc} */
426     public void contextualize( Context context )
427         throws ContextException
428     {
429         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
430     }
431 
432     // ----------------------------------------------------------------------
433     // Private methods
434     // ----------------------------------------------------------------------
435 
436     /**
437      * Init and validate parameters
438      */
439     private void init()
440     {
441         if ( "fo".equalsIgnoreCase( implementation ) )
442         {
443             this.docRenderer = foRenderer;
444         }
445         else if ( "itext".equalsIgnoreCase( implementation ) )
446         {
447             this.docRenderer = itextRenderer;
448         }
449         else
450         {
451             getLog().warn( "Invalid 'implementation' parameter: '" + implementation
452                     + "', using 'fo' as default." );
453 
454             this.docRenderer = foRenderer;
455         }
456 
457         if ( !( "none".equalsIgnoreCase( generateTOC )
458                 || "start".equalsIgnoreCase( generateTOC ) || "end".equalsIgnoreCase( generateTOC ) ) )
459         {
460             getLog().warn( "Invalid 'generateTOC' parameter: '" + generateTOC
461                     + "', using 'start' as default." );
462 
463             this.generateTOC = "start";
464         }
465     }
466 
467     /**
468      * Copy the generated PDF to outputDirectory.
469      *
470      * @throws MojoExecutionException if any
471      * @throws IOException if any
472      * @since 1.1
473      */
474     private void copyGeneratedPdf()
475         throws MojoExecutionException, IOException
476     {
477         if ( outputDirectory.getCanonicalPath().equals( workingDirectory.getCanonicalPath() ) )
478         {
479             return;
480         }
481 
482         String outputName = getDocumentModel( getDefaultLocale() ).getOutputName().trim();
483         if ( !outputName.endsWith( ".pdf" ) )
484         {
485             outputName = outputName.concat( ".pdf" );
486         }
487 
488         for ( final Locale locale : getAvailableLocales() )
489         {
490             File generatedPdfSource = new File( getLocaleDirectory( workingDirectory, locale ), outputName );
491 
492             if ( !generatedPdfSource.exists() )
493             {
494                 getLog().warn( "Unable to find the generated pdf: " + generatedPdfSource.getAbsolutePath() );
495                 continue;
496             }
497 
498             File generatedPdfDest = new File( getLocaleDirectory( outputDirectory, locale ), outputName );
499 
500             FileUtils.copyFile( generatedPdfSource, generatedPdfDest );
501             generatedPdfSource.delete();
502         }
503     }
504 
505     /**
506      * Generate the PDF.
507      *
508      * @throws MojoExecutionException if any
509      * @throws IOException if any
510      * @since 1.1
511      */
512     private void generatePdf()
513         throws MojoExecutionException, IOException
514     {
515         Locale.setDefault( getDefaultLocale() );
516 
517         for ( final Locale locale : getAvailableLocales() )
518         {
519             final File workingDir = getLocaleDirectory( workingDirectory, locale );
520 
521             File siteDirectoryFile = getLocaleDirectory( getSiteDirectoryTmp(), locale );
522 
523             copyResources( locale );
524 
525             generateMavenReports( locale );
526 
527             DocumentRendererContext context = new DocumentRendererContext();
528             context.put( "project", project );
529             context.put( "settings", settings );
530             context.put( "PathTool", new PathTool() );
531             context.put( "FileUtils", new FileUtils() );
532             context.put( "StringUtils", new StringUtils() );
533             context.put( "i18n", i18n );
534             context.put( "generateTOC", generateTOC );
535             context.put( "validate", validate );
536 
537             // Put any of the properties in directly into the Velocity context
538             for ( Map.Entry<Object, Object> entry : project.getProperties().entrySet() )
539             {
540                 context.put( (String) entry.getKey(), entry.getValue() );
541             }
542             
543             final DocumentModel model = aggregate ? getDocumentModel( locale ) : null;
544 
545             try
546             {
547                 // TODO use interface see DOXIASITETOOLS-30
548                 ( (AbstractDocumentRenderer) docRenderer ).render( siteDirectoryFile, workingDir, model, context );
549             }
550             catch ( DocumentRendererException e )
551             {
552                 throw new MojoExecutionException( "Error during document generation: " + e.getMessage(), e );
553             }
554         }
555     }
556 
557     /**
558      * @return the default tmpSiteDirectory.
559      * @throws IOException if any
560      * @since 1.1
561      */
562     private File getSiteDirectoryTmp()
563         throws IOException
564     {
565         if ( this.siteDirectoryTmp == null )
566         {
567             final File tmpSiteDir = new File( workingDirectory, "site.tmp" );
568             prepareTempSiteDirectory( tmpSiteDir );
569 
570             this.siteDirectoryTmp = tmpSiteDir;
571         }
572 
573         return this.siteDirectoryTmp;
574     }
575 
576     /**
577      * @return the default tmpGeneratedSiteDirectory when report will be created.
578      * @since 1.1
579      */
580     private File getGeneratedSiteDirectoryTmp()
581     {
582         if ( this.generatedSiteDirectoryTmp == null )
583         {
584             this.generatedSiteDirectoryTmp = new File( workingDirectory, "generated-site.tmp" );
585         }
586 
587         return this.generatedSiteDirectoryTmp;
588     }
589 
590     /**
591      * Copy all site and generated-site files in the tmpSiteDirectory.
592      * <br/>
593      * <b>Note</b>: ignore copying of <code>generated-site</code> files if they already exist in the
594      * <code>site</code> dir.
595      *
596      * @param tmpSiteDir not null
597      * @throws IOException if any
598      * @since 1.1
599      */
600     private void prepareTempSiteDirectory( final File tmpSiteDir )
601         throws IOException
602     {
603         // safety
604         tmpSiteDir.mkdirs();
605 
606         // copy site
607         if ( siteDirectory.exists() )
608         {
609             FileUtils.copyDirectoryStructure( siteDirectory, tmpSiteDir );
610         }
611 
612         // Remove SCM files
613         List<String> files =
614             FileUtils.getFileAndDirectoryNames( tmpSiteDir, FileUtils.getDefaultExcludesAsString(), null, true,
615                                                 true, true, true );
616         for ( final String fileName : files )
617         {
618             final File file = new File( fileName );
619 
620             if ( file.isDirectory() )
621             {
622                 FileUtils.deleteDirectory( file );
623             }
624             else
625             {
626                 file.delete();
627             }
628         }
629 
630         copySiteDir( generatedSiteDirectory, tmpSiteDir );
631     }
632 
633     /**
634      * Copy the from site dir to the to dir.
635      *
636      * @param from not null
637      * @param to not null
638      * @throws IOException if any
639      * @since 1.1
640      */
641     private void copySiteDir( final File from, final File to )
642         throws IOException
643     {
644         if ( from == null || !from.exists() )
645         {
646             return;
647         }
648 
649         // copy generated-site
650         for ( final Locale locale : getAvailableLocales() )
651         {
652             String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
653             List<String> siteFiles = FileUtils.getFileNames( siteDirectory, "**/*", excludes, false );
654             File siteDirectoryLocale = new File( siteDirectory, locale.getLanguage() );
655             if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) && siteDirectoryLocale.exists() )
656             {
657                 siteFiles = FileUtils.getFileNames( siteDirectoryLocale, "**/*", excludes, false );
658             }
659 
660             List<String> generatedSiteFiles = FileUtils.getFileNames( from, "**/*", excludes, false );
661             File fromLocale = new File( from, locale.getLanguage() );
662             if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) && fromLocale.exists() )
663             {
664                 generatedSiteFiles = FileUtils.getFileNames( fromLocale, "**/*", excludes, false );
665             }
666 
667             for ( final String generatedSiteFile : generatedSiteFiles )
668             {
669                 if ( siteFiles.contains( generatedSiteFile ) )
670                 {
671                     getLog().warn( "Generated-site already contains a file in site: " + generatedSiteFile
672                                        + ". Ignoring copying it!" );
673                     continue;
674                 }
675 
676                 if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
677                 {
678                     if ( fromLocale.exists() )
679                     {
680                         File in = new File( fromLocale, generatedSiteFile );
681                         File out = new File( new File( to, locale.getLanguage() ), generatedSiteFile );
682                         out.getParentFile().mkdirs();
683                         FileUtils.copyFile( in, out );
684                     }
685                 }
686                 else
687                 {
688                     File in = new File( from, generatedSiteFile );
689                     File out = new File( to, generatedSiteFile );
690                     out.getParentFile().mkdirs();
691                     FileUtils.copyFile( in, out );
692                 }
693             }
694         }
695     }
696 
697     /**
698      * Constructs a DocumentModel for the current project. The model is either read from
699      * a descriptor file, if it exists, or constructed from information in the pom and site.xml.
700      *
701      * @param locale not null
702      * @return DocumentModel.
703      * @throws MojoExecutionException if any
704      * @see #appendGeneratedReports(DocumentModel, Locale)
705      */
706     private DocumentModel getDocumentModel( Locale locale )
707         throws MojoExecutionException
708     {
709         if ( docDescriptor.exists() )
710         {
711             DocumentModel doc = getDocumentModelFromDescriptor( locale );
712             // TODO: descriptor model should get merged into default model, see MODELLO-63
713 
714             appendGeneratedReports( doc, locale );
715 
716             return doc;
717         }
718 
719         DocumentModel model = new DocumentModelBuilder( project, getDefaultDecorationModel() ).getDocumentModel();
720 
721         model.getMeta().setGenerator( getDefaultGenerator() );
722         model.getMeta().setLanguage( locale.getLanguage() );
723         model.getCover().setCoverType( i18n.getString( "pdf-plugin", getDefaultLocale(), "toc.type" ) );
724         model.getToc().setName( i18n.getString( "pdf-plugin", getDefaultLocale(), "toc.title" ) );
725 
726         appendGeneratedReports( model, locale );
727 
728         debugLogGeneratedModel( model );
729 
730         return model;
731     }
732 
733     /**
734      * Read a DocumentModel from a file.
735      *
736      * @param locale used to set the language.
737      * @return the DocumentModel read from the configured document descriptor.
738      * @throws org.apache.maven.plugin.MojoExecutionException if the model could not be read.
739      */
740     private DocumentModel getDocumentModelFromDescriptor( Locale locale )
741         throws MojoExecutionException
742     {
743         DocumentModel model;
744 
745         try
746         {
747             model =
748                 new DocumentDescriptorReader( project, getLog() ).readAndFilterDocumentDescriptor( docDescriptor );
749         }
750         catch ( XmlPullParserException ex )
751         {
752             throw new MojoExecutionException( "Error reading DocumentDescriptor!", ex );
753         }
754         catch ( IOException io )
755         {
756             throw new MojoExecutionException( "Error opening DocumentDescriptor!", io );
757         }
758 
759         if ( model.getMeta() == null )
760         {
761             model.setMeta( new DocumentMeta() );
762         }
763 
764         if ( StringUtils.isEmpty( model.getMeta().getLanguage() ) )
765         {
766             model.getMeta().setLanguage( locale.getLanguage() );
767         }
768 
769         if ( StringUtils.isEmpty( model.getMeta().getGenerator() ) )
770         {
771             model.getMeta().setGenerator( getDefaultGenerator() );
772         }
773 
774         return model;
775     }
776 
777     /**
778      * Return the directory for a given Locale and the current default Locale.
779      *
780      * @param basedir the base directory
781      * @param locale a Locale.
782      * @return File.
783      */
784     private File getLocaleDirectory( File basedir, Locale locale )
785     {
786         if ( locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
787         {
788             return basedir;
789         }
790 
791         return new File( basedir, locale.getLanguage() );
792     }
793 
794     /**
795      * @return the default locale from <code>siteTool</code>.
796      * @see #getAvailableLocales()
797      */
798     private Locale getDefaultLocale()
799     {
800         if ( this.defaultLocale == null )
801         {
802             this.defaultLocale = getAvailableLocales().get( 0 );
803         }
804 
805         return this.defaultLocale;
806     }
807 
808     /**
809      * @return the available locales from <code>siteTool</code>.
810      * @see SiteTool#getAvailableLocales(String)
811      */
812     private List<Locale> getAvailableLocales()
813     {
814         if ( this.localesList == null )
815         {
816             this.localesList = siteTool.getAvailableLocales( locales );
817         }
818 
819         return this.localesList;
820     }
821 
822     /**
823      * @return the DecorationModel instance from <code>site.xml</code>
824      * @throws MojoExecutionException if any
825      */
826     private DecorationModel getDefaultDecorationModel()
827         throws MojoExecutionException
828     {
829         if ( this.defaultDecorationModel == null )
830         {
831             final Locale locale = getDefaultLocale();
832 
833             final File basedir = project.getBasedir();
834             final String relativePath =
835                 siteTool.getRelativePath( siteDirectory.getAbsolutePath(), basedir.getAbsolutePath() );
836 
837             final File descriptorFile = siteTool.getSiteDescriptorFromBasedir( relativePath, basedir, locale );
838             DecorationModel decoration = null;
839 
840             if ( descriptorFile.exists() )
841             {
842                 XmlStreamReader reader = null;
843                 try
844                 {
845                     reader = new XmlStreamReader( descriptorFile );
846                     String enc = reader.getEncoding();
847 
848                     String siteDescriptorContent = IOUtil.toString( reader );
849                     siteDescriptorContent =
850                         siteTool.getInterpolatedSiteDescriptorContent( new HashMap<String, String>( 2 ), project,
851                                                                        siteDescriptorContent, enc, enc );
852 
853                     decoration = new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) );
854                 }
855                 catch ( XmlPullParserException e )
856                 {
857                     throw new MojoExecutionException( "Error parsing site descriptor", e );
858                 }
859                 catch ( IOException e )
860                 {
861                     throw new MojoExecutionException( "Error reading site descriptor", e );
862                 }
863                 catch ( SiteToolException e )
864                 {
865                     throw new MojoExecutionException( "Error when interpoling site descriptor", e );
866                 }
867                 finally
868                 {
869                     IOUtil.close( reader );
870                 }
871             }
872 
873             this.defaultDecorationModel = decoration;
874         }
875 
876         return this.defaultDecorationModel;
877     }
878 
879     /**
880      * Parse the decoration model to find the skin artifact and copy its resources to the output dir.
881      *
882      * @param locale not null
883      * @throws MojoExecutionException if any
884      * @see #getDefaultDecorationModel()
885      */
886     private void copyResources( Locale locale )
887         throws MojoExecutionException
888     {
889         final DecorationModel decorationModel = getDefaultDecorationModel();
890         if ( decorationModel == null )
891         {
892             return;
893         }
894 
895         File skinFile;
896         try
897         {
898             skinFile =
899                 siteTool.getSkinArtifactFromRepository( localRepository, project.getRemoteArtifactRepositories(),
900                                                         decorationModel ).getFile();
901         }
902         catch ( SiteToolException e )
903         {
904             throw new MojoExecutionException( "SiteToolException: " + e.getMessage(), e );
905         }
906 
907         if ( skinFile == null )
908         {
909             return;
910         }
911 
912         if ( getLog().isDebugEnabled() )
913         {
914             getLog().debug( "Copy resources from skin artifact: '" + skinFile + "'..." );
915         }
916 
917         try
918         {
919             final SiteRenderingContext context =
920                 siteRenderer.createContextForSkin( skinFile, new HashMap( 2 ), decorationModel, project.getName(),
921                                                    locale );
922             context.addSiteDirectory( new File( siteDirectory, locale.getLanguage() ) );
923 
924             for ( final File siteDirectoryFile : context.getSiteDirectories() )
925             {
926                 siteRenderer.copyResources( context, new File( siteDirectoryFile, "resources" ), workingDirectory );
927             }
928         }
929         catch ( IOException e )
930         {
931             throw new MojoExecutionException( "IOException: " + e.getMessage(), e );
932         }
933     }
934 
935     /**
936      * Construct a default producer.
937      *
938      * @return A String in the form <code>Maven PDF Plugin v. 1.1.1, 'fo' implementation</code>.
939      */
940     private String getDefaultGenerator()
941     {
942         return "Maven PDF Plugin v. " + pluginVersion + ", '" + implementation + "' implementation.";
943     }
944 
945     /**
946      * Write the auto-generated model to disc.
947      *
948      * @param docModel the model to write.
949      */
950     private void debugLogGeneratedModel( final DocumentModel docModel )
951     {
952         if ( getLog().isDebugEnabled() && project != null )
953         {
954             final File outputDir = new File( project.getBuild().getDirectory(), "pdf" );
955 
956             if ( !outputDir.exists() )
957             {
958                 outputDir.mkdirs();
959             }
960 
961             final File doc = FileUtils.createTempFile( "pdf", ".xml", outputDir );
962             final DocumentXpp3Writer xpp3 = new DocumentXpp3Writer();
963 
964             Writer w = null;
965             try
966             {
967                 w = WriterFactory.newXmlWriter( doc );
968                 xpp3.write( w, docModel );
969 
970                 getLog().debug( "Generated a default document model: " + doc.getAbsolutePath() );
971             }
972             catch ( IOException e )
973             {
974                 getLog().error( "Failed to write document model: " + e.getMessage() );
975                 getLog().debug( e );
976             }
977             finally
978             {
979                 IOUtil.close( w );
980             }
981         }
982     }
983 
984     /**
985      * Generate all Maven reports defined in <code>${project.reporting}</code> part
986      * only if <code>generateReports</code> is enabled.
987      *
988      * @param locale not null
989      * @throws MojoExecutionException if any
990      * @throws IOException if any
991      * @since 1.1
992      */
993     private void generateMavenReports( Locale locale )
994         throws MojoExecutionException, IOException
995     {
996         if ( !includeReports )
997         {
998             getLog().info( "Skipped report generation." );
999             return;
1000         }
1001 
1002         if ( project.getReporting() == null )
1003         {
1004             getLog().info( "No report was specified." );
1005             return;
1006         }
1007         
1008         for ( final ReportPlugin reportPlugin : project.getReporting().getPlugins() )
1009         {
1010             final PluginDescriptor pluginDescriptor = getPluginDescriptor( reportPlugin );
1011 
1012             if ( pluginDescriptor != null )
1013             {
1014                 List<String> goals = new ArrayList<String>( 8 );
1015                 for ( final ReportSet reportSet : reportPlugin.getReportSets() )
1016                 {
1017                     for ( String goal : reportSet.getReports() )
1018                     {
1019                         goals.add( goal );
1020                     }
1021                 }
1022     
1023                 List mojoDescriptors = pluginDescriptor.getMojos();
1024                 for ( Object mojoDescriptor1 : mojoDescriptors )
1025                 {
1026                     final MojoDescriptor mojoDescriptor = (MojoDescriptor) mojoDescriptor1;
1027 
1028                     if ( goals.isEmpty() || ( !goals.isEmpty() && goals.contains( mojoDescriptor.getGoal() ) ) )
1029                     {
1030                         MavenReport report = getMavenReport( mojoDescriptor );
1031 
1032                         generateMavenReport( report, mojoDescriptor.getPluginDescriptor().getPluginArtifact(),
1033                                              locale );
1034                     }
1035                 }
1036             }
1037         }
1038 
1039         // generate project-info report
1040         if ( !getGeneratedMavenReports( locale ).isEmpty() )
1041         {
1042             File outDir = new File( getGeneratedSiteDirectoryTmp(), "xdoc" );
1043             if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1044             {
1045                 outDir = new File( new File( getGeneratedSiteDirectoryTmp(), locale.getLanguage() ), "xdoc" );
1046             }
1047             outDir.mkdirs();
1048 
1049             File piReport = new File( outDir, "project-info.xml" );
1050 
1051             StringWriter sw = new StringWriter();
1052 
1053             PdfSink sink = new PdfSink( sw );
1054             ProjectInfoRenderer r = new ProjectInfoRenderer( sink, getGeneratedMavenReports( locale ), i18n, locale );
1055             r.render();
1056 
1057             writeGeneratedReport( sw.toString(), piReport );
1058         }
1059 
1060         // copy generated site
1061         copySiteDir( getGeneratedSiteDirectoryTmp(), getSiteDirectoryTmp() );
1062         copySiteDir( generatedSiteDirectory, getSiteDirectoryTmp() );
1063     }
1064 
1065     /**
1066      * TODO olamy : remove when maven 3 will be the de facto standard :-)
1067      * @param reportPlugin not null
1068      * @return the PluginDescriptor instance for the given reportPlugin.
1069      * @throws MojoExecutionException if any
1070      * @since 1.1
1071      */
1072     private PluginDescriptor getPluginDescriptor( ReportPlugin reportPlugin )
1073         throws MojoExecutionException
1074     {
1075         try
1076         {
1077             return pluginManager.verifyReportPlugin( reportPlugin, project, session );
1078         }
1079         catch ( ArtifactResolutionException e )
1080         {
1081             throw new MojoExecutionException( "ArtifactResolutionException: " + e.getMessage(), e );
1082         }
1083         catch ( ArtifactNotFoundException e )
1084         {
1085             throw new MojoExecutionException( "ArtifactNotFoundException: " + e.getMessage(), e );
1086         }
1087         catch ( PluginNotFoundException e )
1088         {
1089             throw new MojoExecutionException( "PluginNotFoundException: " + e.getMessage(), e );
1090         }
1091         catch ( PluginVersionResolutionException e )
1092         {
1093             throw new MojoExecutionException( "PluginVersionResolutionException: " + e.getMessage(), e );
1094         }
1095         catch ( InvalidVersionSpecificationException e )
1096         {
1097             throw new MojoExecutionException( "InvalidVersionSpecificationException: " + e.getMessage(), e );
1098         }
1099         catch ( InvalidPluginException e )
1100         {
1101             throw new MojoExecutionException( "InvalidPluginException: " + e.getMessage(), e );
1102         }
1103         catch ( PluginManagerException e )
1104         {
1105             throw new MojoExecutionException( "PluginManagerException: " + e.getMessage(), e );
1106         }
1107         catch ( PluginVersionNotFoundException e )
1108         {
1109             throw new MojoExecutionException( "PluginVersionNotFoundException: " + e.getMessage(), e );
1110         }
1111         catch ( NoSuchMethodError e )
1112         {
1113             getLog().info( "Ignoring api call removed in maven 3, no reports are generated!" );
1114             getLog().debug( e );
1115             return null;
1116         }
1117     }
1118 
1119     /**
1120      * @param mojoDescriptor not null
1121      * @return the MavenReport instance for the given mojoDescriptor.
1122      * @throws MojoExecutionException if any
1123      * @since 1.1
1124      */
1125     private MavenReport getMavenReport( MojoDescriptor mojoDescriptor )
1126         throws MojoExecutionException
1127     {
1128         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
1129         try
1130         {
1131             Thread.currentThread()
1132                   .setContextClassLoader( mojoDescriptor.getPluginDescriptor().getClassRealm().getClassLoader() );
1133 
1134             MojoExecution mojoExecution = new MojoExecution( mojoDescriptor );
1135 
1136             return pluginManager.getReport( project, mojoExecution, session );
1137         }
1138         catch ( ArtifactNotFoundException e )
1139         {
1140             throw new MojoExecutionException( "ArtifactNotFoundException: " + e.getMessage(), e );
1141         }
1142         catch ( ArtifactResolutionException e )
1143         {
1144             throw new MojoExecutionException( "ArtifactResolutionException: " + e.getMessage(), e );
1145         }
1146         catch ( PluginConfigurationException e )
1147         {
1148             throw new MojoExecutionException( "PluginConfigurationException: " + e.getMessage(), e );
1149         }
1150         catch ( PluginManagerException e )
1151         {
1152             throw new MojoExecutionException( "PluginManagerException: " + e.getMessage(), e );
1153         }
1154         finally
1155         {
1156             Thread.currentThread().setContextClassLoader( oldClassLoader );
1157         }
1158     }
1159 
1160     /**
1161      * Generate the given Maven report only if it is not an external report and the report could be generated.
1162      *
1163      * @param mojoDescriptor not null, to catch linkage error
1164      * @param report could be null
1165      * @param locale not null
1166      * @throws IOException if any
1167      * @throws MojoExecutionException if any
1168      * @see #isValidGeneratedReport(MojoDescriptor, File, String)
1169      * @since 1.1
1170      */
1171     private void generateMavenReport( MavenReport report, Artifact pluginArtifact, Locale locale )
1172         throws IOException, MojoExecutionException
1173     {
1174         if ( report == null )
1175         {
1176             return;
1177         }
1178 
1179         String localReportName = report.getName( locale );
1180         if ( !report.canGenerateReport() )
1181         {
1182             getLog().info( "Skipped \"" + localReportName + "\" report." );
1183             getLog().debug( "canGenerateReport() was false." );
1184 
1185             return;
1186         }
1187 
1188         if ( report.isExternalReport() )
1189         {
1190             getLog().info( "Skipped external \"" + localReportName + "\" report." );
1191             getLog().debug( "isExternalReport() was false." );
1192 
1193             return;
1194         }
1195 
1196         for ( final MavenReport generatedReport : getGeneratedMavenReports( locale ) )
1197         {
1198             if ( report.getName( locale ).equals( generatedReport.getName( locale ) ) )
1199             {
1200                 if ( getLog().isDebugEnabled() )
1201                 {
1202                     getLog().debug( report.getName( locale ) + " was already generated." );
1203                 }
1204                 return;
1205             }
1206         }
1207 
1208         File outDir = new File( getGeneratedSiteDirectoryTmp(), "xdoc" );
1209         if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1210         {
1211             outDir = new File( new File( getGeneratedSiteDirectoryTmp(), locale.getLanguage() ), "xdoc" );
1212         }
1213         outDir.mkdirs();
1214 
1215         File generatedReport = new File( outDir, report.getOutputName() + ".xml" );
1216 
1217         String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
1218         List<String> files =
1219             FileUtils.getFileNames( siteDirectory, "*/" + report.getOutputName() + ".*", excludes, false );
1220         if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1221         {
1222             files =
1223                 FileUtils.getFileNames( new File( siteDirectory, locale.getLanguage() ), "*/"
1224                     + report.getOutputName() + ".*", excludes, false );
1225         }
1226 
1227         if ( files.size() != 0 )
1228         {
1229             String displayLanguage = locale.getDisplayLanguage( Locale.ENGLISH );
1230 
1231             if ( getLog().isInfoEnabled() )
1232             {
1233                 getLog().info(
1234                                "Skipped \"" + report.getName( locale ) + "\" report, file \""
1235                                    + report.getOutputName() + "\" already exists for the " + displayLanguage
1236                                    + " version." );
1237             }
1238 
1239             return;
1240         }
1241 
1242         if ( getLog().isInfoEnabled() )
1243         {
1244             getLog().info( "Generating \"" + localReportName + "\" report." );
1245         }
1246 
1247         StringWriter sw = new StringWriter();
1248 
1249         PdfSink sink = null;
1250         try
1251         {
1252             sink = new PdfSink( sw );
1253             org.codehaus.doxia.sink.Sink proxy = (org.codehaus.doxia.sink.Sink) Proxy.newProxyInstance(
1254                 org.codehaus.doxia.sink.Sink.class.getClassLoader(),
1255                 new Class[] { org.codehaus.doxia.sink.Sink.class }, new SinkDelegate( sink ) );
1256             report.generate( proxy, locale );
1257         }
1258         catch ( MavenReportException e )
1259         {
1260             throw new MojoExecutionException( "MavenReportException: " + e.getMessage(), e );
1261         }
1262         finally
1263         {
1264             if ( sink != null )
1265             {
1266                 sink.close();
1267             }
1268         }
1269 
1270         writeGeneratedReport( sw.toString(), generatedReport );
1271 
1272         if ( isValidGeneratedReport( pluginArtifact, generatedReport, localReportName ) )
1273         {
1274             getGeneratedMavenReports( locale ).add( report );
1275         }
1276     }
1277 
1278     /**
1279      * @param locale not null
1280      * @return the generated reports
1281      * @see #generateMavenReport(MojoDescriptor, MavenReport, Locale)
1282      * @see #isValidGeneratedReport(MojoDescriptor, File, String)
1283      * @since 1.1
1284      */
1285     private List<MavenReport> getGeneratedMavenReports( Locale locale )
1286     {
1287         if ( this.generatedMavenReports == null )
1288         {
1289             this.generatedMavenReports = new HashMap<Locale, List<MavenReport>>( 2 );
1290         }
1291 
1292         if ( this.generatedMavenReports.get( locale ) == null )
1293         {
1294             this.generatedMavenReports.put( locale, new ArrayList<MavenReport>( 2 ) );
1295         }
1296 
1297         return this.generatedMavenReports.get( locale );
1298     }
1299 
1300     /**
1301      * Append generated reports to the toc only if <code>generateReports</code> is enabled, for instance:
1302      * <pre>
1303      * &lt;item name="Project Reports" ref="/project-info"&gt;
1304      * &nbsp;&nbsp;&lt;item name="Project License" ref="/license" /&gt;
1305      * &nbsp;&nbsp;&lt;item name="Project Team" ref="/team-list" /&gt;
1306      * &nbsp;&nbsp;&lt;item name="Continuous Integration" ref="/integration" /&gt;
1307      * &nbsp;&nbsp;...
1308      * &lt;/item&gt;
1309      * </pre>
1310      *
1311      * @param model not null
1312      * @param locale not null
1313      * @see #generateMavenReports(Locale)
1314      * @since 1.1
1315      */
1316     private void appendGeneratedReports( DocumentModel model, Locale locale )
1317     {
1318         if ( !includeReports )
1319         {
1320             return;
1321         }
1322         if ( getGeneratedMavenReports( locale ).isEmpty() )
1323         {
1324             return;
1325         }
1326 
1327         final DocumentTOCItem documentTOCItem = new DocumentTOCItem();
1328         documentTOCItem.setName( i18n.getString( "pdf-plugin", locale, "toc.project-info.item" ) );
1329         documentTOCItem.setRef( "/project-info" ); // see #generateMavenReports(Locale)
1330 
1331         List<String> addedRef = new ArrayList<String>( 4 );
1332 
1333         List<DocumentTOCItem> items = new ArrayList<DocumentTOCItem>( 4 );
1334 
1335         // append generated report defined as MavenReport
1336         for ( final MavenReport report : getGeneratedMavenReports( locale ) )
1337         {
1338             final DocumentTOCItem reportItem = new DocumentTOCItem();
1339             reportItem.setName( report.getName( locale ) );
1340             reportItem.setRef( "/" + report.getOutputName() );
1341 
1342             items.add( reportItem );
1343 
1344             addedRef.add( report.getOutputName() );
1345         }
1346 
1347         // append all generated reports from generated-site
1348         try
1349         {
1350             if ( generatedSiteDirectory.exists() )
1351             {
1352                 String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
1353                 List<String> generatedDirs = FileUtils.getDirectoryNames( generatedSiteDirectory, "*", excludes,
1354                                                                           true );
1355                 if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
1356                 {
1357                     generatedDirs =
1358                         FileUtils.getFileNames( new File( generatedSiteDirectory, locale.getLanguage() ), "*",
1359                                                 excludes, true );
1360                 }
1361 
1362                 for ( final String generatedDir : generatedDirs )
1363                 {
1364                     List<String> generatedFiles =
1365                         FileUtils.getFileNames( new File( generatedDir ), "**.*", excludes, false );
1366 
1367                     for ( final String generatedFile : generatedFiles )
1368                     {
1369                         final String ref = generatedFile.substring( 0, generatedFile.lastIndexOf( '.' ) );
1370 
1371                         if ( !addedRef.contains( ref ) )
1372                         {
1373                             final String title =
1374                                 getGeneratedDocumentTitle( new File( generatedDir, generatedFile ) );
1375 
1376                             if ( title != null )
1377                             {
1378                                 final DocumentTOCItem reportItem = new DocumentTOCItem();
1379                                 reportItem.setName( title );
1380                                 reportItem.setRef( "/" + ref );
1381 
1382                                 items.add( reportItem );
1383                             }
1384                         }
1385                     }
1386                 }
1387             }
1388         }
1389         catch ( IOException e )
1390         {
1391             getLog().error( "IOException: " + e.getMessage() );
1392             getLog().debug( e );
1393         }
1394 
1395         // append to Toc
1396         documentTOCItem.setItems( items );
1397         model.getToc().addItem( documentTOCItem );
1398     }
1399 
1400     /**
1401      * Parse a generated Doxia file and returns its title.
1402      *
1403      * @param f not null
1404      * @return the xdoc file title or null if an error occurs.
1405      * @throws IOException if any
1406      * @since 1.1
1407      */
1408     private String getGeneratedDocumentTitle( final File f )
1409         throws IOException
1410     {
1411         final IndexEntry entry = new IndexEntry( "index" );
1412         final IndexingSink titleSink = new IndexingSink( entry );
1413 
1414         Reader reader = null;
1415         try
1416         {
1417             reader = ReaderFactory.newXmlReader( f );
1418 
1419             doxia.parse( reader, f.getParentFile().getName(), titleSink );
1420         }
1421         catch ( ParseException e )
1422         {
1423             getLog().error( "ParseException: " + e.getMessage() );
1424             getLog().debug( e );
1425             return null;
1426         }
1427         catch ( ParserNotFoundException e )
1428         {
1429             getLog().error( "ParserNotFoundException: " + e.getMessage() );
1430             getLog().debug( e );
1431             return null;
1432         }
1433         finally
1434         {
1435             IOUtil.close( reader );
1436         }
1437 
1438         return titleSink.getTitle();
1439     }
1440 
1441     /**
1442      * Parsing the generated report to see if it is correct or not. Log the error for the user.
1443      *
1444      * @param mojoDescriptor not null
1445      * @param generatedReport not null
1446      * @param localReportName not null
1447      * @return <code>true</code> if Doxia is able to parse the generated report, <code>false</code> otherwise.
1448      * @since 1.1
1449      */
1450     private boolean isValidGeneratedReport( Artifact pluginArtifact, File generatedReport,
1451                                             String localReportName )
1452     {
1453         SinkAdapter sinkAdapter = new SinkAdapter();
1454         Reader reader = null;
1455         try
1456         {
1457             reader = ReaderFactory.newXmlReader( generatedReport );
1458 
1459             doxia.parse( reader, generatedReport.getParentFile().getName(), sinkAdapter );
1460         }
1461         catch ( ParseException e )
1462         {
1463             StringBuilder sb = new StringBuilder( 1024 );
1464 
1465             sb.append( EOL ).append( EOL );
1466             sb.append( "Error when parsing the generated report: " ).append( generatedReport.getAbsolutePath() );
1467             sb.append( EOL );
1468             sb.append( e.getMessage() );
1469             sb.append( EOL ).append( EOL );
1470 
1471             sb.append( "You could:" ).append( EOL );
1472             sb.append( "  * exclude all reports using -DincludeReports=false" ).append( EOL );
1473             sb.append( "  * remove the " );
1474             sb.append( pluginArtifact.getGroupId() );
1475             sb.append( ":" );
1476             sb.append( pluginArtifact.getArtifactId() );
1477             sb.append( ":" );
1478             sb.append( pluginArtifact.getVersion() );
1479             sb.append( " from the <reporting/> part. To not affect the site generation, " );
1480             sb.append( "you could create a PDF profile." ).append( EOL );
1481             sb.append( EOL );
1482 
1483             MavenProject pluginProject = getReportPluginProject( pluginArtifact );
1484 
1485             if ( pluginProject == null )
1486             {
1487                 sb.append( "You could also contact the Plugin team." ).append( EOL );
1488             }
1489             else
1490             {
1491                 sb.append( "You could also contact the Plugin team:" ).append( EOL );
1492                 if ( pluginProject.getMailingLists() != null && !pluginProject.getMailingLists().isEmpty() )
1493                 {
1494                     boolean appended = false;
1495                     for ( Object o : pluginProject.getMailingLists() )
1496                     {
1497                         MailingList mailingList = (MailingList) o;
1498 
1499                         if ( StringUtils.isNotEmpty( mailingList.getName() )
1500                             && StringUtils.isNotEmpty( mailingList.getPost() ) )
1501                         {
1502                             if ( !appended )
1503                             {
1504                                 sb.append( "  Mailing Lists:" ).append( EOL );
1505                                 appended = true;
1506                             }
1507                             sb.append( "    " ).append( mailingList.getName() );
1508                             sb.append( ": " ).append( mailingList.getPost() );
1509                             sb.append( EOL );
1510                         }
1511                     }
1512                 }
1513                 if ( StringUtils.isNotEmpty( pluginProject.getUrl() ) )
1514                 {
1515                     sb.append( "  Web Site:" ).append( EOL );
1516                     sb.append( "    " ).append( pluginProject.getUrl() );
1517                     sb.append( EOL );
1518                 }
1519                 if ( pluginProject.getIssueManagement() != null
1520                     && StringUtils.isNotEmpty( pluginProject.getIssueManagement().getUrl() ) )
1521                 {
1522                     sb.append( "  Issue Tracking:" ).append( EOL );
1523                     sb.append( "    " ).append( pluginProject.getIssueManagement().getUrl() );
1524                     sb.append( EOL );
1525                 }
1526             }
1527 
1528             sb.append( EOL ).append( "Ignoring the \"" ).append( localReportName )
1529                     .append( "\" report in the PDF." ).append( EOL );
1530 
1531             getLog().error( sb.toString() );
1532             getLog().debug( e );
1533 
1534             return false;
1535         }
1536         catch ( ParserNotFoundException e )
1537         {
1538             getLog().error( "ParserNotFoundException: " + e.getMessage() );
1539             getLog().debug( e );
1540 
1541             return false;
1542         }
1543         catch ( IOException e )
1544         {
1545             getLog().error( "IOException: " + e.getMessage() );
1546             getLog().debug( e );
1547 
1548             return false;
1549         }
1550         finally
1551         {
1552             IOUtil.close( reader );
1553         }
1554 
1555         return true;
1556     }
1557 
1558     /**
1559      * @param pluginDescriptor not null
1560      * @return the MavenProject for the current plugin descriptor or null if an error occurred.
1561      * @since 1.1
1562      */
1563     private MavenProject getReportPluginProject( Artifact pluginArtifact )
1564     {
1565         try
1566         {
1567             return mavenProjectBuilder.buildFromRepository( pluginArtifact, remoteRepositories, localRepository );
1568         }
1569         catch ( ProjectBuildingException e )
1570         {
1571             getLog().error( "ProjectBuildingException: " + e.getMessage() );
1572             getLog().debug( e );
1573         }
1574 
1575         return null;
1576     }
1577     
1578     protected List<MavenReportExecution> getReports()
1579         throws MojoExecutionException
1580     {
1581         if ( isMaven3OrMore() )
1582         {
1583             MavenReportExecutorRequest mavenReportExecutorRequest = new MavenReportExecutorRequest();
1584             mavenReportExecutorRequest.setLocalRepository( localRepository );
1585             mavenReportExecutorRequest.setMavenSession( session );
1586             mavenReportExecutorRequest.setProject( project );
1587             mavenReportExecutorRequest.setReportPlugins( reportPlugins );
1588 
1589             MavenReportExecutor mavenReportExecutor;
1590             try
1591             {
1592                 mavenReportExecutor = (MavenReportExecutor) container.lookup( MavenReportExecutor.class.getName() );
1593             }
1594             catch ( ComponentLookupException e )
1595             {
1596                 throw new MojoExecutionException( "could not get MavenReportExecutor component", e );
1597             }
1598             return mavenReportExecutor.buildMavenReports( mavenReportExecutorRequest );
1599         }
1600 
1601         List<MavenReportExecution> reportExecutions = new ArrayList<MavenReportExecution>( reports.length );
1602         for ( MavenReport report : reports )
1603         {
1604             if ( report.canGenerateReport() )
1605             {
1606                 reportExecutions.add( new MavenReportExecution( report ) );
1607             }
1608         }
1609         return reportExecutions;
1610     }
1611     
1612     /**
1613      * Check the current Maven version to see if it's Maven 3.0 or newer.
1614      */
1615     protected static boolean isMaven3OrMore()
1616     {
1617         try
1618         {
1619             ArtifactVersion mavenVersion = new DefaultArtifactVersion( getMavenVersion() );
1620             return VersionRange.createFromVersionSpec( "[3.0,)" ).containsVersion( mavenVersion );
1621         }
1622         catch ( InvalidVersionSpecificationException e )
1623         {
1624             return false;
1625         }
1626 //        return new ComparableVersion( getMavenVersion() ).compareTo( new ComparableVersion( "3.0" ) ) >= 0;
1627     }
1628 
1629     protected static String getMavenVersion()
1630     {
1631         // This relies on the fact that MavenProject is the in core classloader
1632         // and that the core classloader is for the maven-core artifact
1633         // and that should have a pom.properties file
1634         // if this ever changes, we will have to revisit this code.
1635         final Properties properties = new Properties();
1636         final InputStream in =
1637             MavenProject.class.getClassLoader().getResourceAsStream( "META-INF/maven/org.apache.maven/maven-core/"
1638                                                                          + "pom.properties" );
1639         try
1640         {
1641             properties.load( in );
1642         }
1643         catch ( IOException ioe )
1644         {
1645             return "";
1646         }
1647         finally
1648         {
1649             IOUtil.close( in );
1650         }
1651 
1652         return properties.getProperty( "version" ).trim();
1653     }
1654 
1655     // ----------------------------------------------------------------------
1656     // static methods
1657     // ----------------------------------------------------------------------
1658 
1659     /**
1660      * Write the given content to the given file.
1661      * <br/>
1662      * <b>Note</b>: try also to fix the content due to some issues in
1663      * {@link org.apache.maven.reporting.AbstractMavenReport}.
1664      *
1665      * @param content the given content
1666      * @param toFile the report file
1667      * @throws IOException if any
1668      * @since 1.1
1669      */
1670     private static void writeGeneratedReport( String content, File toFile )
1671         throws IOException
1672     {
1673         if ( StringUtils.isEmpty( content ) )
1674         {
1675             return;
1676         }
1677 
1678         Writer writer = null;
1679         try
1680         {
1681             writer = WriterFactory.newXmlWriter( toFile );
1682             // see PdfSink#table()
1683             writer.write( StringUtils.replace( content, "<table><table", "<table" ) );
1684         }
1685         finally
1686         {
1687             IOUtil.close( writer );
1688         }
1689     }
1690 
1691     /**
1692      * @param locales the list of locales dir to exclude
1693      * @param defaultLocale the default locale.
1694      * @return the comma separated list of default excludes and locales dir.
1695      * @see FileUtils#getDefaultExcludesAsString()
1696      * @since 1.1
1697      */
1698     private static String getDefaultExcludesWithLocales( List<Locale> locales, Locale defaultLocale )
1699     {
1700         String excludesLocales = FileUtils.getDefaultExcludesAsString();
1701         for ( final Locale locale : locales )
1702         {
1703             if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1704             {
1705                 excludesLocales = excludesLocales + ",**/" + locale.getLanguage() + "/*";
1706             }
1707         }
1708 
1709         return excludesLocales;
1710     }
1711 
1712     // ----------------------------------------------------------------------
1713     // Inner class
1714     // ----------------------------------------------------------------------
1715 
1716     /**
1717      * A sink to generate a Maven report as xdoc with some known workarounds.
1718      *
1719      * @since 1.1
1720      */
1721     private static class PdfSink
1722         extends XdocSink
1723     {
1724         protected PdfSink( Writer writer )
1725         {
1726             super( writer );
1727         }
1728 
1729         /** {@inheritDoc} */
1730         public void text( String text )
1731         {
1732             // workaround to fix quotes introduced with MPIR-59 (then removed in MPIR-136)
1733             super.text( StringUtils.replace( text, "\u0092", "'" ) );
1734         }
1735     }
1736 
1737     /**
1738      * Renderer Maven report similar to org.apache.maven.plugins.site.CategorySummaryDocumentRenderer
1739      *
1740      * @since 1.1
1741      */
1742     private static class ProjectInfoRenderer
1743         extends AbstractMavenReportRenderer
1744     {
1745         private final List<MavenReport> generatedReports;
1746 
1747         private final I18N i18n;
1748 
1749         private final Locale locale;
1750 
1751         ProjectInfoRenderer( Sink sink, List<MavenReport> generatedReports, I18N i18n, Locale locale )
1752         {
1753             super( sink );
1754 
1755             this.generatedReports = generatedReports;
1756             this.i18n = i18n;
1757             this.locale = locale;
1758         }
1759 
1760         /** {@inheritDoc} */
1761         public String getTitle()
1762         {
1763             return i18n.getString( "pdf-plugin", locale, "report.project-info.title" );
1764         }
1765 
1766         /** {@inheritDoc} */
1767         public void renderBody()
1768         {
1769             sink.section1();
1770             sink.sectionTitle1();
1771             sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.title" ) );
1772             sink.sectionTitle1_();
1773 
1774             sink.paragraph();
1775             sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.description1" ) + " " );
1776             sink.link( "http://maven.apache.org" );
1777             sink.text( "Maven" );
1778             sink.link_();
1779             sink.text( " " + i18n.getString( "pdf-plugin", locale, "report.project-info.description2" ) );
1780             sink.paragraph_();
1781 
1782             sink.section2();
1783             sink.sectionTitle2();
1784             sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.sectionTitle" ) );
1785             sink.sectionTitle2_();
1786 
1787             sink.table();
1788 
1789             sink.tableRows( new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_LEFT }, false );
1790 
1791             String name = i18n.getString( "pdf-plugin", locale, "report.project-info.column.document" );
1792             String description = i18n.getString( "pdf-plugin", locale, "report.project-info.column.description" );
1793 
1794             sink.tableRow();
1795 
1796             sink.tableHeaderCell( SinkEventAttributeSet.CENTER );
1797 
1798             sink.text( name );
1799 
1800             sink.tableHeaderCell_();
1801 
1802             sink.tableHeaderCell( SinkEventAttributeSet.CENTER );
1803 
1804             sink.text( description );
1805 
1806             sink.tableHeaderCell_();
1807 
1808             sink.tableRow_();
1809 
1810             if ( generatedReports != null )
1811             {
1812                 for ( final MavenReport report : generatedReports )
1813                 {
1814                     sink.tableRow();
1815                     sink.tableCell();
1816                     sink.link( report.getOutputName() + ".html" );
1817                     sink.text( report.getName( locale ) );
1818                     sink.link_();
1819                     sink.tableCell_();
1820                     sink.tableCell();
1821                     sink.text( report.getDescription( locale ) );
1822                     sink.tableCell_();
1823                     sink.tableRow_();
1824                 }
1825             }
1826 
1827             sink.tableRows_();
1828 
1829             sink.table_();
1830 
1831             sink.section2_();
1832 
1833             sink.section1_();
1834         }
1835     }
1836 
1837     /**
1838      * Delegates the method invocations on <code>org.codehaus.doxia.sink.Sink@maven-core-realm</code> to
1839      * <code>org.apache.maven.doxia.sink.Sink@pdf-plugin-realm</code>.
1840      *
1841      * @author Benjamin Bentmann
1842      */
1843     private static class SinkDelegate
1844         implements InvocationHandler
1845     {
1846         private final Sink sink;
1847 
1848         SinkDelegate( Sink sink )
1849         {
1850             this.sink = sink;
1851         }
1852 
1853         /** {@inheritDoc} */
1854         public Object invoke( Object proxy, Method method, Object[] args )
1855             throws Throwable
1856         {
1857             Class<?>[] parameterTypes = method.getParameterTypes();
1858 
1859             for ( int i = parameterTypes.length - 1; i >= 0; i-- )
1860             {
1861                 if ( AttributeSet.class.isAssignableFrom( parameterTypes[i] ) )
1862                 {
1863                     parameterTypes[i] = SinkEventAttributes.class;
1864                 }
1865             }
1866 
1867             if ( args != null )
1868             {
1869                 for ( int i = args.length - 1; i >= 0; i-- )
1870                 {
1871                     if ( AttributeSet.class.isInstance( args[i] ) )
1872                     {
1873                         args[i] = new SinkEventAttributeSet( (AttributeSet) args[i] );
1874                     }
1875                 }
1876             }
1877 
1878             Method target = Sink.class.getMethod( method.getName(), parameterTypes );
1879 
1880             return target.invoke( sink, args );
1881         }
1882     }
1883 }