View Javadoc

1   package org.apache.velocity.context;
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 java.io.StringWriter;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.velocity.app.event.EventCartridge;
29  import org.apache.velocity.exception.MethodInvocationException;
30  import org.apache.velocity.exception.VelocityException;
31  import org.apache.velocity.runtime.RuntimeServices;
32  import org.apache.velocity.runtime.parser.ParserTreeConstants;
33  import org.apache.velocity.runtime.parser.node.ASTReference;
34  import org.apache.velocity.runtime.parser.node.Node;
35  import org.apache.velocity.runtime.resource.Resource;
36  import org.apache.velocity.util.introspection.IntrospectionCacheData;
37  
38  /**
39   * Context for Velocity macro arguments.
40   * 
41   * This special context combines ideas of earlier VMContext and VMProxyArgs
42   * by implementing routing functionality internally. This significantly
43   * reduces memory allocation upon macro invocations.
44   * Since the macro AST is now shared and RuntimeMacro directive is used,
45   * the earlier implementation of precalculating VMProxyArgs would not work.
46   * 
47   * See <a href="http://issues.apache.org/jira/browse/VELOCITY-607">Issue 607</a>
48   * for more info on this class.
49   * @author <a href="mailto:wyla@removeme.sci.fi">Jarkko Viinamaki</a>
50   * @version $Id$
51   * @since 1.6
52   */
53  public class ProxyVMContext extends ChainedInternalContextAdapter
54  {
55      /** container for our macro AST node arguments. Size must be power of 2. */
56      Map vmproxyhash = new HashMap(8, 0.8f);
57  
58      /** container for any local or constant macro arguments. Size must be power of 2. */
59      Map localcontext = new HashMap(8, 0.8f);;
60  
61      /** support for local context scope feature, where all references are local */
62      private boolean localContextScope;
63  
64      /** needed for writing log entries. */
65      private RuntimeServices rsvc;
66  
67      /**
68       * @param inner Velocity context for processing
69       * @param rsvc RuntimeServices provides logging reference
70       * @param localContextScope if true, all references are set to be local
71       */
72      public ProxyVMContext(InternalContextAdapter inner,
73                            RuntimeServices rsvc,
74                            boolean localContextScope)
75      {
76          super(inner);
77  
78          this.localContextScope = localContextScope;
79          this.rsvc = rsvc;
80      }
81  
82      /**
83       * Used to put Velocity macro arguments into this context. 
84       * 
85       * @param context rendering context
86       * @param macroArgumentName name of the macro argument that we received
87       * @param literalMacroArgumentName ".literal.$"+macroArgumentName
88       * @param argumentValue actual value of the macro argument
89       * 
90       * @throws MethodInvocationException
91       */
92      public void addVMProxyArg(InternalContextAdapter context,
93                                String macroArgumentName,
94                                String literalMacroArgumentName,
95                                Node argumentValue) throws MethodInvocationException
96      {
97          if (isConstant(argumentValue))
98          {
99              localcontext.put(macroArgumentName, argumentValue.value(context));
100         }
101         else
102         {
103             vmproxyhash.put(macroArgumentName, argumentValue);
104             localcontext.put(literalMacroArgumentName, argumentValue);
105         }
106     }
107 
108     /**
109      * AST nodes that are considered constants can be directly
110      * saved into the context. Dynamic values are stored in
111      * another argument hashmap.
112      * 
113      * @param node macro argument as AST node
114      * @return true if the node is a constant value
115      */
116     private boolean isConstant(Node node)
117     {
118         switch (node.getType())
119         {
120             case ParserTreeConstants.JJTINTEGERRANGE:
121             case ParserTreeConstants.JJTREFERENCE:
122             case ParserTreeConstants.JJTOBJECTARRAY:
123             case ParserTreeConstants.JJTMAP:
124             case ParserTreeConstants.JJTSTRINGLITERAL:
125             case ParserTreeConstants.JJTTEXT:
126                 return (false);
127             default:
128                 return (true);
129         }
130     }
131 
132     /**
133      * Impl of the Context.put() method.
134      * 
135      * @param key name of item to set
136      * @param value object to set to key
137      * @return old stored object
138      */
139     public Object put(final String key, final Object value)
140     {
141         return put(key, value, localContextScope);
142     }
143 
144     /**
145      * Allows callers to explicitly put objects in the local context, no matter what the
146      * velocimacro.context.local setting says. Needed e.g. for loop variables in foreach.
147      * 
148      * @param key name of item to set.
149      * @param value object to set to key.
150      * @return old stored object
151      */
152     public Object localPut(final String key, final Object value)
153     {
154         return put(key, value, true);
155     }
156 
157     /**
158      * Internal put method to select between local and global scope.
159      * 
160      * @param key name of item to set
161      * @param value object to set to key
162      * @param forceLocal True forces the object into the local scope.
163      * @return old stored object
164      */
165     protected Object put(final String key, final Object value, final boolean forceLocal)
166     {
167         Node astNode = (Node)vmproxyhash.get(key);
168         if (astNode != null)
169         {
170             if (astNode.getType() == ParserTreeConstants.JJTREFERENCE)
171             {
172                 ASTReference ref = (ASTReference)astNode;
173                 if (ref.jjtGetNumChildren() > 0)
174                 {
175                     ref.setValue(innerContext, value);
176                     return null;
177                 }
178                 else
179                 {
180                     return innerContext.put(ref.getRootString(), value);
181                 }
182             }
183         }
184 
185         Object old = localcontext.put(key, value);
186         if (!forceLocal)
187         {
188             old = super.put(key, value);
189         }
190         return old;
191     }
192 
193     /**
194      * Implementation of the Context.get() method.  First checks
195      * localcontext, then arguments, then global context.
196      * 
197      * @param key name of item to get
198      * @return stored object or null
199      */
200     public Object get(String key)
201     {
202         Object o = localcontext.get(key);
203         if (o != null)
204         {
205             return o;
206         }
207 
208         Node astNode = (Node) vmproxyhash.get(key);
209 
210         if (astNode != null)
211         {
212             int type = astNode.getType();
213 
214             // if the macro argument (astNode) is a reference, we need to evaluate it
215             // in case it is a multilevel node
216             if (type == ParserTreeConstants.JJTREFERENCE)
217             {
218                 ASTReference ref = (ASTReference) astNode;
219 
220                 if (ref.jjtGetNumChildren() > 0)
221                 {
222                     return ref.execute(null, innerContext);
223                 }
224                 else
225                 {
226                     Object obj = innerContext.get(ref.getRootString());
227                     if (obj == null && ref.strictRef)
228                     {
229                         if (!innerContext.containsKey(ref.getRootString()))
230                         {
231                             throw new MethodInvocationException("Parameter '" + ref.getRootString() 
232                                 + "' not defined", null, key, ref.getTemplateName(), 
233                                 ref.getLine(), ref.getColumn());
234                         }
235                     }
236                     return obj;
237                 }
238             }
239             else if (type == ParserTreeConstants.JJTTEXT)
240             {
241                 // this really shouldn't happen. text is just a throwaway arg for #foreach()
242                 try
243                 {
244                     StringWriter writer = new StringWriter();
245                     astNode.render(innerContext, writer);
246                     return writer.toString();
247                 }
248                 catch (RuntimeException e)
249                 {
250                     throw e;
251                 }
252                 catch (Exception e)
253                 {
254                     String msg = "ProxyVMContext.get() : error rendering reference";
255                     rsvc.getLog().error(msg, e);
256                     throw new VelocityException(msg, e);
257                 }
258             }
259             else
260             {
261                 // use value method to render other dynamic nodes
262                 return astNode.value(innerContext);
263             }
264         }
265 
266         return super.get(key);
267     }
268 
269     /**
270      * @see org.apache.velocity.context.Context#containsKey(java.lang.Object)
271      */
272     public boolean containsKey(Object key)
273     {
274       return vmproxyhash.containsKey(key)
275           || localcontext.containsKey(key)
276           || super.containsKey(key);
277     }
278 
279     /**
280      * @see org.apache.velocity.context.Context#getKeys()
281      */
282     public Object[] getKeys()
283     {
284         if (localcontext.isEmpty())
285         {
286             return vmproxyhash.keySet().toArray();
287         }
288         else if (vmproxyhash.isEmpty())
289         {
290             return localcontext.keySet().toArray();
291         }
292 
293         HashSet keys = new HashSet(localcontext.keySet());
294         keys.addAll(vmproxyhash.keySet());
295         return keys.toArray();
296     }
297 
298     /**
299      * @see org.apache.velocity.context.Context#remove(java.lang.Object)
300      */
301     public Object remove(Object key)
302     {
303         Object loc = localcontext.remove(key);
304         Object arg = vmproxyhash.remove(key);
305         Object glo = null;
306         if (!localContextScope)
307         {
308             glo = super.remove(key);
309         }
310         if (loc != null)
311         {
312             return loc;
313         }
314         return glo;
315     }
316 
317 }