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