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-2014 ForgeRock AS
015 */
016
017package org.forgerock.doc.maven.post;
018
019import org.apache.commons.io.FileUtils;
020import org.apache.commons.io.FilenameUtils;
021import org.apache.commons.io.IOUtils;
022import org.apache.maven.plugin.MojoExecutionException;
023import org.forgerock.doc.maven.AbstractDocbkxMojo;
024import org.forgerock.doc.maven.utils.HtmlUtils;
025import org.forgerock.doc.maven.utils.SyntaxHighlighterCopier;
026
027import java.io.File;
028import java.io.IOException;
029import java.net.URL;
030import java.util.HashMap;
031
032/**
033 * HTML post-processor for both single-page and chunked HTML formats.
034 */
035public class Html {
036
037    /**
038     * The Mojo that holds configuration and related methods.
039     */
040    private AbstractDocbkxMojo m;
041
042    /**
043     * Constructor setting the Mojo that holds the configuration.
044     *
045     * @param mojo The Mojo that holds the configuration.
046     */
047    public Html(final AbstractDocbkxMojo mojo) {
048        m = mojo;
049
050        outputDirectories = new String[2];
051        outputDirectories[0] = "";
052        outputDirectories[1] = File.separator + FilenameUtils.getBaseName(m.getDocumentSrcName());
053    }
054
055    /**
056     * Post-processes HTML formats.
057     *
058     * @throws MojoExecutionException Failed to post-process HTML.
059     */
060    public void execute() throws MojoExecutionException {
061        // Add JavaScript for manipulating HTML content.
062        addScript();
063
064
065        // Add SyntaxHighlighter files.
066        final File htmlDir = new File(m.getDocbkxOutputDirectory(), "html");
067        final String chunkDirName = FilenameUtils.getBaseName(m.getDocumentSrcName());
068
069        String[] outputDirectories = new String[2 * m.getDocNames().size()];
070
071        int i = 0;
072        for (final String docName : m.getDocNames()) {
073
074            final File docDir = new File(htmlDir, docName);
075
076            // Examples:
077            // ${project.build.directory}/docbkx/html/my-book
078            outputDirectories[i] = docDir.getPath();
079            ++i;
080
081            // ${project.build.directory}/docbkx/html/my-book/index
082            outputDirectories[i] = new File(docDir, chunkDirName).getPath();
083            ++i;
084        }
085
086        SyntaxHighlighterCopier copier =
087                new SyntaxHighlighterCopier(outputDirectories);
088        try {
089            copier.copy();
090        } catch (IOException e) {
091            throw new MojoExecutionException(
092                    "Failed to copy files: " + e.getMessage(), e);
093        }
094
095
096        // Edit the HTML for publication.
097        editBuiltHtml(htmlDir.getPath());
098
099
100        // Optionally fix links to arbitrary resources in chunked HTML.
101        if (m.doCopyResourceFiles() && m.getResourcesDirectory().exists()) {
102
103            final String baseName = FilenameUtils.getBaseName(m.getResourcesDirectory().getPath());
104
105            for (final String docName : m.getDocNames()) {
106
107                final File docDir = new File(htmlDir, docName);
108
109                try {
110                    HtmlUtils.fixResourceLinks(new File(docDir, chunkDirName).getPath(), baseName);
111                } catch (IOException e) {
112                    throw new MojoExecutionException("Failed to update resource links", e);
113                }
114            }
115        }
116    }
117
118    /**
119     * Directories where scripts and CSS are to be added.
120     */
121    private String[] outputDirectories;
122
123    /**
124     * Add JavaScript to include in HTML in each document source directory.
125     * See <a href="http://docbook.sourceforge.net/release/xsl/current/doc/html/html.script.html"
126     * >html.script</a> for details.
127     *
128     * @throws MojoExecutionException Failed to add script.
129     */
130    private void addScript() throws MojoExecutionException {
131
132        final URL scriptUrl = getClass().getResource("/js/" + m.getJavaScriptFileName());
133        String scriptString;
134        try {
135            scriptString = IOUtils.toString(scriptUrl);
136        } catch (IOException ie) {
137            throw new MojoExecutionException("Failed to read " + scriptUrl, ie);
138        }
139
140        if (scriptString != null) {
141            scriptString = scriptString.replace("PROJECT_NAME", m.getProjectName().toLowerCase());
142            scriptString = scriptString.replace("PROJECT_VERSION", m.getProjectVersion());
143            scriptString = scriptString.replace("LATEST_JSON", m.getLatestJson());
144            scriptString = scriptString.replace("DOCS_SITE", m.getDocsSite());
145            scriptString = scriptString.replace("EOSL_JSON", m.getEoslJson());
146        } else {
147            throw new MojoExecutionException(scriptUrl + " was empty");
148        }
149
150        // The html.script parameter should probably take URLs.
151        // When local files are referenced,
152        // the DocBook XSL stylesheets do not copy the .js files.
153        // Instead the files must be copied to the output directories.
154
155        for (final String outputDirectory : outputDirectories) {
156
157            for (final String docName : m.getDocNames()) {
158
159                final File parent = FileUtils.getFile(
160                        m.getDocbkxOutputDirectory(), "html", docName + outputDirectory);
161                final File scriptFile = new File(parent, m.getJavaScriptFileName());
162
163                try {
164                    FileUtils.writeStringToFile(scriptFile, scriptString, "UTF-8");
165                } catch (IOException ie) {
166                    throw new MojoExecutionException(
167                            "Failed to write to " + scriptFile.getPath(), ie);
168                }
169            }
170        }
171    }
172
173    /**
174     * Edit build single-page and chunked HTML.
175     *
176     * <p>
177     *
178     * The HTML built by docbkx-tools does not currently include the following,
179     * which this method adds.
180     *
181     * <ul>
182     * <li>A DOCTYPE declaration (needed by Internet Explorer to interpret CSS</li>
183     * <li>A meta tag for controlling crawling and indexing</li>
184     * <li>JavaScript to call the SyntaxHighlighter brushes</li>
185     * <li>A favicon link</li>
186     * <li>A paragraph about logging issues with a link to JIRA</li>
187     * <li>JavaScript used by Google Analytics</li>
188     * </ul>
189     *
190     * @param htmlDir Directory under which to find HTML output
191     * @throws MojoExecutionException Something went wrong when updating HTML.
192     */
193    final void editBuiltHtml(final String htmlDir) throws MojoExecutionException {
194        try {
195            HashMap<String, String> replacements = new HashMap<String, String>();
196
197            String doctype = IOUtils.toString(
198                    getClass().getResourceAsStream("/starthtml-doctype.txt"), "UTF-8");
199            replacements.put("<html>", doctype);
200
201            // See https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
202            String robots = "<head>" + System.getProperty("line.separator")
203                    + IOUtils.toString(getClass().getResourceAsStream("/robots.txt"), "UTF-8");
204            replacements.put("<head>", robots);
205
206            String favicon = IOUtils.toString(
207                    getClass().getResourceAsStream("/endhead-favicon.txt"), "UTF-8");
208            favicon = favicon.replace("FAVICON-LINK", m.getFaviconLink());
209            replacements.put("</head>", favicon);
210
211            String linkToJira = getLinkToJira();
212
213            String gascript = IOUtils.toString(
214                    getClass().getResourceAsStream("/endbody-ga.txt"), "UTF-8");
215            gascript = gascript.replace("ANALYTICS-ID", m.getGoogleAnalyticsId());
216            replacements.put("</body>", linkToJira + "\n" + gascript);
217
218            HtmlUtils.updateHtml(htmlDir, replacements);
219        } catch (IOException e) {
220            throw new MojoExecutionException(
221                    "Failed to update output HTML correctly: " + e.getMessage());
222        }
223    }
224
225    /**
226     * Return a &lt;p&gt; containing a link to log a bug in JIRA, depending on the project.
227     * The string is not localized.
228     *
229     * @return &lt;p&gt; containing a link to log a bug in JIRA.
230     */
231    private String getLinkToJira() {
232        String link = "<p>&nbsp;</p><div id=\"footer\"><p>Something wrong on this page? "
233                + "<a href=\"JIRA-URL\">Log a documentation bug.</a></p></div>";
234
235        // https://confluence.atlassian.com/display/JIRA/Creating+Issues+via+direct+HTML+links
236        String jiraURL = "https://bugster.forgerock.org/jira/secure/CreateIssueDetails!init.jspa";
237
238        if (m.getProjectName().equalsIgnoreCase("OpenAM")) {
239            jiraURL += "?pid=10000&components=10007&issuetype=1";
240        }
241        if (m.getProjectName().equalsIgnoreCase("OpenDJ")) {
242            jiraURL += "?pid=10040&components=10132&issuetype=1";
243        }
244        if (m.getProjectName().equalsIgnoreCase("OpenICF")) {
245            jiraURL += "?pid=10041&components=10170&issuetype=1";
246        }
247        if (m.getProjectName().equalsIgnoreCase("OpenIDM")) {
248            jiraURL += "?pid=10020&components=10164&issuetype=1";
249        }
250        if (m.getProjectName().equalsIgnoreCase("OpenIG")) {
251            jiraURL += "?pid=10060&components=10220&issuetype=1";
252        }
253        if (m.getProjectName().equalsIgnoreCase("ForgeRock")) { // Just testing
254            jiraURL += "?pid=10010&issuetype=1";
255        }
256
257        if (!jiraURL.contains("pid")) {
258            link = "";
259        } else {
260            link = link.replaceFirst("JIRA-URL", jiraURL);
261        }
262        return link;
263    }
264}