1 package org.apache.velocity.runtime.directive;
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.IOException;
23 import java.io.Writer;
24
25 import org.apache.commons.lang.StringUtils;
26 import org.apache.velocity.context.InternalContextAdapter;
27 import org.apache.velocity.context.ProxyVMContext;
28 import org.apache.velocity.exception.MacroOverflowException;
29 import org.apache.velocity.exception.MethodInvocationException;
30 import org.apache.velocity.exception.TemplateInitException;
31 import org.apache.velocity.exception.VelocityException;
32 import org.apache.velocity.runtime.RuntimeConstants;
33 import org.apache.velocity.runtime.RuntimeServices;
34 import org.apache.velocity.runtime.parser.ParserTreeConstants;
35 import org.apache.velocity.runtime.parser.node.ASTDirective;
36 import org.apache.velocity.runtime.parser.node.Node;
37 import org.apache.velocity.runtime.parser.node.SimpleNode;
38
39 /**
40 * VelocimacroProxy.java
41 *
42 * a proxy Directive-derived object to fit with the current directive system
43 *
44 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
45 * @version $Id: VelocimacroProxy.java 718424 2008-11-17 22:50:43Z nbubna $
46 */
47 public class VelocimacroProxy extends Directive
48 {
49 private String macroName;
50 private String[] argArray = null;
51 private String[] literalArgArray = null;
52 private SimpleNode nodeTree = null;
53 private int numMacroArgs = 0;
54 private boolean preInit = false;
55 private boolean strictArguments;
56 private boolean localContextScope = false;
57 private int maxCallDepth;
58
59 /**
60 * Return name of this Velocimacro.
61 * @return The name of this Velocimacro.
62 */
63 public String getName()
64 {
65 return macroName;
66 }
67
68 /**
69 * Velocimacros are always LINE type directives.
70 * @return The type of this directive.
71 */
72 public int getType()
73 {
74 return LINE;
75 }
76
77 /**
78 * sets the directive name of this VM
79 *
80 * @param name
81 */
82 public void setName(String name)
83 {
84 macroName = name;
85 }
86
87 /**
88 * sets the array of arguments specified in the macro definition
89 *
90 * @param arr
91 */
92 public void setArgArray(String[] arr)
93 {
94 argArray = arr;
95
96 // for performance reasons we precache these strings - they are needed in
97 // "render literal if null" functionality
98 literalArgArray = new String[arr.length];
99 for(int i = 0; i < arr.length; i++)
100 {
101 literalArgArray[i] = ".literal.$" + argArray[i];
102 }
103
104 /*
105 * get the arg count from the arg array. remember that the arg array has the macro name as
106 * it's 0th element
107 */
108
109 numMacroArgs = argArray.length - 1;
110 }
111
112 /**
113 * @param tree
114 */
115 public void setNodeTree(SimpleNode tree)
116 {
117 nodeTree = tree;
118 }
119
120 /**
121 * returns the number of ars needed for this VM
122 *
123 * @return The number of ars needed for this VM
124 */
125 public int getNumArgs()
126 {
127 return numMacroArgs;
128 }
129
130 /**
131 * Renders the macro using the context.
132 *
133 * @param context Current rendering context
134 * @param writer Writer for output
135 * @param node AST that calls the macro
136 * @return True if the directive rendered successfully.
137 * @throws IOException
138 * @throws MethodInvocationException
139 * @throws MacroOverflowException
140 */
141 public boolean render(InternalContextAdapter context, Writer writer, Node node)
142 throws IOException, MethodInvocationException, MacroOverflowException
143 {
144 // wrap the current context and add the macro arguments
145
146 // the creation of this context is a major bottleneck (incl 2x HashMap)
147 final ProxyVMContext vmc = new ProxyVMContext(context, rsvc, localContextScope);
148
149 int callArguments = node.jjtGetNumChildren();
150
151 if (callArguments > 0)
152 {
153 // the 0th element is the macro name
154 for (int i = 1; i < argArray.length && i <= callArguments; i++)
155 {
156 Node macroCallArgument = node.jjtGetChild(i - 1);
157
158 /*
159 * literalArgArray[i] is needed for "render literal if null" functionality.
160 * The value is used in ASTReference render-method.
161 *
162 * The idea is to avoid generating the literal until absolutely necessary.
163 *
164 * This makes VMReferenceMungeVisitor obsolete and it would not work anyway
165 * when the macro AST is shared
166 */
167 vmc.addVMProxyArg(context, argArray[i], literalArgArray[i], macroCallArgument);
168 }
169 }
170
171 /*
172 * check that we aren't already at the max call depth
173 */
174 if (maxCallDepth > 0 && maxCallDepth == vmc.getCurrentMacroCallDepth())
175 {
176 String templateName = vmc.getCurrentTemplateName();
177 Object[] stack = vmc.getMacroNameStack();
178
179 StringBuffer out = new StringBuffer(100)
180 .append("Max calling depth of ").append(maxCallDepth)
181 .append(" was exceeded in Template:").append(templateName)
182 .append(" and Macro:").append(macroName)
183 .append(" with Call Stack:");
184 for (int i = 0; i < stack.length; i++)
185 {
186 if (i != 0)
187 {
188 out.append("->");
189 }
190 out.append(stack[i]);
191 }
192 rsvc.getLog().error(out.toString());
193
194 try
195 {
196 throw new MacroOverflowException(out.toString());
197 }
198 finally
199 {
200 // clean out the macro stack, since we just broke it
201 while (vmc.getCurrentMacroCallDepth() > 0)
202 {
203 vmc.popCurrentMacroName();
204 }
205 }
206 }
207
208 try
209 {
210 // render the velocity macro
211 vmc.pushCurrentMacroName(macroName);
212 nodeTree.render(vmc, writer);
213 vmc.popCurrentMacroName();
214 return true;
215 }
216 catch (RuntimeException e)
217 {
218 throw e;
219 }
220 catch (Exception e)
221 {
222 String msg = "VelocimacroProxy.render() : exception VM = #" + macroName + "()";
223 rsvc.getLog().error(msg, e);
224 throw new VelocityException(msg, e);
225 }
226 }
227
228 /**
229 * The major meat of VelocimacroProxy, init() checks the # of arguments.
230 *
231 * @param rs
232 * @param context
233 * @param node
234 * @throws TemplateInitException
235 */
236 public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
237 throws TemplateInitException
238 {
239 // there can be multiple threads here so avoid double inits
240 synchronized (this)
241 {
242 if (!preInit)
243 {
244 super.init(rs, context, node);
245
246 // this is a very expensive call (ExtendedProperties is very slow)
247 strictArguments = rs.getConfiguration().getBoolean(
248 RuntimeConstants.VM_ARGUMENTS_STRICT, false);
249
250 // support for local context scope feature, where all references are local
251 // we do not have to check this at every invocation of ProxyVMContext
252 localContextScope = rsvc.getBoolean(RuntimeConstants.VM_CONTEXT_LOCALSCOPE, false);
253
254 // get the macro call depth limit
255 maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH);
256
257 // initialize the parsed AST
258 // since this is context independent we need to do this only once so
259 // do it here instead of the render method
260 nodeTree.init(context, rs);
261
262 preInit = true;
263 }
264 }
265
266 // check how many arguments we got
267 int i = node.jjtGetNumChildren();
268
269 // Throw exception for invalid number of arguments?
270 if (getNumArgs() != i)
271 {
272 // If we have a not-yet defined macro, we do get no arguments because
273 // the syntax tree looks different than with a already defined macro.
274 // But we do know that we must be in a macro definition context somewhere up the
275 // syntax tree.
276 // Check for that, if it is true, suppress the error message.
277 // Fixes VELOCITY-71.
278
279 for (Node parent = node.jjtGetParent(); parent != null;)
280 {
281 if ((parent instanceof ASTDirective)
282 && StringUtils.equals(((ASTDirective) parent).getDirectiveName(), "macro"))
283 {
284 return;
285 }
286 parent = parent.jjtGetParent();
287 }
288
289 String msg = "VM #" + macroName + ": too "
290 + ((getNumArgs() > i) ? "few" : "many") + " arguments to macro. Wanted "
291 + getNumArgs() + " got " + i;
292
293 if (strictArguments)
294 {
295 /**
296 * indicate col/line assuming it starts at 0 - this will be corrected one call up
297 */
298 throw new TemplateInitException(msg, context.getCurrentTemplateName(), 0, 0);
299 }
300 else
301 {
302 rsvc.getLog().debug(msg);
303 return;
304 }
305 }
306
307 /* now validate that none of the arguments are plain words, (VELOCITY-614)
308 * they should be string literals, references, inline maps, or inline lists */
309 for (int n=0; n < i; n++)
310 {
311 Node child = node.jjtGetChild(n);
312 if (child.getType() == ParserTreeConstants.JJTWORD)
313 {
314 /* indicate col/line assuming it starts at 0
315 * this will be corrected one call up */
316 throw new TemplateInitException("Invalid arg #"
317 + n + " in VM #" + macroName, context.getCurrentTemplateName(), 0, 0);
318 }
319 }
320 }
321 }
322