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