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