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  
25  import org.apache.commons.lang.StringUtils;
26  import org.apache.velocity.context.InternalContextAdapter;
27  import org.apache.velocity.context.ProxyVMContext;
28  import org.apache.velocity.exception.MacroOverflowException;
29  import org.apache.velocity.exception.MethodInvocationException;
30  import org.apache.velocity.exception.TemplateInitException;
31  import org.apache.velocity.exception.VelocityException;
32  import org.apache.velocity.runtime.RuntimeConstants;
33  import org.apache.velocity.runtime.RuntimeServices;
34  import org.apache.velocity.runtime.parser.ParserTreeConstants;
35  import org.apache.velocity.runtime.parser.node.ASTDirective;
36  import org.apache.velocity.runtime.parser.node.Node;
37  import org.apache.velocity.runtime.parser.node.SimpleNode;
38  
39  /**
40   *  VelocimacroProxy.java
41   *
42   *   a proxy Directive-derived object to fit with the current directive system
43   *
44   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
45   * @version $Id: VelocimacroProxy.java 718424 2008-11-17 22:50:43Z nbubna $
46   */
47  public class VelocimacroProxy extends Directive
48  {
49      private String macroName;
50      private String[] argArray = null;
51      private String[] literalArgArray = null;
52      private SimpleNode nodeTree = null;
53      private int numMacroArgs = 0;
54      private boolean preInit = false;
55      private boolean strictArguments;
56      private boolean localContextScope = false;
57      private int maxCallDepth;
58  
59      /**
60       * Return name of this Velocimacro.
61       * @return The name of this Velocimacro.
62       */
63      public String getName()
64      {
65          return  macroName;
66      }
67  
68      /**
69       * Velocimacros are always LINE type directives.
70       * @return The type of this directive.
71       */
72      public int getType()
73      {
74          return LINE;
75      }
76  
77      /**
78       * sets the directive name of this VM
79       * 
80       * @param name
81       */
82      public void setName(String name)
83      {
84          macroName = name;
85      }
86  
87      /**
88       * sets the array of arguments specified in the macro definition
89       * 
90       * @param arr
91       */
92      public void setArgArray(String[] arr)
93      {
94          argArray = arr;
95          
96          // for performance reasons we precache these strings - they are needed in
97          // "render literal if null" functionality
98          literalArgArray = new String[arr.length];
99          for(int i = 0; i < arr.length; i++)
100         {
101             literalArgArray[i] = ".literal.$" + argArray[i];
102         }
103 
104         /*
105          * get the arg count from the arg array. remember that the arg array has the macro name as
106          * it's 0th element
107          */
108 
109         numMacroArgs = argArray.length - 1;
110     }
111 
112     /**
113      * @param tree
114      */
115     public void setNodeTree(SimpleNode tree)
116     {
117         nodeTree = tree;
118     }
119 
120     /**
121      * returns the number of ars needed for this VM
122      * 
123      * @return The number of ars needed for this VM
124      */
125     public int getNumArgs()
126     {
127         return numMacroArgs;
128     }
129 
130     /**
131      * Renders the macro using the context.
132      * 
133      * @param context Current rendering context
134      * @param writer Writer for output
135      * @param node AST that calls the macro
136      * @return True if the directive rendered successfully.
137      * @throws IOException
138      * @throws MethodInvocationException
139      * @throws MacroOverflowException
140      */
141     public boolean render(InternalContextAdapter context, Writer writer, Node node)
142             throws IOException, MethodInvocationException, MacroOverflowException
143     {
144         // wrap the current context and add the macro arguments
145 
146         // the creation of this context is a major bottleneck (incl 2x HashMap)
147         final ProxyVMContext vmc = new ProxyVMContext(context, rsvc, localContextScope);
148 
149         int callArguments = node.jjtGetNumChildren();
150 
151         if (callArguments > 0)
152         {
153             // the 0th element is the macro name
154             for (int i = 1; i < argArray.length && i <= callArguments; i++)
155             {
156                 Node macroCallArgument = node.jjtGetChild(i - 1);
157 
158                 /*
159                  * literalArgArray[i] is needed for "render literal if null" functionality.
160                  * The value is used in ASTReference render-method.
161                  * 
162                  * The idea is to avoid generating the literal until absolutely necessary.
163                  * 
164                  * This makes VMReferenceMungeVisitor obsolete and it would not work anyway 
165                  * when the macro AST is shared
166                  */
167                 vmc.addVMProxyArg(context, argArray[i], literalArgArray[i], macroCallArgument);
168             }
169         }
170 
171         /*
172          * check that we aren't already at the max call depth
173          */
174         if (maxCallDepth > 0 && maxCallDepth == vmc.getCurrentMacroCallDepth())
175         {
176             String templateName = vmc.getCurrentTemplateName();
177             Object[] stack = vmc.getMacroNameStack();
178 
179             StringBuffer out = new StringBuffer(100)
180                 .append("Max calling depth of ").append(maxCallDepth)
181                 .append(" was exceeded in Template:").append(templateName)
182                 .append(" and Macro:").append(macroName)
183                 .append(" with Call Stack:");
184             for (int i = 0; i < stack.length; i++)
185             {
186                 if (i != 0)
187                 {
188                     out.append("->");
189                 }
190                 out.append(stack[i]);
191             }
192             rsvc.getLog().error(out.toString());
193 
194             try
195             {
196                 throw new MacroOverflowException(out.toString());
197             }
198             finally
199             {
200                 // clean out the macro stack, since we just broke it
201                 while (vmc.getCurrentMacroCallDepth() > 0)
202                 {
203                     vmc.popCurrentMacroName();
204                 }
205             }
206         }
207 
208         try
209         {
210             // render the velocity macro
211             vmc.pushCurrentMacroName(macroName);
212             nodeTree.render(vmc, writer);
213             vmc.popCurrentMacroName();
214             return true;
215         }
216         catch (RuntimeException e)
217         {
218             throw e;
219         }
220         catch (Exception e)
221         {
222             String msg = "VelocimacroProxy.render() : exception VM = #" + macroName + "()";
223             rsvc.getLog().error(msg, e);
224             throw new VelocityException(msg, e);
225         }
226     }
227 
228     /**
229      * The major meat of VelocimacroProxy, init() checks the # of arguments.
230      * 
231      * @param rs
232      * @param context
233      * @param node
234      * @throws TemplateInitException
235      */
236     public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
237             throws TemplateInitException
238     {
239         // there can be multiple threads here so avoid double inits
240         synchronized (this)
241         {
242             if (!preInit)
243             {
244                 super.init(rs, context, node);
245 
246                 // this is a very expensive call (ExtendedProperties is very slow)
247                 strictArguments = rs.getConfiguration().getBoolean(
248                         RuntimeConstants.VM_ARGUMENTS_STRICT, false);
249 
250                 // support for local context scope feature, where all references are local
251                 // we do not have to check this at every invocation of ProxyVMContext
252                 localContextScope = rsvc.getBoolean(RuntimeConstants.VM_CONTEXT_LOCALSCOPE, false);
253 
254                 // get the macro call depth limit
255                 maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH);
256 
257                 // initialize the parsed AST
258                 // since this is context independent we need to do this only once so
259                 // do it here instead of the render method
260                 nodeTree.init(context, rs);
261 
262                 preInit = true;
263             }
264         }
265 
266         // check how many arguments we got
267         int i = node.jjtGetNumChildren();
268 
269         // Throw exception for invalid number of arguments?
270         if (getNumArgs() != i)
271         {
272             // If we have a not-yet defined macro, we do get no arguments because
273             // the syntax tree looks different than with a already defined macro.
274             // But we do know that we must be in a macro definition context somewhere up the
275             // syntax tree.
276             // Check for that, if it is true, suppress the error message.
277             // Fixes VELOCITY-71.
278 
279             for (Node parent = node.jjtGetParent(); parent != null;)
280             {
281                 if ((parent instanceof ASTDirective)
282                         && StringUtils.equals(((ASTDirective) parent).getDirectiveName(), "macro"))
283                 {
284                     return;
285                 }
286                 parent = parent.jjtGetParent();
287             }
288 
289             String msg = "VM #" + macroName + ": too "
290                     + ((getNumArgs() > i) ? "few" : "many") + " arguments to macro. Wanted "
291                     + getNumArgs() + " got " + i;
292 
293             if (strictArguments)
294             {
295                 /**
296                  * indicate col/line assuming it starts at 0 - this will be corrected one call up
297                  */
298                 throw new TemplateInitException(msg, context.getCurrentTemplateName(), 0, 0);
299             }
300             else
301             {
302                 rsvc.getLog().debug(msg);
303                 return;
304             }
305         }
306 
307         /* now validate that none of the arguments are plain words, (VELOCITY-614)
308          * they should be string literals, references, inline maps, or inline lists */
309         for (int n=0; n < i; n++)
310         {
311             Node child = node.jjtGetChild(n);
312             if (child.getType() == ParserTreeConstants.JJTWORD)
313             {
314                 /* indicate col/line assuming it starts at 0
315                  * this will be corrected one call up  */
316                 throw new TemplateInitException("Invalid arg #"
317                     + n + " in VM #" + macroName, context.getCurrentTemplateName(), 0, 0);
318             }
319         }
320     }
321 }
322