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