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