blob: 37eaf19ed6daa9731879a61895e10889d5d0df23 [file] [log] [blame]
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.LogContext;
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)
*/
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
*/
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)
*/
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 && (o != 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() && (o != null))
{
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);
}
/*
* we have no getter... punt...
*/
if (vg == null)
{
if (strictRef)
{
throw new MethodInvocationException("Object '" + o.getClass().getName() +
"' does not contain property '" + identifier + "'", null, 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(), 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(), 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);
}
}
finally
{
rsvc.getLogContext().popLogContext();
}
}
}