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 org.apache.commons.lang.text.StrBuilder;
23
24 import org.apache.velocity.context.InternalContextAdapter;
25 import org.apache.velocity.runtime.log.Log;
26 import org.apache.velocity.runtime.parser.node.Node;
27 import org.apache.velocity.runtime.parser.Token;
28 import org.apache.velocity.runtime.RuntimeConstants;
29 import org.apache.velocity.runtime.RuntimeServices;
30 import org.apache.velocity.exception.ResourceNotFoundException;
31 import org.apache.velocity.exception.ParseErrorException;
32 import org.apache.velocity.exception.MethodInvocationException;
33 import org.apache.velocity.exception.TemplateInitException;
34 import org.apache.velocity.util.introspection.Info;
35
36 import java.io.Writer;
37 import java.io.IOException;
38 import java.util.List;
39
40 /**
41 * This class acts as a proxy for potential macros. When the AST is built
42 * this class is inserted as a placeholder for the macro (whether or not
43 * the macro is actually defined). At render time we check whether there is
44 * a implementation for the macro call. If an implementation cannot be
45 * found the literal text is rendered.
46 * @since 1.6
47 */
48 public class RuntimeMacro extends Directive
49 {
50 /**
51 * Name of the macro
52 */
53 private String macroName;
54
55 /**
56 * source template name
57 */
58 private String sourceTemplate;
59
60 /**
61 * Literal text of the macro
62 */
63 private String literal = null;
64
65 /**
66 * Node of the macro call
67 */
68 private Node node = null;
69
70 /**
71 * Indicates if we are running in strict reference mode.
72 */
73 protected boolean strictRef = false;
74
75 /**
76 * Create a RuntimeMacro instance. Macro name and source
77 * template stored for later use.
78 *
79 * @param macroName name of the macro
80 * @param sourceTemplate template where macro call is made
81 */
82 public RuntimeMacro(String macroName, String sourceTemplate)
83 {
84 if (macroName == null || sourceTemplate == null)
85 {
86 throw new IllegalArgumentException("Null arguments");
87 }
88
89 this.macroName = macroName;
90 this.sourceTemplate = sourceTemplate;
91 }
92
93 /**
94 * Return name of this Velocimacro.
95 *
96 * @return The name of this Velocimacro.
97 */
98 public String getName()
99 {
100 return macroName;
101 }
102
103 /**
104 * Velocimacros are always LINE
105 * type directives.
106 *
107 * @return The type of this directive.
108 */
109 public int getType()
110 {
111 return LINE;
112 }
113
114
115 /**
116 * Intialize the Runtime macro. At the init time no implementation so we
117 * just save the values to use at the render time.
118 *
119 * @param rs runtime services
120 * @param context InternalContextAdapter
121 * @param node node containing the macro call
122 */
123 public void init(RuntimeServices rs, InternalContextAdapter context,
124 Node node)
125 {
126 super.init(rs, context, node);
127 rsvc = rs;
128 this.node = node;
129
130 /**
131 * Only check for strictRef setting if this really looks like a macro,
132 * so strict mode doesn't balk at things like #E0E0E0 in a template.
133 */
134 Token t = node.getLastToken();
135 if (t.image.charAt(0) == ')')
136 {
137 strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
138 }
139 }
140
141 /**
142 * It is probably quite rare that we need to render the macro literal
143 * so do it only on-demand and then cache the value. This tactic helps to
144 * reduce memory usage a bit.
145 */
146 private String getLiteral()
147 {
148 if (literal == null)
149 {
150 StrBuilder buffer = new StrBuilder();
151 Token t = node.getFirstToken();
152
153 while (t != null && t != node.getLastToken())
154 {
155 buffer.append(t.image);
156 t = t.next;
157 }
158
159 if (t != null)
160 {
161 buffer.append(t.image);
162 }
163
164 literal = buffer.toString();
165 }
166 return literal;
167 }
168
169
170 /**
171 * Velocimacro implementation is not known at the init time. So look for
172 * a implementation in the macro libaries and if finds one renders it. The
173 * actual rendering is delegated to the VelocimacroProxy object. When
174 * looking for a macro we first loot at the template with has the
175 * macro call then we look at the macro lbraries in the order they appear
176 * in the list. If a macro has many definitions above look up will
177 * determine the precedence.
178 *
179 * @param context
180 * @param writer
181 * @param node
182 * @return true if the rendering is successfull
183 * @throws IOException
184 * @throws ResourceNotFoundException
185 * @throws ParseErrorException
186 * @throws MethodInvocationException
187 */
188 public boolean render(InternalContextAdapter context, Writer writer,
189 Node node)
190 throws IOException, ResourceNotFoundException,
191 ParseErrorException, MethodInvocationException
192 {
193 VelocimacroProxy vmProxy = null;
194 String renderingTemplate = context.getCurrentTemplateName();
195
196 /**
197 * first look in the source template
198 */
199 Object o = rsvc.getVelocimacro(macroName, sourceTemplate, renderingTemplate);
200
201 if( o != null )
202 {
203 // getVelocimacro can only return a VelocimacroProxy so we don't need the
204 // costly instanceof check
205 vmProxy = (VelocimacroProxy)o;
206 }
207
208 /**
209 * if not found, look in the macro libraries.
210 */
211 if (vmProxy == null)
212 {
213 List macroLibraries = context.getMacroLibraries();
214 if (macroLibraries != null)
215 {
216 for (int i = macroLibraries.size() - 1; i >= 0; i--)
217 {
218 o = rsvc.getVelocimacro(macroName,
219 (String)macroLibraries.get(i), renderingTemplate);
220
221 // get the first matching macro
222 if (o != null)
223 {
224 vmProxy = (VelocimacroProxy) o;
225 break;
226 }
227 }
228 }
229 }
230
231 if (vmProxy != null)
232 {
233 try
234 {
235 // mainly check the number of arguments
236 vmProxy.init(rsvc, context, node);
237 }
238 catch (TemplateInitException die)
239 {
240 Info info = new Info(sourceTemplate, node.getLine(), node.getColumn());
241 throw new ParseErrorException(die.getMessage() + " at "
242 + Log.formatFileString(info), info);
243 }
244
245 try
246 {
247 return vmProxy.render(context, writer, node);
248 }
249 catch (RuntimeException e)
250 {
251 /**
252 * We catch, the exception here so that we can record in
253 * the logs the template and line number of the macro call
254 * which generate the exception. This information is
255 * especially important for multiple macro call levels.
256 * this is also true for the following catch blocks.
257 */
258 rsvc.getLog().error("Exception in macro #" + macroName + " at " +
259 Log.formatFileString(sourceTemplate, getLine(), getColumn()));
260 throw e;
261 }
262 catch (IOException e)
263 {
264 rsvc.getLog().error("Exception in macro #" + macroName + " at " +
265 Log.formatFileString(sourceTemplate, getLine(), getColumn()));
266 throw e;
267 }
268 }
269 else if (strictRef)
270 {
271 Info info = new Info(sourceTemplate, node.getLine(), node.getColumn());
272 throw new ParseErrorException("Macro '#" + macroName + "' is not defined at "
273 + Log.formatFileString(info), info);
274 }
275
276 /**
277 * If we cannot find an implementation write the literal text
278 */
279 writer.write(getLiteral());
280 return true;
281 }
282 }