diff options
Diffstat (limited to 'velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java')
-rw-r--r-- | velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java new file mode 100644 index 00000000..3cb762a4 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java @@ -0,0 +1,305 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.introspection.Info; +import org.apache.velocity.util.introspection.IntrospectionCacheData; +import org.apache.velocity.util.introspection.VelPropertyGet; + +import java.lang.reflect.InvocationTargetException; + +/** + * ASTIdentifier.java + * + * Method support for identifiers : $foo + * + * mainly used by ASTReference + * + * Introspection is now moved to 'just in time' or at render / execution + * time. There are many reasons why this has to be done, but the + * primary two are thread safety, to remove any context-derived + * information from class member variables. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTIdentifier extends SimpleNode +{ + private String identifier = ""; + + /** + * This is really immutable after the init, so keep one for this node + */ + protected Info uberInfo; + + /** + * Indicates if we are running in strict reference mode. + */ + protected boolean strictRef = false; + + /** + * @param id + */ + public ASTIdentifier(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTIdentifier(Parser p, int id) + { + super(p, id); + } + + /** + * Identifier getter + * @return identifier + */ + public String getIdentifier() + { + return identifier; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * simple init - don't do anything that is context specific. + * just get what we need from the AST, which is static. + * @param context + * @param data + * @return The data object. + * @throws TemplateInitException + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + super.init(context, data); + + identifier = rsvc.useStringInterning() ? getFirstToken().image.intern() : getFirstToken().image; + + uberInfo = new Info(getTemplateName(), getLine(), getColumn()); + + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + + saveTokenImages(); + cleanupParserAndTokens(); + + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#execute(java.lang.Object, org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object execute(Object o, InternalContextAdapter context) + throws MethodInvocationException + { + try + { + rsvc.getLogContext().pushLogContext(this, uberInfo); + + VelPropertyGet vg = null; + + try + { + /* + * first, see if we have this information cached. + */ + + IntrospectionCacheData icd = context.icacheGet(this); + Class<?> clazz = o instanceof Class<?> ? (Class<?>)o : o.getClass(); + + /* + * if we have the cache data and the class of the object we are + * invoked with is the same as that in the cache, then we must + * be all right. The last 'variable' is the method name, and + * that is fixed in the template :) + */ + + if ( icd != null && (icd.contextData == clazz) ) + { + vg = (VelPropertyGet) icd.thingy; + } + else + { + /* + * otherwise, do the introspection, and cache it. Use the + * uberspector + */ + + vg = rsvc.getUberspect().getPropertyGet(o, identifier, uberInfo); + + if (vg != null && vg.isCacheable()) + { + icd = new IntrospectionCacheData(); + icd.contextData = clazz; + icd.thingy = vg; + context.icachePut(this,icd); + } + } + } + + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "ASTIdentifier.execute() : identifier = "+identifier; + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + + /* + * we have no getter... punt... + */ + + if (vg == null) + { + if (strictRef) + { + throw new MethodInvocationException("Object '" + o.getClass().getName() + + "' does not contain property '" + identifier + "'", + null, rsvc.getLogContext().getStackTrace(), identifier, + uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn()); + } + else + { + return null; + } + } + + /* + * now try and execute. If we get a MIE, throw that + * as the app wants to get these. If not, log and punt. + */ + try + { + return vg.invoke(o); + } + catch(InvocationTargetException ite) + { + /* + * if we have an event cartridge, see if it wants to veto + * also, let non-Exception Throwables go... + */ + + Throwable t = ite.getTargetException(); + if (t instanceof Exception) + { + try + { + return EventHandlerUtil.methodException(rsvc, context, o.getClass(), vg.getMethodName(), + (Exception) t, uberInfo); + } + + /* + * If the event handler throws an exception, then wrap it + * in a MethodInvocationException. + */ + catch( Exception e ) + { + throw new MethodInvocationException( + "Invocation of method '" + vg.getMethodName() + "'" + + " in " + o.getClass() + + " threw exception " + + ite.getTargetException().toString(), + ite.getTargetException(), rsvc.getLogContext().getStackTrace(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn()); + } + } + else + { + /* + * no event cartridge to override. Just throw + */ + + throw new MethodInvocationException( + "Invocation of method '" + vg.getMethodName() + "'" + + " in " + o.getClass() + + " threw exception " + + ite.getTargetException().toString(), + ite.getTargetException(), rsvc.getLogContext().getStackTrace(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn()); + + + } + } + catch(IllegalArgumentException iae) + { + return null; + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "ASTIdentifier() : exception invoking method " + + "for identifier '" + identifier + "' in " + + o.getClass(); + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + } + finally + { + rsvc.getLogContext().popLogContext(); + } + } + + /** + * Returns the string ".<i>identifier</i>". This method is only used for displaying the VTL stacktrace + * when a rendering error is encountered when runtime.log.track_location is true. + * @return + */ + @Override + public String literal() + { + if (literal != null) + { + return literal; + } + return literal = '.' + getIdentifier(); + } +} |