Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.velocity.runtime.AbstractRuntimeInstance Maven / Gradle / Ivy
package org.apache.velocity.runtime;
import org.apache.velocity.Template;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.EventHandler;
import org.apache.velocity.app.event.IncludeEventHandler;
import org.apache.velocity.app.event.InvalidReferenceEventHandler;
import org.apache.velocity.app.event.MethodExceptionEventHandler;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import org.apache.velocity.context.Context;
import org.apache.velocity.context.InternalContextAdapterImpl;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.TemplateInitException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.directive.Macro;
import org.apache.velocity.runtime.directive.Scope;
import org.apache.velocity.runtime.directive.StopCommand;
import org.apache.velocity.runtime.parser.LogContext;
import org.apache.velocity.runtime.parser.ParseException;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.apache.velocity.runtime.resource.ContentResource;
//import org.apache.velocity.runtime.resource.ResourceManager;
import org.apache.velocity.util.ClassUtils;
import org.apache.velocity.util.ExtProperties;
import org.apache.velocity.util.RuntimeServicesAware;
import org.apache.velocity.util.introspection.ChainableUberspector;
import org.apache.velocity.util.introspection.LinkingUberspector;
import org.apache.velocity.util.introspection.Uberspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zodiac.sdk.toolkit.util.lang.StrUtil;
import org.zodiac.template.velocity.platform.parser.PlatformParser;
import org.zodiac.template.velocity.platform.parser.PlatformSimpleNode;
import org.zodiac.template.velocity.platform.runtime.PlatformParserPool;
import org.zodiac.template.velocity.platform.runtime.PlatformRuntimeServices;
import org.zodiac.template.velocity.platform.runtime.resource.PlatformResourceManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
public abstract class AbstractRuntimeInstance implements RuntimeConstants, PlatformRuntimeServices {
/**
* VelocimacroFactory object to manage VMs
*/
private VelocimacroFactory vmFactory = null;
/**
* The Runtime logger. The default instance is the "org.apache.velocity" logger.
*/
private Logger log = LoggerFactory.getLogger(DEFAULT_RUNTIME_LOG_NAME);
/**
* The Runtime parser pool
*/
private PlatformParserPool parserPool;
/**
* Indicate whether the Runtime is in the midst of initialization.
*/
private boolean initializing = false;
/**
* Indicate whether the Runtime has been fully initialized.
*/
private volatile boolean initialized = false;
/**
* These are the properties that are laid down over top of the default properties when requested.
*/
private ExtProperties overridingProperties = null;
/**
* This is a hashtable of initialized directives. The directives that populate this hashtable are taken from the
* RUNTIME_DEFAULT_DIRECTIVES property file.
*/
private Map runtimeDirectives = new Hashtable<>();
/**
* Copy of the actual runtimeDirectives that is shared between parsers. Whenever directives are updated, the
* synchronized runtimeDirectives is first updated and then an unsynchronized copy of it is passed to parsers.
*/
private Map runtimeDirectivesShared;
/**
* Object that houses the configuration options for the velocity runtime. The ExtProperties object allows the
* convenient retrieval of a subset of properties. For example all the properties for a resource loader can be
* retrieved from the main ExtProperties object using something like the following:
*
* ExtProperties loaderConfiguration = configuration.subset(loaderID);
*
* And a configuration is a lot more convenient to deal with then conventional properties objects, or Maps.
*/
private ExtProperties configuration = new ExtProperties();
private PlatformResourceManager resourceManager = null;
/**
* This stores the engine-wide set of event handlers. Event handlers for each specific merge are stored in the
* context.
*/
private EventCartridge eventCartridge = null;
/**
* Whether to use string interning
*/
private boolean stringInterning = false;
/**
* Scope name for evaluate(...) calls.
*/
private String evaluateScopeName = "evaluate";
/**
* Scope names for which to provide scope control objects in the context
*/
private Set enabledScopeControls = new HashSet<>();
/**
* Opaque reference to something specified by the application for use in application supplied/specified pluggable
* components
*/
private Map applicationAttributes = null;
/**
* Uberspector
*/
private Uberspect uberSpect;
/**
* Default encoding
*/
private String defaultEncoding;
/**
* Space gobbling mode
*/
private SpaceGobbling spaceGobbling;
/**
* Whether hyphen is allowed in identifiers
*/
private boolean hyphenAllowedInIdentifiers;
/**
* The LogContext object used to track location in templates
*/
private LogContext logContext;
/**
* Configured parser class
*
*/
private Constructor extends PlatformParser> parserConstructor;
/**
* Configured replacement characters in parser grammar
*
*/
private ParserConfiguration parserConfiguration;
protected AbstractRuntimeInstance() {
reset();
}
/**
* This is the primary initialization method in the Velocity Runtime. The systems that are setup/initialized here
* are as follows:
*
*
* Logging System
* ResourceManager
* EventHandler
* Parser Pool
* Global Cache
* Static Content Include System
* Velocimacro System
*
*/
@Override
public synchronized void init() {
if (!initialized && !initializing) {
try {
log.debug("Initializing Velocity, Calling init()...");
initializing = true;
log.trace("*****************************");
log.debug("Starting Apache Velocity v" + VelocityEngineVersion.VERSION);
log.trace("RuntimeInstance initializing.");
initializeProperties();
initializeSelfProperties();
initializeLog();
initializeResourceManager();
initializeDirectives();
initializeEventHandlers();
initializeParserPool();
initializeIntrospection();
initializeScopeSettings();
/*
* Initialize the VM Factory. It will use the properties
* accessible from Runtime, so keep this here at the end.
*/
vmFactory.initVelocimacro();
log.trace("RuntimeInstance successfully initialized.");
initialized = true;
initializing = false;
} catch (RuntimeException re) {
/*Initialization failed at some point... try to reset everything.*/
try {
reset();
} catch (RuntimeException re2) {
} /*refer throwing the original exception.*/
throw re;
} finally {
initializing = false;
}
}
}
/**
* Resets the instance, so Velocity can be re-initialized again.
*
* @since 2.0.0
*/
public synchronized void reset() {
this.configuration = new ExtProperties();
this.defaultEncoding = null;
this.evaluateScopeName = "evaluate";
this.eventCartridge = null;
this.initialized = false;
this.initializing = false;
this.overridingProperties = null;
this.parserPool = null;
this.enabledScopeControls.clear();
this.resourceManager = null;
this.runtimeDirectives = new Hashtable<>();
this.runtimeDirectivesShared = null;
this.uberSpect = null;
this.stringInterning = false;
this.parserConfiguration = new ParserConfiguration();
/*
* Create a VM factory, introspector, and application attributes.
*/
vmFactory = new VelocimacroFactory(this);
/*
* And a store for the application attributes.
*/
applicationAttributes = new HashMap<>();
}
/**
* Returns true if the RuntimeInstance has been successfully initialized.
*
* @return True if the RuntimeInstance has been successfully initialized.
* @since 1.5
*/
@Override
public boolean isInitialized() {
return initialized;
}
/**
* Init or die! (with some log help, of course).
*/
private void requireInitialization() {
if (!initialized) {
try {
init();
} catch (Exception e) {
log.error("Could not auto-initialize Velocity", e);
throw new RuntimeException("Velocity could not be initialized!", e);
}
}
}
/**
* Initialize runtime internal properties
*/
private void initializeSelfProperties() {
/* Initialize string interning (defaults to false). */
stringInterning = getBoolean(RUNTIME_STRING_INTERNING, true);
/* Initialize indentation mode (defaults to 'lines'). */
String im = getString(SPACE_GOBBLING, "lines");
try {
spaceGobbling = SpaceGobbling.valueOf(im.toUpperCase(Locale.ROOT));
} catch (NoSuchElementException nse) {
spaceGobbling = SpaceGobbling.LINES;
}
/* Init parser behavior. */
hyphenAllowedInIdentifiers = getBoolean(PARSER_HYPHEN_ALLOWED, false);
}
private char getConfiguredCharacter(String configKey, char defaultChar) {
String configuredChar = getString(configKey);
if (configuredChar != null) {
if (configuredChar.length() != 1) {
throw new IllegalArgumentException(String
.format("value of '%s' must be a single character string, but is '%s'", configKey, configuredChar));
}
return configuredChar.charAt(0);
}
return defaultChar;
}
/**
* Gets the classname for the Uberspect introspection package and instantiates an instance.
*/
private void initializeIntrospection() {
String[] uberspectors = configuration.getStringArray(RuntimeConstants.UBERSPECT_CLASSNAME);
for (String rm : uberspectors) {
Object o = null;
try {
o = ClassUtils.getNewInstance(rm);
} catch (ClassNotFoundException cnfe) {
String err = "The specified class for Uberspect (" + rm
+ ") does not exist or is not accessible to the current classloader.";
log.error(err);
throw new VelocityException(err, cnfe);
} catch (InstantiationException ie) {
throw new VelocityException("Could not instantiate class '" + rm + "'", ie);
} catch (IllegalAccessException ae) {
throw new VelocityException("Cannot access class '" + rm + "'", ae);
}
if (!(o instanceof Uberspect)) {
String err = "The specified class for Uberspect (" + rm + ") does not implement "
+ Uberspect.class.getName() + "; Velocity is not initialized correctly.";
log.error(err);
throw new VelocityException(err);
}
Uberspect u = (Uberspect)o;
if (u instanceof RuntimeServicesAware) {
((RuntimeServicesAware)u).setRuntimeServices(this);
}
if (uberSpect == null) {
uberSpect = u;
} else {
if (u instanceof ChainableUberspector) {
((ChainableUberspector)u).wrap(uberSpect);
uberSpect = u;
} else {
uberSpect = new LinkingUberspector(uberSpect, u);
}
}
}
if (uberSpect != null) {
uberSpect.init();
} else {
/*
* someone screwed up. Lets not fool around...
*/
String err = "It appears that no class was specified as the"
+ " Uberspect. Please ensure that all configuration" + " information is correct.";
log.error(err);
throw new VelocityException(err);
}
}
/**
* Initializes the Velocity Runtime with properties file. The properties file may be in the file system proper, or
* the properties file may be in the classpath.
*/
private void setDefaultProperties() {
InputStream inputStream = null;
try {
inputStream = getClass().getClassLoader().getResourceAsStream(DEFAULT_RUNTIME_PROPERTIES);
if (inputStream == null) {
throw new IOException("Resource not found: " + DEFAULT_RUNTIME_PROPERTIES);
}
configuration.load(inputStream);
/* populate 'defaultEncoding' member */
defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT);
log.debug("Default Properties resource: {}", DEFAULT_RUNTIME_PROPERTIES);
} catch (IOException ioe) {
String msg = "Cannot get Velocity Runtime default properties!";
log.error(msg, ioe);
throw new RuntimeException(msg, ioe);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException ioe) {
String msg = "Cannot close Velocity Runtime default properties!";
log.error(msg, ioe);
throw new RuntimeException(msg, ioe);
}
}
}
/**
* Allows an external system to set a property in the Velocity Runtime.
*
* @param key property key
* @param value property value
*/
@Override
public void setProperty(String key, Object value) {
if (overridingProperties == null) {
overridingProperties = new ExtProperties();
}
overridingProperties.setProperty(key, value);
}
/**
* Add all properties contained in the file fileName to the RuntimeInstance properties
*
* @param fileName File nname.
*/
public void setProperties(String fileName) {
ExtProperties props = null;
try {
props = new ExtProperties(fileName);
} catch (IOException e) {
throw new VelocityException("Error reading properties from '" + fileName + "'", e);
}
Enumeration en = props.keys();
while (en.hasMoreElements()) {
String key = en.nextElement();
setProperty(key, props.get(key));
}
}
/**
* Add all the properties in props to the RuntimeInstance properties
*
* @param props {@link Properties}
*/
public void setProperties(Properties props) {
Enumeration en = props.keys();
while (en.hasMoreElements()) {
String key = en.nextElement().toString();
setProperty(key, props.get(key));
}
}
/**
* Allow an external system to set an ExtProperties object to use.
*
* @param configuration {@link ExtProperties}
* @since 2.0
*/
@Override
public void setConfiguration(ExtProperties configuration) {
if (overridingProperties == null) {
overridingProperties = configuration;
} else {
/*Avoid possible ConcurrentModificationException.*/
if (overridingProperties != configuration) {
overridingProperties.combine(configuration);
}
}
}
/**
* Add a property to the configuration. If it already exists then the value stated here will be added to the
* configuration entry. For example, if
*
* resource.loader = file
*
* is already present in the configuration and you
*
* addProperty("resource.loader", "classpath")
*
* Then you will end up with a Vector like the following:
*
* ["file", "classpath"]
*
* @param key Property key.
* @param value Property value.
*/
@Override
public void addProperty(String key, Object value) {
if (overridingProperties == null) {
overridingProperties = new ExtProperties();
}
overridingProperties.addProperty(key, value);
}
/**
* Clear the values pertaining to a particular property.
*
* @param key of property to clear
*/
@Override
public void clearProperty(String key) {
if (overridingProperties != null) {
overridingProperties.clearProperty(key);
}
}
/**
* Allows an external caller to get a property. The calling routine is required to know the type, as this routine
* will return an Object, as that is what properties can be.
*
* @param key property to return
* @return Value of the property or null if it does not exist.
*/
@Override
public Object getProperty(String key) {
Object o = null;
/*
* Before initialization, check the user-entered properties first.
*/
if (!initialized && overridingProperties != null) {
o = overridingProperties.get(key);
}
/*
* After initialization, configuration will hold all properties.
*/
if (o == null) {
o = configuration.getProperty(key);
}
if (o instanceof String) {
return StrUtil.trim((String)o);
} else {
return o;
}
}
/**
* Initialize Velocity properties, if the default properties have not been laid down first then do so. Then proceed
* to process any overriding properties. Laying down the default properties gives a much greater chance of having a
* working system.
*/
private void initializeProperties() {
/*
* Always lay down the default properties first as
* to provide a solid base.
*/
if (!configuration.isInitialized()) {
setDefaultProperties();
}
if (overridingProperties != null) {
configuration.combine(overridingProperties);
}
}
/**
* Initialize the Velocity Runtime with a Properties object.
*
* @param p Velocity properties for initialization
*/
@Override
public void init(Properties p) {
setConfiguration(ExtProperties.convertProperties(p));
init();
}
/**
* Initialize the Velocity Runtime with a properties file path.
*
* @param configurationFile Configuration file path.
*/
@Override
public void init(String configurationFile) {
setProperties(configurationFile);
init();
}
private void initializeResourceManager() {
/*
* Which resource manager?
*/
Object inst = getProperty(RuntimeConstants.RESOURCE_MANAGER_INSTANCE);
String rm = getString(RuntimeConstants.RESOURCE_MANAGER_CLASS);
if (inst != null) {
if (PlatformResourceManager.class.isAssignableFrom(inst.getClass())) {
resourceManager = (PlatformResourceManager)inst;
resourceManager.initialize(this);
} else {
String msg = inst.getClass().getName()
+ " object set as resource.manager.instance is not a valid org.zodiac.template.velocity.ext.PlatfromResourceManager.";
log.error(msg);
throw new VelocityException(msg);
}
} else if (rm != null && rm.length() > 0) {
/*
* If something was specified, then make one.
* if that isn't a ResourceManager, consider
* this a huge error and throw
*/
Object o = null;
try {
o = ClassUtils.getNewInstance(rm);
} catch (ClassNotFoundException cnfe) {
String err = "The specified class for ResourceManager (" + rm
+ ") does not exist or is not accessible to the current classloader.";
log.error(err);
throw new VelocityException(err, cnfe);
} catch (InstantiationException ie) {
throw new VelocityException("Could not instantiate class '" + rm + "'", ie);
} catch (IllegalAccessException ae) {
throw new VelocityException("Cannot access class '" + rm + "'", ae);
}
if (!(o instanceof PlatformResourceManager)) {
String err = "The specified class for ResourceManager (" + rm + ") does not implement "
+ PlatformResourceManager.class.getName() + "; Velocity is not initialized correctly.";
log.error(err);
throw new VelocityException(err);
}
resourceManager = (PlatformResourceManager)o;
resourceManager.initialize(this);
setProperty(RESOURCE_MANAGER_INSTANCE, resourceManager);
} else {
/*
* Someone screwed up. Lets not fool around...
*/
String err = "It appears that no class or instance was specified as the"
+ " ResourceManager. Please ensure that all configuration" + " information is correct.";
log.error(err);
throw new VelocityException(err);
}
}
private void initializeEventHandlers() {
eventCartridge = new EventCartridge();
eventCartridge.setRuntimeServices(this);
/*
* For each type of event handler, get the class name, instantiate it, and store it.
*/
String[] referenceinsertion = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION);
if (referenceinsertion != null) {
for (String aReferenceinsertion : referenceinsertion) {
EventHandler ev = initializeSpecificEventHandler(aReferenceinsertion,
RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, ReferenceInsertionEventHandler.class);
if (ev != null) {
eventCartridge.addReferenceInsertionEventHandler((ReferenceInsertionEventHandler)ev);
}
}
}
String[] methodexception = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION);
if (methodexception != null) {
for (String aMethodexception : methodexception) {
EventHandler ev = initializeSpecificEventHandler(aMethodexception,
RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, MethodExceptionEventHandler.class);
if (ev != null) {
eventCartridge.addMethodExceptionHandler((MethodExceptionEventHandler)ev);
}
}
}
String[] includeHandler = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INCLUDE);
if (includeHandler != null) {
for (String anIncludeHandler : includeHandler) {
EventHandler ev = initializeSpecificEventHandler(anIncludeHandler,
RuntimeConstants.EVENTHANDLER_INCLUDE, IncludeEventHandler.class);
if (ev != null) {
eventCartridge.addIncludeEventHandler((IncludeEventHandler)ev);
}
}
}
String[] invalidReferenceSet = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES);
if (invalidReferenceSet != null) {
for (String anInvalidReferenceSet : invalidReferenceSet) {
EventHandler ev = initializeSpecificEventHandler(anInvalidReferenceSet,
RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES, InvalidReferenceEventHandler.class);
if (ev != null) {
eventCartridge.addInvalidReferenceEventHandler((InvalidReferenceEventHandler)ev);
}
}
}
}
private EventHandler initializeSpecificEventHandler(String classname, String paramName,
Class> EventHandlerInterface) {
if (classname != null && classname.length() > 0) {
Object o = null;
try {
o = ClassUtils.getNewInstance(classname);
} catch (ClassNotFoundException cnfe) {
String err = "The specified class for " + paramName + " (" + classname
+ ") does not exist or is not accessible to the current classloader.";
log.error(err);
throw new VelocityException(err, cnfe);
} catch (InstantiationException ie) {
throw new VelocityException("Could not instantiate class '" + classname + "'", ie);
} catch (IllegalAccessException ae) {
throw new VelocityException("Cannot access class '" + classname + "'", ae);
}
if (!EventHandlerInterface.isAssignableFrom(EventHandlerInterface)) {
String err = "The specified class for " + paramName + " (" + classname + ") does not implement "
+ EventHandlerInterface.getName() + "; Velocity is not initialized correctly.";
log.error(err);
throw new VelocityException(err);
}
EventHandler ev = (EventHandler)o;
if (ev instanceof RuntimeServicesAware) {
((RuntimeServicesAware)ev).setRuntimeServices(this);
}
return ev;
} else {
return null;
}
}
/**
* Initialize the Velocity logging system.
*/
private void initializeLog() {
/*If we were provided a specific logger or logger name, let's use it.*/
try {
/* If a Logger instance was set as a configuration
* value, use that. This is any class the user specifies.
*/
Object o = getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE);
if (o != null) {
/*Check for a Logger.*/
if (Logger.class.isAssignableFrom(o.getClass())) {
/*Looks ok.*/
log = (Logger)o;
} else {
String msg = o.getClass().getName()
+ " object set as runtime.log.instance is not a valid org.slf4j.Logger implementation.";
log.error(msg);
throw new VelocityException(msg);
}
} else {
/* Otherwise, see if a logger name was specified.
*/
o = getProperty(RuntimeConstants.RUNTIME_LOG_NAME);
if (o != null) {
if (o instanceof String) {
log = LoggerFactory.getLogger((String)o);
} else {
String msg = o.getClass().getName() + " object set as runtime.log.name is not a valid string.";
log.error(msg);
throw new VelocityException(msg);
}
}
}
/* Else keep our default Velocity logger
*/
/* Initialize LogContext. */
boolean trackLocation = getBoolean(RUNTIME_LOG_TRACK_LOCATION, false);
logContext = new LogContext(trackLocation);
} catch (Exception e) {
throw new VelocityException("Error initializing log: " + e.getMessage(), e);
}
}
/**
* This methods initializes all the directives that are used by the Velocity Runtime. The directives to be
* initialized are listed in the RUNTIME_DEFAULT_DIRECTIVES properties file.
*/
private void initializeDirectives() {
Properties directiveProperties = new Properties();
/*
* Grab the properties file with the list of directives
* that we should initialize.
*/
InputStream inputStream = null;
try {
inputStream = getClass().getResourceAsStream('/' + DEFAULT_RUNTIME_DIRECTIVES);
if (inputStream == null) {
throw new VelocityException("Error loading directive.properties! "
+ "Something is very wrong if these properties " + "aren't being located. Either your Velocity "
+ "distribution is incomplete or your Velocity " + "jar file is corrupted!");
}
directiveProperties.load(inputStream);
} catch (IOException ioe) {
String msg = "Error while loading directive properties!";
log.error(msg, ioe);
throw new RuntimeException(msg, ioe);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException ioe) {
String msg = "Cannot close directive properties!";
log.error(msg, ioe);
throw new RuntimeException(msg, ioe);
}
}
/*
* Grab all the values of the properties. These
* are all class names for example:
*
* org.apache.velocity.runtime.directive.Foreach
*/
Enumeration directiveClasses = directiveProperties.elements();
while (directiveClasses.hasMoreElements()) {
String directiveClass = (String)directiveClasses.nextElement();
loadDirective(directiveClass);
log.debug("Loaded System Directive: {}", directiveClass);
}
/*
* now the user's directives
*/
String[] userdirective = configuration.getStringArray(CUSTOM_DIRECTIVES);
for (String anUserdirective : userdirective) {
loadDirective(anUserdirective);
log.debug("Loaded User Directive: {}", anUserdirective);
}
}
/**
* Programatically add a directive.
*
* @param directive {@link Directive}
*/
public synchronized void addDirective(Directive directive) {
runtimeDirectives.put(directive.getName(), directive);
updateSharedDirectivesMap();
}
/**
* Retrieve a previously instantiated directive.
*
* @param name name of the directive
* @return The {@link Directive} for that name
*/
@Override
public Directive getDirective(String name) {
return runtimeDirectivesShared.get(name);
}
/**
* Remove a directive.
*
* @param name name of the directive.
*/
public synchronized void removeDirective(String name) {
runtimeDirectives.remove(name);
updateSharedDirectivesMap();
}
/**
* Makes an unsynchronized copy of the directives map that is used for Directive lookups by all parsers.
*
* This follows Copy-on-Write pattern. The cost of creating a new map is acceptable since directives are typically
* set and modified only during Velocity setup phase.
*/
private void updateSharedDirectivesMap() {
runtimeDirectivesShared = new HashMap<>(runtimeDirectives);
}
/**
* instantiates and loads the directive with some basic checks
*
* @param directiveClass classname of directive to load
*/
public void loadDirective(String directiveClass) {
try {
Object o = ClassUtils.getNewInstance(directiveClass);
if (o instanceof Directive) {
Directive directive = (Directive)o;
addDirective(directive);
} else {
String msg =
directiveClass + " does not implement " + Directive.class.getName() + "; it cannot be loaded.";
log.error(msg);
throw new VelocityException(msg);
}
}
// The ugly threesome: ClassNotFoundException,
// IllegalAccessException, InstantiationException.
// Ignore Findbugs complaint for now.
catch (Exception e) {
String msg = "Failed to load Directive: " + directiveClass;
log.error(msg, e);
throw new VelocityException(msg, e);
}
}
/**
* Initializes the Velocity parser pool.
*/
private void initializeParserPool() {
/*
* First initialize parser class. If it's not valid or not found, it will generate an error
* later on in this method when parser creation is tester.
*/
String parserClassName = getString(PARSER_CLASS, DEFAULT_PARSER_CLASS);
Class extends PlatformParser> parserClass;
try {
parserClass = (Class extends PlatformParser>)ClassUtils.getClass(parserClassName);
} catch (ClassNotFoundException cnfe) {
throw new VelocityException("parser class not found: " + parserClassName, cnfe);
}
try {
parserConstructor = parserClass.getConstructor(RuntimeServices.class);
} catch (NoSuchMethodException nsme) {
throw new VelocityException("parser class must provide a constructor taking a RuntimeServices argument",
nsme);
}
/*
* Which parser pool?
*/
String pp = getString(RuntimeConstants.PARSER_POOL_CLASS);
if (pp != null && pp.length() > 0) {
/*
* if something was specified, then make one.
* if that isn't a ParserPool, consider
* this a huge error and throw
*/
Object o = null;
try {
o = ClassUtils.getNewInstance(pp);
} catch (ClassNotFoundException cnfe) {
String err = "The specified class for ParserPool (" + pp
+ ") does not exist (or is not accessible to the current classloader.";
log.error(err);
throw new VelocityException(err, cnfe);
} catch (InstantiationException ie) {
throw new VelocityException("Could not instantiate class '" + pp + "'", ie);
} catch (IllegalAccessException ae) {
throw new VelocityException("Cannot access class '" + pp + "'", ae);
}
if (!(o instanceof ParserPool)) {
String err = "The specified class for ParserPool (" + pp + ") does not implement " + ParserPool.class
+ " Velocity not initialized correctly.";
log.error(err);
throw new VelocityException(err);
}
parserPool = (PlatformParserPool)o;
parserPool.initialize(this);
/*
* Test parser creation and use generated parser to fill up customized characters
*/
PlatformParser parser = parserPool.getParser();
parserConfiguration = new ParserConfiguration();
parserConfiguration.setDollarChar(parser.dollar());
parserConfiguration.setHashChar(parser.hash());
parserConfiguration.setAtChar(parser.at());
parserConfiguration.setAsteriskChar(parser.asterisk());
parserPool.put(parser);
} else {
/*
* someone screwed up. Lets not fool around...
*/
String err = "It appears that no class was specified as the"
+ " ParserPool. Please ensure that all configuration" + " information is correct.";
log.error(err);
throw new VelocityException(err);
}
}
/**
* Returns a JavaCC generated Parser.
*
* @return Parser javacc generated parser
*/
@Override
public PlatformParser createNewParser() {
requireInitialization();
try {
return parserConstructor.newInstance(this);
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new VelocityException("could not build new parser class", e);
}
}
/**
* Parse the input and return the root of AST node structure.
*
* In the event that it runs out of parsers in the pool, it will create and let them be GC'd dynamically, logging
* that it has to do that. This is considered an exceptional condition. It is expected that the user will set the
* PARSER_POOL_SIZE property appropriately for their application. We will revisit this.
*
* @param reader Reader retrieved by a resource loader
* @param template template being parsed
* @return A root node representing the template as an AST tree.
* @throws ParseException When the template could not be parsed.
*/
@Override
public PlatformSimpleNode parse(Reader reader, Template template) throws ParseException {
requireInitialization();
PlatformParser parser = parserPool.getParser();
boolean keepParser = true;
if (parser == null) {
/*
* If we couldn't get a parser from the pool make one and log it.
*/
log.info("Runtime: ran out of parsers. Creating a new one. Please increment the {} property. The current value is too small.", PARSER_POOL_SIZE);
parser = createNewParser();
keepParser = false;
}
try {
return (PlatformSimpleNode)parser.parse(reader, template);
} finally {
if (keepParser) {
/* Drop the parser Template reference to allow garbage collection */
parser.resetCurrentTemplate();
parserPool.put(parser);
}
}
}
@Override
public PlatformSimpleNode parse(Reader reader, String templateName) throws ParseException {
return _parse(reader, templateName, parserPool);
}
private PlatformSimpleNode _parse(Reader reader, String templateName, PlatformParserPool parserPool)
throws ParseException {
requireInitialization();
PlatformParser parser = parserPool.getParser();
boolean keepParser = true;
if (parser == null) {
/*
* If we couldn't get a parser from the pool make one and log it.
*/
if (log.isInfoEnabled()) {
log.info("Runtime : ran out of parsers. Creating a new one. Please increment the {} property. The current value is too small.", PARSER_POOL_SIZE);
}
parser = createNewParser();
keepParser = false;
}
try {
return parser.parse(reader, templateName);
} finally {
if (keepParser) {
parserPool.put(parser);
}
}
}
private void initializeScopeSettings() {
ExtProperties scopes = configuration.subset(CONTEXT_SCOPE_CONTROL);
if (scopes != null) {
Iterator scopeIterator = scopes.getKeys();
while (scopeIterator.hasNext()) {
String scope = scopeIterator.next();
boolean enabled = scopes.getBoolean(scope);
if (enabled)
enabledScopeControls.add(scope);
}
}
}
/**
* Renders the input string using the context into the output writer. To be used when a template is dynamically
* constructed, or want to use Velocity as a token replacer.
* Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call does not know about
* macros defined during previous calls.
*
* @param context context to use in rendering input string
* @param out Writer in which to render the output
* @param logTag string to be used as the template name for log messages in case of error
* @param instring input string containing the VTL to be rendered
*
* @return {@code True} if successful, false otherwise. If false, see Velocity runtime log
* @throws ParseErrorException The template could not be parsed.
* @throws MethodInvocationException A method on a context object could not be invoked.
* @throws ResourceNotFoundException A referenced resource could not be loaded.
* @since Velocity 1.6
*/
@Override
public boolean evaluate(Context context, Writer out, String logTag, String instring) {
return evaluate(context, out, logTag, new StringReader(instring));
}
/**
* Renders the input reader using the context into the output writer. To be used when a template is dynamically
* constructed, or want to use Velocity as a token replacer.
* Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call does not know about
* macros defined during previous calls.
*
* @param context context to use in rendering input string
* @param writer Writer in which to render the output
* @param logTag string to be used as the template name for log messages in case of error
* @param reader Reader containing the VTL to be rendered
*
* @return {@code True} if successful, false otherwise. If false, see Velocity runtime log
* @throws ParseErrorException The template could not be parsed.
* @throws MethodInvocationException A method on a context object could not be invoked.
* @throws ResourceNotFoundException A referenced resource could not be loaded.
* @since Velocity 1.6
*/
@Override
public boolean evaluate(Context context, Writer writer, String logTag, Reader reader) {
if (logTag == null) {
throw new NullPointerException(
"logTag (i.e. template name) cannot be null, you must provide an identifier for the content being evaluated");
}
PlatformSimpleNode nodeTree = null;
Template t = new Template();
t.setName(logTag);
try {
nodeTree = parse(reader, t);
} catch (ParseException pex) {
throw new ParseErrorException(pex, null);
} catch (TemplateInitException pex) {
throw new ParseErrorException(pex, null);
}
if (nodeTree == null) {
return false;
} else {
return render(context, writer, logTag, nodeTree);
}
}
/**
* Initializes and renders the AST {@link SimpleNode} using the context into the output writer.
*
* @param context context to use in rendering input string
* @param writer Writer in which to render the output
* @param logTag string to be used as the template name for log messages in case of error
* @param nodeTree SimpleNode which is the root of the AST to be rendered
*
* @return {@code True} if successful, false otherwise. If false, see Velocity runtime log for errors
* @throws ParseErrorException The template could not be parsed.
* @throws MethodInvocationException A method on a context object could not be invoked.
* @throws ResourceNotFoundException A referenced resource could not be loaded.
* @since Velocity 1.6
*/
public boolean render(Context context, Writer writer, String logTag, PlatformSimpleNode nodeTree) {
/*
* We want to init then render.
*/
InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
ica.pushCurrentTemplateName(logTag);
try {
try {
nodeTree.init(ica, this);
} catch (TemplateInitException pex) {
throw new ParseErrorException(pex, null);
}
/*
* Pass through application level runtime exceptions.
*/
catch (RuntimeException e) {
throw e;
} catch (Exception e) {
String msg = "RuntimeInstance.render(): init exception for tag = " + logTag;
log.error(msg, e);
throw new VelocityException(msg, e, getLogContext().getStackTrace());
}
try {
if (isScopeControlEnabled(evaluateScopeName)) {
Object previous = ica.get(evaluateScopeName);
context.put(evaluateScopeName, new Scope(this, previous));
}
/*
* optionally put the context in itself if asked so
*/
String self = getString(CONTEXT_AUTOREFERENCE_KEY);
if (self != null)
context.put(self, context);
nodeTree.render(ica, writer);
} catch (StopCommand stop) {
if (!stop.isFor(this)) {
throw stop;
} else {
log.debug(stop.getMessage());
}
} catch (IOException e) {
throw new VelocityException("IO Error in writer: " + e.getMessage(), e,
getLogContext().getStackTrace());
}
} finally {
ica.popCurrentTemplateName();
if (isScopeControlEnabled(evaluateScopeName)) {
Object obj = ica.get(evaluateScopeName);
if (obj instanceof Scope) {
Scope scope = (Scope)obj;
if (scope.getParent() != null) {
ica.put(evaluateScopeName, scope.getParent());
} else if (scope.getReplaced() != null) {
ica.put(evaluateScopeName, scope.getReplaced());
} else {
ica.remove(evaluateScopeName);
}
}
}
}
return true;
}
/**
* Invokes a currently registered Velocimacro with the params provided and places the rendered stream into the
* writer.
* Note: currently only accepts args to the VM if they are in the context.
* Note: only macros in the global context can be called. This method doesn't find macros defined by templates
* during previous mergeTemplate calls if Velocity.VM_PERM_INLINE_LOCAL has been enabled.
*
* @param vmName name of Velocimacro to call
* @param logTag string to be used for template name in case of error. if null, the vmName will be used
* @param params keys for args used to invoke Velocimacro, in java format rather than VTL (eg "foo" or "bar" rather
* than "$foo" or "$bar")
* @param context Context object containing data/objects used for rendering.
* @param writer Writer for output stream
* @return {@code True} if Velocimacro exists and successfully invoked, false otherwise.
* @since 1.6
*/
@Override
public boolean invokeVelocimacro(final String vmName, String logTag, String[] params, final Context context,
final Writer writer) {
/* check necessary parameters */
if (vmName == null || context == null || writer == null) {
String msg =
"RuntimeInstance.invokeVelocimacro(): invalid call: vmName, context, and writer must not be null";
log.error(msg);
throw new NullPointerException(msg);
}
/* handle easily corrected parameters */
if (logTag == null) {
logTag = vmName;
}
if (params == null) {
params = new String[0];
}
/* does the VM exist? (only global scope is scanned so this doesn't find inline macros in templates) */
if (!isVelocimacro(vmName, null)) {
String msg = "RuntimeInstance.invokeVelocimacro(): VM '" + vmName + "' is not registered.";
log.error(msg);
throw new VelocityException(msg, null, getLogContext().getStackTrace());
}
/* now just create the VM call, and use evaluate */
StringBuilder template = new StringBuilder(String.valueOf(parserConfiguration.getHashChar()));
template.append(vmName);
template.append("(");
for (String param : params) {
template.append(" $");
template.append(param);
}
template.append(" )");
return evaluate(context, writer, logTag, template.toString());
}
/**
* Retrieves and caches the configured default encoding for better performance. (VELOCITY-606).
*
* @return The encoding.
*/
private String getDefaultEncoding() {
return defaultEncoding;
}
/**
* Returns a Template
from the resource manager. This method assumes that the character encoding of the
* template is set by the resource.default_encoding
property. The default is UTF-8.
*
* @param name The file name of the desired template.
* @return The template.
* @throws ResourceNotFoundException if template not found from any available source.
* @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
*/
@Override
public Template getTemplate(String name) throws ResourceNotFoundException, ParseErrorException {
return getTemplate(name, null);
}
/**
* Returns a Template
from the resource manager
*
* @param name The name of the desired template.
* @param encoding Character encoding of the template
* @return The template.
* @throws ResourceNotFoundException if template not found from any available source.
* @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
*/
@Override
public Template getTemplate(String name, String encoding) throws ResourceNotFoundException, ParseErrorException {
requireInitialization();
if (encoding == null) {
encoding = getDefaultEncoding();
}
return (Template)resourceManager.getResource(name, PlatformResourceManager.RESOURCE_TEMPLATE, encoding);
}
/**
* Returns a static content resource from the resource manager. Uses the current value if INPUT_ENCODING as the
* character encoding.
*
* @param name Name of content resource to get
* @return parsed ContentResource object ready for use
* @throws ResourceNotFoundException if template not found from any available source.
* @throws ParseErrorException When the template could not be parsed.
*/
@Override
public ContentResource getContent(String name) throws ResourceNotFoundException, ParseErrorException {
/*
* the encoding is irrelvant as we don't do any converstion
* the bytestream should be dumped to the output stream
*/
return getContent(name, getDefaultEncoding());
}
/**
* Returns a static content resource from the resource manager.
*
* @param name Name of content resource to get
* @param encoding Character encoding to use
* @return parsed ContentResource object ready for use
* @throws ResourceNotFoundException if template not found from any available source.
* @throws ParseErrorException When the template could not be parsed.
*/
@Override
public ContentResource getContent(String name, String encoding)
throws ResourceNotFoundException, ParseErrorException {
requireInitialization();
return (ContentResource)resourceManager.getResource(name, PlatformResourceManager.RESOURCE_CONTENT, encoding);
}
/**
* Determines if a template exists and returns name of the loader that provides it. This is a slightly less hokey
* way to support the Velocity.resourceExists() utility method, which was broken when per-template encoding was
* introduced. We can revisit this.
*
* @param resourceName Name of template or content resource
* @return class name of loader than can provide it
*/
@Override
public String getLoaderNameForResource(String resourceName) {
requireInitialization();
return resourceManager.getLoaderNameForResource(resourceName);
}
/**
* Returns the configured logger.
*
* @return A Logger instance
* @since 1.5
*/
@Override
public Logger getLog() {
return log;
}
/**
* Get a logger for the specified child namespace. If a logger was configured using the runtime.log.instance
* configuration property, returns this instance. Otherwise, uses SLF4J LoggerFactory on baseNamespace '.'
* childNamespace.
*
* @param childNamespace Cild namespace.
* @return child namespace logger
*/
@Override
public Logger getLog(String childNamespace) {
Logger log = (Logger)getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE);
if (log == null) {
String loggerName = getString(RUNTIME_LOG_NAME, DEFAULT_RUNTIME_LOG_NAME) + "." + childNamespace;
log = LoggerFactory.getLogger(loggerName);
}
return log;
}
/**
* Get the LogContext object used to tack locations in templates.
*
* @return LogContext object
* @since 2.2
*/
@Override
public LogContext getLogContext() {
return logContext;
}
/**
* String property accessor method with default to hide the configuration implementation.
*
* @param key property key
* @param defaultValue default value to return if key not found in resource manager.
* @return value of key or default
*/
@Override
public String getString(String key, String defaultValue) {
return configuration.getString(key, defaultValue);
}
/**
* Returns the appropriate VelocimacroProxy object if vmName is a valid current Velocimacro.
*
* @param vmName Name of velocimacro requested
* @param renderingTemplate Template we are currently rendering. This information is needed when
* VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL setting is true and template contains a macro with the same name
* as the global macro library.
* @param template Template which acts as the host for the macro
*
* @return VelocimacroProxy
*/
@Override
public Directive getVelocimacro(String vmName, Template renderingTemplate, Template template) {
return vmFactory.getVelocimacro(vmName, renderingTemplate, template);
}
/**
* Adds a new Velocimacro. Usually called by Macro only while parsing.
*
* @param name Name of velocimacro
* @param macro root AST node of the parsed macro
* @param macroArgs Array of macro arguments, containing the #macro() arguments and default values. the 0th is the
* name.
* @param definingTemplate Template containing the source of the macro
*
* @return boolean True if added, false if rejected for some reason (either parameters or permission settings)
*/
@Override
public boolean addVelocimacro(String name, Node macro, List macroArgs, Template definingTemplate) {
return vmFactory.addVelocimacro(stringInterning ? name.intern() : name, macro, macroArgs, definingTemplate);
}
/**
* Checks to see if a VM exists
*
* @param vmName Name of the Velocimacro.
* @param template Template on which to look for the Macro.
* @return True if VM by that name exists, false if not
*/
@Override
public boolean isVelocimacro(String vmName, Template template) {
return vmFactory.isVelocimacro(stringInterning ? vmName.intern() : vmName, template);
}
/* --------------------------------------------------------------------
* R U N T I M E A C C E S S O R M E T H O D S
* --------------------------------------------------------------------
* These are the getXXX() methods that are a simple wrapper
* around the configuration object. This is an attempt
* to make a the Velocity Runtime the single access point
* for all things Velocity, and allow the Runtime to
* adhere as closely as possible the the Mediator pattern
* which is the ultimate goal.
* --------------------------------------------------------------------
*/
/**
* String property accessor method to hide the configuration implementation
*
* @param key property key
* @return value of key or null
*/
@Override
public String getString(String key) {
return StrUtil.trim(configuration.getString(key));
}
/**
* Int property accessor method to hide the configuration implementation.
*
* @param key Property key
* @return value
*/
@Override
public int getInt(String key) {
return configuration.getInt(key);
}
/**
* Int property accessor method to hide the configuration implementation.
*
* @param key property key
* @param defaultValue The default value.
* @return value
*/
@Override
public int getInt(String key, int defaultValue) {
return configuration.getInt(key, defaultValue);
}
/**
* Boolean property accessor method to hide the configuration implementation.
*
* @param key property key
* @param def The default value if property not found.
* @return value of key or default value
*/
@Override
public boolean getBoolean(String key, boolean def) {
return configuration.getBoolean(key, def);
}
/**
* Return the velocity runtime configuration object.
*
* @return Configuration object which houses the Velocity runtime properties.
*/
@Override
public ExtProperties getConfiguration() {
return configuration;
}
/**
* Returns the event handlers for the application.
*
* @return The event handlers for the application.
* @since 1.5
*/
@Override
public EventCartridge getApplicationEventCartridge() {
return eventCartridge;
}
/**
* Gets the application attribute for the given key
*
* @param key The attribute key.
* @return The application attribute for the given key.
*/
@Override
public Object getApplicationAttribute(Object key) {
return applicationAttributes.get(key);
}
/**
* Sets the application attribute for the given key
*
* @param key The attribute key.
* @param o The new application attribute.
* @return The old value of this attribute or null if it hasn't been set before.
*/
@Override
public Object setApplicationAttribute(Object key, Object o) {
return applicationAttributes.put(key, o);
}
/**
* Returns the Uberspect object for this Instance.
*
* @return The Uberspect object for this Instance.
*/
@Override
public Uberspect getUberspect() {
return uberSpect;
}
/**
* Whether to use string interning
*
* @return boolean
*/
@Override
public boolean useStringInterning() {
return stringInterning;
}
/**
* get space gobbling mode
*
* @return indentation mode
*/
@Override
public SpaceGobbling getSpaceGobbling() {
return spaceGobbling;
}
/**
* Get whether hyphens are allowed in identifiers
*
* @return configured boolean flag.
*/
@Override
public boolean isHyphenAllowedInIdentifiers() {
return hyphenAllowedInIdentifiers;
}
/**
* Get whether to provide a scope control object for this scope
*
* @param scopeName Scope
* @return scope control enabled
*/
@Override
public boolean isScopeControlEnabled(String scopeName) {
return enabledScopeControls.contains(scopeName);
}
@Override
public ParserConfiguration getParserConfiguration() {
return parserConfiguration;
}
}