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 import java.util.ArrayList;
26
27 import org.apache.velocity.Template;
28 import org.apache.velocity.app.event.EventHandlerUtil;
29 import org.apache.velocity.context.InternalContextAdapter;
30 import org.apache.velocity.exception.MethodInvocationException;
31 import org.apache.velocity.exception.ParseErrorException;
32 import org.apache.velocity.exception.ResourceNotFoundException;
33 import org.apache.velocity.exception.VelocityException;
34 import org.apache.velocity.exception.TemplateInitException;
35 import org.apache.velocity.runtime.RuntimeConstants;
36 import org.apache.velocity.runtime.RuntimeServices;
37 import org.apache.velocity.runtime.log.Log;
38 import org.apache.velocity.runtime.parser.node.Node;
39 import org.apache.velocity.runtime.parser.node.SimpleNode;
40
41 /**
42 * Pluggable directive that handles the <code>#parse()</code>
43 * statement in VTL.
44 *
45 * <pre>
46 * Notes:
47 * -----
48 * 1) The parsed source material can only come from somewhere in
49 * the TemplateRoot tree for security reasons. There is no way
50 * around this. If you want to include content from elsewhere on
51 * your disk, use a link from somwhere under Template Root to that
52 * content.
53 *
54 * 2) There is a limited parse depth. It is set as a property
55 * "directive.parse.max.depth = 10" by default. This 10 deep
56 * limit is a safety feature to prevent infinite loops.
57 * </pre>
58 *
59 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
60 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
61 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
62 * @version $Id: Parse.java 928253 2010-03-27 19:39:04Z nbubna $
63 */
64 public class Parse extends InputBase
65 {
66 private int maxDepth;
67
68 /**
69 * Return name of this directive.
70 * @return The name of this directive.
71 */
72 public String getName()
73 {
74 return "parse";
75 }
76
77 /**
78 * Overrides the default to use "template", so that all templates
79 * can use the same scope reference, whether rendered via #parse
80 * or direct merge.
81 */
82 public String getScopeName()
83 {
84 return "template";
85 }
86
87 /**
88 * Return type of this directive.
89 * @return The type of this directive.
90 */
91 public int getType()
92 {
93 return LINE;
94 }
95
96 /**
97 * Init's the #parse directive.
98 * @param rs
99 * @param context
100 * @param node
101 * @throws TemplateInitException
102 */
103 public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
104 throws TemplateInitException
105 {
106 super.init(rs, context, node);
107
108 this.maxDepth = rsvc.getInt(RuntimeConstants.PARSE_DIRECTIVE_MAXDEPTH, 10);
109 }
110
111 /**
112 * iterates through the argument list and renders every
113 * argument that is appropriate. Any non appropriate
114 * arguments are logged, but render() continues.
115 * @param context
116 * @param writer
117 * @param node
118 * @return True if the directive rendered successfully.
119 * @throws IOException
120 * @throws ResourceNotFoundException
121 * @throws ParseErrorException
122 * @throws MethodInvocationException
123 */
124 public boolean render( InternalContextAdapter context,
125 Writer writer, Node node)
126 throws IOException, ResourceNotFoundException, ParseErrorException,
127 MethodInvocationException
128 {
129 /*
130 * did we get an argument?
131 */
132 if ( node.jjtGetNumChildren() == 0 )
133 {
134 throw new VelocityException("#parse(): argument missing at " +
135 Log.formatFileString(this));
136 }
137
138 /*
139 * does it have a value? If you have a null reference, then no.
140 */
141 Object value = node.jjtGetChild(0).value( context );
142 if (value == null && rsvc.getLog().isDebugEnabled())
143 {
144 rsvc.getLog().debug("#parse(): null argument at " +
145 Log.formatFileString(this));
146 }
147
148 /*
149 * get the path
150 */
151 String sourcearg = value == null ? null : value.toString();
152
153 /*
154 * check to see if the argument will be changed by the event cartridge
155 */
156 String arg = EventHandlerUtil.includeEvent( rsvc, context, sourcearg, context.getCurrentTemplateName(), getName());
157
158 /*
159 * a null return value from the event cartridge indicates we should not
160 * input a resource.
161 */
162 if (arg == null)
163 {
164 // abort early, but still consider it a successful rendering
165 return true;
166 }
167
168
169 if (maxDepth > 0)
170 {
171 /*
172 * see if we have exceeded the configured depth.
173 */
174 Object[] templateStack = context.getTemplateNameStack();
175 if (templateStack.length >= maxDepth)
176 {
177 StringBuffer path = new StringBuffer();
178 for( int i = 0; i < templateStack.length; ++i)
179 {
180 path.append( " > " + templateStack[i] );
181 }
182 rsvc.getLog().error("Max recursion depth reached (" +
183 templateStack.length + ')' + " File stack:" +
184 path);
185 return false;
186 }
187 }
188
189 /*
190 * now use the Runtime resource loader to get the template
191 */
192
193 Template t = null;
194
195 try
196 {
197 t = rsvc.getTemplate( arg, getInputEncoding(context) );
198 }
199 catch ( ResourceNotFoundException rnfe )
200 {
201 /*
202 * the arg wasn't found. Note it and throw
203 */
204 rsvc.getLog().error("#parse(): cannot find template '" + arg +
205 "', called at " + Log.formatFileString(this));
206 throw rnfe;
207 }
208 catch ( ParseErrorException pee )
209 {
210 /*
211 * the arg was found, but didn't parse - syntax error
212 * note it and throw
213 */
214 rsvc.getLog().error("#parse(): syntax error in #parse()-ed template '"
215 + arg + "', called at " + Log.formatFileString(this));
216 throw pee;
217 }
218 /**
219 * pass through application level runtime exceptions
220 */
221 catch( RuntimeException e )
222 {
223 rsvc.getLog().error("Exception rendering #parse(" + arg + ") at " +
224 Log.formatFileString(this));
225 throw e;
226 }
227 catch ( Exception e)
228 {
229 String msg = "Exception rendering #parse(" + arg + ") at " +
230 Log.formatFileString(this);
231 rsvc.getLog().error(msg, e);
232 throw new VelocityException(msg, e);
233 }
234
235 /**
236 * Add the template name to the macro libraries list
237 */
238 List macroLibraries = context.getMacroLibraries();
239
240 /**
241 * if macroLibraries are not set create a new one
242 */
243 if (macroLibraries == null)
244 {
245 macroLibraries = new ArrayList();
246 }
247
248 context.setMacroLibraries(macroLibraries);
249
250 macroLibraries.add(arg);
251
252 /*
253 * and render it
254 */
255 try
256 {
257 preRender(context);
258 context.pushCurrentTemplateName(arg);
259
260 ((SimpleNode) t.getData()).render(context, writer);
261 }
262 catch( StopCommand stop )
263 {
264 if (!stop.isFor(this))
265 {
266 throw stop;
267 }
268 }
269 /**
270 * pass through application level runtime exceptions
271 */
272 catch( RuntimeException e )
273 {
274 /**
275 * Log #parse errors so the user can track which file called which.
276 */
277 rsvc.getLog().error("Exception rendering #parse(" + arg + ") at " +
278 Log.formatFileString(this));
279 throw e;
280 }
281 catch ( Exception e )
282 {
283 String msg = "Exception rendering #parse(" + arg + ") at " +
284 Log.formatFileString(this);
285 rsvc.getLog().error(msg, e);
286 throw new VelocityException(msg, e);
287 }
288 finally
289 {
290 context.popCurrentTemplateName();
291 postRender(context);
292 }
293
294 /*
295 * note - a blocked input is still a successful operation as this is
296 * expected behavior.
297 */
298
299 return true;
300 }
301
302 }
303