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