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 }