View Javadoc

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