All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.velocity.runtime.AbstractRuntimeInstance Maven / Gradle / Ivy

package org.apache.velocity.runtime;

import org.apache.velocity.Template;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.EventHandler;
import org.apache.velocity.app.event.IncludeEventHandler;
import org.apache.velocity.app.event.InvalidReferenceEventHandler;
import org.apache.velocity.app.event.MethodExceptionEventHandler;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import org.apache.velocity.context.Context;
import org.apache.velocity.context.InternalContextAdapterImpl;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.TemplateInitException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.directive.Macro;
import org.apache.velocity.runtime.directive.Scope;
import org.apache.velocity.runtime.directive.StopCommand;
import org.apache.velocity.runtime.parser.LogContext;
import org.apache.velocity.runtime.parser.ParseException;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.apache.velocity.runtime.resource.ContentResource;
//import org.apache.velocity.runtime.resource.ResourceManager;
import org.apache.velocity.util.ClassUtils;
import org.apache.velocity.util.ExtProperties;
import org.apache.velocity.util.RuntimeServicesAware;
import org.apache.velocity.util.introspection.ChainableUberspector;
import org.apache.velocity.util.introspection.LinkingUberspector;
import org.apache.velocity.util.introspection.Uberspect;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zodiac.sdk.toolkit.util.lang.StrUtil;
import org.zodiac.template.velocity.platform.parser.PlatformParser;
import org.zodiac.template.velocity.platform.parser.PlatformSimpleNode;
import org.zodiac.template.velocity.platform.runtime.PlatformParserPool;
import org.zodiac.template.velocity.platform.runtime.PlatformRuntimeServices;
import org.zodiac.template.velocity.platform.runtime.resource.PlatformResourceManager;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;

public abstract class AbstractRuntimeInstance implements RuntimeConstants, PlatformRuntimeServices {

    /**
     * VelocimacroFactory object to manage VMs
     */
    private VelocimacroFactory vmFactory = null;

    /**
     * The Runtime logger. The default instance is the "org.apache.velocity" logger.
     */
    private Logger log = LoggerFactory.getLogger(DEFAULT_RUNTIME_LOG_NAME);

    /**
     * The Runtime parser pool
     */
    private PlatformParserPool parserPool;

    /**
     * Indicate whether the Runtime is in the midst of initialization.
     */
    private boolean initializing = false;

    /**
     * Indicate whether the Runtime has been fully initialized.
     */
    private volatile boolean initialized = false;

    /**
     * These are the properties that are laid down over top of the default properties when requested.
     */
    private ExtProperties overridingProperties = null;

    /**
     * This is a hashtable of initialized directives. The directives that populate this hashtable are taken from the
     * RUNTIME_DEFAULT_DIRECTIVES property file.
     */
    private Map runtimeDirectives = new Hashtable<>();
    /**
     * Copy of the actual runtimeDirectives that is shared between parsers. Whenever directives are updated, the
     * synchronized runtimeDirectives is first updated and then an unsynchronized copy of it is passed to parsers.
     */
    private Map runtimeDirectivesShared;

    /**
     * Object that houses the configuration options for the velocity runtime. The ExtProperties object allows the
     * convenient retrieval of a subset of properties. For example all the properties for a resource loader can be
     * retrieved from the main ExtProperties object using something like the following:
     *
     * ExtProperties loaderConfiguration = configuration.subset(loaderID);
     *
     * And a configuration is a lot more convenient to deal with then conventional properties objects, or Maps.
     */
    private ExtProperties configuration = new ExtProperties();

    private PlatformResourceManager resourceManager = null;

    /**
     * This stores the engine-wide set of event handlers. Event handlers for each specific merge are stored in the
     * context.
     */
    private EventCartridge eventCartridge = null;

    /**
     * Whether to use string interning
     */
    private boolean stringInterning = false;

    /**
     * Scope name for evaluate(...) calls.
     */
    private String evaluateScopeName = "evaluate";

    /**
     * Scope names for which to provide scope control objects in the context
     */
    private Set enabledScopeControls = new HashSet<>();

    /**
     * Opaque reference to something specified by the application for use in application supplied/specified pluggable
     * components
     */
    private Map applicationAttributes = null;

    /**
     * Uberspector
     */
    private Uberspect uberSpect;

    /**
     * Default encoding
     */
    private String defaultEncoding;

    /**
     * Space gobbling mode
     */
    private SpaceGobbling spaceGobbling;

    /**
     * Whether hyphen is allowed in identifiers
     */
    private boolean hyphenAllowedInIdentifiers;

    /**
     * The LogContext object used to track location in templates
     */
    private LogContext logContext;

    /**
     * Configured parser class
     * 
     */
    private Constructor parserConstructor;

    /**
     * Configured replacement characters in parser grammar
     * 
     */
    private ParserConfiguration parserConfiguration;

    protected AbstractRuntimeInstance() {
        reset();
    }

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