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 org.apache.commons.lang.text.StrBuilder;
23  
24  import org.apache.velocity.context.InternalContextAdapter;
25  import org.apache.velocity.runtime.log.Log;
26  import org.apache.velocity.runtime.parser.node.Node;
27  import org.apache.velocity.runtime.parser.Token;
28  import org.apache.velocity.runtime.RuntimeConstants;
29  import org.apache.velocity.runtime.RuntimeServices;
30  import org.apache.velocity.exception.ResourceNotFoundException;
31  import org.apache.velocity.exception.ParseErrorException;
32  import org.apache.velocity.exception.MethodInvocationException;
33  import org.apache.velocity.exception.TemplateInitException;
34  import org.apache.velocity.util.introspection.Info;
35  
36  import java.io.Writer;
37  import java.io.IOException;
38  import java.util.List;
39  
40  /**
41   * This class acts as a proxy for potential macros.  When the AST is built
42   * this class is inserted as a placeholder for the macro (whether or not
43   * the macro is actually defined).  At render time we check whether there is
44   * a implementation for the macro call. If an implementation cannot be
45   * found the literal text is rendered.
46   * @since 1.6
47   */
48  public class RuntimeMacro extends Directive
49  {
50      /**
51       * Name of the macro
52       */
53      private String macroName;
54  
55      /**
56       * source template name
57       */
58      private String sourceTemplate;
59  
60      /**
61       * Literal text of the macro
62       */
63      private String literal = null;
64  
65      /**
66       * Node of the macro call
67       */
68      private Node node = null;
69  
70      /**
71       * Indicates if we are running in strict reference mode.
72       */
73      protected boolean strictRef = false;
74      
75      /**
76       * Create a RuntimeMacro instance. Macro name and source
77       * template stored for later use.
78       *
79       * @param macroName name of the macro
80       * @param sourceTemplate template where macro call is made
81       */
82      public RuntimeMacro(String macroName, String sourceTemplate)
83      {
84          if (macroName == null || sourceTemplate == null)
85          {
86              throw new IllegalArgumentException("Null arguments");
87          }
88          
89          this.macroName = macroName;
90          this.sourceTemplate = sourceTemplate;
91      }
92  
93      /**
94       * Return name of this Velocimacro.
95       *
96       * @return The name of this Velocimacro.
97       */
98      public String getName()
99      {
100         return macroName;
101     }
102 
103     /**
104      * Velocimacros are always LINE
105      * type directives.
106      *
107      * @return The type of this directive.
108      */
109     public int getType()
110     {
111         return LINE;
112     }
113 
114 
115     /**
116      * Intialize the Runtime macro. At the init time no implementation so we
117      * just save the values to use at the render time.
118      *
119      * @param rs runtime services
120      * @param context InternalContextAdapter
121      * @param node node containing the macro call
122      */
123     public void init(RuntimeServices rs, InternalContextAdapter context,
124                      Node node)
125     {
126         super.init(rs, context, node);
127         rsvc = rs;
128         this.node = node;
129         
130         /**
131          * Only check for strictRef setting if this really looks like a macro,
132          * so strict mode doesn't balk at things like #E0E0E0 in a template.
133          */
134         Token t = node.getLastToken();
135         if (t.image.charAt(0) == ')')
136         {
137             strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
138         }
139     }
140 
141     /**
142      * It is probably quite rare that we need to render the macro literal
143      * so do it only on-demand and then cache the value. This tactic helps to
144      * reduce memory usage a bit.
145      */
146     private String getLiteral()
147     {
148         if (literal == null)
149         {
150             StrBuilder buffer = new StrBuilder();
151             Token t = node.getFirstToken();
152 
153             while (t != null && t != node.getLastToken())
154             {
155                 buffer.append(t.image);
156                 t = t.next;
157             }
158 
159             if (t != null)
160             {
161                 buffer.append(t.image);
162             }
163 
164             literal = buffer.toString();
165         }
166         return literal;
167     }
168     
169 
170     /**
171      * Velocimacro implementation is not known at the init time. So look for
172      * a implementation in the macro libaries and if finds one renders it. The
173      * actual rendering is delegated to the VelocimacroProxy object. When
174      * looking for a macro we first loot at the template with has the
175      * macro call then we look at the macro lbraries in the order they appear
176      * in the list. If a macro has many definitions above look up will
177      * determine the precedence.
178      *
179      * @param context
180      * @param writer
181      * @param node
182      * @return true if the rendering is successfull
183      * @throws IOException
184      * @throws ResourceNotFoundException
185      * @throws ParseErrorException
186      * @throws MethodInvocationException
187      */
188     public boolean render(InternalContextAdapter context, Writer writer,
189                           Node node)
190             throws IOException, ResourceNotFoundException,
191             ParseErrorException, MethodInvocationException
192     {
193         VelocimacroProxy vmProxy = null;
194         String renderingTemplate = context.getCurrentTemplateName();
195         
196         /**
197          * first look in the source template
198          */
199         Object o = rsvc.getVelocimacro(macroName, sourceTemplate, renderingTemplate);
200 
201         if( o != null )
202         {
203             // getVelocimacro can only return a VelocimacroProxy so we don't need the
204             // costly instanceof check
205             vmProxy = (VelocimacroProxy)o;
206         }
207 
208         /**
209          * if not found, look in the macro libraries.
210          */
211         if (vmProxy == null)
212         {
213             List macroLibraries = context.getMacroLibraries();
214             if (macroLibraries != null)
215             {
216                 for (int i = macroLibraries.size() - 1; i >= 0; i--)
217                 {
218                     o = rsvc.getVelocimacro(macroName,
219                             (String)macroLibraries.get(i), renderingTemplate);
220 
221                     // get the first matching macro
222                     if (o != null)
223                     {
224                         vmProxy = (VelocimacroProxy) o;
225                         break;
226                     }
227                 }
228             }
229         }
230 
231         if (vmProxy != null)
232         {
233             try
234             {
235             	// mainly check the number of arguments
236                 vmProxy.init(rsvc, context, node);
237             }
238             catch (TemplateInitException die)
239             {
240                 Info info = new Info(sourceTemplate, node.getLine(), node.getColumn());
241                 throw new ParseErrorException(die.getMessage() + " at "
242                     + Log.formatFileString(info), info);
243             }
244 
245             try
246             {
247                 return vmProxy.render(context, writer, node);
248             }
249             catch (RuntimeException e)
250             {
251                 /**
252                  * We catch, the exception here so that we can record in
253                  * the logs the template and line number of the macro call
254                  * which generate the exception.  This information is
255                  * especially important for multiple macro call levels.
256                  * this is also true for the following catch blocks.
257                  */
258                 rsvc.getLog().error("Exception in macro #" + macroName + " at " +
259                   Log.formatFileString(sourceTemplate, getLine(), getColumn()));
260                 throw e;
261             }
262             catch (IOException e)
263             {
264                 rsvc.getLog().error("Exception in macro #" + macroName + " at " +
265                   Log.formatFileString(sourceTemplate, getLine(), getColumn()));
266                 throw e;
267             }
268         }
269         else if (strictRef)
270         {
271             Info info = new Info(sourceTemplate, node.getLine(), node.getColumn());
272             throw new ParseErrorException("Macro '#" + macroName + "' is not defined at "
273                 + Log.formatFileString(info), info);
274         }
275         
276         /**
277          * If we cannot find an implementation write the literal text
278          */
279         writer.write(getLiteral());
280         return true;
281     }
282 }