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 import java.util.List;
25
26 import org.apache.commons.lang.text.StrBuilder;
27 import org.apache.velocity.context.InternalContextAdapter;
28 import org.apache.velocity.exception.MethodInvocationException;
29 import org.apache.velocity.exception.ParseErrorException;
30 import org.apache.velocity.exception.ResourceNotFoundException;
31 import org.apache.velocity.exception.TemplateInitException;
32 import org.apache.velocity.exception.VelocityException;
33 import org.apache.velocity.runtime.Renderable;
34 import org.apache.velocity.runtime.RuntimeConstants;
35 import org.apache.velocity.runtime.RuntimeServices;
36 import org.apache.velocity.runtime.log.Log;
37 import org.apache.velocity.runtime.parser.ParserTreeConstants;
38 import org.apache.velocity.runtime.parser.Token;
39 import org.apache.velocity.runtime.parser.node.Node;
40 import org.apache.velocity.util.introspection.Info;
41
42 /**
43 * This class acts as a proxy for potential macros. When the AST is built
44 * this class is inserted as a placeholder for the macro (whether or not
45 * the macro is actually defined). At render time we check whether there is
46 * a implementation for the macro call. If an implementation cannot be
47 * found the literal text is rendered.
48 * @since 1.6
49 */
50 public class RuntimeMacro extends Directive
51 {
52 /**
53 * Name of the macro
54 */
55 private String macroName;
56
57 /**
58 * Literal text of the macro
59 */
60 private String literal = null;
61
62 /**
63 * Node of the macro call
64 */
65 private Node node = null;
66
67 /**
68 * Indicates if we are running in strict reference mode.
69 */
70 protected boolean strictRef = false;
71
72 /**
73 * badArgsErrorMsg will be non null if the arguments to this macro
74 * are deamed bad at init time, see the init method. If his is non null, then this macro
75 * cannot be rendered, and if there is an attempt to render we throw an exception
76 * with this as the message.
77 */
78 private String badArgsErrorMsg = null;
79
80 /**
81 * Create a RuntimeMacro instance. Macro name and source
82 * template stored for later use.
83 *
84 * @param macroName name of the macro
85 */
86 public RuntimeMacro(String macroName)
87 {
88 if (macroName == null)
89 {
90 throw new IllegalArgumentException("Null arguments");
91 }
92
93 this.macroName = macroName.intern();
94 }
95
96 /**
97 * Return name of this Velocimacro.
98 *
99 * @return The name of this Velocimacro.
100 */
101 public String getName()
102 {
103 return macroName;
104 }
105
106 /**
107 * Override to always return "macro". We don't want to use
108 * the macro name here, since when writing VTL that uses the
109 * scope, we are within a #macro call. The macro name will instead
110 * be used as the scope name when defining the body of a BlockMacro.
111 */
112 public String getScopeName()
113 {
114 return "macro";
115 }
116
117 /**
118 * Velocimacros are always LINE
119 * type directives.
120 *
121 * @return The type of this directive.
122 */
123 public int getType()
124 {
125 return LINE;
126 }
127
128
129 /**
130 * Intialize the Runtime macro. At the init time no implementation so we
131 * just save the values to use at the render time.
132 *
133 * @param rs runtime services
134 * @param context InternalContextAdapter
135 * @param node node containing the macro call
136 */
137 public void init(RuntimeServices rs, InternalContextAdapter context,
138 Node node)
139 {
140 super.init(rs, context, node);
141 rsvc = rs;
142 this.node = node;
143
144 /**
145 * Apply strictRef setting only if this really looks like a macro,
146 * so strict mode doesn't balk at things like #E0E0E0 in a template.
147 * compare with ")" is a simple #foo() style macro, comparing to
148 * "#end" is a block style macro. We use starts with because the token
149 * may end with '\n'
150 */
151 Token t = node.getLastToken();
152 if (t.image.startsWith(")") || t.image.startsWith("#end"))
153 {
154 strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
155 }
156
157 // Validate that none of the arguments are plain words, (VELOCITY-614)
158 // they should be string literals, references, inline maps, or inline lists
159 for (int n=0; n < node.jjtGetNumChildren(); n++)
160 {
161 Node child = node.jjtGetChild(n);
162 if (child.getType() == ParserTreeConstants.JJTWORD)
163 {
164 badArgsErrorMsg = "Invalid arg '" + child.getFirstToken().image
165 + "' in macro #" + macroName + " at " + Log.formatFileString(child);
166
167 if (strictRef) // If strict, throw now
168 {
169 /* indicate col/line assuming it starts at 0
170 * this will be corrected one call up */
171 throw new TemplateInitException(badArgsErrorMsg,
172 context.getCurrentTemplateName(), 0, 0);
173 }
174 }
175 }
176 }
177
178 /**
179 * It is probably quite rare that we need to render the macro literal
180 * so do it only on-demand and then cache the value. This tactic helps to
181 * reduce memory usage a bit.
182 */
183 private String getLiteral()
184 {
185 if (literal == null)
186 {
187 StrBuilder buffer = new StrBuilder();
188 Token t = node.getFirstToken();
189
190 while (t != null && t != node.getLastToken())
191 {
192 buffer.append(t.image);
193 t = t.next;
194 }
195
196 if (t != null)
197 {
198 buffer.append(t.image);
199 }
200
201 literal = buffer.toString();
202 }
203 return literal;
204 }
205
206
207 /**
208 * Velocimacro implementation is not known at the init time. So look for
209 * a implementation in the macro libaries and if finds one renders it. The
210 * actual rendering is delegated to the VelocimacroProxy object. When
211 * looking for a macro we first loot at the template with has the
212 * macro call then we look at the macro lbraries in the order they appear
213 * in the list. If a macro has many definitions above look up will
214 * determine the precedence.
215 *
216 * @param context
217 * @param writer
218 * @param node
219 * @return true if the rendering is successful
220 * @throws IOException
221 * @throws ResourceNotFoundException
222 * @throws ParseErrorException
223 * @throws MethodInvocationException
224 */
225 public boolean render(InternalContextAdapter context, Writer writer,
226 Node node)
227 throws IOException, ResourceNotFoundException,
228 ParseErrorException, MethodInvocationException
229 {
230 return render(context, writer, node, null);
231 }
232
233 /**
234 * This method is used with BlockMacro when we want to render a macro with a body AST.
235 *
236 * @param context
237 * @param writer
238 * @param node
239 * @param body AST block that was enclosed in the macro body.
240 * @return true if the rendering is successful
241 * @throws IOException
242 * @throws ResourceNotFoundException
243 * @throws ParseErrorException
244 * @throws MethodInvocationException
245 */
246 public boolean render(InternalContextAdapter context, Writer writer,
247 Node node, Renderable body)
248 throws IOException, ResourceNotFoundException,
249 ParseErrorException, MethodInvocationException
250 {
251 VelocimacroProxy vmProxy = null;
252 String renderingTemplate = context.getCurrentTemplateName();
253
254 /**
255 * first look in the source template
256 */
257 Object o = rsvc.getVelocimacro(macroName, getTemplateName(), renderingTemplate);
258
259 if( o != null )
260 {
261 // getVelocimacro can only return a VelocimacroProxy so we don't need the
262 // costly instanceof check
263 vmProxy = (VelocimacroProxy)o;
264 }
265
266 /**
267 * if not found, look in the macro libraries.
268 */
269 if (vmProxy == null)
270 {
271 List macroLibraries = context.getMacroLibraries();
272 if (macroLibraries != null)
273 {
274 for (int i = macroLibraries.size() - 1; i >= 0; i--)
275 {
276 o = rsvc.getVelocimacro(macroName,
277 (String)macroLibraries.get(i), renderingTemplate);
278
279 // get the first matching macro
280 if (o != null)
281 {
282 vmProxy = (VelocimacroProxy) o;
283 break;
284 }
285 }
286 }
287 }
288
289 if (vmProxy != null)
290 {
291 try
292 {
293 // mainly check the number of arguments
294 vmProxy.checkArgs(context, node, body != null);
295 }
296 catch (TemplateInitException die)
297 {
298 throw new ParseErrorException(die.getMessage() + " at "
299 + Log.formatFileString(node), new Info(node));
300 }
301
302 if (badArgsErrorMsg != null)
303 {
304 throw new TemplateInitException(badArgsErrorMsg,
305 context.getCurrentTemplateName(), node.getColumn(), node.getLine());
306 }
307
308 try
309 {
310 preRender(context);
311 return vmProxy.render(context, writer, node, body);
312 }
313 catch (StopCommand stop)
314 {
315 if (!stop.isFor(this))
316 {
317 throw stop;
318 }
319 return true;
320 }
321 catch (RuntimeException e)
322 {
323 /**
324 * We catch, the exception here so that we can record in
325 * the logs the template and line number of the macro call
326 * which generate the exception. This information is
327 * especially important for multiple macro call levels.
328 * this is also true for the following catch blocks.
329 */
330 rsvc.getLog().error("Exception in macro #" + macroName + " called at " +
331 Log.formatFileString(node));
332 throw e;
333 }
334 catch (IOException e)
335 {
336 rsvc.getLog().error("Exception in macro #" + macroName + " called at " +
337 Log.formatFileString(node));
338 throw e;
339 }
340 finally
341 {
342 postRender(context);
343 }
344 }
345 else if (strictRef)
346 {
347 throw new VelocityException("Macro '#" + macroName + "' is not defined at "
348 + Log.formatFileString(node));
349 }
350
351 /**
352 * If we cannot find an implementation write the literal text
353 */
354 writer.write(getLiteral());
355 return true;
356 }
357 }