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  
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 }