• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.apache.velocity.runtime;
2 
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21 
22 import org.apache.velocity.Template;
23 import org.apache.velocity.app.event.EventCartridge;
24 import org.apache.velocity.app.event.EventHandler;
25 import org.apache.velocity.app.event.IncludeEventHandler;
26 import org.apache.velocity.app.event.InvalidReferenceEventHandler;
27 import org.apache.velocity.app.event.MethodExceptionEventHandler;
28 import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
29 import org.apache.velocity.context.Context;
30 import org.apache.velocity.context.InternalContextAdapterImpl;
31 import org.apache.velocity.exception.MethodInvocationException;
32 import org.apache.velocity.exception.ParseErrorException;
33 import org.apache.velocity.exception.ResourceNotFoundException;
34 import org.apache.velocity.exception.TemplateInitException;
35 import org.apache.velocity.exception.VelocityException;
36 import org.apache.velocity.runtime.directive.Directive;
37 import org.apache.velocity.runtime.directive.Macro;
38 import org.apache.velocity.runtime.directive.Scope;
39 import org.apache.velocity.runtime.directive.StopCommand;
40 import org.apache.velocity.runtime.parser.LogContext;
41 import org.apache.velocity.runtime.parser.ParseException;
42 import org.apache.velocity.runtime.parser.Parser;
43 import org.apache.velocity.runtime.parser.node.Node;
44 import org.apache.velocity.runtime.parser.node.SimpleNode;
45 import org.apache.velocity.runtime.resource.ContentResource;
46 import org.apache.velocity.runtime.resource.ResourceManager;
47 import org.apache.velocity.util.ClassUtils;
48 import org.apache.velocity.util.ExtProperties;
49 import org.apache.velocity.util.RuntimeServicesAware;
50 import org.apache.velocity.util.introspection.ChainableUberspector;
51 import org.apache.velocity.util.introspection.LinkingUberspector;
52 import org.apache.velocity.util.introspection.Uberspect;
53 
54 import org.apache.commons.lang3.StringUtils;
55 
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58 
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.Reader;
62 import java.io.StringReader;
63 import java.io.Writer;
64 import java.lang.reflect.Constructor;
65 import java.lang.reflect.InvocationTargetException;
66 import java.util.Enumeration;
67 import java.util.HashMap;
68 import java.util.HashSet;
69 import java.util.Hashtable;
70 import java.util.Iterator;
71 import java.util.List;
72 import java.util.Locale;
73 import java.util.Map;
74 import java.util.NoSuchElementException;
75 import java.util.Properties;
76 import java.util.Set;
77 
78 /**
79  * <p>This is the Runtime system for Velocity. It is the
80  * single access point for all functionality in Velocity.
81  * It adheres to the mediator pattern and is the only
82  * structure that developers need to be familiar with
83  * in order to get Velocity to perform.</p>
84  *
85  * <p>The Runtime will also cooperate with external
86  * systems, which can make all needed setProperty() calls
87  * before calling init().</p>
88  * <pre>
89  * -----------------------------------------------------------------------
90  * 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
91  * -----------------------------------------------------------------------
92  * init()
93  *
94  * If init() is called by itself the RuntimeInstance will initialize
95  * with a set of default values.
96  * -----------------------------------------------------------------------
97  * init(String/Properties)
98  *
99  * In this case the default velocity properties are layed down
100  * first to provide a solid base, then any properties provided
101  * in the given properties object will override the corresponding
102  * default property.
103  * -----------------------------------------------------------------------
104  * </pre>
105  *
106  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
107  * @author <a href="mailto:jlb@houseofdistraction.com">Jeff Bowden</a>
108  * @author <a href="mailto:geirm@optonline.net">Geir Magusson Jr.</a>
109  * @version $Id$
110  */
111 public class RuntimeInstance implements RuntimeConstants, RuntimeServices
112 {
113     /**
114      *  VelocimacroFactory object to manage VMs
115      */
116     private  VelocimacroFactory vmFactory = null;
117 
118     /**
119      * The Runtime logger.  The default instance is the "org.apache.velocity" logger.
120      */
121     private Logger log = LoggerFactory.getLogger(DEFAULT_RUNTIME_LOG_NAME);
122 
123     /**
124      * The Runtime parser pool
125      */
126     private ParserPool parserPool;
127 
128     /**
129      * Indicate whether the Runtime is in the midst of initialization.
130      */
131     private boolean initializing = false;
132 
133     /**
134      * Indicate whether the Runtime has been fully initialized.
135      */
136     private volatile boolean initialized = false;
137 
138     /**
139      * These are the properties that are laid down over top
140      * of the default properties when requested.
141      */
142     private ExtProperties overridingProperties = null;
143 
144     /**
145      * This is a hashtable of initialized directives.
146      * The directives that populate this hashtable are
147      * taken from the RUNTIME_DEFAULT_DIRECTIVES
148      * property file.
149      */
150     private Map<String, Directive> runtimeDirectives = new Hashtable<>();
151     /**
152      * Copy of the actual runtimeDirectives that is shared between
153      * parsers. Whenever directives are updated, the synchronized
154      * runtimeDirectives is first updated and then an unsynchronized
155      * copy of it is passed to parsers.
156      */
157     private Map<String, Directive> runtimeDirectivesShared;
158 
159     /**
160      * Object that houses the configuration options for
161      * the velocity runtime. The ExtProperties object allows
162      * the convenient retrieval of a subset of properties.
163      * For example all the properties for a resource loader
164      * can be retrieved from the main ExtProperties object
165      * using something like the following:
166      *
167      * ExtProperties loaderConfiguration =
168      *         configuration.subset(loaderID);
169      *
170      * And a configuration is a lot more convenient to deal
171      * with then conventional properties objects, or Maps.
172      */
173     private  ExtProperties configuration = new ExtProperties();
174 
175     private ResourceManager resourceManager = null;
176 
177     /**
178      * This stores the engine-wide set of event handlers.  Event handlers for
179      * each specific merge are stored in the context.
180      */
181     private EventCartridge eventCartridge = null;
182 
183     /**
184      * Whether to use string interning
185      */
186     private boolean stringInterning = false;
187 
188     /**
189      * Scope name for evaluate(...) calls.
190      */
191     private String evaluateScopeName = "evaluate";
192 
193     /**
194      * Scope names for which to provide scope control objects in the context
195      */
196     private Set<String> enabledScopeControls = new HashSet<>();
197 
198     /**
199      *  Opaque reference to something specified by the
200      *  application for use in application supplied/specified
201      *  pluggable components
202      */
203     private Map<Object, Object> applicationAttributes = null;
204 
205     /**
206      *  Uberspector
207      */
208     private Uberspect uberSpect;
209 
210     /**
211      * Default encoding
212      */
213     private String defaultEncoding;
214 
215     /**
216      * Space gobbling mode
217      */
218     private SpaceGobbling spaceGobbling;
219 
220     /**
221      * Whether hyphen is allowed in identifiers
222      */
223     private boolean hyphenAllowedInIdentifiers;
224 
225     /**
226      * The LogContext object used to track location in templates
227      */
228     private LogContext logContext;
229 
230     /**
231      * Configured parser class
232      * @since 2.2
233      */
234     private Constructor<? extends Parser> parserConstructor;
235 
236     /**
237      * Configured replacement characters in parser grammar
238      * @since 2.2
239      */
240     private ParserConfiguration parserConfiguration;
241 
242     /**
243      * Creates a new RuntimeInstance object.
244      */
RuntimeInstance()245     public RuntimeInstance()
246     {
247         reset();
248     }
249 
250     /**
251      * This is the primary initialization method in the Velocity
252      * Runtime. The systems that are setup/initialized here are
253      * as follows:
254      *
255      * <ul>
256      *   <li>Logging System</li>
257      *   <li>ResourceManager</li>
258      *   <li>EventHandler</li>
259      *   <li>Parser Pool</li>
260      *   <li>Global Cache</li>
261      *   <li>Static Content Include System</li>
262      *   <li>Velocimacro System</li>
263      * </ul>
264      */
265     @Override
init()266     public synchronized void init()
267     {
268         if (!initialized && !initializing)
269         {
270             try
271             {
272                 log.debug("Initializing Velocity, Calling init()...");
273                 initializing = true;
274 
275                 log.trace("*****************************");
276                 log.debug("Starting Apache Velocity v" + VelocityEngineVersion.VERSION);
277                 log.trace("RuntimeInstance initializing.");
278 
279                 initializeProperties();
280                 initializeSelfProperties();
281                 initializeLog();
282                 initializeResourceManager();
283                 initializeDirectives();
284                 initializeEventHandlers();
285                 initializeParserPool();
286 
287                 initializeIntrospection();
288                 initializeScopeSettings();
289                 /*
290                  *  initialize the VM Factory.  It will use the properties
291                  * accessible from Runtime, so keep this here at the end.
292                  */
293                 vmFactory.initVelocimacro();
294 
295                 log.trace("RuntimeInstance successfully initialized.");
296 
297                 initialized = true;
298                 initializing = false;
299             }
300             catch(RuntimeException re)
301             {
302                 // initialization failed at some point... try to reset everything
303                 try
304                 {
305                     reset();
306                 }
307                 catch(RuntimeException re2) {} // prefer throwing the original exception
308                 throw re;
309             }
310             finally
311             {
312                 initializing = false;
313             }
314         }
315     }
316 
317     /**
318      * Resets the instance, so Velocity can be re-initialized again.
319      *
320      * @since 2.0.0
321      */
reset()322     public synchronized void reset()
323     {
324         this.configuration = new ExtProperties();
325         this.defaultEncoding = null;
326         this.evaluateScopeName = "evaluate";
327         this.eventCartridge = null;
328         this.initialized = false;
329         this.initializing = false;
330         this.overridingProperties = null;
331         this.parserPool = null;
332         this.enabledScopeControls.clear();
333         this.resourceManager = null;
334         this.runtimeDirectives = new Hashtable<>();
335         this.runtimeDirectivesShared = null;
336         this.uberSpect = null;
337         this.stringInterning = false;
338         this.parserConfiguration = new ParserConfiguration();
339 
340         /*
341          *  create a VM factory, introspector, and application attributes
342          */
343         vmFactory = new VelocimacroFactory( this );
344 
345         /*
346          * and a store for the application attributes
347          */
348         applicationAttributes = new HashMap<>();
349     }
350 
351     /**
352      * Returns true if the RuntimeInstance has been successfully initialized.
353      * @return True if the RuntimeInstance has been successfully initialized.
354      * @since 1.5
355      */
356     @Override
isInitialized()357     public boolean isInitialized()
358     {
359         return initialized;
360     }
361 
362     /**
363      * Init or die! (with some log help, of course)
364      */
requireInitialization()365     private void requireInitialization()
366     {
367         if (!initialized)
368         {
369             try
370             {
371                 init();
372             }
373             catch (Exception e)
374             {
375                 log.error("Could not auto-initialize Velocity", e);
376                 throw new RuntimeException("Velocity could not be initialized!", e);
377             }
378         }
379     }
380 
381     /**
382      *  Initialize runtime internal properties
383      */
initializeSelfProperties()384     private void initializeSelfProperties()
385     {
386         /* initialize string interning (defaults to false) */
387         stringInterning = getBoolean(RUNTIME_STRING_INTERNING, true);
388 
389         /* initialize indentation mode (defaults to 'lines') */
390         String im = getString(SPACE_GOBBLING, "lines");
391         try
392         {
393             spaceGobbling = SpaceGobbling.valueOf(im.toUpperCase(Locale.ROOT));
394         }
395         catch (NoSuchElementException nse)
396         {
397             spaceGobbling = SpaceGobbling.LINES;
398         }
399 
400         /* init parser behavior */
401         hyphenAllowedInIdentifiers = getBoolean(PARSER_HYPHEN_ALLOWED, false);
402     }
403 
getConfiguredCharacter(String configKey, char defaultChar)404     private char getConfiguredCharacter(String configKey, char defaultChar)
405     {
406         String configuredChar = getString(configKey);
407         if (configuredChar != null)
408         {
409             if (configuredChar.length() != 1)
410             {
411                 throw new IllegalArgumentException(String.format("value of '%s' must be a single character string, but is '%s'", configKey, configuredChar));
412             }
413             return configuredChar.charAt(0);
414         }
415         return defaultChar;
416     }
417 
418     /**
419      *  Gets the classname for the Uberspect introspection package and
420      *  instantiates an instance.
421      */
initializeIntrospection()422     private void initializeIntrospection()
423     {
424         String[] uberspectors = configuration.getStringArray(RuntimeConstants.UBERSPECT_CLASSNAME);
425         for (String rm : uberspectors)
426         {
427             Object o = null;
428 
429             try
430             {
431                 o = ClassUtils.getNewInstance(rm);
432             }
433             catch (ClassNotFoundException cnfe)
434             {
435                 String err = "The specified class for Uberspect (" + rm
436                     + ") does not exist or is not accessible to the current classloader.";
437                 log.error(err);
438                 throw new VelocityException(err, cnfe);
439             }
440             catch (InstantiationException ie)
441             {
442                 throw new VelocityException("Could not instantiate class '" + rm + "'", ie);
443             }
444             catch (IllegalAccessException ae)
445             {
446                 throw new VelocityException("Cannot access class '" + rm + "'", ae);
447             }
448 
449             if (!(o instanceof Uberspect))
450             {
451                 String err = "The specified class for Uberspect ("
452                     + rm + ") does not implement " + Uberspect.class.getName()
453                     + "; Velocity is not initialized correctly.";
454 
455                 log.error(err);
456                 throw new VelocityException(err);
457             }
458 
459             Uberspect u = (Uberspect) o;
460 
461             if (u instanceof RuntimeServicesAware)
462             {
463                 ((RuntimeServicesAware) u).setRuntimeServices(this);
464             }
465 
466             if (uberSpect == null)
467             {
468                 uberSpect = u;
469             } else
470             {
471                 if (u instanceof ChainableUberspector)
472                 {
473                     ((ChainableUberspector) u).wrap(uberSpect);
474                     uberSpect = u;
475                 } else
476                 {
477                     uberSpect = new LinkingUberspector(uberSpect, u);
478                 }
479             }
480         }
481 
482         if(uberSpect != null)
483         {
484             uberSpect.init();
485         }
486         else
487         {
488            /*
489             *  someone screwed up.  Lets not fool around...
490             */
491 
492            String err = "It appears that no class was specified as the"
493            + " Uberspect.  Please ensure that all configuration"
494            + " information is correct.";
495 
496            log.error(err);
497            throw new VelocityException(err);
498         }
499     }
500 
501     /**
502      * Initializes the Velocity Runtime with properties file.
503      * The properties file may be in the file system proper,
504      * or the properties file may be in the classpath.
505      */
setDefaultProperties()506     private void setDefaultProperties()
507     {
508         InputStream inputStream = null;
509         try
510         {
511             inputStream = getClass().getClassLoader()
512                 .getResourceAsStream(DEFAULT_RUNTIME_PROPERTIES);
513 
514             if (inputStream == null)
515                 throw new IOException("Resource not found: " + DEFAULT_RUNTIME_PROPERTIES);
516 
517             configuration.load( inputStream );
518 
519             /* populate 'defaultEncoding' member */
520             defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT);
521 
522             log.debug("Default Properties resource: {}", DEFAULT_RUNTIME_PROPERTIES);
523         }
524         catch (IOException ioe)
525         {
526             String msg = "Cannot get Velocity Runtime default properties!";
527             log.error(msg, ioe);
528             throw new RuntimeException(msg, ioe);
529         }
530         finally
531         {
532             try
533             {
534                 if (inputStream != null)
535                 {
536                     inputStream.close();
537                 }
538             }
539             catch (IOException ioe)
540             {
541                 String msg = "Cannot close Velocity Runtime default properties!";
542                 log.error(msg, ioe);
543                 throw new RuntimeException(msg, ioe);
544             }
545         }
546     }
547 
548     /**
549      * Allows an external system to set a property in
550      * the Velocity Runtime.
551      *
552      * @param key property key
553      * @param  value property value
554      */
555     @Override
setProperty(String key, Object value)556     public void setProperty(String key, Object value)
557     {
558         if (overridingProperties == null)
559         {
560             overridingProperties = new ExtProperties();
561         }
562 
563         overridingProperties.setProperty(key, value);
564     }
565 
566 
567     /**
568      * Add all properties contained in the file fileName to the RuntimeInstance properties
569      * @param fileName
570      */
setProperties(String fileName)571     public void setProperties(String fileName)
572     {
573         ExtProperties props = null;
574         try
575         {
576               props = new ExtProperties(fileName);
577         }
578         catch (IOException e)
579         {
580               throw new VelocityException("Error reading properties from '"
581                 + fileName + "'", e);
582         }
583 
584         Enumeration<String> en = props.keys();
585         while (en.hasMoreElements())
586         {
587             String key = en.nextElement();
588             setProperty(key, props.get(key));
589         }
590     }
591 
592 
593     /**
594      * Add all the properties in props to the RuntimeInstance properties
595      * @param props
596      */
setProperties(Properties props)597     public void setProperties(Properties props)
598     {
599         Enumeration en = props.keys();
600         while (en.hasMoreElements())
601         {
602             String key = en.nextElement().toString();
603             setProperty(key, props.get(key));
604         }
605     }
606 
607     /**
608      * Allow an external system to set an ExtProperties
609      * object to use.
610      *
611      * @param  configuration
612      * @since 2.0
613      */
614     @Override
setConfiguration(ExtProperties configuration)615     public void setConfiguration(ExtProperties configuration)
616     {
617         if (overridingProperties == null)
618         {
619             overridingProperties = configuration;
620         }
621         else
622         {
623             // Avoid possible ConcurrentModificationException
624             if (overridingProperties != configuration)
625             {
626                 overridingProperties.combine(configuration);
627             }
628         }
629     }
630 
631     /**
632      * Add a property to the configuration. If it already
633      * exists then the value stated here will be added
634      * to the configuration entry. For example, if
635      *
636      * resource.loader = file
637      *
638      * is already present in the configuration and you
639      *
640      * addProperty("resource.loader", "classpath")
641      *
642      * Then you will end up with a Vector like the
643      * following:
644      *
645      * ["file", "classpath"]
646      *
647      * @param  key
648      * @param  value
649      */
650     @Override
addProperty(String key, Object value)651     public void addProperty(String key, Object value)
652     {
653         if (overridingProperties == null)
654         {
655             overridingProperties = new ExtProperties();
656         }
657 
658         overridingProperties.addProperty(key, value);
659     }
660 
661     /**
662      * Clear the values pertaining to a particular
663      * property.
664      *
665      * @param key of property to clear
666      */
667     @Override
clearProperty(String key)668     public void clearProperty(String key)
669     {
670         if (overridingProperties != null)
671         {
672             overridingProperties.clearProperty(key);
673         }
674     }
675 
676     /**
677      *  Allows an external caller to get a property.  The calling
678      *  routine is required to know the type, as this routine
679      *  will return an Object, as that is what properties can be.
680      *
681      *  @param key property to return
682      *  @return Value of the property or null if it does not exist.
683      */
684     @Override
getProperty(String key)685     public Object getProperty(String key)
686     {
687         Object o = null;
688 
689         /*
690          * Before initialization, check the user-entered properties first.
691          */
692         if (!initialized && overridingProperties != null)
693         {
694             o = overridingProperties.get(key);
695         }
696 
697         /*
698          * After initialization, configuration will hold all properties.
699          */
700         if (o == null)
701         {
702             o = configuration.getProperty(key);
703         }
704         if (o instanceof String)
705         {
706             return StringUtils.trim((String) o);
707         }
708         else
709         {
710             return o;
711         }
712     }
713 
714     /**
715      * Initialize Velocity properties, if the default
716      * properties have not been laid down first then
717      * do so. Then proceed to process any overriding
718      * properties. Laying down the default properties
719      * gives a much greater chance of having a
720      * working system.
721      */
initializeProperties()722     private void initializeProperties()
723     {
724         /*
725          * Always lay down the default properties first as
726          * to provide a solid base.
727          */
728         if ( !configuration.isInitialized() )
729         {
730             setDefaultProperties();
731         }
732 
733         if( overridingProperties != null )
734         {
735             configuration.combine(overridingProperties);
736 
737             /* reinitialize defaultEncoding in case it is overridden */
738             defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT);
739         }
740     }
741 
742     /**
743      * Initialize the Velocity Runtime with a Properties
744      * object.
745      *
746      * @param p Velocity properties for initialization
747      */
748     @Override
init(Properties p)749     public void init(Properties p)
750     {
751         setConfiguration(ExtProperties.convertProperties(p));
752         init();
753     }
754 
755     /**
756      * Initialize the Velocity Runtime with a
757      * properties file path.
758      *
759      * @param configurationFile
760      */
761     @Override
init(String configurationFile)762     public void init(String configurationFile)
763     {
764         setProperties(configurationFile);
765         init();
766     }
767 
initializeResourceManager()768     private void initializeResourceManager()
769     {
770         /*
771          * Which resource manager?
772          */
773         Object inst = getProperty(RuntimeConstants.RESOURCE_MANAGER_INSTANCE);
774         String rm = getString(RuntimeConstants.RESOURCE_MANAGER_CLASS);
775 
776         if (inst != null)
777         {
778             if (ResourceManager.class.isAssignableFrom(inst.getClass()))
779             {
780                 resourceManager = (ResourceManager)inst;
781                 resourceManager.initialize(this);
782             }
783             else
784             {
785                 String msg = inst.getClass().getName() + " object set as resource.manager.instance is not a valid org.apache.velocity.runtime.resource.ResourceManager.";
786                 log.error(msg);
787                 throw new VelocityException(msg);
788             }
789         }
790         else if (rm != null && rm.length() > 0)
791         {
792             /*
793              *  if something was specified, then make one.
794              *  if that isn't a ResourceManager, consider
795              *  this a huge error and throw
796              */
797 
798             Object o = null;
799 
800             try
801             {
802                o = ClassUtils.getNewInstance( rm );
803             }
804             catch (ClassNotFoundException cnfe )
805             {
806                 String err = "The specified class for ResourceManager (" + rm
807                     + ") does not exist or is not accessible to the current classloader.";
808                 log.error(err);
809                 throw new VelocityException(err, cnfe);
810             }
811             catch (InstantiationException ie)
812             {
813               throw new VelocityException("Could not instantiate class '" + rm + "'", ie);
814             }
815             catch (IllegalAccessException ae)
816             {
817               throw new VelocityException("Cannot access class '" + rm + "'", ae);
818             }
819 
820             if (!(o instanceof ResourceManager))
821             {
822                 String err = "The specified class for ResourceManager (" + rm
823                     + ") does not implement " + ResourceManager.class.getName()
824                     + "; Velocity is not initialized correctly.";
825 
826                 log.error(err);
827                 throw new VelocityException(err);
828             }
829 
830             resourceManager = (ResourceManager) o;
831             resourceManager.initialize(this);
832             setProperty(RESOURCE_MANAGER_INSTANCE, resourceManager);
833          }
834          else
835          {
836             /*
837              *  someone screwed up.  Lets not fool around...
838              */
839 
840             String err = "It appears that no class or instance was specified as the"
841             + " ResourceManager.  Please ensure that all configuration"
842             + " information is correct.";
843 
844             log.error(err);
845             throw new VelocityException( err );
846         }
847     }
848 
initializeEventHandlers()849     private void initializeEventHandlers()
850     {
851 
852         eventCartridge = new EventCartridge();
853         eventCartridge.setRuntimeServices(this);
854 
855         /*
856          * For each type of event handler, get the class name, instantiate it, and store it.
857          */
858 
859         String[] referenceinsertion = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION);
860         if ( referenceinsertion != null )
861         {
862             for (String aReferenceinsertion : referenceinsertion)
863             {
864                 EventHandler ev = initializeSpecificEventHandler(aReferenceinsertion, RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, ReferenceInsertionEventHandler.class);
865                 if (ev != null)
866                     eventCartridge.addReferenceInsertionEventHandler((ReferenceInsertionEventHandler) ev);
867             }
868         }
869 
870         String[] methodexception = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION);
871         if ( methodexception != null )
872         {
873             for (String aMethodexception : methodexception)
874             {
875                 EventHandler ev = initializeSpecificEventHandler(aMethodexception, RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, MethodExceptionEventHandler.class);
876                 if (ev != null)
877                     eventCartridge.addMethodExceptionHandler((MethodExceptionEventHandler) ev);
878             }
879         }
880 
881         String[] includeHandler = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INCLUDE);
882         if ( includeHandler != null )
883         {
884             for (String anIncludeHandler : includeHandler)
885             {
886                 EventHandler ev = initializeSpecificEventHandler(anIncludeHandler, RuntimeConstants.EVENTHANDLER_INCLUDE, IncludeEventHandler.class);
887                 if (ev != null)
888                     eventCartridge.addIncludeEventHandler((IncludeEventHandler) ev);
889             }
890         }
891 
892         String[] invalidReferenceSet = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES);
893         if ( invalidReferenceSet != null )
894         {
895             for (String anInvalidReferenceSet : invalidReferenceSet)
896             {
897                 EventHandler ev = initializeSpecificEventHandler(anInvalidReferenceSet, RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES, InvalidReferenceEventHandler.class);
898                 if (ev != null)
899                 {
900                     eventCartridge.addInvalidReferenceEventHandler((InvalidReferenceEventHandler) ev);
901                 }
902             }
903         }
904 
905 
906     }
907 
initializeSpecificEventHandler(String classname, String paramName, Class<?> EventHandlerInterface)908     private EventHandler initializeSpecificEventHandler(String classname, String paramName, Class<?> EventHandlerInterface)
909     {
910         if ( classname != null && classname.length() > 0)
911         {
912             Object o = null;
913             try
914             {
915                 o = ClassUtils.getNewInstance(classname);
916             }
917             catch (ClassNotFoundException cnfe )
918             {
919                 String err = "The specified class for "
920                     + paramName + " (" + classname
921                     + ") does not exist or is not accessible to the current classloader.";
922                 log.error(err);
923                 throw new VelocityException(err, cnfe);
924             }
925             catch (InstantiationException ie)
926             {
927               throw new VelocityException("Could not instantiate class '" + classname + "'", ie);
928             }
929             catch (IllegalAccessException ae)
930             {
931               throw new VelocityException("Cannot access class '" + classname + "'", ae);
932             }
933 
934             if (!EventHandlerInterface.isAssignableFrom(EventHandlerInterface))
935             {
936                 String err = "The specified class for " + paramName + " ("
937                     + classname + ") does not implement "
938                     + EventHandlerInterface.getName()
939                     + "; Velocity is not initialized correctly.";
940 
941                 log.error(err);
942                 throw new VelocityException(err);
943             }
944 
945             EventHandler ev = (EventHandler) o;
946             if ( ev instanceof RuntimeServicesAware )
947                 ((RuntimeServicesAware) ev).setRuntimeServices(this);
948             return ev;
949 
950         } else
951             return null;
952     }
953 
954     /**
955      * Initialize the Velocity logging system.
956      */
initializeLog()957     private void initializeLog()
958     {
959         // if we were provided a specific logger or logger name, let's use it
960         try
961         {
962             /* If a Logger instance was set as a configuration
963              * value, use that.  This is any class the user specifies.
964              */
965             Object o = getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE);
966             if (o != null)
967             {
968                 // check for a Logger
969                 if (Logger.class.isAssignableFrom(o.getClass()))
970                 {
971                     //looks ok
972                     log = (Logger)o;
973                 }
974                 else
975                 {
976                     String msg = o.getClass().getName() + " object set as runtime.log.instance is not a valid org.slf4j.Logger implementation.";
977                     log.error(msg);
978                     throw new VelocityException(msg);
979                 }
980             }
981             else
982             {
983                 /* otherwise, see if a logger name was specified.
984                  */
985                 o = getProperty(RuntimeConstants.RUNTIME_LOG_NAME);
986                 if (o != null)
987                 {
988                     if (o instanceof String)
989                     {
990                         log = LoggerFactory.getLogger((String)o);
991                     }
992                     else
993                     {
994                         String msg = o.getClass().getName() + " object set as runtime.log.name is not a valid string.";
995                         log.error(msg);
996                         throw new VelocityException(msg);
997                     }
998                 }
999             }
1000             /* else keep our default Velocity logger
1001              */
1002 
1003             /* Initialize LogContext */
1004             boolean trackLocation = getBoolean(RUNTIME_LOG_TRACK_LOCATION, false);
1005             logContext = new LogContext(trackLocation);
1006         }
1007         catch (Exception e)
1008         {
1009             throw new VelocityException("Error initializing log: " + e.getMessage(), e);
1010         }
1011     }
1012 
1013 
1014     /**
1015      * This methods initializes all the directives
1016      * that are used by the Velocity Runtime. The
1017      * directives to be initialized are listed in
1018      * the RUNTIME_DEFAULT_DIRECTIVES properties
1019      * file.
1020      */
initializeDirectives()1021     private void initializeDirectives()
1022     {
1023         Properties directiveProperties = new Properties();
1024 
1025         /*
1026          * Grab the properties file with the list of directives
1027          * that we should initialize.
1028          */
1029 
1030         InputStream inputStream = null;
1031 
1032         try
1033         {
1034             inputStream = getClass().getResourceAsStream('/' + DEFAULT_RUNTIME_DIRECTIVES);
1035 
1036             if (inputStream == null)
1037             {
1038                 throw new VelocityException("Error loading directive.properties! " +
1039                                     "Something is very wrong if these properties " +
1040                                     "aren't being located. Either your Velocity " +
1041                                     "distribution is incomplete or your Velocity " +
1042                                     "jar file is corrupted!");
1043             }
1044 
1045             directiveProperties.load(inputStream);
1046 
1047         }
1048         catch (IOException ioe)
1049         {
1050             String msg = "Error while loading directive properties!";
1051             log.error(msg, ioe);
1052             throw new RuntimeException(msg, ioe);
1053         }
1054         finally
1055         {
1056             try
1057             {
1058                 if (inputStream != null)
1059                 {
1060                     inputStream.close();
1061                 }
1062             }
1063             catch (IOException ioe)
1064             {
1065                 String msg = "Cannot close directive properties!";
1066                 log.error(msg, ioe);
1067                 throw new RuntimeException(msg, ioe);
1068             }
1069         }
1070 
1071 
1072         /*
1073          * Grab all the values of the properties. These
1074          * are all class names for example:
1075          *
1076          * org.apache.velocity.runtime.directive.Foreach
1077          */
1078         Enumeration directiveClasses = directiveProperties.elements();
1079 
1080         while (directiveClasses.hasMoreElements())
1081         {
1082             String directiveClass = (String) directiveClasses.nextElement();
1083             loadDirective(directiveClass);
1084             log.debug("Loaded System Directive: {}", directiveClass);
1085         }
1086 
1087         /*
1088          *  now the user's directives
1089          */
1090 
1091         String[] userdirective = configuration.getStringArray(CUSTOM_DIRECTIVES);
1092 
1093         for (String anUserdirective : userdirective)
1094         {
1095             loadDirective(anUserdirective);
1096             log.debug("Loaded User Directive: {}", anUserdirective);
1097         }
1098 
1099     }
1100 
1101     /**
1102      * Programatically add a directive.
1103      * @param directive
1104      */
addDirective(Directive directive)1105     public synchronized void addDirective(Directive directive)
1106     {
1107         runtimeDirectives.put(directive.getName(), directive);
1108         updateSharedDirectivesMap();
1109     }
1110 
1111     /**
1112      * Retrieve a previously instantiated directive.
1113      * @param name name of the directive
1114      * @return the {@link Directive} for that name
1115      */
1116     @Override
getDirective(String name)1117     public Directive getDirective(String name)
1118     {
1119         return runtimeDirectivesShared.get(name);
1120     }
1121 
1122     /**
1123      * Remove a directive.
1124      * @param name name of the directive.
1125      */
removeDirective(String name)1126     public synchronized void removeDirective(String name)
1127     {
1128         runtimeDirectives.remove(name);
1129         updateSharedDirectivesMap();
1130     }
1131 
1132     /**
1133      * Makes an unsynchronized copy of the directives map
1134      * that is used for Directive lookups by all parsers.
1135      *
1136      * This follows Copy-on-Write pattern. The cost of creating
1137      * a new map is acceptable since directives are typically
1138      * set and modified only during Velocity setup phase.
1139      */
updateSharedDirectivesMap()1140     private void updateSharedDirectivesMap()
1141     {
1142         runtimeDirectivesShared = new HashMap<>(runtimeDirectives);
1143     }
1144 
1145     /**
1146      *  instantiates and loads the directive with some basic checks
1147      *
1148      *  @param directiveClass classname of directive to load
1149      */
loadDirective(String directiveClass)1150     public void loadDirective(String directiveClass)
1151     {
1152         try
1153         {
1154             Object o = ClassUtils.getNewInstance( directiveClass );
1155 
1156             if (o instanceof Directive)
1157             {
1158                 Directive directive = (Directive) o;
1159                 addDirective(directive);
1160             }
1161             else
1162             {
1163                 String msg = directiveClass + " does not implement "
1164                     + Directive.class.getName() + "; it cannot be loaded.";
1165                 log.error(msg);
1166                 throw new VelocityException(msg);
1167             }
1168         }
1169         // The ugly threesome:  ClassNotFoundException,
1170         // IllegalAccessException, InstantiationException.
1171         // Ignore Findbugs complaint for now.
1172         catch (Exception e)
1173         {
1174             String msg = "Failed to load Directive: " + directiveClass;
1175             log.error(msg, e);
1176             throw new VelocityException(msg, e);
1177         }
1178     }
1179 
1180 
1181     /**
1182      * Initializes the Velocity parser pool.
1183      */
initializeParserPool()1184     private void initializeParserPool()
1185     {
1186         /*
1187          * First initialize parser class. If it's not valid or not found, it will generate an error
1188          * later on in this method when parser creation is tester.
1189          */
1190         String parserClassName = getString(PARSER_CLASS, DEFAULT_PARSER_CLASS);
1191         Class<? extends Parser> parserClass;
1192         try
1193         {
1194             parserClass = (Class<? extends Parser>)ClassUtils.getClass(parserClassName);
1195         }
1196         catch (ClassNotFoundException cnfe)
1197         {
1198             throw new VelocityException("parser class not found: " + parserClassName, cnfe);
1199         }
1200         try
1201         {
1202             parserConstructor = parserClass.getConstructor(RuntimeServices.class);
1203         }
1204         catch (NoSuchMethodException nsme)
1205         {
1206             throw new VelocityException("parser class must provide a constructor taking a RuntimeServices argument", nsme);
1207         }
1208 
1209         /*
1210          * Which parser pool?
1211          */
1212         String pp = getString(RuntimeConstants.PARSER_POOL_CLASS);
1213 
1214         if (pp != null && pp.length() > 0)
1215         {
1216             /*
1217              *  if something was specified, then make one.
1218              *  if that isn't a ParserPool, consider
1219              *  this a huge error and throw
1220              */
1221 
1222             Object o = null;
1223 
1224             try
1225             {
1226                 o = ClassUtils.getNewInstance( pp );
1227             }
1228             catch (ClassNotFoundException cnfe )
1229             {
1230                 String err = "The specified class for ParserPool ("
1231                     + pp
1232                     + ") does not exist (or is not accessible to the current classloader.";
1233                 log.error(err);
1234                 throw new VelocityException(err, cnfe);
1235             }
1236             catch (InstantiationException ie)
1237             {
1238               throw new VelocityException("Could not instantiate class '" + pp + "'", ie);
1239             }
1240             catch (IllegalAccessException ae)
1241             {
1242               throw new VelocityException("Cannot access class '" + pp + "'", ae);
1243             }
1244 
1245             if (!(o instanceof ParserPool))
1246             {
1247                 String err = "The specified class for ParserPool ("
1248                     + pp + ") does not implement " + ParserPool.class
1249                     + " Velocity not initialized correctly.";
1250 
1251                 log.error(err);
1252                 throw new VelocityException(err);
1253             }
1254 
1255             parserPool = (ParserPool) o;
1256 
1257             parserPool.initialize(this);
1258 
1259             /*
1260              * test parser creation and use generated parser to fill up customized characters
1261              */
1262             Parser parser = parserPool.get();
1263             parserConfiguration = new ParserConfiguration();
1264             parserConfiguration.setDollarChar(parser.dollar());
1265             parserConfiguration.setHashChar(parser.hash());
1266             parserConfiguration.setAtChar(parser.at());
1267             parserConfiguration.setAsteriskChar(parser.asterisk());
1268             parserPool.put(parser);
1269         }
1270         else
1271         {
1272             /*
1273              *  someone screwed up.  Lets not fool around...
1274              */
1275 
1276             String err = "It appears that no class was specified as the"
1277                 + " ParserPool.  Please ensure that all configuration"
1278                 + " information is correct.";
1279 
1280             log.error(err);
1281             throw new VelocityException( err );
1282         }
1283 
1284     }
1285 
1286     /**
1287      * Returns a JavaCC generated Parser.
1288      *
1289      * @return Parser javacc generated parser
1290      */
1291     @Override
createNewParser()1292     public Parser createNewParser()
1293     {
1294         requireInitialization();
1295         try
1296         {
1297             return parserConstructor.newInstance(this);
1298         }
1299         catch (IllegalAccessException | InstantiationException | InvocationTargetException e)
1300         {
1301             throw new VelocityException("could not build new parser class", e);
1302         }
1303     }
1304 
1305     /**
1306      * Parse the input and return the root of
1307      * AST node structure.
1308      * <br><br>
1309      *  In the event that it runs out of parsers in the
1310      *  pool, it will create and let them be GC'd
1311      *  dynamically, logging that it has to do that.  This
1312      *  is considered an exceptional condition.  It is
1313      *  expected that the user will set the
1314      *  PARSER_POOL_SIZE property appropriately for their
1315      *  application.  We will revisit this.
1316      *
1317      * @param reader Reader retrieved by a resource loader
1318      * @param template template being parsed
1319      * @return A root node representing the template as an AST tree.
1320      * @throws ParseException When the template could not be parsed.
1321      */
1322     @Override
parse(Reader reader, Template template)1323     public SimpleNode parse(Reader reader, Template template)
1324         throws ParseException
1325     {
1326         requireInitialization();
1327 
1328         Parser parser = parserPool.get();
1329         boolean keepParser = true;
1330         if (parser == null)
1331         {
1332             /*
1333              *  if we couldn't get a parser from the pool make one and log it.
1334              */
1335             log.info("Runtime: ran out of parsers. Creating a new one. "
1336                      + " Please increment the parser.pool.size property."
1337                      + " The current value is too small.");
1338             parser = createNewParser();
1339             keepParser = false;
1340         }
1341 
1342         try
1343         {
1344             return parser.parse(reader, template);
1345         }
1346         finally
1347         {
1348             if (keepParser)
1349             {
1350                 /* drop the parser Template reference to allow garbage collection */
1351                 parser.resetCurrentTemplate();
1352                 parserPool.put(parser);
1353             }
1354 
1355         }
1356     }
1357 
initializeScopeSettings()1358     private void initializeScopeSettings()
1359     {
1360         ExtProperties scopes = configuration.subset(CONTEXT_SCOPE_CONTROL);
1361         if (scopes != null)
1362         {
1363             Iterator<String> scopeIterator = scopes.getKeys();
1364             while (scopeIterator.hasNext())
1365             {
1366                 String scope = scopeIterator.next();
1367                 boolean enabled = scopes.getBoolean(scope);
1368                 if (enabled) enabledScopeControls.add(scope);
1369             }
1370         }
1371     }
1372 
1373     /**
1374      * Renders the input string using the context into the output writer.
1375      * To be used when a template is dynamically constructed, or want to use
1376      * Velocity as a token replacer.
1377      * <br>
1378      * Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call
1379      * does not know about macros defined during previous calls.
1380      *
1381      * @param context context to use in rendering input string
1382      * @param out  Writer in which to render the output
1383      * @param logTag  string to be used as the template name for log
1384      *                messages in case of error
1385      * @param instring input string containing the VTL to be rendered
1386      *
1387      * @return true if successful, false otherwise.  If false, see
1388      *              Velocity runtime log
1389      * @throws ParseErrorException The template could not be parsed.
1390      * @throws MethodInvocationException A method on a context object could not be invoked.
1391      * @throws ResourceNotFoundException A referenced resource could not be loaded.
1392      * @since Velocity 1.6
1393      */
1394     @Override
evaluate(Context context, Writer out, String logTag, String instring)1395     public boolean evaluate(Context context, Writer out,
1396                             String logTag, String instring)
1397     {
1398         return evaluate(context, out, logTag, new StringReader(instring));
1399     }
1400 
1401     /**
1402      * Renders the input reader using the context into the output writer.
1403      * To be used when a template is dynamically constructed, or want to
1404      * use Velocity as a token replacer.
1405      * <br>
1406      * Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call
1407      * does not know about macros defined during previous calls.
1408      *
1409      * @param context context to use in rendering input string
1410      * @param writer  Writer in which to render the output
1411      * @param logTag  string to be used as the template name for log messages
1412      *                in case of error
1413      * @param reader Reader containing the VTL to be rendered
1414      *
1415      * @return true if successful, false otherwise.  If false, see
1416      *              Velocity runtime log
1417      * @throws ParseErrorException The template could not be parsed.
1418      * @throws MethodInvocationException A method on a context object could not be invoked.
1419      * @throws ResourceNotFoundException A referenced resource could not be loaded.
1420      * @since Velocity 1.6
1421      */
1422     @Override
evaluate(Context context, Writer writer, String logTag, Reader reader)1423     public boolean evaluate(Context context, Writer writer,
1424                             String logTag, Reader reader)
1425     {
1426         if (logTag == null)
1427         {
1428             throw new NullPointerException("logTag (i.e. template name) cannot be null, you must provide an identifier for the content being evaluated");
1429         }
1430 
1431         SimpleNode nodeTree = null;
1432         Template t = new Template();
1433         t.setName(logTag);
1434         try
1435         {
1436             nodeTree = parse(reader, t);
1437         }
1438         catch (ParseException pex)
1439         {
1440             throw new ParseErrorException(pex, null);
1441         }
1442         catch (TemplateInitException pex)
1443         {
1444             throw new ParseErrorException(pex, null);
1445         }
1446 
1447         if (nodeTree == null)
1448         {
1449             return false;
1450         }
1451         else
1452         {
1453             return render(context, writer, logTag, nodeTree);
1454         }
1455     }
1456 
1457 
1458     /**
1459      * Initializes and renders the AST {@link SimpleNode} using the context
1460      * into the output writer.
1461      *
1462      * @param context context to use in rendering input string
1463      * @param writer  Writer in which to render the output
1464      * @param logTag  string to be used as the template name for log messages
1465      *                in case of error
1466      * @param nodeTree SimpleNode which is the root of the AST to be rendered
1467      *
1468      * @return true if successful, false otherwise.  If false, see
1469      *              Velocity runtime log for errors
1470      * @throws ParseErrorException The template could not be parsed.
1471      * @throws MethodInvocationException A method on a context object could not be invoked.
1472      * @throws ResourceNotFoundException A referenced resource could not be loaded.
1473      * @since Velocity 1.6
1474      */
render(Context context, Writer writer, String logTag, SimpleNode nodeTree)1475     public boolean render(Context context, Writer writer,
1476                           String logTag, SimpleNode nodeTree)
1477     {
1478         /*
1479          * we want to init then render
1480          */
1481         InternalContextAdapterImpl ica =
1482             new InternalContextAdapterImpl(context);
1483 
1484         ica.pushCurrentTemplateName(logTag);
1485 
1486         try
1487         {
1488             try
1489             {
1490                 nodeTree.init(ica, this);
1491             }
1492             catch (TemplateInitException pex)
1493             {
1494                 throw new ParseErrorException(pex, null);
1495             }
1496             /*
1497              * pass through application level runtime exceptions
1498              */
1499             catch(RuntimeException e)
1500             {
1501                 throw e;
1502             }
1503             catch(Exception e)
1504             {
1505                 String msg = "RuntimeInstance.render(): init exception for tag = "+logTag;
1506                 log.error(msg, e);
1507                 throw new VelocityException(msg, e, getLogContext().getStackTrace());
1508             }
1509 
1510             try
1511             {
1512                 if (isScopeControlEnabled(evaluateScopeName))
1513                 {
1514                     Object previous = ica.get(evaluateScopeName);
1515                     context.put(evaluateScopeName, new Scope(this, previous));
1516                 }
1517                 /*
1518                  * optionally put the context in itself if asked so
1519                  */
1520                 String self = getString(CONTEXT_AUTOREFERENCE_KEY);
1521                 if (self != null) context.put(self, context);
1522                 nodeTree.render(ica, writer);
1523             }
1524             catch (StopCommand stop)
1525             {
1526                 if (!stop.isFor(this))
1527                 {
1528                     throw stop;
1529                 }
1530                 else
1531                 {
1532                     log.debug(stop.getMessage());
1533                 }
1534             }
1535             catch (IOException e)
1536             {
1537                 throw new VelocityException("IO Error in writer: " + e.getMessage(), e, getLogContext().getStackTrace());
1538             }
1539         }
1540         finally
1541         {
1542             ica.popCurrentTemplateName();
1543             if (isScopeControlEnabled(evaluateScopeName))
1544             {
1545                 Object obj = ica.get(evaluateScopeName);
1546                 if (obj instanceof Scope)
1547                 {
1548                     Scope scope = (Scope)obj;
1549                     if (scope.getParent() != null)
1550                     {
1551                         ica.put(evaluateScopeName, scope.getParent());
1552                     }
1553                     else if (scope.getReplaced() != null)
1554                     {
1555                         ica.put(evaluateScopeName, scope.getReplaced());
1556                     }
1557                     else
1558                     {
1559                         ica.remove(evaluateScopeName);
1560                     }
1561                 }
1562             }
1563         }
1564 
1565         return true;
1566     }
1567 
1568     /**
1569      * Invokes a currently registered Velocimacro with the params provided
1570      * and places the rendered stream into the writer.
1571      * <br>
1572      * Note: currently only accepts args to the VM if they are in the context.
1573      * <br>
1574      * Note: only macros in the global context can be called. This method doesn't find macros defined by
1575      * templates during previous mergeTemplate calls if Velocity.VM_PERM_INLINE_LOCAL has been enabled.
1576      *
1577      * @param vmName name of Velocimacro to call
1578      * @param logTag string to be used for template name in case of error. if null,
1579      *               the vmName will be used
1580      * @param params keys for args used to invoke Velocimacro, in java format
1581      *               rather than VTL (eg  "foo" or "bar" rather than "$foo" or "$bar")
1582      * @param context Context object containing data/objects used for rendering.
1583      * @param writer  Writer for output stream
1584      * @return true if Velocimacro exists and successfully invoked, false otherwise.
1585      * @since 1.6
1586      */
1587     @Override
invokeVelocimacro(final String vmName, String logTag, String[] params, final Context context, final Writer writer)1588     public boolean invokeVelocimacro(final String vmName, String logTag,
1589                                      String[] params, final Context context,
1590                                      final Writer writer)
1591      {
1592         /* check necessary parameters */
1593         if (vmName == null || context == null || writer == null)
1594         {
1595             String msg = "RuntimeInstance.invokeVelocimacro(): invalid call: vmName, context, and writer must not be null";
1596             log.error(msg);
1597             throw new NullPointerException(msg);
1598         }
1599 
1600         /* handle easily corrected parameters */
1601         if (logTag == null)
1602         {
1603             logTag = vmName;
1604         }
1605         if (params == null)
1606         {
1607             params = new String[0];
1608         }
1609 
1610         /* does the VM exist? (only global scope is scanned so this doesn't find inline macros in templates) */
1611         if (!isVelocimacro(vmName, null))
1612         {
1613             String msg = "RuntimeInstance.invokeVelocimacro(): VM '" + vmName
1614                          + "' is not registered.";
1615             log.error(msg);
1616             throw new VelocityException(msg, null, getLogContext().getStackTrace());
1617         }
1618 
1619         /* now just create the VM call, and use evaluate */
1620         StringBuilder template = new StringBuilder(String.valueOf(parserConfiguration.getHashChar()));
1621         template.append(vmName);
1622         template.append("(");
1623          for (String param : params)
1624          {
1625              template.append(" $");
1626              template.append(param);
1627          }
1628         template.append(" )");
1629 
1630         return evaluate(context, writer, logTag, template.toString());
1631     }
1632 
1633     /**
1634      * Retrieves and caches the configured default encoding
1635      * for better performance. (VELOCITY-606)
1636      */
getDefaultEncoding()1637     private String getDefaultEncoding()
1638     {
1639         return defaultEncoding;
1640     }
1641 
1642     /**
1643      * Returns a <code>Template</code> from the resource manager.
1644      * This method assumes that the character encoding of the
1645      * template is set by the <code>resource.default_encoding</code>
1646      * property. The default is UTF-8.
1647      *
1648      * @param name The file name of the desired template.
1649      * @return     The template.
1650      * @throws ResourceNotFoundException if template not found
1651      *          from any available source.
1652      * @throws ParseErrorException if template cannot be parsed due
1653      *          to syntax (or other) error.
1654      */
1655     @Override
getTemplate(String name)1656     public Template getTemplate(String name)
1657         throws ResourceNotFoundException, ParseErrorException
1658     {
1659         return getTemplate(name, null);
1660     }
1661 
1662     /**
1663      * Returns a <code>Template</code> from the resource manager
1664      *
1665      * @param name The  name of the desired template.
1666      * @param encoding Character encoding of the template
1667      * @return     The template.
1668      * @throws ResourceNotFoundException if template not found
1669      *          from any available source.
1670      * @throws ParseErrorException if template cannot be parsed due
1671      *          to syntax (or other) error.
1672      */
1673     @Override
getTemplate(String name, String encoding)1674     public Template getTemplate(String name, String  encoding)
1675         throws ResourceNotFoundException, ParseErrorException
1676     {
1677         requireInitialization();
1678         if (encoding == null) encoding = getDefaultEncoding();
1679         return (Template)
1680                 resourceManager.getResource(name,
1681                     ResourceManager.RESOURCE_TEMPLATE, encoding);
1682     }
1683 
1684     /**
1685      * Returns a static content resource from the
1686      * resource manager.  Uses the current value
1687      * if INPUT_ENCODING as the character encoding.
1688      *
1689      * @param name Name of content resource to get
1690      * @return parsed ContentResource object ready for use
1691      * @throws ResourceNotFoundException if template not found
1692      *          from any available source.
1693      * @throws ParseErrorException When the template could not be parsed.
1694      */
1695     @Override
getContent(String name)1696     public ContentResource getContent(String name)
1697         throws ResourceNotFoundException, ParseErrorException
1698     {
1699         /*
1700          *  the encoding is irrelvant as we don't do any converstion
1701          *  the bytestream should be dumped to the output stream
1702          */
1703 
1704         return getContent(name, getDefaultEncoding());
1705     }
1706 
1707     /**
1708      * Returns a static content resource from the
1709      * resource manager.
1710      *
1711      * @param name Name of content resource to get
1712      * @param encoding Character encoding to use
1713      * @return parsed ContentResource object ready for use
1714      * @throws ResourceNotFoundException if template not found
1715      *          from any available source.
1716      * @throws ParseErrorException When the template could not be parsed.
1717      */
1718     @Override
getContent(String name, String encoding)1719     public ContentResource getContent(String name, String encoding)
1720         throws ResourceNotFoundException, ParseErrorException
1721     {
1722         requireInitialization();
1723 
1724         return (ContentResource)
1725                 resourceManager.getResource(name,
1726                         ResourceManager.RESOURCE_CONTENT, encoding);
1727     }
1728 
1729 
1730     /**
1731      *  Determines if a template exists and returns name of the loader that
1732      *  provides it.  This is a slightly less hokey way to support
1733      *  the Velocity.resourceExists() utility method, which was broken
1734      *  when per-template encoding was introduced.  We can revisit this.
1735      *
1736      *  @param resourceName Name of template or content resource
1737      *  @return class name of loader than can provide it
1738      */
1739     @Override
getLoaderNameForResource(String resourceName)1740     public String getLoaderNameForResource(String resourceName)
1741     {
1742         requireInitialization();
1743 
1744         return resourceManager.getLoaderNameForResource(resourceName);
1745     }
1746 
1747     /**
1748      * Returns the configured logger.
1749      *
1750      * @return A Logger instance
1751      * @since 1.5
1752      */
1753     @Override
getLog()1754     public Logger getLog()
1755     {
1756         return log;
1757     }
1758 
1759     /**
1760      * Get a logger for the specified child namespace.
1761      * If a logger was configured using the runtime.log.instance configuration property, returns this instance.
1762      * Otherwise, uses SLF4J LoggerFactory on baseNamespace '.' childNamespace.
1763      * @param childNamespace
1764      * @return child namespace logger
1765      */
1766     @Override
getLog(String childNamespace)1767     public Logger getLog(String childNamespace)
1768     {
1769         Logger log = (Logger)getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE);
1770         if (log == null)
1771         {
1772             String loggerName = getString(RUNTIME_LOG_NAME, DEFAULT_RUNTIME_LOG_NAME) + "." + childNamespace;
1773             log = LoggerFactory.getLogger(loggerName);
1774         }
1775         return log;
1776     }
1777 
1778     /**
1779      * Get the LogContext object used to tack locations in templates.
1780      * @return LogContext object
1781      * @since 2.2
1782      */
1783     @Override
getLogContext()1784     public LogContext getLogContext()
1785     {
1786         return logContext;
1787     }
1788 
1789     /**
1790      * String property accessor method with default to hide the
1791      * configuration implementation.
1792      *
1793      * @param key property key
1794      * @param defaultValue  default value to return if key not
1795      *               found in resource manager.
1796      * @return value of key or default
1797      */
1798     @Override
getString(String key, String defaultValue)1799     public String getString(String key, String defaultValue)
1800     {
1801         return configuration.getString(key, defaultValue);
1802     }
1803 
1804     /**
1805      * Returns the appropriate VelocimacroProxy object if vmName
1806      * is a valid current Velocimacro.
1807      *
1808      * @param vmName  Name of velocimacro requested
1809      * @param renderingTemplate Template we are currently rendering. This
1810      *    information is needed when VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL setting is true
1811      *    and template contains a macro with the same name as the global macro library.
1812      * @param template Template which acts as the host for the macro
1813      *
1814      * @return VelocimacroProxy
1815      */
1816     @Override
getVelocimacro(String vmName, Template renderingTemplate, Template template)1817     public Directive getVelocimacro(String vmName, Template renderingTemplate, Template template)
1818     {
1819         return vmFactory.getVelocimacro(vmName, renderingTemplate, template);
1820     }
1821 
1822     /**
1823      * Adds a new Velocimacro. Usually called by Macro only while parsing.
1824      *
1825      * @param name  Name of velocimacro
1826      * @param macro  root AST node of the parsed macro
1827      * @param macroArgs  Array of macro arguments, containing the
1828      *        #macro() arguments and default values.  the 0th is the name.
1829      * @param definingTemplate Template containing the source of the macro
1830      *
1831      * @return boolean  True if added, false if rejected for some
1832      *                  reason (either parameters or permission settings)
1833      */
1834     @Override
addVelocimacro(String name, Node macro, List<Macro.MacroArg> macroArgs, Template definingTemplate)1835     public boolean addVelocimacro(String name,
1836                                   Node macro,
1837                                   List<Macro.MacroArg> macroArgs,
1838                                   Template definingTemplate)
1839     {
1840         return vmFactory.addVelocimacro(stringInterning ? name.intern() : name, macro, macroArgs, definingTemplate);
1841     }
1842 
1843     /**
1844      *  Checks to see if a VM exists
1845      *
1846      * @param vmName Name of the Velocimacro.
1847      * @param template Template on which to look for the Macro.
1848      * @return True if VM by that name exists, false if not
1849      */
1850     @Override
isVelocimacro(String vmName, Template template)1851     public boolean isVelocimacro(String vmName, Template template)
1852     {
1853         return vmFactory.isVelocimacro(stringInterning ? vmName.intern() : vmName, template);
1854     }
1855 
1856     /* --------------------------------------------------------------------
1857      * R U N T I M E  A C C E S S O R  M E T H O D S
1858      * --------------------------------------------------------------------
1859      * These are the getXXX() methods that are a simple wrapper
1860      * around the configuration object. This is an attempt
1861      * to make a the Velocity Runtime the single access point
1862      * for all things Velocity, and allow the Runtime to
1863      * adhere as closely as possible the the Mediator pattern
1864      * which is the ultimate goal.
1865      * --------------------------------------------------------------------
1866      */
1867 
1868     /**
1869      * String property accessor method to hide the configuration implementation
1870      * @param key  property key
1871      * @return   value of key or null
1872      */
1873     @Override
getString(String key)1874     public String getString(String key)
1875     {
1876         return StringUtils.trim(configuration.getString(key));
1877     }
1878 
1879     /**
1880      * Int property accessor method to hide the configuration implementation.
1881      *
1882      * @param key Property key
1883      * @return value
1884      */
1885     @Override
getInt(String key)1886     public int getInt(String key)
1887     {
1888         return configuration.getInt(key);
1889     }
1890 
1891     /**
1892      * Int property accessor method to hide the configuration implementation.
1893      *
1894      * @param key  property key
1895      * @param defaultValue The default value.
1896      * @return value
1897      */
1898     @Override
getInt(String key, int defaultValue)1899     public int getInt(String key, int defaultValue)
1900     {
1901         return configuration.getInt(key, defaultValue);
1902     }
1903 
1904     /**
1905      * Boolean property accessor method to hide the configuration implementation.
1906      *
1907      * @param key property key
1908      * @param def The default value if property not found.
1909      * @return value of key or default value
1910      */
1911     @Override
getBoolean(String key, boolean def)1912     public boolean getBoolean(String key, boolean def)
1913     {
1914         return configuration.getBoolean(key, def);
1915     }
1916 
1917     /**
1918      * Return the velocity runtime configuration object.
1919      *
1920      * @return Configuration object which houses the Velocity runtime
1921      * properties.
1922      */
1923     @Override
getConfiguration()1924     public ExtProperties getConfiguration()
1925     {
1926         return configuration;
1927     }
1928 
1929     /**
1930      * Returns the event handlers for the application.
1931      * @return The event handlers for the application.
1932      * @since 1.5
1933      */
1934     @Override
getApplicationEventCartridge()1935     public EventCartridge getApplicationEventCartridge()
1936     {
1937         return eventCartridge;
1938     }
1939 
1940 
1941     /**
1942      *  Gets the application attribute for the given key
1943      *
1944      * @param key
1945      * @return The application attribute for the given key.
1946      */
1947     @Override
getApplicationAttribute(Object key)1948     public Object getApplicationAttribute(Object key)
1949     {
1950         return applicationAttributes.get(key);
1951     }
1952 
1953     /**
1954      *   Sets the application attribute for the given key
1955      *
1956      * @param key
1957      * @param o The new application attribute.
1958      * @return The old value of this attribute or null if it hasn't been set before.
1959      */
1960     @Override
setApplicationAttribute(Object key, Object o)1961     public Object setApplicationAttribute(Object key, Object o)
1962     {
1963         return applicationAttributes.put(key, o);
1964     }
1965 
1966     /**
1967      * Returns the Uberspect object for this Instance.
1968      *
1969      * @return The Uberspect object for this Instance.
1970      */
1971     @Override
getUberspect()1972     public Uberspect getUberspect()
1973     {
1974         return uberSpect;
1975     }
1976 
1977     /**
1978      * Whether to use string interning
1979      *
1980      * @return boolean
1981      */
1982     @Override
useStringInterning()1983     public boolean useStringInterning()
1984     {
1985         return stringInterning;
1986     }
1987 
1988     /**
1989      * get space gobbling mode
1990      * @return indentation mode
1991      */
1992     @Override
getSpaceGobbling()1993     public SpaceGobbling getSpaceGobbling()
1994     {
1995         return spaceGobbling;
1996     }
1997 
1998     /**
1999      * get whether hyphens are allowed in identifiers
2000      * @return configured boolean flag
2001      */
2002     @Override
isHyphenAllowedInIdentifiers()2003     public boolean isHyphenAllowedInIdentifiers()
2004     {
2005         return hyphenAllowedInIdentifiers;
2006     }
2007 
2008     /**
2009      * Get whether to provide a scope control object for this scope
2010      * @param scopeName
2011      * @return scope control enabled
2012      * @since 2.1
2013      */
2014     @Override
isScopeControlEnabled(String scopeName)2015     public boolean isScopeControlEnabled(String scopeName)
2016     {
2017         return enabledScopeControls.contains(scopeName);
2018     }
2019 
2020     @Override
getParserConfiguration()2021     public ParserConfiguration getParserConfiguration()
2022     {
2023         return parserConfiguration;
2024     }
2025 }
2026