diff options
author | Antonio Petrelli <apetrelli@apache.org> | 2010-07-01 19:00:14 +0000 |
---|---|---|
committer | Antonio Petrelli <apetrelli@apache.org> | 2010-07-01 19:00:14 +0000 |
commit | f557075071a8dd848b36220f2a904a2f84e73e11 (patch) | |
tree | bcb523b1238b07c9b3b900cd0808a5d2028a51d0 /velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java | |
parent | 9604bbd6847ba884bacddf88f09528820c48ccc0 (diff) | |
download | apache-velocity-engine-f557075071a8dd848b36220f2a904a2f84e73e11.tar.gz |
VELOCITYSB-4
Renamed directories of modules.
git-svn-id: https://svn.apache.org/repos/asf/velocity/sandbox/maven-reorg/engine/trunk@959747 13f79535-47bb-0310-9956-ffa450edef68
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 | 1852 |
1 files changed, 1852 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..62dc7a54 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java @@ -0,0 +1,1852 @@ +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 java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.collections.ExtendedProperties; +import org.apache.commons.lang.text.StrBuilder; +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.VelocimacroProxy; +import org.apache.velocity.runtime.directive.Scope; +import org.apache.velocity.runtime.directive.StopCommand; +import org.apache.velocity.runtime.log.Log; +import org.apache.velocity.runtime.log.LogManager; +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.RuntimeServicesAware; +import org.apache.velocity.util.StringUtils; +import org.apache.velocity.util.introspection.ChainableUberspector; +import org.apache.velocity.util.introspection.Introspector; +import org.apache.velocity.util.introspection.LinkingUberspector; +import org.apache.velocity.util.introspection.Uberspect; +import org.apache.velocity.util.introspection.UberspectLoggable; + +/** + * 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. + * + * The Runtime will also cooperate with external + * systems like Turbine. Runtime properties can + * set and then the Runtime is initialized. + * + * Turbine, for example, knows where the templates + * are to be loaded from, and where the Velocity + * log file should be placed. + * + * So in the case of Velocity cooperating with Turbine + * the code might look something like the following: + * + * <blockquote><code><pre> + * ri.setProperty(Runtime.FILE_RESOURCE_LOADER_PATH, templatePath); + * ri.setProperty(Runtime.RUNTIME_LOG, pathToVelocityLog); + * ri.init(); + * </pre></code></blockquote> + * + * <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. We start with an instance of + * a 'primordial logger', which just collects log messages + * then, when the log system is initialized, all the + * messages get dumpted out of the primordial one into the real one. + */ + private Log log = new Log(); + + /** + * 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 ExtendedProperties 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 ExtendedProperties 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 ExtendedProperties object + * using something like the following: + * + * ExtendedProperties loaderConfiguration = + * configuration.subset(loaderID); + * + * And a configuration is a lot more convenient to deal + * with then conventional properties objects, or Maps. + */ + private ExtendedProperties configuration = new ExtendedProperties(); + + 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; + + /* + * Each runtime instance has it's own introspector + * to ensure that each instance is completely separate. + */ + private Introspector introspector = null; + + /* + * Settings for provision of root scope for evaluate(...) calls. + */ + private String evaluateScopeName = "evaluate"; + private boolean provideEvaluateScope = false; + + /* + * Opaque reference to something specificed by the + * application for use in application supplied/specified + * pluggable components + */ + private Map applicationAttributes = null; + private Uberspect uberSpect; + private String encoding; + + /** + * 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> + */ + public synchronized void init() + { + if (!initialized && !initializing) + { + log.debug("Initializing Velocity, Calling init()..."); + initializing = true; + + log.trace("*******************************************************************"); + log.debug("Starting Apache Velocity v2.0-dev (compiled: 2009-02-05 07:40:05)"); + log.trace("RuntimeInstance initializing."); + + initializeProperties(); + initializeLog(); + initializeResourceManager(); + initializeDirectives(); + initializeEventHandlers(); + initializeParserPool(); + + initializeIntrospection(); + initializeEvaluateScopeSettings(); + /* + * initialize the VM Factory. It will use the properties + * accessable from Runtime, so keep this here at the end. + */ + vmFactory.initVelocimacro(); + + log.trace("RuntimeInstance successfully initialized."); + + initialized = true; + initializing = false; + } + } + + /** + * Resets the instance, so Velocity can be re-initialized again. + * + * @since 2.0.0 + */ + public synchronized void reset() + { + this.configuration = new ExtendedProperties(); + this.encoding = null; + this.evaluateScopeName = "evaluate"; + this.eventCartridge = null; + this.initialized = false; + this.initializing = false; + this.log = new Log(); + this.overridingProperties = null; + this.parserPool = null; + this.provideEvaluateScope = false; + this.resourceManager = null; + this.runtimeDirectives = new Hashtable(); + this.runtimeDirectivesShared = null; + this.uberSpect = null; + + /* + * create a VM factory, introspector, and application attributes + */ + vmFactory = new VelocimacroFactory( this ); + + /* + * make a new introspector and initialize it + */ + introspector = new Introspector(getLog()); + + /* + * 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 + */ + public boolean isInitialized() + { + return initialized; + } + + /** + * Init or die! (with some log help, of course) + */ + private void requireInitialization() + { + if (!initialized) + { + try + { + init(); + } + catch (Exception e) + { + getLog().error("Could not auto-initialize Velocity", e); + throw new RuntimeException("Velocity could not be initialized!", e); + } + } + } + + /** + * Gets the classname for the Uberspect introspection package and + * instantiates an instance. + */ + private void initializeIntrospection() + { + String[] uberspectors = configuration.getStringArray(RuntimeConstants.UBERSPECT_CLASSNAME); + for (int i=0; i <uberspectors.length;i++) + { + String rm = uberspectors[i]; + 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 UberspectLoggable) + { + ((UberspectLoggable)u).setLog(getLog()); + } + + 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() + .getResourceAsStream('/' + DEFAULT_RUNTIME_PROPERTIES); + + configuration.load( inputStream ); + + if (log.isDebugEnabled()) + { + log.debug("Default Properties File: " + + new File(DEFAULT_RUNTIME_PROPERTIES).getPath()); + } + + + } + 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 + */ + public void setProperty(String key, Object value) + { + if (overridingProperties == null) + { + overridingProperties = new ExtendedProperties(); + } + + overridingProperties.setProperty(key, value); + } + + + /** + * Add all properties contained in the file fileName to the RuntimeInstance properties + */ + public void setProperties(String fileName) + { + ExtendedProperties props = null; + try + { + props = new ExtendedProperties(fileName); + } + catch (IOException e) + { + throw new VelocityException("Error reading properties from '" + + fileName + "'", e); + } + + Enumeration en = props.keys(); + while (en.hasMoreElements()) + { + String key = en.nextElement().toString(); + setProperty(key, props.get(key)); + } + } + + + /** + * Add all the properties in props to the RuntimeInstance 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 ExtendedProperties + * object to use. This is useful where the external + * system also uses the ExtendedProperties class and + * the velocity configuration is a subset of + * parent application's configuration. This is + * the case with Turbine. + * + * @param configuration + */ + public void setConfiguration( ExtendedProperties 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 + */ + public void addProperty(String key, Object value) + { + if (overridingProperties == null) + { + overridingProperties = new ExtendedProperties(); + } + + overridingProperties.addProperty(key, value); + } + + /** + * Clear the values pertaining to a particular + * property. + * + * @param key of property to clear + */ + 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. + */ + 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.nullTrim((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() == false) + { + setDefaultProperties(); + } + + if( overridingProperties != null) + { + configuration.combine(overridingProperties); + } + } + + /** + * Initialize the Velocity Runtime with a Properties + * object. + * + * @param p Velocity properties for initialization + */ + public void init(Properties p) + { + setProperties(ExtendedProperties.convertProperties(p)); + init(); + } + + private void setProperties(ExtendedProperties p) + { + if (overridingProperties == null) + { + overridingProperties = p; + } + else + { + overridingProperties.combine(p); + } + } + + /** + * Initialize the Velocity Runtime with the name of + * ExtendedProperties object. + * + * @param configurationFile + */ + public void init(String configurationFile) + { + try + { + setProperties(new ExtendedProperties(configurationFile)); + } + catch (IOException e) + { + throw new VelocityException("Error reading properties from '" + + configurationFile + "'", e); + } + init(); + } + + private void initializeResourceManager() + { + /* + * Which resource manager? + */ + String rm = getString(RuntimeConstants.RESOURCE_MANAGER_CLASS); + + 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); + } + else + { + /* + * someone screwed up. Lets not fool around... + */ + + String err = "It appears that no class 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(); + + /** + * 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 ( int i=0; i < referenceinsertion.length; i++ ) + { + EventHandler ev = initializeSpecificEventHandler(referenceinsertion[i],RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION,ReferenceInsertionEventHandler.class); + if (ev != null) + eventCartridge.addReferenceInsertionEventHandler((ReferenceInsertionEventHandler) ev); + } + } + + String[] methodexception = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION); + if ( methodexception != null ) + { + for ( int i=0; i < methodexception.length; i++ ) + { + EventHandler ev = initializeSpecificEventHandler(methodexception[i],RuntimeConstants.EVENTHANDLER_METHODEXCEPTION,MethodExceptionEventHandler.class); + if (ev != null) + eventCartridge.addMethodExceptionHandler((MethodExceptionEventHandler) ev); + } + } + + String[] includeHandler = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INCLUDE); + if ( includeHandler != null ) + { + for ( int i=0; i < includeHandler.length; i++ ) + { + EventHandler ev = initializeSpecificEventHandler(includeHandler[i],RuntimeConstants.EVENTHANDLER_INCLUDE,IncludeEventHandler.class); + if (ev != null) + eventCartridge.addIncludeEventHandler((IncludeEventHandler) ev); + } + } + + String[] invalidReferenceSet = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES); + if ( invalidReferenceSet != null ) + { + for ( int i=0; i < invalidReferenceSet.length; i++ ) + { + EventHandler ev = initializeSpecificEventHandler(invalidReferenceSet[i],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() + { + // since the Log we started with was just placeholding, + // let's update it with the real LogChute settings. + try + { + LogManager.updateLog(this.log, this); + } + 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("userdirective"); + + for( int i = 0; i < userdirective.length; i++) + { + loadDirective(userdirective[i]); + if (log.isDebugEnabled()) + { + log.debug("Loaded User Directive: " + userdirective[i]); + } + } + + } + + /** + * 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 + */ + public Directive getDirective(String name) + { + return (Directive) 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() + { + Map tmp = new HashMap(runtimeDirectives); + runtimeDirectivesShared = tmp; + } + + /** + * 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() + { + /* + * 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); + } + 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 + */ + public Parser createNewParser() + { + requireInitialization(); + + Parser parser = new Parser(this); + return parser; + } + + /** + * 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 string String to be parsed + * @param templateName name of the template being parsed + * @return A root node representing the template as an AST tree. + * @throws ParseException When the string could not be parsed as a template. + * @since 1.6 + */ + public SimpleNode parse(String string, String templateName) + throws ParseException + { + return parse(new StringReader(string), templateName); + } + + /** + * 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 templateName name of the template being parsed + * @return A root node representing the template as an AST tree. + * @throws ParseException When the template could not be parsed. + */ + public SimpleNode parse(Reader reader, String templateName) + throws ParseException + { + /* + * do it and dump the VM namespace for this template + */ + return parse(reader, templateName, true); + } + + /** + * Parse the input and return the root of the AST node structure. + * + * @param reader Reader retrieved by a resource loader + * @param templateName name of the template being parsed + * @param dumpNamespace flag to dump the Velocimacro namespace for this template + * @return A root node representing the template as an AST tree. + * @throws ParseException When the template could not be parsed. + */ + public SimpleNode parse(Reader reader, String templateName, boolean dumpNamespace) + throws ParseException + { + requireInitialization(); + + Parser 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. + */ + if (log.isInfoEnabled()) + { + 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 + { + /* + * dump namespace if we are told to. Generally, you want to + * do this - you don't in special circumstances, such as + * when a VM is getting init()-ed & parsed + */ + if (dumpNamespace) + { + dumpVMNamespace(templateName); + } + return parser.parse(reader, templateName); + } + finally + { + if (keepParser) + { + parserPool.put(parser); + } + + } + } + + private void initializeEvaluateScopeSettings() + { + String property = evaluateScopeName+'.'+PROVIDE_SCOPE_CONTROL; + provideEvaluateScope = getBoolean(property, provideEvaluateScope); + } + + /** + * 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. + * + * @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 + */ + 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. + * + * @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 + */ + 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; + try + { + nodeTree = parse(reader, logTag); + } + 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; + getLog().error(msg, e); + throw new VelocityException(msg, e); + } + + try + { + if (provideEvaluateScope) + { + Object previous = ica.get(evaluateScopeName); + context.put(evaluateScopeName, new Scope(this, previous)); + } + nodeTree.render(ica, writer); + } + catch (StopCommand stop) + { + if (!stop.isFor(this)) + { + throw stop; + } + else if (getLog().isDebugEnabled()) + { + getLog().debug(stop.getMessage()); + } + } + catch (IOException e) + { + throw new VelocityException("IO Error in writer: " + e.getMessage(), e); + } + } + finally + { + ica.popCurrentTemplateName(); + if (provideEvaluateScope) + { + 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. + * + * @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 + */ + 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"; + getLog().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? */ + if (!isVelocimacro(vmName, logTag)) + { + String msg = "RuntimeInstance.invokeVelocimacro() : VM '" + vmName + + "' is not registered."; + getLog().error(msg); + throw new VelocityException(msg); + } + + /* now just create the VM call, and use evaluate */ + StrBuilder template = new StrBuilder("#"); + template.append(vmName); + template.append("("); + for( int i = 0; i < params.length; i++) + { + template.append(" $"); + template.append(params[i]); + } + template.append(" )"); + + return evaluate(context, writer, logTag, template.toString()); + } + + /** + * Retrieves and caches the configured default encoding + * for better performance. (VELOCITY-606) + */ + private String getDefaultEncoding() + { + if (encoding == null) + { + encoding = getString(INPUT_ENCODING, ENCODING_DEFAULT); + } + return encoding; + } + + /** + * Returns a <code>Template</code> from the resource manager. + * This method assumes that the character encoding of the + * template is set by the <code>input.encoding</code> + * property. The default is "ISO-8859-1" + * + * @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. + */ + public Template getTemplate(String name) + throws ResourceNotFoundException, ParseErrorException + { + return getTemplate(name, getDefaultEncoding()); + } + + /** + * 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. + */ + public Template getTemplate(String name, String encoding) + throws ResourceNotFoundException, ParseErrorException + { + requireInitialization(); + + 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. + */ + 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. + */ + 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 + */ + public String getLoaderNameForResource(String resourceName) + { + requireInitialization(); + + return resourceManager.getLoaderNameForResource(resourceName); + } + + /** + * Returns a convenient Log instance that wraps the current LogChute. + * Use this to log error messages. It has the usual methods. + * + * @return A convenience Log instance that wraps the current LogChute. + * @since 1.5 + */ + public Log getLog() + { + return log; + } + + /** + * 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 + */ + 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 templateName Name of the template that contains the velocimacro. + * @return The requested VelocimacroProxy. + * @since 1.6 + */ + public Directive getVelocimacro(String vmName, String templateName) + { + return vmFactory.getVelocimacro( vmName, templateName ); + } + + /** + * Returns the appropriate VelocimacroProxy object if vmName + * is a valid current Velocimacro. + * + * @param vmName Name of velocimacro requested + * @param templateName Name of the namespace. + * @param renderingTemplate Name of the 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. + * + * @since Velocity 1.6 + * + * @return VelocimacroProxy + */ + public Directive getVelocimacro(String vmName, String templateName, String renderingTemplate) + { + return vmFactory.getVelocimacro( vmName, templateName, renderingTemplate ); + } + + /** + * Return a list of VelocimacroProxies that are defined by the given + * template name. + */ + public List<VelocimacroProxy> getVelocimacros(String templateName) + { + return vmFactory.getVelocimacros(templateName); + } + + + /** + * Adds a new Velocimacro. Usually called by Macro only while parsing. + * + * Called by org.apache.velocity.runtime.directive.processAndRegister + * + * @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 sourceTemplate + * + * @since Velocity 1.6 + * + * @return boolean True if added, false if rejected for some + * reason (either parameters or permission settings) + */ + public boolean addVelocimacro( String name, + Node macro, + List<Macro.MacroArg> macroArgs, + String sourceTemplate ) + { + return vmFactory.addVelocimacro(name.intern(), macro, macroArgs, sourceTemplate); + } + + + /** + * Checks to see if a VM exists + * + * @param vmName Name of the Velocimacro. + * @param templateName Template on which to look for the Macro. + * @return True if VM by that name exists, false if not + */ + public boolean isVelocimacro( String vmName, String templateName ) + { + return vmFactory.isVelocimacro(vmName.intern(), templateName); + } + + /** + * tells the vmFactory to dump the specified namespace. This is to support + * clearing the VM list when in inline-VM-local-scope mode + * @param namespace Namespace to dump. + * @return True if namespace was dumped successfully. + */ + public boolean dumpVMNamespace(String namespace) + { + return vmFactory.dumpVMNamespace( namespace ); + } + + /* -------------------------------------------------------------------- + * 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 + */ + public String getString(String key) + { + return StringUtils.nullTrim(configuration.getString(key)); + } + + /** + * Int property accessor method to hide the configuration implementation. + * + * @param key Property key + * @return value + */ + 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 + */ + 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 + */ + 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. + */ + public ExtendedProperties getConfiguration() + { + return configuration; + } + + /** + * Return the Introspector for this instance + * @return The Introspector for this instance + */ + public Introspector getIntrospector() + { + return introspector; + } + + /** + * Returns the event handlers for the application. + * @return The event handlers for the application. + * @since 1.5 + */ + public EventCartridge getApplicationEventCartridge() + { + return eventCartridge; + } + + + /** + * Gets the application attribute for the given key + * + * @param key + * @return The application attribute for the given key. + */ + 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. + */ + 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. + */ + public Uberspect getUberspect() + { + return uberSpect; + } + +} |