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.velocity.context.InternalContextAdapter;
26 import org.apache.velocity.context.ProxyVMContext;
27 import org.apache.velocity.exception.MacroOverflowException;
28 import org.apache.velocity.exception.MethodInvocationException;
29 import org.apache.velocity.exception.TemplateInitException;
30 import org.apache.velocity.exception.VelocityException;
31 import org.apache.velocity.runtime.Renderable;
32 import org.apache.velocity.runtime.RuntimeConstants;
33 import org.apache.velocity.runtime.RuntimeServices;
34 import org.apache.velocity.runtime.log.Log;
35 import org.apache.velocity.runtime.parser.node.Node;
36 import org.apache.velocity.runtime.parser.node.SimpleNode;
37
38 /**
39 * VelocimacroProxy.java
40 *
41 * a proxy Directive-derived object to fit with the current directive system
42 *
43 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
44 * @version $Id: VelocimacroProxy.java 898032 2010-01-11 19:51:03Z nbubna $
45 */
46 public class VelocimacroProxy extends Directive
47 {
48 private String macroName;
49 private String[] argArray = null;
50 private String[] literalArgArray = null;
51 private SimpleNode nodeTree = null;
52 private int numMacroArgs = 0;
53 private boolean strictArguments;
54 private boolean localContextScope = false;
55 private int maxCallDepth;
56 private String bodyReference;
57
58 /**
59 * Return name of this Velocimacro.
60 * @return The name of this Velocimacro.
61 */
62 public String getName()
63 {
64 return macroName;
65 }
66
67 /**
68 * Velocimacros are always LINE type directives.
69 * @return The type of this directive.
70 */
71 public int getType()
72 {
73 return LINE;
74 }
75
76 /**
77 * sets the directive name of this VM
78 *
79 * @param name
80 */
81 public void setName(String name)
82 {
83 macroName = name;
84 }
85
86 /**
87 * sets the array of arguments specified in the macro definition
88 *
89 * @param arr
90 */
91 public void setArgArray(String[] arr)
92 {
93 argArray = arr;
94
95 // for performance reasons we precache these strings - they are needed in
96 // "render literal if null" functionality
97 literalArgArray = new String[arr.length];
98 for(int i = 0; i < arr.length; i++)
99 {
100 literalArgArray[i] = ".literal.$" + argArray[i];
101 }
102
103 /*
104 * get the arg count from the arg array. remember that the arg array has the macro name as
105 * it's 0th element
106 */
107
108 numMacroArgs = argArray.length - 1;
109 }
110
111 /**
112 * @param tree
113 */
114 public void setNodeTree(SimpleNode tree)
115 {
116 nodeTree = tree;
117 }
118
119 /**
120 * returns the number of ars needed for this VM
121 *
122 * @return The number of ars needed for this VM
123 */
124 public int getNumArgs()
125 {
126 return numMacroArgs;
127 }
128
129 public boolean render(InternalContextAdapter context, Writer writer, Node node)
130 throws IOException, MethodInvocationException, MacroOverflowException
131 {
132 return render(context, writer, node, null);
133 }
134
135 /**
136 * Renders the macro using the context.
137 *
138 * @param context Current rendering context
139 * @param writer Writer for output
140 * @param node AST that calls the macro
141 * @return True if the directive rendered successfully.
142 * @throws IOException
143 * @throws MethodInvocationException
144 * @throws MacroOverflowException
145 */
146 public boolean render(InternalContextAdapter context, Writer writer,
147 Node node, Renderable body)
148 throws IOException, MethodInvocationException, MacroOverflowException
149 {
150 // wrap the current context and add the macro arguments
151
152 // the creation of this context is a major bottleneck (incl 2x HashMap)
153 final ProxyVMContext vmc = new ProxyVMContext(context, rsvc, localContextScope);
154
155 int callArguments = node.jjtGetNumChildren();
156
157 if (callArguments > 0)
158 {
159 // the 0th element is the macro name
160 for (int i = 1; i < argArray.length && i <= callArguments; i++)
161 {
162 /*
163 * literalArgArray[i] is needed for "render literal if null" functionality.
164 * The value is used in ASTReference render-method.
165 *
166 * The idea is to avoid generating the literal until absolutely necessary.
167 *
168 * This makes VMReferenceMungeVisitor obsolete and it would not work anyway
169 * when the macro AST is shared
170 */
171 vmc.addVMProxyArg(context, argArray[i], literalArgArray[i], node.jjtGetChild(i - 1));
172 }
173 }
174
175 // if this macro was invoked by a call directive, we might have a body AST here. Put it into context.
176 if( body != null )
177 {
178 vmc.addVMProxyArg(context, bodyReference, "", body);
179 }
180
181 /*
182 * check that we aren't already at the max call depth
183 */
184 if (maxCallDepth > 0 && maxCallDepth == vmc.getCurrentMacroCallDepth())
185 {
186 Object[] stack = vmc.getMacroNameStack();
187
188 StringBuffer out = new StringBuffer(100)
189 .append("Max calling depth of ").append(maxCallDepth)
190 .append(" was exceeded in macro '").append(macroName)
191 .append("' with Call Stack:");
192 for (int i = 0; i < stack.length; i++)
193 {
194 if (i != 0)
195 {
196 out.append("->");
197 }
198 out.append(stack[i]);
199 }
200 out.append(" at " + Log.formatFileString(this));
201 rsvc.getLog().error(out.toString());
202
203 // clean out the macro stack, since we just broke it
204 while (vmc.getCurrentMacroCallDepth() > 0)
205 {
206 vmc.popCurrentMacroName();
207 }
208
209 throw new MacroOverflowException(out.toString());
210 }
211
212 try
213 {
214 // render the velocity macro
215 vmc.pushCurrentMacroName(macroName);
216 nodeTree.render(vmc, writer);
217 vmc.popCurrentMacroName();
218 return true;
219 }
220 catch (RuntimeException e)
221 {
222 throw e;
223 }
224 catch (Exception e)
225 {
226 String msg = "VelocimacroProxy.render() : exception VM = #" + macroName + "()";
227 rsvc.getLog().error(msg, e);
228 throw new VelocityException(msg, e);
229 }
230 }
231
232 /**
233 * Initialize members of VelocimacroProxy. called from MacroEntry
234 */
235 public void init(RuntimeServices rs)
236 {
237 rsvc = rs;
238
239 // this is a very expensive call (ExtendedProperties is very slow)
240 strictArguments = rs.getConfiguration().getBoolean(
241 RuntimeConstants.VM_ARGUMENTS_STRICT, false);
242
243 // support for local context scope feature, where all references are local
244 // we do not have to check this at every invocation of ProxyVMContext
245 localContextScope = rsvc.getBoolean(RuntimeConstants.VM_CONTEXT_LOCALSCOPE, false);
246 if (localContextScope && rsvc.getLog().isWarnEnabled())
247 {
248 // only warn once per runtime, so this isn't obnoxious
249 String key = "velocimacro.context.localscope.warning";
250 Boolean alreadyWarned = (Boolean)rsvc.getApplicationAttribute(key);
251 if (alreadyWarned == null)
252 {
253 rsvc.setApplicationAttribute(key, Boolean.TRUE);
254 rsvc.getLog()
255 .warn("The "+RuntimeConstants.VM_CONTEXT_LOCALSCOPE+
256 " feature is deprecated and will be removed in Velocity 2.0."+
257 " Instead, please use the $macro scope to store references"+
258 " that must be local to your macros (e.g. "+
259 "#set( $macro.foo = 'bar' ) and $macro.foo). This $macro"+
260 " namespace is automatically created and destroyed for you at"+
261 " the beginning and end of the macro rendering.");
262 }
263 }
264
265 // get the macro call depth limit
266 maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH);
267
268 // get name of the reference that refers to AST block passed to block macro call
269 bodyReference = rsvc.getString(RuntimeConstants.VM_BODY_REFERENCE, "bodyContent");
270 }
271
272
273 /**
274 * Build an error message for not providing the correct number of arguments
275 */
276 private String buildErrorMsg(Node node, int numArgsProvided)
277 {
278 String msg = "VM #" + macroName + ": too "
279 + ((getNumArgs() > numArgsProvided) ? "few" : "many") + " arguments to macro. Wanted "
280 + getNumArgs() + " got " + numArgsProvided;
281 return msg;
282 }
283
284 /**
285 * check if we are calling this macro with the right number of arguments. If
286 * we are not, and strictArguments is active, then throw TemplateInitException.
287 * This method is called during macro render, so it must be thread safe.
288 */
289 public void checkArgs(InternalContextAdapter context, Node node, boolean hasBody)
290 {
291 // check how many arguments we have
292 int i = node.jjtGetNumChildren();
293
294 // if macro call has a body (BlockMacro) then don't count the body as an argument
295 if( hasBody )
296 i--;
297
298 // Throw exception for invalid number of arguments?
299 if (getNumArgs() != i)
300 {
301 if (strictArguments)
302 {
303 /**
304 * indicate col/line assuming it starts at 0 - this will be corrected one call up
305 */
306 throw new TemplateInitException(buildErrorMsg(node, i),
307 context.getCurrentTemplateName(), 0, 0);
308 }
309 else if (rsvc.getLog().isDebugEnabled())
310 {
311 rsvc.getLog().debug(buildErrorMsg(node, i));
312 return;
313 }
314 }
315 }
316 }
317