VelocityTemplateEngine.java
package org.andromda.templateengines.velocity;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.andromda.core.common.AndroMDALogger;
import org.andromda.core.common.Constants;
import org.andromda.core.common.ExceptionUtils;
import org.andromda.core.common.Merger;
import org.andromda.core.common.ResourceUtils;
import org.andromda.core.common.ResourceWriter;
import org.andromda.core.templateengine.TemplateEngine;
import org.andromda.core.templateengine.TemplateEngineException;
import org.apache.commons.collections.ExtendedProperties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.log.Log4JLogChute;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.apache.velocity.runtime.resource.loader.FileResourceLoader;
import org.apache.velocity.tools.generic.EscapeTool;
/**
* The TemplateEngine implementation for VelocityTemplateEngine template processor.
*
* @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
* @author Chad Brandon
* @see "http://jakarta.apache.org/velocity/"
*/
public class VelocityTemplateEngine
implements TemplateEngine
{
/**
* Log4J logger
*/
protected static Logger logger = null;
/**
* Log4J appender
*/
protected FileAppender appender = null;
/**
* The directory we look in to find velocity properties.
*/
private static final String PROPERTIES_DIR = "META-INF/";
/**
* The suffix for the the velocity properties.
*/
private static final String PROPERTIES_SUFFIX = "-velocity.properties";
/**
* The location to which temporary templates are written
*/
private static final String TEMPORARY_TEMPLATE_LOCATION = Constants.TEMPORARY_DIRECTORY + "velocity/merged";
/**
* The location of external templates
*/
private String mergeLocation;
/**
* The current namespace this template engine is running within.
*/
private String namespace;
/**
* The VelocityEngine instance to use
*/
private VelocityEngine velocityEngine;
/**
* The VelocityContext instance to use
*/
private VelocityContext velocityContext;
/**
* The Macro Libraries
*/
private final List<String> macroLibraries = new ArrayList<String>();
/**
* Stores a collection of templates that have already been
* discovered by the velocity engine
*/
private final Map<String, Template> discoveredTemplates = new HashMap<String, Template>();
/**
* Stores the merged template files that are deleted at shutdown.
*/
private final Collection<File> mergedTemplateFiles = new ArrayList<File>();
/**
* Initialized the engine
* @param namespace
* @throws Exception
* @see org.andromda.core.templateengine.TemplateEngine#initialize(String)
*/
public void initialize(final String namespace)
throws Exception
{
this.namespace = namespace;
this.initLogger(namespace);
ExtendedProperties engineProperties = new ExtendedProperties();
// Tell VelocityTemplateEngine it should also use the
// classpath when searching for templates
// IMPORTANT: file,andromda.plugins the ordering of these
// two things matters, the ordering allows files to override
// the resources found on the classpath.
engineProperties.setProperty(VelocityEngine.RESOURCE_LOADER, "file,classpath");
engineProperties.setProperty(
"file." + VelocityEngine.RESOURCE_LOADER + ".class",
FileResourceLoader.class.getName());
engineProperties.setProperty(
"classpath." + VelocityEngine.RESOURCE_LOADER + ".class",
ClasspathResourceLoader.class.getName());
// Configure Velocity logger
engineProperties.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
"org.apache.velocity.runtime.log.Log4JLogChute" );
engineProperties.setProperty(Log4JLogChute.RUNTIME_LOG_LOG4J_LOGGER, logger.getName());
// Let this template engine know about the macro libraries.
for (String macroLibrary : getMacroLibraries())
{
engineProperties.addProperty(
VelocityEngine.VM_LIBRARY,
macroLibrary);
}
this.velocityEngine = new VelocityEngine();
this.velocityEngine.setExtendedProperties(engineProperties);
if (this.mergeLocation != null)
{
// set the file resource path (to the merge location)
velocityEngine.addProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, this.mergeLocation);
}
// if the namespace requires a merge add the temporary template
// location to which merged templates are written
if (Merger.instance().requiresMerge(this.namespace))
{
velocityEngine.addProperty(
VelocityEngine.FILE_RESOURCE_LOADER_PATH,
this.getMergedTemplatesLocation());
}
this.addProperties(namespace);
this.velocityEngine.init();
}
/**
* Adds any properties found within META-INF/'plugin name'-velocity.properties
* @param pluginName name of the plugin
* @throws java.io.IOException if resource could not be found
*/
private void addProperties(String pluginName)
throws IOException
{
// see if the velocity properties exist for the current plugin
URL propertiesUri =
ResourceUtils.getResource(PROPERTIES_DIR + StringUtils.trimToEmpty(pluginName) + PROPERTIES_SUFFIX);
if (propertiesUri != null)
{
if (logger.isDebugEnabled())
{
logger.debug("loading properties from --> '" + propertiesUri + '\'');
}
Properties properties = new Properties();
properties.load(propertiesUri.openStream());
for (Map.Entry entry : properties.entrySet())
{
final String property = (String) entry.getKey();
final String value = (String)entry.getValue();
if (logger.isDebugEnabled())
{
logger.debug("setting property '" + property + "' with --> '" + value + '\'');
}
this.velocityEngine.setProperty(property, value);
}
}
}
/**
* @see org.andromda.core.templateengine.TemplateEngine#processTemplate(String, java.util.Map,
* java.io.Writer)
*/
public void processTemplate(
final String templateFile,
final Map<String, Object> templateObjects,
final Writer output)
throws Exception
{
final String methodName = "VelocityTemplateEngine.processTemplate";
if (logger.isDebugEnabled())
{
logger.debug(
"performing " + methodName + " with templateFile '" + templateFile + "' and templateObjects '" +
templateObjects + '\'');
}
ExceptionUtils.checkEmpty("templateFile", templateFile);
ExceptionUtils.checkNull("output", output);
this.velocityContext = new VelocityContext();
this.loadVelocityContext(templateObjects);
Template template = this.discoveredTemplates.get(templateFile);
if (template == null)
{
template = this.velocityEngine.getTemplate(templateFile);
// We check to see if the namespace requires a merge, and if so
final Merger merger = Merger.instance();
if (merger.requiresMerge(this.namespace))
{
final String mergedTemplateLocation = this.getMergedTemplateLocation(templateFile);
final InputStream resource = template.getResourceLoader().getResourceStream(templateFile);
ResourceWriter.instance().writeStringToFile(
merger.getMergedString(resource, this.namespace),
mergedTemplateLocation);
template = this.velocityEngine.getTemplate(templateFile);
this.mergedTemplateFiles.add(new File(mergedTemplateLocation));
resource.close();
}
this.discoveredTemplates.put(templateFile, template);
}
template.merge(velocityContext, output);
}
/**
* Loads the internal {@link #velocityContext} from the
* given Map of template objects.
*
* @param templateObjects Map containing objects to add to the template context.
*/
private void loadVelocityContext(final Map<String, Object> templateObjects)
{
if (templateObjects != null)
{
// copy the templateObjects to the velocityContext
for (Map.Entry<String, Object> entry : templateObjects.entrySet())
{
this.velocityContext.put(entry.getKey(), entry.getValue());
}
}
// add velocity tools (Escape tool)
this.velocityContext.put("esc", new EscapeTool());
}
/**
* Gets location to which the given <code>templateName</code>
* has its merged output written.
* @param templatePath the relative path to the template.
* @return the complete merged template location.
*/
private String getMergedTemplateLocation(String templatePath)
{
return this.getMergedTemplatesLocation() + '/' + templatePath;
}
/**
* Gets the location to which merge templates are written. These
* must be written in order to replace the unmerged ones when Velocity
* performs its template search.
*
* @return the merged templates location.
*/
private String getMergedTemplatesLocation()
{
return TEMPORARY_TEMPLATE_LOCATION + '/' + this.namespace;
}
/**
* The log tag used for evaluation (this can be any abitrary name).
*/
private static final String LOG_TAG = "logtag";
/**
* @see org.andromda.core.templateengine.TemplateEngine#getEvaluatedExpression(String, java.util.Map)
*/
public String getEvaluatedExpression(final String expression, final Map<String, Object> templateObjects)
{
String evaluatedExpression = null;
if (StringUtils.isNotBlank(expression))
{
// reuse last created context, need it for processing $generateFilename
if (this.velocityContext == null)
{
this.velocityContext = new VelocityContext();
this.loadVelocityContext(templateObjects);
}
try
{
final StringWriter writer = new StringWriter();
this.velocityEngine.evaluate(this.velocityContext, writer, LOG_TAG, expression);
evaluatedExpression = writer.toString();
}
catch (final Throwable throwable)
{
throw new TemplateEngineException(throwable);
}
}
return evaluatedExpression;
}
/**
* @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 libraryName)
{
this.macroLibraries.add(libraryName);
}
/**
* @see org.andromda.core.templateengine.TemplateEngine#setMergeLocation(String)
*/
public void setMergeLocation(String mergeLocation)
{
this.mergeLocation = mergeLocation;
}
/**
* @see org.andromda.core.templateengine.TemplateEngine#shutdown()
*/
public void shutdown()
{
// Deletes the merged templates location (these are the templates that were created just for merging
// purposes and so therefore are no longer needed after the engine is shutdown).
FileUtils.deleteQuietly(new File(TEMPORARY_TEMPLATE_LOCATION));
this.discoveredTemplates.clear();
this.velocityEngine = null;
if(null!=logger && null!=appender)
{
logger.removeAppender(appender);
}
}
/**
* Opens a log file for this namespace.
*
* @param pluginName Name of this plugin
* @throws IOException if the file cannot be opened
*/
private void initLogger(final String pluginName)
throws IOException
{
logger = AndroMDALogger.getNamespaceLogger(pluginName);
logger.setAdditivity(false);
appender =
new FileAppender(
new PatternLayout("%-5p %d - %m%n"),
AndroMDALogger.getNamespaceLogFileName(pluginName),
true);
logger.addAppender(appender);
}
}