/**************************************************************************************
* Copyright (c) The AspectWerkz Team. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the BSD style license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.joinpoint;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.StringTokenizer;
import java.util.Iterator;
import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.WeakHashMap;
import java.io.ObjectInputStream;
import org.codehaus.aspectwerkz.AspectWerkz;
import org.codehaus.aspectwerkz.Aspect;
import org.codehaus.aspectwerkz.util.Strings;
import org.codehaus.aspectwerkz.joinpoint.control.JoinPointController;
import org.codehaus.aspectwerkz.joinpoint.control.ControllerFactory;
import org.codehaus.aspectwerkz.metadata.MethodMetaData;
import org.codehaus.aspectwerkz.metadata.MetaData;
import org.codehaus.aspectwerkz.metadata.ReflectionMetaDataMaker;
import org.codehaus.aspectwerkz.metadata.ClassMetaData;
import org.codehaus.aspectwerkz.pointcut.MethodPointcut;
import org.codehaus.aspectwerkz.transform.TransformationUtil;
/**
* Matches well defined point of execution in the program where a
* method is executed.<br/>
* Stores meta data from the join point.
* I.e. a reference to original object an method, the parameters to A
* the result from the original method invocation etc.<br/>
* Handles the invocation of the advices added to the join point.
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r</a>
*/
public abstract class MethodJoinPoint implements JoinPoint {
/**
* The AspectWerkz system for this join point.
*/
protected transient AspectWerkz m_system;
/**
* The method pointcut.
*/
protected MethodPointcut[] m_pointcuts;
/**
* Meta-data for the class.
*/
protected ClassMetaData m_classMetaData;
/**
* Meta-data for the method.
*/
protected MethodMetaData m_methodMetaData;
/**
* The id of the method for this join point.
*/
protected int m_methodId;
/**
* The target object's class.
*/
protected Class m_targetClass;
/**
* A reference to the original method.
*/
protected Method m_originalMethod;
/**
* The result from the method invocation.
*/
protected Object m_result = null;
/**
* The parameters to the method invocation.
*/
protected Object[] m_parameters = new Object[0];
/**
* The UUID for the AspectWerkz system to use.
*/
protected String m_uuid;
/**
* Caches the throws pointcuts that are created at runtime.
*/
protected Map m_throwsJoinPointCache = new WeakHashMap();
/**
* The cflow pointcuts that this join point needs to be part of to execute its advices.
*/
protected List m_cflowPointcuts;
/**
* The controller object that controls the execution of advices for the join point.
*/
protected JoinPointController m_controller = null;
/**
* Checks that the method invocation chain is not reentrant.
*/
protected boolean m_reentrancyCheck = false; // must be set to false as initial value
/**
* Marks the join point as non-reentrant.
*/
protected boolean m_isNonReentrant = false;
/**
* Creates a new MethodJoinPoint object.
*
* @param uuid the UUID for the AspectWerkz system to use
* @param methodId the id of the method
* @param controllerClass the class name of the controller class to use
*/
public MethodJoinPoint(final String uuid, final int methodId, final String controllerClass) {
if (uuid == null) throw new IllegalArgumentException("uuid can not be null");
if (methodId < 0) throw new IllegalArgumentException("method id can not be less that zero");
m_system = AspectWerkz.getSystem(uuid);
m_system.initialize();
m_uuid = uuid;
m_methodId = methodId;
m_controller = ControllerFactory.createController(controllerClass);
}
/**
* Returns the AspectWerkz system.
*
* @return the system
*/
public AspectWerkz getSystem() {
return m_system;
}
/**
* Sets the method pointcuts (overwrites the old ones).
*
* @param pointcuts the method pointcuts
*/
public void setPointcuts(final MethodPointcut[] pointcuts) {
m_pointcuts = pointcuts;
}
/**
* Returns the method pointcuts.
*
* @return the method pointcuts
*/
public MethodPointcut[] getPointcuts() {
return m_pointcuts;
}
/**
* Returns the cflow pointcuts.
*
* @return the cflow pointcuts
*/
public List getCFlowPointcuts() {
return m_cflowPointcuts;
}
/**
* Sets the cflow pointcuts (overwrites the old ones)
* @param pointcuts the cflow pointcuts
*/
public void setCFlowPointcuts(final List pointcuts) {
m_cflowPointcuts = pointcuts;
}
/**
* Returns the method meta-data.
*
* @return the method meta-data
*/
public MethodMetaData getMethodMetaData() {
return m_methodMetaData;
}
/**
* Returns the internal method id.
*
* @return the internal method id
*/
public int getMethodId() {
return m_methodId;
}
/**
* Returns the original method.
*
* @return the original method
*/
public Method getOriginalMethod() {
return m_originalMethod;
}
/**
* Returns the UUID for the AspectWerkz system.
*
* @return the UUID
*/
public String getUuid() {
return m_uuid;
}
/**
* Returns the original object.
*
* @return the original object
*/
public abstract Object getTargetObject();
/**
* Returns the original class.
*
* @return the original class
*/
public Class getTargetClass() {
return m_targetClass;
}
/**
* Returns the original method.
*
* @return the original method
*/
public Method getMethod() {
return m_originalMethod;
}
/**
* Returns the method name of the original invocation.
*
* @return the method name
*/
public String getMethodName() {
// grab the original method name, ex: "__originalMethod SEP <nameToExtract> SEP 3"
final String[] tokens = Strings.splitString(m_originalMethod.getName(), TransformationUtil.DELIMITER);
return tokens[1];
}
/**
* Returns the parameters from the original invocation.
*
* @return the parameters
*/
public Object[] getParameters() {
return m_parameters;
}
/**
* Returns the parameter types from the original invocation.
*
* @return the parameter types
*/
public Class[] getParameterTypes() {
return m_originalMethod.getParameterTypes();
}
/**
* Returns the return type from the original invocation.
*
* @return the return type
*/
public Class getReturnType() {
return m_originalMethod.getReturnType();
}
/**
* Returns the result from the original invocation.
*
* @return the result
*/
public Object getResult() {
return m_result;
}
/**
* Sets the result.
*
* @param result the result as an object
*/
public void setResult(final Object result) {
m_result = result;
}
/**
* Sets the parameters.
*
* @param parameters the parameters as a list of objects
*/
public void setParameters(final Object[] parameters) {
if (parameters == null) throw new IllegalArgumentException("parameter list can not be null");
m_parameters = parameters;
}
/**
* Walks through the pointcuts and invokes all its advices. When the last
* advice of the last pointcut has been invoked, the original method is
* invoked. Is called recursively.
*
* @return the result from the next invocation
* @throws Throwable
*/
public Object proceed() throws Throwable {
return m_controller.proceed(this);
}
/**
* To be called instead of proceed() when a new thread is spawned.
* Otherwise the result is unpredicable.
*
* @return the result from the next invocation
* @throws Throwable
*/
public Object proceedInNewThread() throws Throwable {
return deepCopy().proceed();
}
/**
* Makes a deep copy of the join point.
*
* @return the clone of the join point
*/
protected abstract MethodJoinPoint deepCopy();
/**
* Invokes the origignal method.
*
* @return the result from the method invocation
* @throws Throwable the exception from the original method
*/
public Object invokeOriginalMethod() throws Throwable {
if (m_isNonReentrant) {
if (m_reentrancyCheck) return m_result;
m_reentrancyCheck = true;
}
try {
m_result = m_originalMethod.invoke(getTargetObject(), m_parameters);
}
catch (InvocationTargetException e) {
handleException(e);
}
return m_result;
}
/**
* Creates meta-data for the join point.
*/
protected void createMetaData() {
m_classMetaData = ReflectionMetaDataMaker.createClassMetaData(getTargetClass());
m_methodMetaData = ReflectionMetaDataMaker.createMethodMetaData(
getMethodName(),
getParameterTypes(),
getReturnType()
);
}
/**
* We are at this point with no poincuts defined => the method has
* a ThrowsJoinPoint defined at this method, since the method is
* already advised, create a new method pointcut for this method.
*/
protected void handleThrowsPointcut() {
List pointcuts = m_system.getMethodPointcuts(m_classMetaData, m_methodMetaData);
m_pointcuts = new MethodPointcut[pointcuts.size()];
int i = 0;
for (Iterator it = pointcuts.iterator(); it.hasNext(); i++) {
m_pointcuts[i] = (MethodPointcut)it.next();
}
}
/**
* Handles the exceptions. If the method is registered in a ThrowsPointcut
* redirect to the ThrowsJoinPoint in question, otherwise just get the
* original exception, fake the stacktrace A rethrow it.
* Caches the throws join points that are created at runtime.
*
* @param e the wrapped exception
* @throws Throwable the original exception
*/
protected void handleException(final InvocationTargetException e) throws Throwable {
final Throwable cause = e.getTargetException();
// take a look in the cache first
Integer hash = calculateHash(
m_targetClass.getName(),
m_methodMetaData,
cause.getClass().getName()
);
ThrowsJoinPoint joinPoint = (ThrowsJoinPoint)m_throwsJoinPointCache.get(hash);
if (joinPoint != null) {
joinPoint.proceed();
}
boolean hasThrowsPointcut = false;
Collection aspects = m_system.getAspects();
for (Iterator it = aspects.iterator(); it.hasNext();) {
Aspect aspect = (Aspect)it.next();
if (aspect.hasThrowsPointcut(
m_classMetaData,
m_methodMetaData,
cause.getClass().getName())) {
hasThrowsPointcut = true;
break;
}
}
if (hasThrowsPointcut) {
// create a new join point A put it in the cache
synchronized (m_throwsJoinPointCache) {
joinPoint = new ThrowsJoinPoint(m_uuid, this, cause);
m_throwsJoinPointCache.put(hash, joinPoint);
}
joinPoint.proceed();
}
else {
AspectWerkz.fakeStackTrace(cause, getTargetClass().getName());
throw cause;
}
}
/**
* Creates a pattern for the method for the joinpoint.
*
* @return the pattern
*/
protected String createMethodPattern() {
StringBuffer pattern = new StringBuffer();
pattern.append(getReturnType().getName());
pattern.append(' ');
pattern.append(getMethodName());
pattern.append('(');
Class[] parameterTypes = getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class parameterType = parameterTypes[i];
pattern.append(parameterType.getName());
if (i != parameterTypes.length - 1) {
pattern.append(',');
}
}
pattern.append(')');
return pattern.toString();
}
/**
* Creates an advices not correctly mapped message.
*
* @return the message
*/
public String createAdviceNotCorrectlyMappedMessage() {
StringBuffer cause = new StringBuffer();
cause.append("around advices for ");
cause.append(getTargetClass().getName());
cause.append("#");
cause.append(getMethodName());
cause.append(" are not correctly mapped");
return cause.toString();
}
/**
* Provides custom deserialization.
*
* @param stream the object input stream containing the serialized object
* @throws java.lang.Exception in case of failure
*/
private void readObject(final ObjectInputStream stream) throws Exception {
ObjectInputStream.GetField fields = stream.readFields();
m_uuid = (String)fields.get("m_uuid", null);
m_methodId = fields.get("m_methodId", -1);
m_targetClass = (Class)fields.get("m_targetClass", null);
m_originalMethod = (Method)fields.get("m_originalMethod", null);
m_result = fields.get("m_result", null);
m_parameters = (Object[])fields.get("m_parameters", null);
m_pointcuts = (MethodPointcut[])fields.get("m_pointcuts", null);
m_controller = (JoinPointController)fields.get("m_controller", null);
m_classMetaData = (ClassMetaData)fields.get("m_classMetaData", null);
m_methodMetaData = (MethodMetaData)fields.get("m_fieldMetaData", null);
m_reentrancyCheck = fields.get("m_reentrancyCheck", false);
m_system = AspectWerkz.getSystem(m_uuid);
m_system.initialize();
}
/**
* Calculates the hash for the class name, the meta-data A
* the exception class name.
*
* @param className the class name
* @param metaData the meta-data
* @param exceptionClassName the class name of the exception
* @return the hash
*/
protected Integer calculateHash(final String className,
final MetaData metaData,
final String exceptionClassName) {
int hash = 17;
hash = 37 * hash + className.hashCode();
hash = 37 * hash + metaData.hashCode();
hash = 37 * hash + exceptionClassName.hashCode();
Integer hashKey = new Integer(hash);
return hashKey;
}
// --- over-ridden methods ---
protected static boolean areEqualsOrBothNull(final Object o1, final Object o2) {
if (null == o1) return (null == o2);
return o1.equals(o2);
}
public String toString() {
return "["
+ super.toString()
+ ": "
+ m_methodId
+ "," + m_targetClass
+ "," + m_originalMethod
+ "," + m_parameters
+ "," + m_result
+ "," + m_classMetaData
+ "," + m_methodMetaData
+ "," + m_pointcuts
+ "," + m_controller
+ "]";
}
public int hashCode() {
int result = 17;
result = 37 * result + hashCodeOrZeroIfNull(m_targetClass);
result = 37 * result + hashCodeOrZeroIfNull(m_originalMethod);
result = 37 * result + hashCodeOrZeroIfNull(m_parameters);
result = 37 * result + hashCodeOrZeroIfNull(m_result);
result = 37 * result + hashCodeOrZeroIfNull(m_classMetaData);
result = 37 * result + hashCodeOrZeroIfNull(m_methodMetaData);
result = 37 * result + hashCodeOrZeroIfNull(m_pointcuts);
result = 37 * result + hashCodeOrZeroIfNull(m_controller);
result = 37 * result + m_methodId;
return result;
}
protected static int hashCodeOrZeroIfNull(final Object o) {
if (null == o) return 19;
return o.hashCode();
}
}