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 2014-2015 ForgeRock AS.
015 */
016package org.forgerock.doc.maven.utils;
017
018import freemarker.template.Configuration;
019import freemarker.template.Template;
020import freemarker.template.TemplateException;
021import freemarker.template.TemplateExceptionHandler;
022import org.apache.commons.io.FileUtils;
023import org.apache.commons.io.FilenameUtils;
024import org.apache.maven.plugin.MojoExecutionException;
025import org.forgerock.doc.maven.AbstractDocbkxMojo;
026import org.forgerock.doc.maven.utils.helper.NameMethod;
027
028import java.io.File;
029import java.io.FileOutputStream;
030import java.io.IOException;
031import java.io.OutputStreamWriter;
032import java.io.Writer;
033import java.util.ArrayList;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037import java.util.Set;
038
039/**
040 * Build olink target database files.
041 *
042 * <br>
043 *
044 * O(utside)links are a DocBook convention for describing links between XML documents.
045 * For background, read Bob Stayton's explanation of
046 * <a href="http://www.sagehill.net/docbookxsl/Olinking.html"
047 * >Olinking between documents</a>.
048 *
049 * <br>
050 *
051 * The olink resolution mechanism described therein and used here
052 * depends on the relative locations of output files.
053 */
054public final class OLinkUtils {
055
056    /**
057     * Construct an olink target database document, except for chunked HTML.
058     *
059     * <br>
060     *
061     * The document is an XML file that wraps target data documents.
062     * On a first pass, the docbkx-tools plugin applies the DocBook stylesheets
063     * with settings to generate such target data documents
064     * for each top-level DocBook document in a build.
065     * On a second pass, the docbkx-tools plugin applies the DocBook stylesheets
066     * with settings to resolve olinks in output documents.
067     * During the second pass, the docbkx-tools plugin requires
068     * this wrapper document to handle the target data documents
069     * with the correct relative locations.
070     *
071     * <br>
072     *
073     * You pass the path to the target database document to docbkx-tools
074     * as the value of the {@code &lt;targetDatabaseDocument>} parameter.
075     *
076     * @param file              The file in which to write the target database.
077     * @param format            Output format such as {@code pdf}, {@code html}.
078     * @param mojo              Mojo with configuration information about the project.
079     *
080     * @throws IOException              Failed to write to the target database file.
081     * @throws MojoExecutionException   Failed to read document names from the mojo.
082     */
083    public static void createTargetDatabase(File file,
084                                            String format,
085                                            AbstractDocbkxMojo mojo)
086            throws IOException, MojoExecutionException {
087        createTargetDatabase(file, format, mojo, false);
088    }
089
090    /**
091     * Construct an olink target database document for chunked HTML.
092     *
093     * <br>
094     *
095     * The document is an XML file that wraps target data documents.
096     * On a first pass, the docbkx-tools plugin applies the DocBook stylesheets
097     * with settings to generate such target data documents
098     * for each top-level DocBook document in a build.
099     * On a second pass, the docbkx-tools plugin applies the DocBook stylesheets
100     * with settings to resolve olinks in output documents.
101     * During the second pass, the docbkx-tools plugin requires
102     * this wrapper document to handle the target data documents
103     * with the correct relative locations.
104     *
105     * <br>
106     *
107     * You pass the path to the target database document to docbkx-tools
108     * as the value of the {@code &lt;targetDatabaseDocument>} parameter.
109     *
110     * @param file              The file in which to write the target database.
111     * @param format            Output format such as {@code pdf}, {@code html}.
112     * @param mojo              Mojo with configuration information about the project.
113     * @param isChunkedHtml     Set {@code true} for chunked HTML.
114     *
115     * @throws IOException              Failed to write to the target database file.
116     * @throws MojoExecutionException   Failed to read document names from the mojo.
117     */
118    public static void createTargetDatabase(File file,
119                                            String format,
120                                            AbstractDocbkxMojo mojo,
121                                            boolean isChunkedHtml)
122            throws IOException, MojoExecutionException {
123
124        createTargetDatabase(
125                file,
126                mojo.getBuildDirectory().getAbsolutePath(),
127                mojo.getDocNames(),
128                FilenameUtils.getBaseName(mojo.getDocumentSrcName()),
129                format,
130                isChunkedHtml,
131                mojo.getProjectName(),
132                mojo.getProjectVersion());
133    }
134
135    /**
136     * Construct an olink target database document.
137     *
138     * <br>
139     *
140     * The document is an XML file that wraps target data documents.
141     * On a first pass, the docbkx-tools plugin applies the DocBook stylesheets
142     * with settings to generate such target data documents
143     * for each top-level DocBook document in a build.
144     * On a second pass, the docbkx-tools plugin applies the DocBook stylesheets
145     * with settings to resolve olinks in output documents.
146     * During the second pass, the docbkx-tools plugin requires
147     * this wrapper document to handle the target data documents
148     * with the correct relative locations.
149     *
150     * <br>
151     *
152     * You pass the path to the target database document to docbkx-tools
153     * as the value of the {@code &lt;targetDatabaseDocument>} parameter.
154     *
155     * @param file              The file in which to write the target database.
156     * @param basePath          Full path to parent of docbkx directory,
157     *                          such as {@code /path/to/target}.
158     * @param docNames          Names of documents in the set.
159     * @param documentSrcName   Top-level DocBook XML source document base name.
160     * @param format            Output format such as {@code pdf}, {@code html}.
161     * @param isChunked         Set {@code true} for chunked HTML.
162     * @param projectName       Name of the current project, such as {@code OpenAM}.
163     * @param projectVersion    Version for the current project. Can be empty.
164     *
165     * @throws IOException  Failed to write to the target database file.
166     */
167    private static void createTargetDatabase(File file,
168                                            String basePath,
169                                            Set<String> docNames,
170                                            String documentSrcName,
171                                            String format,
172                                            boolean isChunked,
173                                            String projectName,
174                                            String projectVersion)
175            throws IOException {
176
177        // This implementation uses FreeMarker templates.
178        // For details, see http://freemarker.org.
179
180        // FreeMarker templates require a configuration.
181        Configuration configuration = getConfiguration();
182
183        // FreeMarker templates are data driven. The model here is a map.
184        Map<String, Object> map = getModel(
185                basePath,
186                docNames,
187                documentSrcName,
188                format,
189                isChunked,
190                projectName,
191                projectVersion);
192
193        // Apply the FreeMarker template using the data.
194        Template template = configuration.getTemplate("olinkdb.ftl");
195        FileOutputStream out = FileUtils.openOutputStream(file);
196        Writer writer = new OutputStreamWriter(out);
197        try {
198            template.process(map, writer);
199        } catch (TemplateException e) {
200            throw new IOException("Failed to write template", e);
201        } finally {
202            writer.close();
203            out.close();
204        }
205    }
206
207    private static Configuration configuration;
208
209    /**
210     * Get a FreeMarker configuration for applying templates.
211     *
212     * @return              A FreeMarker configuration.
213     */
214    private static Configuration getConfiguration() {
215        if (configuration != null) {
216            return configuration;
217        }
218
219        configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
220        configuration.setClassForTemplateLoading(OLinkUtils.class, "/templates");
221        configuration.setDefaultEncoding("UTF-8");
222        configuration.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
223
224        return configuration;
225    }
226
227    /**
228     * Get the FreeMaker data for use when applying templates.
229     *
230     * @param basePath          Full path to parent of docbkx directory,
231     *                          such as {@code /path/to/target}.
232     * @param docNames          Names of documents in the set.
233     * @param documentSrcName   Top-level DocBook XML source document base name.
234     * @param format            Output format such as {@code pdf}, {@code html}.
235     * @param isChunked         Set {@code true} for chunked HTML.
236     * @param projectName       Name of the current project, such as {@code OpenAM}.
237     * @param projectVersion    Version for the current project. Can be empty.
238     *
239     * @return                  FreeMarker data for use when applying templates.
240     */
241    private static Map<String, Object> getModel(String basePath,
242                                                Set<String> docNames,
243                                                String documentSrcName,
244                                                String format,
245                                                boolean isChunked,
246                                                String projectName,
247                                                String projectVersion) {
248
249        /*
250         baseName:        base name for document source, such as index
251         basePath:        base of absolute path to target data file
252         docNames:        list of document names such as reference and admin-guide
253         extension:       output file extension such as html, pdf, or xhtml
254         format:          output format such as xhtml5 or epub
255         isChunked:       whether the output format is chunked HTML
256         name():          wrapper to call NameUtils.renameDoc()
257         projectName:     project name such as OpenAM
258         projectVersion:  project version such as 3.1.0
259         type:            output file type such as html, pdf, or xhtml
260         */
261
262        Map<String, Object> map = new HashMap<String, Object>();
263
264        map.put("baseName", documentSrcName);
265        map.put("basePath", basePath);
266
267        List<String> docs = new ArrayList<String>(docNames.size());
268        docs.addAll(docNames);
269        map.put("docNames", docs);
270
271        String extension = format;
272        if (format.equals("xhtml5")) {
273            format = "xhtml";
274            extension = "xhtml";
275        }
276        if (format.equals("bootstrap")) {
277            extension = "html";
278        }
279        map.put("extension", extension);
280
281        map.put("format", format);
282        map.put("isChunked", isChunked);
283
284        map.put("name", new NameMethod());
285
286        map.put("projectName", projectName);
287        map.put("projectVersion", projectVersion);
288
289        String type = format;
290        if (format.equals("pdf") || format.equals("rtf")) {
291            type = "fo";
292        }
293        if (format.equals("xhtml")) {
294            type = "xhtml";
295        }
296        if (format.equals("bootstrap")) {
297            type = "html";
298        }
299        map.put("type", type);
300
301        return map;
302    }
303
304    private OLinkUtils() {
305        // Not used.
306    }
307}