FreeMarkerTemplateEngine.java

package org.andromda.templateengines.freemarker;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.andromda.core.common.AndroMDALogger;
import org.andromda.core.common.ExceptionUtils;
import org.andromda.core.templateengine.TemplateEngine;
import org.andromda.core.templateengine.TemplateEngineException;
import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;

/**
 * The TemplateEngine implementation for the FreeMarker template processor.
 *
 * @author Chad Brandon
 * @author Olaf Muliawan
 * @author Matthias Bohlen (usage of context classloader so that it works in Maven environments)
 * @see <a href="http://www.freemarker.org">Freemarker</a>
 */
public class FreeMarkerTemplateEngine implements TemplateEngine
{
    /**
     * @see org.andromda.core.templateengine.TemplateEngine#initialize(String)
     */
    public void initialize(String namespace) throws Exception
    {
        this.initLogger(namespace);
    }

    /**
     * The main Configuration object of Freemarker.
     */
    protected Configuration configuration = null;

    /**
     * @see org.andromda.core.templateengine.TemplateEngine#processTemplate(String,
     *      java.util.Map, java.io.Writer)
     */
    public void processTemplate(String templateFile, Map<String, Object> templateObjects, Writer output) throws Exception
    {
        ExceptionUtils.checkEmpty("templateFile", templateFile);
        ExceptionUtils.checkNull("output", output);

        if (this.configuration == null)
        {
            this.configuration = new Configuration();

            // tell FreeMarker it should use the ContextClassLoader when
            // searching for templates
            this.configuration.setTemplateLoader(new ContextResourceLoader());

            // use Bean Wrapper, in order to get maximal reflection
            // capabilities
            this.configuration.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
        }

        // create the template
        final Template template = this.configuration.getTemplate(templateFile);

        if (templateObjects == null)
        {
            templateObjects = new HashMap<String, Object>();
        }

        // - merge data model with template
        template.process(templateObjects, output);
    }

    /**
     * Template loader which accesses the context classloader because otherwise
     * templates will not be found properly in a multi-classloader environment.
     */
    private static class ContextResourceLoader extends freemarker.cache.URLTemplateLoader
    {
        protected URL getURL(String name)
        {
            return Thread.currentThread().getContextClassLoader().getResource(name);
        }
    }

    /**
     * @see org.andromda.core.templateengine.TemplateEngine#shutdown()
     */
    public void shutdown()
    {
        this.shutdownLogger();
        this.configuration = null;
    }

    /**
     * Stores the macro libraries the template.
     */
    private List<String> macroLibraries = new ArrayList<String>();

    /**
     * @see org.andromda.core.templateengine.TemplateEngine#getMacroLibraries()
     */
    public List<String> getMacroLibraries()
    {
        return this.macroLibraries;
    }

    /**
     * @see org.andromda.core.templateengine.TemplateEngine#addMacroLibrary(String)
     */
    public void addMacroLibrary(String macroLibrary)
    {
        this.macroLibraries.add(macroLibrary);
    }

    /**
     * @see org.andromda.core.templateengine.TemplateEngine#setMergeLocation(String)
     */
    public void setMergeLocation(String mergeLocation)
    {
    }

    /**
     * The name of the temporary string template.
     */
    private static final String STRING_TEMPLATE = "stringTemplate";

    /**
     * @see org.andromda.core.templateengine.TemplateEngine#getEvaluatedExpression(String,
     *      java.util.Map)
     */
    public String getEvaluatedExpression(final String expression, Map<String, Object> templateObjects)
    {
        try
        {
            // - create the template
            final Template template = new Template(STRING_TEMPLATE, new StringReader(expression), new Configuration());

            if (templateObjects == null)
            {
                templateObjects = new HashMap<String, Object>();
            }

            final StringWriter output = new StringWriter();

            // - merge data model with template
            template.process(templateObjects, output);

            return output.toString();
        }
        catch (final Throwable throwable)
        {
            throw new TemplateEngineException(throwable);
        }
    }

    /**
     * null logger
     */
    protected static Logger logger = null;

    /**
     * Opens a log file for this plugin.
     *
     * @throws IOException
     *             if the file cannot be opened
     */
    private void initLogger(String pluginName) throws IOException
    {
        logger = AndroMDALogger.getNamespaceLogger(pluginName);
        logger.setAdditivity(false);
        FileAppender appender = new FileAppender(new PatternLayout("%-5p %d - %m%n"), AndroMDALogger
                .getNamespaceLogFileName(pluginName), true);
        logger.addAppender(appender);
    }

    /**
     * Shutdown the associated logger.
     */
    private void shutdownLogger()
    {
        Enumeration appenders = logger.getAllAppenders();
        while (appenders.hasMoreElements())
        {
            Appender appender = (Appender) appenders.nextElement();
            if (appender.getName() != null)
            {
                appender.close();
            }
        }
    }
}