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 }