diff options
Diffstat (limited to 'velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java')
-rw-r--r-- | velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java | 2025 |
1 files changed, 2025 insertions, 0 deletions
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java new file mode 100644 index 00000000..3d77055d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java @@ -0,0 +1,2025 @@ +package org.apache.velocity.runtime; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache 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://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +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.Parser; +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.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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; + +/** + * <p>This is the Runtime system for Velocity. It is the + * single access point for all functionality in Velocity. + * It adheres to the mediator pattern and is the only + * structure that developers need to be familiar with + * in order to get Velocity to perform.</p> + * + * <p>The Runtime will also cooperate with external + * systems, which can make all needed setProperty() calls + * before calling init().</p> + * <pre> + * ----------------------------------------------------------------------- + * N O T E S O N R U N T I M E I N I T I A L I Z A T I O N + * ----------------------------------------------------------------------- + * init() + * + * If init() is called by itself the RuntimeInstance will initialize + * with a set of default values. + * ----------------------------------------------------------------------- + * init(String/Properties) + * + * In this case the default velocity properties are layed down + * first to provide a solid base, then any properties provided + * in the given properties object will override the corresponding + * default property. + * ----------------------------------------------------------------------- + * </pre> + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:jlb@houseofdistraction.com">Jeff Bowden</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magusson Jr.</a> + * @version $Id$ + */ +public class RuntimeInstance implements RuntimeConstants, RuntimeServices +{ + /** + * 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 ParserPool 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<String, Directive> 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<String, Directive> 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 ResourceManager 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<String> enabledScopeControls = new HashSet<>(); + + /** + * Opaque reference to something specified by the + * application for use in application supplied/specified + * pluggable components + */ + private Map<Object, Object> 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 + * @since 2.2 + */ + private Constructor<? extends Parser> parserConstructor; + + /** + * Configured replacement characters in parser grammar + * @since 2.2 + */ + private ParserConfiguration parserConfiguration; + + /** + * Creates a new RuntimeInstance object. + */ + public RuntimeInstance() + { + reset(); + } + + /** + * This is the primary initialization method in the Velocity + * Runtime. The systems that are setup/initialized here are + * as follows: + * + * <ul> + * <li>Logging System</li> + * <li>ResourceManager</li> + * <li>EventHandler</li> + * <li>Parser Pool</li> + * <li>Global Cache</li> + * <li>Static Content Include System</li> + * <li>Velocimacro System</li> + * </ul> + */ + @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) {} // prefer 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 + */ + 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<String> 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 + */ + 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 + * @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 + * @param 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 StringUtils.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); + + /* reinitialize defaultEncoding in case it is overridden */ + defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT); + } + } + + /** + * 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 + */ + @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 (ResourceManager.class.isAssignableFrom(inst.getClass())) + { + resourceManager = (ResourceManager)inst; + resourceManager.initialize(this); + } + else + { + String msg = inst.getClass().getName() + " object set as resource.manager.instance is not a valid org.apache.velocity.runtime.resource.ResourceManager."; + 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 ResourceManager)) + { + String err = "The specified class for ResourceManager (" + rm + + ") does not implement " + ResourceManager.class.getName() + + "; Velocity is not initialized correctly."; + + log.error(err); + throw new VelocityException(err); + } + + resourceManager = (ResourceManager) 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 + */ + 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 Parser> parserClass; + try + { + parserClass = (Class<? extends Parser>)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 = (ParserPool) o; + + parserPool.initialize(this); + + /* + * test parser creation and use generated parser to fill up customized characters + */ + Parser parser = parserPool.get(); + 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 Parser 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. + * <br><br> + * 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 SimpleNode parse(Reader reader, Template template) + throws ParseException + { + requireInitialization(); + + Parser parser = parserPool.get(); + 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 parser.pool.size property." + + " The current value is too small."); + parser = createNewParser(); + keepParser = false; + } + + try + { + return parser.parse(reader, template); + } + finally + { + if (keepParser) + { + /* drop the parser Template reference to allow garbage collection */ + parser.resetCurrentTemplate(); + parserPool.put(parser); + } + + } + } + + private void initializeScopeSettings() + { + ExtProperties scopes = configuration.subset(CONTEXT_SCOPE_CONTROL); + if (scopes != null) + { + Iterator<String> 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. + * <br> + * 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 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. + * <br> + * 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 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"); + } + + SimpleNode 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 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, SimpleNode 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. + * <br> + * Note: currently only accepts args to the VM if they are in the context. + * <br> + * 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 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) + */ + private String getDefaultEncoding() + { + return defaultEncoding; + } + + /** + * Returns a <code>Template</code> from the resource manager. + * This method assumes that the character encoding of the + * template is set by the <code>resource.default_encoding</code> + * 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 <code>Template</code> 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, + ResourceManager.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, + ResourceManager.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 + * @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<Macro.MacroArg> 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 StringUtils.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 + * @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 + * @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 + * @return scope control enabled + * @since 2.1 + */ + @Override + public boolean isScopeControlEnabled(String scopeName) + { + return enabledScopeControls.contains(scopeName); + } + + @Override + public ParserConfiguration getParserConfiguration() + { + return parserConfiguration; + } +} |