001package org.andromda.templateengines.freemarker;
002
003import java.io.IOException;
004import java.io.StringReader;
005import java.io.StringWriter;
006import java.io.Writer;
007import java.net.URL;
008import java.util.ArrayList;
009import java.util.Enumeration;
010import java.util.HashMap;
011import java.util.List;
012import java.util.Map;
013import org.andromda.core.common.AndroMDALogger;
014import org.andromda.core.common.ExceptionUtils;
015import org.andromda.core.templateengine.TemplateEngine;
016import org.andromda.core.templateengine.TemplateEngineException;
017import org.apache.log4j.Appender;
018import org.apache.log4j.FileAppender;
019import org.apache.log4j.Logger;
020import org.apache.log4j.PatternLayout;
021import freemarker.template.Configuration;
022import freemarker.template.ObjectWrapper;
023import freemarker.template.Template;
024
025/**
026 * The TemplateEngine implementation for the FreeMarker template processor.
027 *
028 * @author Chad Brandon
029 * @author Olaf Muliawan
030 * @author Matthias Bohlen (usage of context classloader so that it works in Maven environments)
031 * @see <a href="http://www.freemarker.org">Freemarker</a>
032 */
033public class FreeMarkerTemplateEngine implements TemplateEngine
034{
035    /**
036     * @see org.andromda.core.templateengine.TemplateEngine#initialize(String)
037     */
038    public void initialize(String namespace) throws Exception
039    {
040        this.initLogger(namespace);
041    }
042
043    /**
044     * The main Configuration object of Freemarker.
045     */
046    protected Configuration configuration = null;
047
048    /**
049     * @see org.andromda.core.templateengine.TemplateEngine#processTemplate(String,
050     *      java.util.Map, java.io.Writer)
051     */
052    public void processTemplate(String templateFile, Map<String, Object> templateObjects, Writer output) throws Exception
053    {
054        ExceptionUtils.checkEmpty("templateFile", templateFile);
055        ExceptionUtils.checkNull("output", output);
056
057        if (this.configuration == null)
058        {
059            this.configuration = new Configuration();
060
061            // tell FreeMarker it should use the ContextClassLoader when
062            // searching for templates
063            this.configuration.setTemplateLoader(new ContextResourceLoader());
064
065            // use Bean Wrapper, in order to get maximal reflection
066            // capabilities
067            this.configuration.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
068        }
069
070        // create the template
071        final Template template = this.configuration.getTemplate(templateFile);
072
073        if (templateObjects == null)
074        {
075            templateObjects = new HashMap<String, Object>();
076        }
077
078        // - merge data model with template
079        template.process(templateObjects, output);
080    }
081
082    /**
083     * Template loader which accesses the context classloader because otherwise
084     * templates will not be found properly in a multi-classloader environment.
085     */
086    private static class ContextResourceLoader extends freemarker.cache.URLTemplateLoader
087    {
088        protected URL getURL(String name)
089        {
090            return Thread.currentThread().getContextClassLoader().getResource(name);
091        }
092    }
093
094    /**
095     * @see org.andromda.core.templateengine.TemplateEngine#shutdown()
096     */
097    public void shutdown()
098    {
099        this.shutdownLogger();
100        this.configuration = null;
101    }
102
103    /**
104     * Stores the macro libraries the template.
105     */
106    private List<String> macroLibraries = new ArrayList<String>();
107
108    /**
109     * @see org.andromda.core.templateengine.TemplateEngine#getMacroLibraries()
110     */
111    public List<String> getMacroLibraries()
112    {
113        return this.macroLibraries;
114    }
115
116    /**
117     * @see org.andromda.core.templateengine.TemplateEngine#addMacroLibrary(String)
118     */
119    public void addMacroLibrary(String macroLibrary)
120    {
121        this.macroLibraries.add(macroLibrary);
122    }
123
124    /**
125     * @see org.andromda.core.templateengine.TemplateEngine#setMergeLocation(String)
126     */
127    public void setMergeLocation(String mergeLocation)
128    {
129    }
130
131    /**
132     * The name of the temporary string template.
133     */
134    private static final String STRING_TEMPLATE = "stringTemplate";
135
136    /**
137     * @see org.andromda.core.templateengine.TemplateEngine#getEvaluatedExpression(String,
138     *      java.util.Map)
139     */
140    public String getEvaluatedExpression(final String expression, Map<String, Object> templateObjects)
141    {
142        try
143        {
144            // - create the template
145            final Template template = new Template(STRING_TEMPLATE, new StringReader(expression), new Configuration());
146
147            if (templateObjects == null)
148            {
149                templateObjects = new HashMap<String, Object>();
150            }
151
152            final StringWriter output = new StringWriter();
153
154            // - merge data model with template
155            template.process(templateObjects, output);
156
157            return output.toString();
158        }
159        catch (final Throwable throwable)
160        {
161            throw new TemplateEngineException(throwable);
162        }
163    }
164
165    /**
166     * null logger
167     */
168    protected static Logger logger = null;
169
170    /**
171     * Opens a log file for this plugin.
172     *
173     * @throws IOException
174     *             if the file cannot be opened
175     */
176    private void initLogger(String pluginName) throws IOException
177    {
178        logger = AndroMDALogger.getNamespaceLogger(pluginName);
179        logger.setAdditivity(false);
180        FileAppender appender = new FileAppender(new PatternLayout("%-5p %d - %m%n"), AndroMDALogger
181                .getNamespaceLogFileName(pluginName), true);
182        logger.addAppender(appender);
183    }
184
185    /**
186     * Shutdown the associated logger.
187     */
188    private void shutdownLogger()
189    {
190        Enumeration appenders = logger.getAllAppenders();
191        while (appenders.hasMoreElements())
192        {
193            Appender appender = (Appender) appenders.nextElement();
194            if (appender.getName() != null)
195            {
196                appender.close();
197            }
198        }
199    }
200}