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 }