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