View Javadoc

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