View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2012-2014 ForgeRock AS
15   */
16  
17  package org.forgerock.doc.maven.post;
18  
19  import org.apache.commons.io.FileUtils;
20  import org.apache.commons.io.FilenameUtils;
21  import org.apache.commons.io.IOUtils;
22  import org.apache.maven.plugin.MojoExecutionException;
23  import org.forgerock.doc.maven.AbstractDocbkxMojo;
24  import org.forgerock.doc.maven.utils.HtmlUtils;
25  import org.forgerock.doc.maven.utils.SyntaxHighlighterCopier;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.net.URL;
30  import java.util.HashMap;
31  
32  /**
33   * HTML post-processor for both single-page and chunked HTML formats.
34   */
35  public class Html {
36  
37      /**
38       * The Mojo that holds configuration and related methods.
39       */
40      private AbstractDocbkxMojo m;
41  
42      /**
43       * Constructor setting the Mojo that holds the configuration.
44       *
45       * @param mojo The Mojo that holds the configuration.
46       */
47      public Html(final AbstractDocbkxMojo mojo) {
48          m = mojo;
49  
50          outputDirectories = new String[2];
51          outputDirectories[0] = "";
52          outputDirectories[1] = File.separator + FilenameUtils.getBaseName(m.getDocumentSrcName());
53      }
54  
55      /**
56       * Post-processes HTML formats.
57       *
58       * @throws MojoExecutionException Failed to post-process HTML.
59       */
60      public void execute() throws MojoExecutionException {
61          // Add JavaScript for manipulating HTML content.
62          addScript();
63  
64  
65          // Add SyntaxHighlighter files.
66          final File htmlDir = new File(m.getDocbkxOutputDirectory(), "html");
67          final String chunkDirName = FilenameUtils.getBaseName(m.getDocumentSrcName());
68  
69          String[] outputDirectories = new String[2 * m.getDocNames().size()];
70  
71          int i = 0;
72          for (final String docName : m.getDocNames()) {
73  
74              final File docDir = new File(htmlDir, docName);
75  
76              // Examples:
77              // ${project.build.directory}/docbkx/html/my-book
78              outputDirectories[i] = docDir.getPath();
79              ++i;
80  
81              // ${project.build.directory}/docbkx/html/my-book/index
82              outputDirectories[i] = new File(docDir, chunkDirName).getPath();
83              ++i;
84          }
85  
86          SyntaxHighlighterCopier copier =
87                  new SyntaxHighlighterCopier(outputDirectories);
88          try {
89              copier.copy();
90          } catch (IOException e) {
91              throw new MojoExecutionException(
92                      "Failed to copy files: " + e.getMessage(), e);
93          }
94  
95  
96          // Edit the HTML for publication.
97          editBuiltHtml(htmlDir.getPath());
98  
99  
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 }