All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.pdf.PdfMojo Maven / Gradle / Ivy

package org.apache.maven.plugins.pdf;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.io.input.XmlStreamReader;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.doxia.Doxia;
import org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer;
import org.apache.maven.doxia.docrenderer.DocumentRenderer;
import org.apache.maven.doxia.docrenderer.DocumentRendererContext;
import org.apache.maven.doxia.docrenderer.DocumentRendererException;
import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer;
import org.apache.maven.doxia.document.DocumentMeta;
import org.apache.maven.doxia.document.DocumentModel;
import org.apache.maven.doxia.document.DocumentTOC;
import org.apache.maven.doxia.document.DocumentTOCItem;
import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Writer;
import org.apache.maven.doxia.index.IndexEntry;
import org.apache.maven.doxia.index.IndexingSink;
import org.apache.maven.doxia.module.xdoc.XdocSink;
import org.apache.maven.doxia.parser.ParseException;
import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
import org.apache.maven.doxia.sink.impl.SinkAdapter;
import org.apache.maven.doxia.site.decoration.DecorationModel;
import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.doxia.siterenderer.RendererException;
import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
import org.apache.maven.doxia.tools.SiteTool;
import org.apache.maven.doxia.tools.SiteToolException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.model.Reporting;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.MavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.apache.maven.reporting.exec.MavenReportExecution;
import org.apache.maven.reporting.exec.MavenReportExecutor;
import org.apache.maven.reporting.exec.MavenReportExecutorRequest;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.context.Context;
import org.codehaus.plexus.context.ContextException;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.PathTool;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.WriterFactory;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Generates a PDF document for a project documentation usually published as web site (with maven-site-plugin).
 *
 * @author ltheussl
 */
