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.velocity.context.InternalContextAdapter;
26  import org.apache.velocity.context.ProxyVMContext;
27  import org.apache.velocity.exception.MacroOverflowException;
28  import org.apache.velocity.exception.MethodInvocationException;
29  import org.apache.velocity.exception.TemplateInitException;
30  import org.apache.velocity.exception.VelocityException;
31  import org.apache.velocity.runtime.Renderable;
32  import org.apache.velocity.runtime.RuntimeConstants;
33  import org.apache.velocity.runtime.RuntimeServices;
34  import org.apache.velocity.runtime.log.Log;
35  import org.apache.velocity.runtime.parser.node.Node;
36  import org.apache.velocity.runtime.parser.node.SimpleNode;
37  
38  /**
39   *  VelocimacroProxy.java
40   *
41   *   a proxy Directive-derived object to fit with the current directive system
42   *
43   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
44   * @version $Id: VelocimacroProxy.java 898032 2010-01-11 19:51:03Z nbubna $
45   */
46  public class VelocimacroProxy extends Directive
47  {
48      private String macroName;
49      private String[] argArray = null;
50      private String[] literalArgArray = null;
51      private SimpleNode nodeTree = null;
52      private int numMacroArgs = 0;
53      private boolean strictArguments;
54      private boolean localContextScope = false;
55      private int maxCallDepth;
56      private String bodyReference;
57  
58      /**
59       * Return name of this Velocimacro.
60       * @return The name of this Velocimacro.
61       */
62      public String getName()
63      {
64          return  macroName;
65      }
66  
67      /**
68       * Velocimacros are always LINE type directives.
69       * @return The type of this directive.
70       */
71      public int getType()
72      {
73          return LINE;
74      }
75  
76      /**
77       * sets the directive name of this VM
78       * 
79       * @param name
80       */
81      public void setName(String name)
82      {
83          macroName = name;
84      }
85  
86      /**
87       * sets the array of arguments specified in the macro definition
88       * 
89       * @param arr
90       */
91      public void setArgArray(String[] arr)
92      {
93          argArray = arr;
94          
95          // for performance reasons we precache these strings - they are needed in
96          // "render literal if null" functionality
97          literalArgArray = new String[arr.length];
98          for(int i = 0; i < arr.length; i++)
99          {
100             literalArgArray[i] = ".literal.$" + argArray[i];
101         }
102 
103         /*
104          * get the arg count from the arg array. remember that the arg array has the macro name as
105          * it's 0th element
106          */
107 
108         numMacroArgs = argArray.length - 1;
109     }
110 
111     /**
112      * @param tree
113      */
114     public void setNodeTree(SimpleNode tree)
115     {
116         nodeTree = tree;
117     }
118 
119     /**
120      * returns the number of ars needed for this VM
121      * 
122      * @return The number of ars needed for this VM
123      */
124     public int getNumArgs()
125     {
126         return numMacroArgs;
127     }
128 
129     public boolean render(InternalContextAdapter context, Writer writer, Node node)
130             throws IOException, MethodInvocationException, MacroOverflowException
131     {
132         return render(context, writer, node, null);
133     }
134     
135     /**
136      * Renders the macro using the context.
137      * 
138      * @param context Current rendering context
139      * @param writer Writer for output
140      * @param node AST that calls the macro
141      * @return True if the directive rendered successfully.
142      * @throws IOException
143      * @throws MethodInvocationException
144      * @throws MacroOverflowException
145      */
146     public boolean render(InternalContextAdapter context, Writer writer,
147                           Node node, Renderable body)
148             throws IOException, MethodInvocationException, MacroOverflowException
149     {
150         // wrap the current context and add the macro arguments
151 
152         // the creation of this context is a major bottleneck (incl 2x HashMap)
153         final ProxyVMContext vmc = new ProxyVMContext(context, rsvc, localContextScope);
154 
155         int callArguments = node.jjtGetNumChildren();
156 
157         if (callArguments > 0)
158         {
159             // the 0th element is the macro name
160             for (int i = 1; i < argArray.length && i <= callArguments; i++)
161             {
162                 /*
163                  * literalArgArray[i] is needed for "render literal if null" functionality.
164                  * The value is used in ASTReference render-method.
165                  * 
166                  * The idea is to avoid generating the literal until absolutely necessary.
167                  * 
168                  * This makes VMReferenceMungeVisitor obsolete and it would not work anyway 
169                  * when the macro AST is shared
170                  */
171                 vmc.addVMProxyArg(context, argArray[i], literalArgArray[i], node.jjtGetChild(i - 1));
172             }
173         }
174         
175         // if this macro was invoked by a call directive, we might have a body AST here. Put it into context.
176         if( body != null )
177         {
178             vmc.addVMProxyArg(context, bodyReference, "", body);
179         }
180 
181         /*
182          * check that we aren't already at the max call depth
183          */
184         if (maxCallDepth > 0 && maxCallDepth == vmc.getCurrentMacroCallDepth())
185         {
186             Object[] stack = vmc.getMacroNameStack();
187 
188             StringBuffer out = new StringBuffer(100)
189                 .append("Max calling depth of ").append(maxCallDepth)
190                 .append(" was exceeded in macro '").append(macroName)
191                 .append("' with Call Stack:");
192             for (int i = 0; i < stack.length; i++)
193             {
194                 if (i != 0)
195                 {
196                     out.append("->");
197                 }
198                 out.append(stack[i]);
199             }
200             out.append(" at " + Log.formatFileString(this));
201             rsvc.getLog().error(out.toString());
202             
203             // clean out the macro stack, since we just broke it
204             while (vmc.getCurrentMacroCallDepth() > 0)
205             {
206                 vmc.popCurrentMacroName();
207             }
208 
209             throw new MacroOverflowException(out.toString());
210         }
211 
212         try
213         {
214             // render the velocity macro
215             vmc.pushCurrentMacroName(macroName);
216             nodeTree.render(vmc, writer);
217             vmc.popCurrentMacroName();
218             return true;
219         }
220         catch (RuntimeException e)
221         {
222             throw e;
223         }
224         catch (Exception e)
225         {
226             String msg = "VelocimacroProxy.render() : exception VM = #" + macroName + "()";
227             rsvc.getLog().error(msg, e);
228             throw new VelocityException(msg, e);
229         }
230     }
231 
232     /**
233      * Initialize members of VelocimacroProxy.  called from MacroEntry
234      */
235     public void init(RuntimeServices rs)
236     {
237         rsvc = rs;
238       
239         // this is a very expensive call (ExtendedProperties is very slow)
240         strictArguments = rs.getConfiguration().getBoolean(
241             RuntimeConstants.VM_ARGUMENTS_STRICT, false);
242 
243         // support for local context scope feature, where all references are local
244         // we do not have to check this at every invocation of ProxyVMContext
245         localContextScope = rsvc.getBoolean(RuntimeConstants.VM_CONTEXT_LOCALSCOPE, false);
246         if (localContextScope && rsvc.getLog().isWarnEnabled())
247         {
248             // only warn once per runtime, so this isn't obnoxious
249             String key = "velocimacro.context.localscope.warning";
250             Boolean alreadyWarned = (Boolean)rsvc.getApplicationAttribute(key);
251             if (alreadyWarned == null)
252             {
253                 rsvc.setApplicationAttribute(key, Boolean.TRUE);
254                 rsvc.getLog()
255                 .warn("The "+RuntimeConstants.VM_CONTEXT_LOCALSCOPE+
256                       " feature is deprecated and will be removed in Velocity 2.0."+
257                       " Instead, please use the $macro scope to store references"+
258                       " that must be local to your macros (e.g. "+
259                       "#set( $macro.foo = 'bar' ) and $macro.foo).  This $macro"+
260                       " namespace is automatically created and destroyed for you at"+
261                       " the beginning and end of the macro rendering.");
262             }
263         }
264 
265         // get the macro call depth limit
266         maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH);
267 
268         // get name of the reference that refers to AST block passed to block macro call
269         bodyReference = rsvc.getString(RuntimeConstants.VM_BODY_REFERENCE, "bodyContent");
270     }
271     
272 
273     /**
274      * Build an error message for not providing the correct number of arguments
275      */
276     private String buildErrorMsg(Node node, int numArgsProvided)
277     {
278         String msg = "VM #" + macroName + ": too "
279           + ((getNumArgs() > numArgsProvided) ? "few" : "many") + " arguments to macro. Wanted "
280           + getNumArgs() + " got " + numArgsProvided;      
281         return msg;
282     }
283     
284     /**
285      * check if we are calling this macro with the right number of arguments.  If 
286      * we are not, and strictArguments is active, then throw TemplateInitException.
287      * This method is called during macro render, so it must be thread safe.
288      */
289     public void checkArgs(InternalContextAdapter context, Node node, boolean hasBody)
290     {
291         // check how many arguments we have
292         int i = node.jjtGetNumChildren();
293         
294         // if macro call has a body (BlockMacro) then don't count the body as an argument
295         if( hasBody )
296             i--;
297 
298         // Throw exception for invalid number of arguments?
299         if (getNumArgs() != i)
300         {
301             if (strictArguments)
302             {
303                 /**
304                  * indicate col/line assuming it starts at 0 - this will be corrected one call up
305                  */
306                 throw new TemplateInitException(buildErrorMsg(node, i), 
307                     context.getCurrentTemplateName(), 0, 0);
308             }
309             else if (rsvc.getLog().isDebugEnabled())
310             {
311                 rsvc.getLog().debug(buildErrorMsg(node, i));
312                 return;
313             }
314         }
315     }
316 }
317