001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2012-2015 ForgeRock AS.
015 */
016
017package org.forgerock.doc.maven.build;
018
019import org.apache.commons.io.FileUtils;
020import org.apache.commons.io.FilenameUtils;
021import org.apache.maven.plugin.MojoExecutionException;
022import org.forgerock.doc.maven.AbstractDocbkxMojo;
023import org.forgerock.doc.maven.pre.Fop;
024import org.forgerock.doc.maven.utils.NameUtils;
025import org.forgerock.doc.maven.utils.OLinkUtils;
026import org.twdata.maven.mojoexecutor.MojoExecutor;
027
028import java.io.File;
029import java.io.IOException;
030import java.util.ArrayList;
031import java.util.Iterator;
032
033/**
034 * Build FO output formats.
035 */
036public class Fo {
037
038    /**
039     * The Mojo that holds configuration and related methods.
040     */
041    private AbstractDocbkxMojo m;
042
043    /**
044     * The Executor to run docbkx-tools.
045     */
046    private final Executor executor;
047
048    /**
049     * Constructor setting the Mojo that holds the configuration.
050     *
051     * @param mojo The Mojo that holds the configuration.
052     */
053    public Fo(final AbstractDocbkxMojo mojo) {
054        m = mojo;
055        this.executor = new Executor();
056    }
057
058    /**
059     * Supported FO formats include "pdf" and "rtf".
060     */
061    private String format = "pdf";
062
063    /**
064     * Get the format.
065     * Defaults to PDF unless the format has been set to RTF.
066     *
067     * @return The format, either "pdf" or "rtf".
068     */
069    public String getFormat() {
070        return format;
071    }
072
073    /**
074     * Set the format to PDF or RTF.
075     * Defaults to PDF unless RTF is specified (case does not matter).
076     *
077     * @param format Either {@code pdf} or {@code rtf}.
078     */
079    public void setFormat(final String format) {
080        if (format.equalsIgnoreCase("rtf")) {
081            this.format = "rtf";
082        } else {
083            this.format = "pdf";
084        }
085    }
086
087    /**
088     * Build documents from DocBook XML sources.
089     *
090     * @throws MojoExecutionException Failed to build output.
091     */
092    public void execute() throws MojoExecutionException {
093        executor.prepareOlinkDB();
094        executor.build();
095    }
096
097    /**
098     * Get absolute path to an Olink target database XML document
099     * that points to the individual generated Olink DB files, for FO (PDF, RTF).
100     *
101     * @return Absolute path to the file.
102     * @throws MojoExecutionException Could not write target DB file.
103     */
104    final String getTargetDB() throws MojoExecutionException {
105        File targetDB = new File(m.getBuildDirectory(), "olinkdb-" + getFormat() + ".xml");
106
107        try {
108            OLinkUtils.createTargetDatabase(targetDB, getFormat(), m);
109        } catch (Exception e) {
110            throw new MojoExecutionException(
111                    "Failed to write link target database: " + targetDB.getPath(), e);
112        }
113
114        return targetDB.getPath();
115    }
116
117    /**
118     * Enclose methods to run plugins.
119     */
120    class Executor extends MojoExecutor {
121
122        // Absolute path to Olink target database XML document.
123        private String targetDatabaseDocument;
124
125        /**
126         * Get the olink target database XML document path.
127         *
128         * @return Absolute path to the file.
129         * @throws MojoExecutionException Could not write target DB file.
130         */
131        String getTargetDatabaseDocument() throws MojoExecutionException {
132            // If it has not been set yet, then set it now.
133            if (targetDatabaseDocument == null || targetDatabaseDocument.isEmpty()) {
134                targetDatabaseDocument = getTargetDB();
135            }
136
137            return targetDatabaseDocument;
138        }
139
140        /**
141         * Prepare olink target database from DocBook XML sources.
142         *
143         * @throws MojoExecutionException Failed to build target database.
144         */
145        void prepareOlinkDB() throws MojoExecutionException {
146
147            // Due to https://code.google.com/p/docbkx-tools/issues/detail?id=112
148            // RTF generation does not work with docbkx-tools 2.0.15 or 2.0.16.
149            // Rather than try also to fix olinks in RTF,
150            // skip this until that issue is resolved.
151            if (getFormat().equalsIgnoreCase("rtf")) {
152                return;
153            }
154
155            for (String docName : m.getDocNames()) {
156                ArrayList<MojoExecutor.Element> cfg = new ArrayList<MojoExecutor.Element>();
157                cfg.addAll(m.getBaseConfiguration());
158                cfg.add(element(name("xincludeSupported"), m.isXincludeSupported()));
159                cfg.add(element(name("sourceDirectory"), m.path(m.getDocbkxModifiableSourcesDirectory())));
160                cfg.add(element(name("fop1Extensions"), "1"));
161                cfg.add(element(name("fopLogLevel"), m.getFopLogLevel()));
162                cfg.add(element(name("collectXrefTargets"), "yes"));
163                if (getFormat().equalsIgnoreCase("pdf")) {
164                    cfg.add(element(name("insertOlinkPdfFrag"), "1"));
165                }
166                cfg.add(element(name("includes"), docName + "/" + m.getDocumentSrcName()));
167                cfg.add(element(name("currentDocid"), docName));
168                cfg.add(element(name("targetDatabaseDocument"), getTargetDatabaseDocument()));
169                cfg.add(element(name("targetDirectory"), m.path(m.getDocbkxOutputDirectory()) + "/" + getFormat()));
170                cfg.add(element(name("targetsFilename"), m.getDocumentSrcName() + ".fo.target.db"));
171
172                executeMojo(
173                        plugin(
174                                groupId("com.agilejava.docbkx"),
175                                artifactId("docbkx-maven-plugin"),
176                                version(m.getDocbkxVersion())),
177                        goal("generate-" + getFormat()),
178                        configuration(cfg.toArray(new Element[cfg.size()])),
179                        executionEnvironment(m.getProject(), m.getSession(), m.getPluginManager())
180                );
181
182                File outputDir = FileUtils.getFile(
183                        m.getBaseDir(), "target", "docbkx", getFormat(), docName);
184
185                // The following output directory should be where the files are
186                // for versions of docbkx-tools that honor <targetsFilename>.
187                if (!outputDir.exists()) {
188                    outputDir = new File(m.getDocbkxOutputDirectory(),
189                            getFormat() + File.separator + docName);
190                }
191
192                try {
193                    String[] extensions = {"fo", getFormat()};
194                    Iterator<File> files =
195                            FileUtils.iterateFiles(outputDir, extensions, true);
196                    while (files.hasNext()) {
197                        FileUtils.forceDelete(files.next());
198                    }
199                } catch (IOException e) {
200                    throw new MojoExecutionException(
201                            "Cannot delete a file: " + e.getMessage());
202                }
203            }
204        }
205
206        /**
207         * Build documents from DocBook XML sources.
208         *
209         * @throws MojoExecutionException Failed to build the output.
210         */
211        void build() throws MojoExecutionException {
212
213            for (String docName : m.getDocNames()) {
214                ArrayList<MojoExecutor.Element> cfg = new ArrayList<MojoExecutor.Element>();
215                cfg.addAll(m.getBaseConfiguration());
216                cfg.add(element(name("foCustomization"), m.path(m.getFoCustomization())));
217                cfg.add(element(name("fop1Extensions"), "1"));
218
219                if (getFormat().equalsIgnoreCase("pdf")) {
220                    cfg.add(element(name("insertOlinkPdfFrag"), "1"));
221                }
222
223                // Due to https://code.google.com/p/docbkx-tools/issues/detail?id=112
224                // RTF generation does not work with docbkx-tools 2.0.15 or 2.0.16.
225                // New features like <fopLogLevel> cannot be used with RTF for now.
226                if (!getFormat().equalsIgnoreCase("rtf")) {
227                    cfg.add(element(name("fopLogLevel"), m.getFopLogLevel()));
228                }
229
230                // Due to https://code.google.com/p/docbkx-tools/issues/detail?id=112
231                // skip olink resolution with RTF for now.
232                if (!getFormat().equalsIgnoreCase("rtf")) {
233                    cfg.add(element(name("targetDatabaseDocument"), getTargetDatabaseDocument()));
234                }
235
236                cfg.add(element(name("targetDirectory"), m.path(m.getDocbkxOutputDirectory()) + "/" + getFormat()));
237
238                final String fontDir = m.path(m.getFontsDirectory());
239                cfg.add(Fop.getFontsElement(fontDir));
240
241                cfg.add(element(name("includes"), docName + "/" + m.getDocumentSrcName()));
242                cfg.add(element(name("currentDocid"), docName));
243
244                // Due to https://code.google.com/p/docbkx-tools/issues/detail?id=112
245                // if the format is RTF, stick with 2.0.14 for now.
246                String docbkxVersion = m.getDocbkxVersion();
247                if (format.equalsIgnoreCase("rtf")) {
248                    docbkxVersion = "2.0.14";
249                }
250
251                executeMojo(
252                        plugin(
253                                groupId("com.agilejava.docbkx"),
254                                artifactId("docbkx-maven-plugin"),
255                                version(docbkxVersion),
256                                dependencies(
257                                        dependency(
258                                                groupId("net.sf.offo"),
259                                                artifactId("fop-hyph"),
260                                                version(m.getFopHyphVersion())))),
261                        goal("generate-" + getFormat()),
262                        configuration(cfg.toArray(new Element[cfg.size()])),
263                        executionEnvironment(m.getProject(), m.getSession(), m.getPluginManager()));
264
265                // Avoid each new document overwriting the last.
266                File file = FileUtils.getFile(
267                        m.getDocbkxOutputDirectory(), getFormat(),
268                        FilenameUtils.getBaseName(m.getDocumentSrcName()) + "." + getFormat());
269
270                try {
271                    NameUtils.renameDocument(file, docName, m.getProjectName());
272                } catch (IOException e) {
273                    throw new MojoExecutionException("Failed to rename document", e);
274                }
275            }
276        }
277    }
278}