View Javadoc

1   package org.apache.velocity.runtime.parser.node;
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.lang.reflect.InvocationTargetException;
23  
24  import org.apache.velocity.app.event.EventHandlerUtil;
25  import org.apache.velocity.context.InternalContextAdapter;
26  import org.apache.velocity.exception.MethodInvocationException;
27  import org.apache.velocity.exception.TemplateInitException;
28  import org.apache.velocity.exception.VelocityException;
29  import org.apache.velocity.runtime.RuntimeConstants;
30  import org.apache.velocity.runtime.parser.Parser;
31  import org.apache.velocity.util.introspection.Info;
32  import org.apache.velocity.util.introspection.IntrospectionCacheData;
33  import org.apache.velocity.util.introspection.VelPropertyGet;
34  
35  /**
36   *  ASTIdentifier.java
37   *
38   *  Method support for identifiers :  $foo
39   *
40   *  mainly used by ASTRefrence
41   *
42   *  Introspection is now moved to 'just in time' or at render / execution
43   *  time. There are many reasons why this has to be done, but the
44   *  primary two are   thread safety, to remove any context-derived
45   *  information from class member  variables.
46   *
47   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
48   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
49   * @version $Id: ASTIdentifier.java 720228 2008-11-24 16:58:33Z nbubna $
50   */
51  public class ASTIdentifier extends SimpleNode
52  {
53      private String identifier = "";
54  
55      /**
56       *  This is really immutable after the init, so keep one for this node
57       */
58      protected Info uberInfo;
59      
60      /**
61       * Indicates if we are running in strict reference mode.
62       */
63      protected boolean strictRef = false;
64  
65      /**
66       * @param id
67       */
68      public ASTIdentifier(int id)
69      {
70          super(id);
71      }
72  
73      /**
74       * @param p
75       * @param id
76       */
77      public ASTIdentifier(Parser p, int id)
78      {
79          super(p, id);
80      }
81  
82  
83      /**
84       * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
85       */
86      public Object jjtAccept(ParserVisitor visitor, Object data)
87      {
88          return visitor.visit(this, data);
89      }
90  
91      /**
92       *  simple init - don't do anything that is context specific.
93       *  just get what we need from the AST, which is static.
94       * @param context
95       * @param data
96       * @return The data object.
97       * @throws TemplateInitException
98       */
99      public  Object init(InternalContextAdapter context, Object data)
100         throws TemplateInitException
101     {
102         super.init(context, data);
103 
104         identifier = getFirstToken().image;
105 
106         uberInfo = new Info(getTemplateName(), getLine(), getColumn());
107 
108         strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
109         
110         return data;
111     }
112 
113     /**
114      * @see org.apache.velocity.runtime.parser.node.SimpleNode#execute(java.lang.Object, org.apache.velocity.context.InternalContextAdapter)
115      */
116     public Object execute(Object o, InternalContextAdapter context)
117         throws MethodInvocationException
118     {
119 
120         VelPropertyGet vg = null;
121 
122         try
123         {
124             /*
125              *  first, see if we have this information cached.
126              */
127 
128             IntrospectionCacheData icd = context.icacheGet(this);
129 
130             /*
131              * if we have the cache data and the class of the object we are
132              * invoked with is the same as that in the cache, then we must
133              * be allright.  The last 'variable' is the method name, and
134              * that is fixed in the template :)
135              */
136 
137             if ( icd != null && (o != null) && (icd.contextData == o.getClass()) )
138             {
139                 vg = (VelPropertyGet) icd.thingy;
140             }
141             else
142             {
143                 /*
144                  *  otherwise, do the introspection, and cache it.  Use the
145                  *  uberspector
146                  */
147 
148                 vg = rsvc.getUberspect().getPropertyGet(o,identifier, uberInfo);
149 
150                 if (vg != null && vg.isCacheable() && (o != null))
151                 {
152                     icd = new IntrospectionCacheData();
153                     icd.contextData = o.getClass();
154                     icd.thingy = vg;
155                     context.icachePut(this,icd);
156                 }
157             }
158         }
159 
160         /**
161          * pass through application level runtime exceptions
162          */
163         catch( RuntimeException e )
164         {
165             throw e;
166         }
167         catch(Exception e)
168         {
169             String msg = "ASTIdentifier.execute() : identifier = "+identifier;
170             log.error(msg, e);
171             throw new VelocityException(msg, e);
172         }
173 
174         /*
175          *  we have no getter... punt...
176          */
177 
178         if (vg == null)
179         {
180             if (strictRef)
181             {
182                 throw new MethodInvocationException("Object '" + o.getClass().getName() +              
183                     "' does not contain property '" + identifier + "'", null, identifier,
184                     uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
185             }
186             else
187             {
188                 return null;
189             }
190         }
191 
192         /*
193          *  now try and execute.  If we get a MIE, throw that
194          *  as the app wants to get these.  If not, log and punt.
195          */
196         try
197         {
198             return vg.invoke(o);
199         }
200         catch(InvocationTargetException ite)
201         {
202             /*
203              *  if we have an event cartridge, see if it wants to veto
204              *  also, let non-Exception Throwables go...
205              */
206 
207             Throwable t = ite.getTargetException();
208             if (t instanceof Exception)
209             {
210                 try
211                 {
212                     return EventHandlerUtil.methodException(rsvc, context, o.getClass(), vg.getMethodName(),
213                             (Exception) t);
214                 }
215 
216                 /**
217                  * If the event handler throws an exception, then wrap it
218                  * in a MethodInvocationException.  Don't pass through RuntimeExceptions like other
219                  * similar catchall code blocks.
220                  */
221                 catch( Exception e )
222                 {
223                     throw new MethodInvocationException(
224                       "Invocation of method '" + vg.getMethodName() + "'"
225                       + " in  " + o.getClass()
226                       + " threw exception "
227                       + ite.getTargetException().toString(),
228                       ite.getTargetException(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn());
229                 }
230             }
231             else
232             {
233                 /*
234                  * no event cartridge to override. Just throw
235                  */
236 
237                 throw  new MethodInvocationException(
238                 "Invocation of method '" + vg.getMethodName() + "'"
239                 + " in  " + o.getClass()
240                 + " threw exception "
241                 + ite.getTargetException().toString(),
242                 ite.getTargetException(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn());
243 
244 
245             }
246         }
247         catch(IllegalArgumentException iae)
248         {
249             return null;
250         }
251         /**
252          * pass through application level runtime exceptions
253          */
254         catch( RuntimeException e )
255         {
256             throw e;
257         }
258         catch(Exception e)
259         {
260             String msg = "ASTIdentifier() : exception invoking method "
261                         + "for identifier '" + identifier + "' in "
262                         + o.getClass();
263             log.error(msg, e);
264             throw new VelocityException(msg, e);
265         }
266     }
267 }