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