@Mojo( name = "pdf", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true )
public class PdfMojo
    extends AbstractPdfMojo implements Contextualizable
{

    /**
     * The vm line separator
     */
    private static final String EOL = System.getProperty( "line.separator" );

    /**
     * FO Document Renderer.
     */
    @Component( hint = "fo" )
    private PdfRenderer foRenderer;

    /**
     * Internationalization.
     */
    @Component
    private I18N i18n;

    /**
     * IText Document Renderer.
     */
    @Component( hint = "itext" )
    private PdfRenderer itextRenderer;

    /**
     * A comma separated list of locales supported by Maven.
     * The first valid token will be the default Locale for this instance of the Java Virtual Machine.
     */
    @Parameter( property = "locales" )
    private String locales;

    /**
     * Site renderer.
     */
    @Component
    private Renderer siteRenderer;

    /**
     * SiteTool.
     */
    @Component
    private SiteTool siteTool;

    /**
     * Doxia.
     *
     * @since 1.1
     */
    @Component
    private Doxia doxia;

    /**
     * The Maven Project Object.
     */
    @Parameter( defaultValue = "${project}", readonly = true, required = true )
    protected MavenProject project;

    /**
     * The Maven Settings.
     *
     * @since 1.1
     */
    @Parameter( defaultValue = "${settings}", readonly = true, required = true )
    private Settings settings;

    /**
     * The current build session instance.
     *
     * @since 1.1
     */
    @Parameter( defaultValue = "${session}", readonly = true, required = true )
    private MavenSession session;

    /**
     * Directory containing source for apt, fml and xdoc docs.
     */
    @Parameter( defaultValue = "${basedir}/src/site", required = true )
    private File siteDirectory;

    /**
     * Directory containing generated sources for apt, fml and xdoc docs.
     *
     * @since 1.1
     */
    @Parameter( defaultValue = "${project.build.directory}/generated-site", required = true )
    private File generatedSiteDirectory;

    /**
     * Output directory where PDF files should be created.
     */
    @Parameter( defaultValue = "${project.build.directory}/pdf", required = true )
    private File outputDirectory;

    /**
     * Working directory for working files like temp files/resources.
     */
    @Parameter( defaultValue = "${project.build.directory}/pdf", required = true )
    private File workingDirectory;

    /**
     * File that contains the DocumentModel of the PDF to generate.
     */
    @Parameter( defaultValue = "src/site/pdf.xml" )
    private File docDescriptor;

    /**
     * Identifies the framework to use for pdf generation: either "fo" (default) or "itext".
     */
    @Parameter( property = "implementation", defaultValue = "fo", required = true )
    private String implementation;

    /**
     * The local repository.
     */
    @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
    private ArtifactRepository localRepository;

    /**
     * The remote repositories where artifacts are located.
     *
     * @since 1.1
     */
    @Parameter( defaultValue = "${project.remoteArtifactRepositories}"  )
    private List remoteRepositories;

    /**
     * If true, aggregate all source documents in one pdf, otherwise generate one pdf for each
     * source document.
     */
    @Parameter( property = "aggregate", defaultValue = "true" )
    private boolean aggregate;

    /**
     * The current version of this plugin.
     */
    @Parameter( defaultValue = "${plugin.version}", readonly = true )
    private String pluginVersion;

    /**
     * If true, generate all Maven reports defined in ${project.reporting} and append
     * them as a new entry in the TOC (Table Of Contents).
     * Note: Including the report generation could fail the PDF generation or increase the build time.
     *
     * @since 1.1
     */
    @Parameter( property = "includeReports", defaultValue = "true" )
    private boolean includeReports;

    /**
     * Generate a TOC (Table Of Content) for all items defined in the <toc/> element from the document descriptor.
     * 
* Possible values are: 'none', 'start' and 'end'. * * @since 1.1 */ @Parameter( property = "generateTOC", defaultValue = "start" ) private String generateTOC; /** * Whether to validate xml input documents. * If set to true, all input documents in xml format * (in particular xdoc and fml) will be validated and any error will * lead to a build failure. * * @since 1.2 */ @Parameter( property = "validate", defaultValue = "false" ) private boolean validate; /** * Reports (Maven 2). * * @since 1.3 */ @Parameter( defaultValue = "${reports}", required = true, readonly = true ) private MavenReport[] reports; /** * Reports (Maven 3). * * @since 1.5 */ @Parameter( defaultValue = "${project.reporting}", readonly = true ) private Reporting reporting; /** * The current document Renderer. * @see #implementation */ private DocumentRenderer docRenderer; /** * The default locale. */ private Locale defaultLocale; /** * The available locales list. */ private List localesList; /** * The default decoration model. */ private DecorationModel defaultDecorationModel; /** * The temp Generated Site dir to have generated reports by this plugin. * * @since 1.1 */ private File generatedSiteDirectoryTmp; /** * A map of generated MavenReport list using locale as key. * * @since 1.1 */ private Map> generatedMavenReports; /** * @since 1.3 */ private PlexusContainer container; /** {@inheritDoc} */ public void execute() throws MojoExecutionException { init(); try { generatePdf(); } catch ( IOException e ) { debugLogGeneratedModel( getDocumentModel( Locale.ENGLISH ) ); throw new MojoExecutionException( "Error during document generation: " + e.getMessage(), e ); } try { copyGeneratedPdf(); } catch ( IOException e ) { throw new MojoExecutionException( "Error copying generated PDF: " + e.getMessage(), e ); } } /** {@inheritDoc} */ public void contextualize( Context context ) throws ContextException { container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY ); } protected File getOutputDirectory() { return outputDirectory; } protected File getWorkingDirectory() { return workingDirectory; } protected boolean isIncludeReports() { return includeReports; } /** * Init and validate parameters */ private void init() { if ( "fo".equalsIgnoreCase( implementation ) ) { this.docRenderer = foRenderer; } else if ( "itext".equalsIgnoreCase( implementation ) ) { this.docRenderer = itextRenderer; } else { getLog().warn( "Invalid 'implementation' parameter: '" + implementation + "', using 'fo' as default." ); this.docRenderer = foRenderer; } if ( !( "none".equalsIgnoreCase( generateTOC ) || "start".equalsIgnoreCase( generateTOC ) || "end".equalsIgnoreCase( generateTOC ) ) ) { getLog().warn( "Invalid 'generateTOC' parameter: '" + generateTOC + "', using 'start' as default." ); this.generateTOC = "start"; } } /** * Copy the generated PDF to outputDirectory. * * @throws MojoExecutionException if any * @throws IOException if any * @since 1.1 */ private void copyGeneratedPdf() throws MojoExecutionException, IOException { boolean requireCopy = !getOutputDirectory().getCanonicalPath().equals( getWorkingDirectory().getCanonicalPath() ); String outputName = getDocumentModel( getDefaultLocale() ).getOutputName().trim(); if ( !outputName.endsWith( ".pdf" ) ) { outputName = outputName.concat( ".pdf" ); } for ( final Locale locale : getAvailableLocales() ) { File generatedPdfSource = new File( getLocaleDirectory( getWorkingDirectory(), locale ), outputName ); if ( !generatedPdfSource.exists() ) { getLog().warn( "Unable to find the generated pdf: " + generatedPdfSource.getAbsolutePath() ); continue; } File generatedPdfDest = new File( getLocaleDirectory( getOutputDirectory(), locale ), outputName ); if ( requireCopy ) { FileUtils.copyFile( generatedPdfSource, generatedPdfDest ); generatedPdfSource.delete(); } getLog().info( "pdf generated: " + generatedPdfDest ); } } /** * Generate the PDF. * * @throws MojoExecutionException if any * @throws IOException if any * @since 1.1 */ private void generatePdf() throws MojoExecutionException, IOException { Locale.setDefault( getDefaultLocale() ); for ( final Locale locale : getAvailableLocales() ) { final File workingDir = getLocaleDirectory( getWorkingDirectory(), locale ); File siteDirectoryFile = getLocaleDirectory( getSiteDirectoryTmp(), locale ); copyResources( locale ); // generated xdoc sources for reports generateMavenReports( locale ); // render all Doxia source files to pdf (were handwritten or generated by reports) DocumentRendererContext context = new DocumentRendererContext(); context.put( "project", project ); context.put( "settings", settings ); context.put( "PathTool", new PathTool() ); context.put( "FileUtils", new FileUtils() ); context.put( "StringUtils", new StringUtils() ); context.put( "i18n", i18n ); context.put( "generateTOC", generateTOC ); context.put( "validate", validate ); // Put any of the properties in directly into the Velocity context for ( Map.Entry entry : project.getProperties().entrySet() ) { context.put( (String) entry.getKey(), entry.getValue() ); } final DocumentModel model = aggregate ? getDocumentModel( locale ) : null; try { // TODO use interface see DOXIASITETOOLS-30 ( (AbstractDocumentRenderer) docRenderer ).render( siteDirectoryFile, workingDir, model, context ); } catch ( DocumentRendererException e ) { throw new MojoExecutionException( "Error during document generation: " + e.getMessage(), e ); } } } /** * @return the default tmpGeneratedSiteDirectory when report will be created. * @since 1.1 */ private File getGeneratedSiteDirectoryTmp() { if ( this.generatedSiteDirectoryTmp == null ) { this.generatedSiteDirectoryTmp = new File( getWorkingDirectory(), "generated-site.tmp" ); } return this.generatedSiteDirectoryTmp; } /** * Copy all site and generated-site files in the tmpSiteDirectory. *
* Note: ignore copying of generated-site files if they already exist in the * site dir. * * @param tmpSiteDir not null * @throws IOException if any * @since 1.1 */ protected void prepareTempSiteDirectory( final File tmpSiteDir ) throws IOException { // safety tmpSiteDir.mkdirs(); // copy site if ( siteDirectory.exists() ) { FileUtils.copyDirectoryStructure( siteDirectory, tmpSiteDir ); } // Remove SCM files List files = FileUtils.getFileAndDirectoryNames( tmpSiteDir, FileUtils.getDefaultExcludesAsString(), null, true, true, true, true ); for ( final String fileName : files ) { final File file = new File( fileName ); if ( file.isDirectory() ) { FileUtils.deleteDirectory( file ); } else { file.delete(); } } copySiteDir( generatedSiteDirectory, tmpSiteDir ); } /** * Copy the from site dir to the to dir. * * @param from not null * @param to not null * @throws IOException if any * @since 1.1 */ private void copySiteDir( final File from, final File to ) throws IOException { if ( from == null || !from.exists() ) { return; } // copy generated-site for ( final Locale locale : getAvailableLocales() ) { String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() ); List siteFiles = siteDirectory.exists() ? FileUtils.getFileNames( siteDirectory, "**/*", excludes, false ) : new ArrayList<>(); File siteDirectoryLocale = new File( siteDirectory, locale.getLanguage() ); if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) && siteDirectoryLocale.exists() ) { siteFiles = FileUtils.getFileNames( siteDirectoryLocale, "**/*", excludes, false ); } List generatedSiteFiles = FileUtils.getFileNames( from, "**/*", excludes, false ); File fromLocale = new File( from, locale.getLanguage() ); if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) && fromLocale.exists() ) { generatedSiteFiles = FileUtils.getFileNames( fromLocale, "**/*", excludes, false ); } for ( final String generatedSiteFile : generatedSiteFiles ) { if ( siteFiles.contains( generatedSiteFile ) ) { getLog().warn( "Generated-site already contains a file in site: " + generatedSiteFile + ". Ignoring copying it!" ); continue; } if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) ) { if ( fromLocale.exists() ) { File in = new File( fromLocale, generatedSiteFile ); File out = new File( new File( to, locale.getLanguage() ), generatedSiteFile ); out.getParentFile().mkdirs(); FileUtils.copyFile( in, out ); } } else { File in = new File( from, generatedSiteFile ); File out = new File( to, generatedSiteFile ); out.getParentFile().mkdirs(); FileUtils.copyFile( in, out ); } } } } /** * Constructs a DocumentModel for the current project. The model is either read from * a descriptor file, if it exists, or constructed from information in the pom and site.xml. * * @param locale not null * @return DocumentModel. * @throws MojoExecutionException if any * @see #appendGeneratedReports(DocumentModel, Locale) */ private DocumentModel getDocumentModel( Locale locale ) throws MojoExecutionException { if ( docDescriptor.exists() ) { DocumentModel doc = getDocumentModelFromDescriptor( locale ); // TODO: descriptor model should get merged into default model, see MODELLO-63 appendGeneratedReports( doc, locale ); saveTOC( doc.getToc(), locale ); return doc; } DocumentModel model = new DocumentModelBuilder( project, getDefaultDecorationModel() ).getDocumentModel(); model.getMeta().setGenerator( getDefaultGenerator() ); model.getMeta().setLanguage( locale.getLanguage() ); model.getCover().setCoverType( i18n.getString( "pdf-plugin", getDefaultLocale(), "toc.type" ) ); model.getToc().setName( i18n.getString( "pdf-plugin", getDefaultLocale(), "toc.title" ) ); appendGeneratedReports( model, locale ); saveTOC( model.getToc(), locale ); debugLogGeneratedModel( model ); return model; } /** * Read a DocumentModel from a file. * * @param locale used to set the language. * @return the DocumentModel read from the configured document descriptor. * @throws org.apache.maven.plugin.MojoExecutionException if the model could not be read. */ private DocumentModel getDocumentModelFromDescriptor( Locale locale ) throws MojoExecutionException { DocumentModel model; try { model = new DocumentDescriptorReader( project, getLog(), locale ).readAndFilterDocumentDescriptor( docDescriptor ); } catch ( XmlPullParserException ex ) { throw new MojoExecutionException( "Error reading DocumentDescriptor!", ex ); } catch ( IOException io ) { throw new MojoExecutionException( "Error opening DocumentDescriptor!", io ); } if ( model.getMeta() == null ) { model.setMeta( new DocumentMeta() ); } if ( StringUtils.isEmpty( model.getMeta().getLanguage() ) ) { model.getMeta().setLanguage( locale.getLanguage() ); } if ( StringUtils.isEmpty( model.getMeta().getGenerator() ) ) { model.getMeta().setGenerator( getDefaultGenerator() ); } return model; } /** * Return the directory for a given Locale and the current default Locale. * * @param basedir the base directory * @param locale a Locale. * @return File. */ private File getLocaleDirectory( File basedir, Locale locale ) { if ( locale.getLanguage().equals( getDefaultLocale().getLanguage() ) ) { return basedir; } return new File( basedir, locale.getLanguage() ); } /** * @return the default locale from siteTool. * @see #getAvailableLocales() */ private Locale getDefaultLocale() { if ( this.defaultLocale == null ) { this.defaultLocale = getAvailableLocales().get( 0 ); } return this.defaultLocale; } /** * @return the available locales from siteTool. */ private List getAvailableLocales() { if ( this.localesList == null ) { this.localesList = siteTool.getSiteLocales( locales ); } return this.localesList; } /** * @return the DecorationModel instance from site.xml * @throws MojoExecutionException if any */ private DecorationModel getDefaultDecorationModel() throws MojoExecutionException { if ( this.defaultDecorationModel == null ) { final Locale locale = getDefaultLocale(); final File descriptorFile = siteTool.getSiteDescriptor( siteDirectory, locale ); DecorationModel decoration = null; if ( descriptorFile.exists() ) { try ( XmlStreamReader reader = new XmlStreamReader( descriptorFile ) ) { String siteDescriptorContent = IOUtil.toString( reader ); siteDescriptorContent = siteTool.getInterpolatedSiteDescriptorContent( new HashMap<>( 2 ), project, siteDescriptorContent ); decoration = new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); } catch ( XmlPullParserException e ) { throw new MojoExecutionException( "Error parsing site descriptor", e ); } catch ( IOException e ) { throw new MojoExecutionException( "Error reading site descriptor", e ); } catch ( SiteToolException e ) { throw new MojoExecutionException( "Error when interpoling site descriptor", e ); } } this.defaultDecorationModel = decoration; } return this.defaultDecorationModel; } /** * Parse the decoration model to find the skin artifact and copy its resources to the output dir. * * @param locale not null * @throws MojoExecutionException if any * @see #getDefaultDecorationModel() */ private void copyResources( Locale locale ) throws MojoExecutionException { final DecorationModel decorationModel = getDefaultDecorationModel(); if ( decorationModel == null ) { return; } Artifact skinArtifact; try { skinArtifact = siteTool.getSkinArtifactFromRepository( localRepository, project.getRemoteArtifactRepositories(), decorationModel ); } catch ( SiteToolException e ) { throw new MojoExecutionException( "SiteToolException: " + e.getMessage(), e ); } if ( skinArtifact == null ) { return; } if ( getLog().isDebugEnabled() ) { getLog().debug( "Copy resources from skin artifact: '" + skinArtifact.getId() + "'..." ); } try { final SiteRenderingContext context = siteRenderer.createContextForSkin( skinArtifact, new HashMap<>( 2 ), decorationModel, project.getName(), locale ); context.addSiteDirectory( new File( siteDirectory, locale.getLanguage() ) ); siteRenderer.copyResources( context, getWorkingDirectory() ); } catch ( IOException e ) { throw new MojoExecutionException( "IOException: " + e.getMessage(), e ); } catch ( RendererException e ) { throw new MojoExecutionException( "RendererException: " + e.getMessage(), e ); } } /** * Construct a default producer. * * @return A String in the form Maven PDF Plugin v. 1.1.1, 'fo' implementation. */ private String getDefaultGenerator() { return "Maven PDF Plugin v. " + pluginVersion + ", '" + implementation + "' implementation."; } /** * Write the auto-generated model to disc. * * @param docModel the model to write. */ private void debugLogGeneratedModel( final DocumentModel docModel ) { if ( getLog().isDebugEnabled() && project != null ) { final File outputDir = new File( project.getBuild().getDirectory(), "pdf" ); if ( !outputDir.exists() ) { outputDir.mkdirs(); } final File doc = FileUtils.createTempFile( "pdf", ".xml", outputDir ); final DocumentXpp3Writer xpp3 = new DocumentXpp3Writer(); try ( Writer writer = WriterFactory.newXmlWriter( doc ) ) { xpp3.write( writer, docModel ); getLog().debug( "Generated a default document model: " + doc.getAbsolutePath() ); } catch ( IOException e ) { getLog().error( "Failed to write document model: " + e.getMessage() ); getLog().debug( e ); } } } /** * Generate all Maven reports defined in ${project.reporting} to xdoc source * only if generateReports is enabled. * * @param locale not null * @throws MojoExecutionException if any * @throws IOException if any * @since 1.1 */ private void generateMavenReports( Locale locale ) throws MojoExecutionException, IOException { if ( !isIncludeReports() ) { getLog().info( "Skipped report generation." ); return; } if ( project.getReporting() == null ) { getLog().info( "No report was specified." ); return; } List reportExecutions = getReports(); for ( MavenReportExecution reportExecution : reportExecutions ) { generateMavenReport( reportExecution, locale ); } // copy generated site copySiteDir( getGeneratedSiteDirectoryTmp(), getSiteDirectoryTmp() ); copySiteDir( generatedSiteDirectory, getSiteDirectoryTmp() ); } /** * Generate the given Maven report to an xdoc source file, * only if it is not an external report and the report could be generated. * * @param reportExecution not null * @param locale not null * @throws IOException if any * @throws MojoExecutionException if any * @since 1.1 */ private void generateMavenReport( MavenReportExecution reportExecution, Locale locale ) throws IOException, MojoExecutionException { MavenReport report = reportExecution.getMavenReport(); String localReportName = report.getName( locale ); if ( !reportExecution.canGenerateReport() ) { getLog().info( "Skipped \"" + localReportName + "\" report." ); getLog().debug( "canGenerateReport() was false." ); return; } if ( report.isExternalReport() ) { getLog().info( "Skipped external \"" + localReportName + "\" report (not supported by pdf plugin)." ); getLog().debug( "isExternalReport() was false." ); return; } for ( final MavenReport generatedReport : getGeneratedMavenReports( locale ) ) { if ( report.getName( locale ).equals( generatedReport.getName( locale ) ) ) { if ( getLog().isDebugEnabled() ) { getLog().debug( report.getName( locale ) + " was already generated." ); } return; } } File outDir = new File( getGeneratedSiteDirectoryTmp(), "xdoc" ); if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) ) { outDir = new File( new File( getGeneratedSiteDirectoryTmp(), locale.getLanguage() ), "xdoc" ); } outDir.mkdirs(); File generatedReport = new File( outDir, report.getOutputName() + ".xml" ); if ( siteDirectory.exists() ) { String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() ); List files = FileUtils.getFileNames( siteDirectory, "*/" + report.getOutputName() + ".*", excludes, false ); if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) ) { files = FileUtils.getFileNames( new File( siteDirectory, locale.getLanguage() ), "*/" + report.getOutputName() + ".*", excludes, false ); } if ( files.size() != 0 ) { String displayLanguage = locale.getDisplayLanguage( Locale.ENGLISH ); if ( getLog().isInfoEnabled() ) { getLog().info( "Skipped \"" + report.getName( locale ) + "\" report, file \"" + report.getOutputName() + "\" already exists for the " + displayLanguage + " version." ); } return; } } if ( getLog().isInfoEnabled() ) { getLog().info( "Generating \"" + localReportName + "\" report." ); } // The report will eventually generate output by itself, so we set its output directory anyway. report.setReportOutputDirectory( outDir ); StringWriter sw = new StringWriter(); PdfXdocSink pdfXdocSink = null; try { pdfXdocSink = new PdfXdocSink( sw ); renderReportToSink( reportExecution, locale, pdfXdocSink ); } catch ( MavenReportException e ) { String goal = reportExecution.getPlugin().getArtifactId() + ':' + reportExecution.getPlugin().getVersion() + ':' + reportExecution.getGoal(); throw new MojoExecutionException( "Error generating " + goal + " report", e ); } finally { if ( pdfXdocSink != null ) { pdfXdocSink.close(); } } if ( getLog().isDebugEnabled() ) { getLog().debug( "Writing generated xdoc to " + generatedReport ); } writeGeneratedReport( sw.toString(), generatedReport ); // keep generated report xdoc only if it is valid if ( isValidGeneratedReportXdoc( reportExecution.getPlugin().getId() + ':' + reportExecution.getGoal(), generatedReport, localReportName ) ) { getGeneratedMavenReports( locale ).add( report ); } } /** * see org.apache.maven.plugins.site.render.ReportDocumentRenderer#renderDocument(...) * * @param reportExec * @param locale * @param sink * @throws MavenReportException */ private void renderReportToSink( MavenReportExecution reportExec, Locale locale, PdfXdocSink sink ) throws MavenReportException { ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { if ( reportExec.getClassLoader() != null ) { Thread.currentThread().setContextClassLoader( reportExec.getClassLoader() ); } MavenReport report = reportExec.getMavenReport(); /*if ( report instanceof MavenMultiPageReport ) { // extended multi-page API ( (MavenMultiPageReport) report ).generate( mainSink, multiPageSinkFactory, locale ); } else if ( generateMultiPage( locale, multiPageSinkFactory, mainSink ) ) { // extended multi-page API for Maven 2.2, only accessible by reflection API } else {*/ // old single-page-only API report.generate( sink, locale ); //} } finally { if ( reportExec.getClassLoader() != null ) { Thread.currentThread().setContextClassLoader( originalClassLoader ); } } } /** * @param locale not null * @return the generated reports * @since 1.1 */ private List getGeneratedMavenReports( Locale locale ) { if ( this.generatedMavenReports == null ) { this.generatedMavenReports = new HashMap<>( 2 ); } this.generatedMavenReports.computeIfAbsent( locale, k -> new ArrayList<>( 2 ) ); return this.generatedMavenReports.get( locale ); } /** * Append generated reports to the toc only if generateReports is enabled, for instance: *
     * <item name="Project Reports" ref="project-info">
     *   <item name="Project License" ref="license" />
     *   <item name="Project Team" ref="team-list" />
     *   <item name="Continuous Integration" ref="integration" />
     *   ...
     * </item>
     * 
* * @param model not null * @param locale not null * @see #generateMavenReports(Locale) * @since 1.1 */ protected void appendGeneratedReports( DocumentModel model, Locale locale ) { if ( !isIncludeReports() ) { return; } if ( getGeneratedMavenReports( locale ).isEmpty() ) { return; } final DocumentTOCItem documentTOCItem = new DocumentTOCItem(); documentTOCItem.setName( i18n.getString( "pdf-plugin", locale, "toc.project-info.item" ) ); documentTOCItem.setRef( "project-info" ); // see #generateMavenReports(Locale) List addedRef = new ArrayList<>( 4 ); List items = new ArrayList<>( 4 ); // append generated report defined as MavenReport for ( final MavenReport report : getGeneratedMavenReports( locale ) ) { final DocumentTOCItem reportItem = new DocumentTOCItem(); reportItem.setName( report.getName( locale ) ); reportItem.setRef( report.getOutputName() ); items.add( reportItem ); addedRef.add( report.getOutputName() ); } // append all generated reports from generated-site try { if ( generatedSiteDirectory.exists() ) { String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() ); List generatedDirs = FileUtils.getDirectoryNames( generatedSiteDirectory, "*", excludes, true ); if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) ) { generatedDirs = FileUtils.getFileNames( new File( generatedSiteDirectory, locale.getLanguage() ), "*", excludes, true ); } for ( final String generatedDir : generatedDirs ) { List generatedFiles = FileUtils.getFileNames( new File( generatedDir ), "**.*", excludes, false ); for ( final String generatedFile : generatedFiles ) { final String ref = generatedFile.substring( 0, generatedFile.lastIndexOf( '.' ) ); if ( !addedRef.contains( ref ) ) { final String title = getGeneratedDocumentTitle( new File( generatedDir, generatedFile ) ); if ( title != null ) { final DocumentTOCItem reportItem = new DocumentTOCItem(); reportItem.setName( title ); reportItem.setRef( ref ); items.add( reportItem ); } } } } } } catch ( IOException e ) { getLog().error( "IOException: " + e.getMessage() ); getLog().debug( e ); } // append to Toc documentTOCItem.setItems( items ); model.getToc().addItem( documentTOCItem ); } private void saveTOC( DocumentTOC toc, Locale locale ) { try { TocFileHelper.saveTOC( getWorkingDirectory(), toc, locale ); } catch ( IOException e ) { getLog().error( "Error while writing table of contents", e ); } } /** * Parse a generated Doxia file and returns its title. * * @param f not null * @return the xdoc file title or null if an error occurs. * @throws IOException if any * @since 1.1 */ private String getGeneratedDocumentTitle( final File f ) throws IOException { final IndexEntry entry = new IndexEntry( "index" ); final IndexingSink titleSink = new IndexingSink( entry ); try ( Reader reader = ReaderFactory.newXmlReader( f ) ) { doxia.parse( reader, f.getParentFile().getName(), titleSink ); } catch ( ParseException e ) { getLog().error( "ParseException: " + e.getMessage() ); getLog().debug( e ); return null; } catch ( ParserNotFoundException e ) { getLog().error( "ParserNotFoundException: " + e.getMessage() ); getLog().debug( e ); return null; } return titleSink.getTitle(); } /** * Parsing the generated report to see if it is correct or not. Log the error for the user. * * @param fullGoal not null * @param generatedReport not null * @param localReportName not null * @return true if Doxia is able to parse the generated report, false otherwise. * @since 1.1 */ private boolean isValidGeneratedReportXdoc( String fullGoal, File generatedReport, String localReportName ) { SinkAdapter sinkAdapter = new SinkAdapter(); try ( Reader reader = ReaderFactory.newXmlReader( generatedReport ) ) { doxia.parse( reader, "xdoc", sinkAdapter ); } catch ( ParseException e ) { String sb = EOL + "Error when parsing the generated report xdoc file: " + generatedReport.getAbsolutePath() + EOL + e.getMessage() + EOL + "You could:" + EOL + " * exclude all reports using -DincludeReports=false" + EOL + " * remove the " + fullGoal + " from the part. To not affect the site generation, " + "you could create a PDF profile." + EOL + "Ignoring the \"" + localReportName + "\" report in the PDF." + EOL; getLog().error( sb ); getLog().debug( e ); return false; } catch ( ParserNotFoundException e ) { getLog().error( "ParserNotFoundException: " + e.getMessage() ); getLog().debug( e ); return false; } catch ( IOException e ) { getLog().error( "IOException: " + e.getMessage() ); getLog().debug( e ); return false; } return true; } protected List getReports() throws MojoExecutionException { MavenReportExecutorRequest mavenReportExecutorRequest = new MavenReportExecutorRequest(); mavenReportExecutorRequest.setLocalRepository( localRepository ); mavenReportExecutorRequest.setMavenSession( session ); mavenReportExecutorRequest.setProject( project ); mavenReportExecutorRequest.setReportPlugins( getReportingPlugins() ); MavenReportExecutor mavenReportExecutor; try { mavenReportExecutor = (MavenReportExecutor) container.lookup( MavenReportExecutor.class.getName() ); } catch ( ComponentLookupException e ) { throw new MojoExecutionException( "could not get MavenReportExecutor component", e ); } return mavenReportExecutor.buildMavenReports( mavenReportExecutorRequest ); } /** * Get the report plugins from reporting section, adding if necessary (i.e. not excluded) * default reports (i.e. maven-project-info-reports) * * @return the effective list of reports * @since 1.5 */ private ReportPlugin[] getReportingPlugins() { List reportingPlugins = reporting.getPlugins(); // MSITE-806: add default report plugin like done in maven-model-builder DefaultReportingConverter boolean hasMavenProjectInfoReportsPlugin = false; for ( ReportPlugin plugin : reportingPlugins ) { if ( "org.apache.maven.plugins".equals( plugin.getGroupId() ) && "maven-project-info-reports-plugin".equals( plugin.getArtifactId() ) ) { hasMavenProjectInfoReportsPlugin = true; break; } } if ( !reporting.isExcludeDefaults() && !hasMavenProjectInfoReportsPlugin ) { ReportPlugin mpir = new ReportPlugin(); mpir.setArtifactId( "maven-project-info-reports-plugin" ); reportingPlugins.add( mpir ); } return reportingPlugins.toArray( new ReportPlugin[0] ); } /** * Write the given content to the given file. *
* Note: try also to fix the content due to some issues in * {@link org.apache.maven.reporting.AbstractMavenReport}. * * @param content the given content * @param toFile the report file * @throws IOException if any * @since 1.1 */ private static void writeGeneratedReport( String content, File toFile ) throws IOException { if ( StringUtils.isEmpty( content ) ) { return; } try ( Writer writer = WriterFactory.newXmlWriter( toFile ) ) { // see PdfSink#table() writer.write( StringUtils.replace( content, " locales, Locale defaultLocale ) { StringBuilder excludesLocales = new StringBuilder( FileUtils.getDefaultExcludesAsString() ); for ( final Locale locale : locales ) { if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) ) { excludesLocales.append( ",**/" ).append( locale.getLanguage() ).append( "/*" ); } } return excludesLocales.toString(); } /** * A sink to render a Maven report as a generated xdoc file, with some known workarounds. * * @since 1.1 */ private static class PdfXdocSink extends XdocSink implements org.codehaus.doxia.sink.Sink { protected PdfXdocSink( Writer writer ) { super( writer ); } /** {@inheritDoc} */ public void text( String text ) { // workaround to fix quotes introduced with MPIR-59 (then removed in MPIR-136) super.text( StringUtils.replace( text, "\u0092", "'" ) ); } public void tableRow() { // To be backward compatible: TODO add to XdocSink if ( !this.tableRows ) { tableRows( null, false ); } super.tableRow( null ); } } }