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         Object old = localcontext.put(key, value);
168         if (!forceLocal)
169         {
170             old = super.put(key, value);
171         }
172         return old;
173     }
174 
175     /**
176      * Implementation of the Context.get() method.  First checks
177      * localcontext, then arguments, then global context.
178      * 
179      * @param key name of item to get
180      * @return stored object or null
181      */
182     public Object get(String key)
183     {
184         Object o = localcontext.get(key);
185         if (o != null)
186         {
187             return o;
188         }
189 
190         Node astNode = (Node) vmproxyhash.get(key);
191 
192         if (astNode != null)
193         {
194             int type = astNode.getType();
195 
196             // if the macro argument (astNode) is a reference, we need to evaluate it
197             // in case it is a multilevel node
198             if (type == ParserTreeConstants.JJTREFERENCE)
199             {
200                 ASTReference ref = (ASTReference) astNode;
201 
202                 if (ref.jjtGetNumChildren() > 0)
203                 {
204                     return ref.execute(null, innerContext);
205                 }
206                 else
207                 {
208                     Object obj = innerContext.get(ref.getRootString());
209                     if (obj == null && ref.strictRef)
210                     {
211                         if (!innerContext.containsKey(ref.getRootString()))
212                         {
213                             throw new MethodInvocationException("Parameter '" + ref.getRootString() 
214                                 + "' not defined", null, key, ref.getTemplateName(), 
215                                 ref.getLine(), ref.getColumn());
216                         }
217                     }
218                     return obj;
219                 }
220             }
221             else if (type == ParserTreeConstants.JJTTEXT)
222             {
223                 // this really shouldn't happen. text is just a throwaway arg for #foreach()
224                 try
225                 {
226                     StringWriter writer = new StringWriter();
227                     astNode.render(innerContext, writer);
228                     return writer.toString();
229                 }
230                 catch (RuntimeException e)
231                 {
232                     throw e;
233                 }
234                 catch (Exception e)
235                 {
236                     String msg = "ProxyVMContext.get() : error rendering reference";
237                     rsvc.getLog().error(msg, e);
238                     throw new VelocityException(msg, e);
239                 }
240             }
241             else
242             {
243                 // use value method to render other dynamic nodes
244                 return astNode.value(innerContext);
245             }
246         }
247 
248         return super.get(key);
249     }
250 
251     /**
252      * @see org.apache.velocity.context.Context#containsKey(java.lang.Object)
253      */
254     public boolean containsKey(Object key)
255     {
256       return vmproxyhash.containsKey(key)
257           || localcontext.containsKey(key)
258           || super.containsKey(key);
259     }
260 
261     /**
262      * @see org.apache.velocity.context.Context#getKeys()
263      */
264     public Object[] getKeys()
265     {
266         if (localcontext.isEmpty())
267         {
268             return vmproxyhash.keySet().toArray();
269         }
270         else if (vmproxyhash.isEmpty())
271         {
272             return localcontext.keySet().toArray();
273         }
274 
275         HashSet keys = new HashSet(localcontext.keySet());
276         keys.addAll(vmproxyhash.keySet());
277         return keys.toArray();
278     }
279 
280     /**
281      * @see org.apache.velocity.context.Context#remove(java.lang.Object)
282      */
283     public Object remove(Object key)
284     {
285         Object loc = localcontext.remove(key);
286         Object arg = vmproxyhash.remove(key);
287         Object glo = null;
288         if (!localContextScope)
289         {
290             glo = super.remove(key);
291         }
292         if (loc != null)
293         {
294             return loc;
295         }
296         return glo;
297     }
298 
299 }