package controllers;
import static play.modules.pdf.PDF.renderPDF;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jj.play.org.eclipse.mylyn.wikitext.core.parser.MarkupParser;
import jj.play.org.eclipse.mylyn.wikitext.textile.core.TextileLanguage;
import org.apache.commons.lang.time.DurationFormatUtils;
import org.apache.commons.lang.time.StopWatch;
import play.Logger;
import play.Play;
import play.libs.IO;
import play.modules.pdf.PDF.Options;
import play.mvc.Controller;
import play.mvc.Finally;
/**
* Defines several actions that allow to print the Play! documentation as PDF. To enable additional log messages from
* the PDF module, just use the following parameter when starting the application:
*
* play run -Dxr.util-logging.loggingEnabled=true
*
* The PDF module help is available in the 'Installed modules' section in the sidebar on:
*
* http://localhost:9000/@documentation/home
*
* Or directly at:
*
* http://localhost:9000/@documentation/modules/pdf/home
*
*/
public class Application extends Controller {
private static StopWatch watch;
/**
* Renders the specified image. This action will be triggered for example for the following url:
*
* http://localhost:9000/images/help
*
* It will return the 'help.png' image file from the Play! documentation.
*
* @param id identifier (i.e. filename) of the image
* @throws IOException in case of error
* @throws MalformedURLException in case of error
*/
public static void image(String id) throws MalformedURLException, IOException {
File file = new File("/" + Play.frameworkPath + "/documentation/images/" + id + ".png");
if (file.exists()) {
Logger.debug("Serving image at '%s'", file.getAbsolutePath());
response.setContentTypeIfNotSet("image/png");
renderBinary(file.toURI().toURL().openStream());
} else {
Logger.error("Unable to serve missing image at '%s'", file.getAbsolutePath());
notFound();
}
}
/**
* Displays the welcome page.
*/
public static void index() {
Set<String> modules = Play.modules.keySet();
render(modules);
}
/**
* Generates a PDF document for the specified documentation section.
*
* @param id identifier (i.e. filename) of the documentation section
* @param html optionally specifies whether the result of this action should be displayed as HTML (to ease debugging) or not
* @throws IOException in case of error
*/
public static void generate(String id, Boolean html) throws IOException {
notFoundIfNull(id);
Logger.info("Starting generation of documentation section '%s'", id);
// Builds the HTML for the requested Textile page
String textile = getTextile(id);
String content = toHTML(textile);
String title = getTitle(textile);
// Handles the special case of the homepage which will trigger the generation of the whole documentation
if (id.equals("home")) {
// Adds each page linked from any numbered list on the homepage, like for example:
//
// # "Installation guide":install
//
final Pattern pattern = Pattern.compile("^#\\s*\"[^\"]+\":([^#\\s]+)", Pattern.MULTILINE);
final Matcher matcher = pattern.matcher(textile);
while (matcher.find()) {
id = matcher.group(1);
if (!id.startsWith("http://") && !id.startsWith("/")) {
content += toHTML(getTextile(id));
}
}
}
if ((html != null) && html) {
render(content, title);
} else {
watch = new StopWatch();
watch.start();
Options options = new Options();
options.FOOTER = "<span style='font-size: 8pt;font-style:italic;color: #666;'>Generated with Play! Framework PDF Module</span><span style=\" color: rgb(141, 172, 38);float: right;font-size: 8pt;\">Page <pagenumber>/<pagecount></span>";
options.filename = id + ".pdf";
renderPDF(content, options, title);
}
}
/**
* Loads the Textile markup of the specified documentation section.
*
* @param id identifier (i.e. filename) of the documentation section
* @return the Textile markup of the specified documentation section
* @throws IOException in case of error
*/
private static String getTextile(String id) throws IOException {
String textile = "";
File file = new File(Play.frameworkPath + "/documentation/manual/" + id + ".textile");
if (file.exists()) {
textile = IO.readContentAsString(file);
Logger.debug("Loaded documentation section '%s' in '%s' successfully", id, file.getAbsolutePath());
} else {
Logger.error("Unable to load documentation section '%s' in '%s'", id, file.getAbsolutePath());
}
return textile;
}
/**
* Retrieves the main title from the specified Textile markup.
*
* @param textile content formated with the Textile syntax
* @return the main title from the specified Textile markup
*/
private static String getTitle(String textile) {
if (!textile.isEmpty()) {
return textile.split("\n")[0].substring(3).trim();
} else {
return "";
}
}
/**
* Logs a message as soon as the generation of a PDF document is finished.
*/
@Finally(only = {"generate"})
private static void log() {
if (watch != null) {
watch.stop();
Logger.info("Generated documentation as PDF successfully in %s", DurationFormatUtils.formatDurationWords(watch.getTime(), true, true));
watch = null;
} else {
Logger.info("Generated documentation as HTML successfully");
}
}
/**
* Converts a Textile markup into its HTML counterpart.
*
* @param textile content formated with the Textile syntax
* @return the corresponding HTML markup
*/
private static String toHTML(String textile) {
// Converts the Textile markup into an HTML page
String html = new MarkupParser(new TextileLanguage()).parseToHtml(textile);
// Makes sure image paths are absolute, as otherwise the wrong route will be called. The following Textile markup:
//
// !images/help!
//
// Will indeed generate something like this:
//
// <img border="0" src="images/help"/>
//
html = html.replaceAll("src=\"images/", "src=\"/images/");
// Extracts only the body of this HTML page
return html.substring(html.indexOf("<body>") + 6, html.lastIndexOf("</body>"));
}
}