DocUtil.java

/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License
 * at:
 *
 *   http://opensource.org/licenses/ecl2.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package org.opencastproject.util.doc;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;

import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * This provides methods for handling documentation generation The is mainly for generating REST documentation but it
 * could be used for other things as well
 *
 * @see DocData
 */
public final class DocUtil {

  private static final Logger logger = LoggerFactory.getLogger(DocUtil.class);

  private static Configuration freemarkerConfig; // reusable template processor

  static {
    // initialize the freemarker template engine
    reset();
  }

  /** Disable construction of this utility class */
  private DocUtil() {
  }

  public static void reset() {
    freemarkerConfig = null;
    // static initializer
    freemarkerConfig = new Configuration();
    freemarkerConfig.setObjectWrapper(new DefaultObjectWrapper());
    freemarkerConfig.clearTemplateCache();
    logger.debug("Created new freemarker template processor for DocUtils");
  }

  /**
   * Handles the replacement of the variable strings within textual templates and also allows the setting of variables
   * for the control of logical branching within the text template as well<br>
   * Uses and expects freemarker (http://freemarker.org/) style templates (that is using ${name} as the marker for a
   * replacement)<br>
   * NOTE: These should be compatible with Velocity (http://velocity.apache.org/) templates if you use the formal
   * notation (formal: ${variable}, shorthand: $variable)
   *
   * @param templateName
   *          this is the key to cache the template under
   * @param textTemplate
   *          a freemarker/velocity style text template, cannot be null or empty string
   * @param data
   *          a set of replacement values which are in the map like so:<br>
   *          key =&gt; value (String =&gt; Object)<br>
   *          "username" =&gt; "aaronz"<br>
   * @return the processed template
   */
  public static String processTextTemplate(String templateName, String textTemplate, Map<String, Object> data) {
    if (freemarkerConfig == null) {
      throw new IllegalStateException("freemarkerConfig is not initialized");
    }
    if (StringUtils.isEmpty(templateName)) {
      throw new IllegalArgumentException("The templateName cannot be null or empty string, "
              + "please specify a key name to use when processing this template (can be anything moderately unique)");
    }
    if (data == null || data.size() == 0) {
      return textTemplate;
    }
    if (StringUtils.isEmpty(textTemplate)) {
      throw new IllegalArgumentException("The textTemplate cannot be null or empty string, "
              + "please pass in at least something in the template or do not call this method");
    }

    // get the template
    Template template;
    try {
      template = new Template(templateName, new StringReader(textTemplate), freemarkerConfig);
    } catch (ParseException e) {
      String msg = "Failure while parsing the Doc template (" + templateName + "), template is invalid: " + e
              + " :: template=" + textTemplate;
      logger.error(msg);
      throw new RuntimeException(msg, e);
    } catch (IOException e) {
      throw new RuntimeException("Failure while creating freemarker template", e);
    }

    // process the template
    String result;
    try {
      Writer output = new StringWriter();
      template.process(data, output);
      result = output.toString();
      logger.debug("Generated complete document ({} chars) from template ({})", result.length(), templateName);
    } catch (TemplateException e) {
      logger.error("Failed while processing the Doc template ({})", templateName, e);
      result = "ERROR:: Failed while processing the template (" + templateName + "): " + e + "\n Template: "
              + textTemplate + "\n Data: " + data;
    } catch (IOException e) {
      throw new RuntimeException("Failure while sending freemarker output to stream", e);
    }

    return result;
  }

  /**
   * Use this method to generate the documentation using passed in document data, allows the user to specify the
   * template that is used
   *
   * @param data
   *          any populated DocData object
   * @param template
   *          any freemarker template which works with the DocData data structure
   * @return the documentation (e.g. REST html) as a string
   * @throws IllegalArgumentException
   *           if the input data is invalid in some way
   * @see DocData
   */
  public static String generate(DocData data, String template) {
    if (template == null) {
      throw new IllegalArgumentException("template must be set");
    }
    return processTextTemplate(data.getMetaData("name"), template, data.toMap());
  }

  /**
   * Loads a template based on the given path
   *
   * @param path
   *          the path to load the template from (uses the current classloader)
   * @return the template as a string
   */
  public static String loadTemplate(String path) {
    String textTemplate;
    InputStream in = null;
    try {
      in = DocUtil.class.getResourceAsStream(path);
      if (in == null) {
        throw new NullPointerException("No template file could be found at: " + path);
      }
      textTemplate = new String(IOUtils.toByteArray(in));
    } catch (Exception e) {
      logger.error("failed to load template file from path (" + path + "): " + e, e);
      textTemplate = null;
    } finally {
      IOUtils.closeQuietly(in);
    }
    return textTemplate;
  }

}