/**************************************************************************************
* Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the LGPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.aspect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
import org.codehaus.aspectwerkz.joinpoint.JoinPoint;
import org.codehaus.aspectwerkz.transform.TransformationUtil;
import org.codehaus.aspectwerkz.CrossCuttingInfo;
import org.codehaus.aspectwerkz.DeploymentModel;
/**
* Implements the default aspect container strategy.
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r</a>
*/
public class DefaultAspectContainerStrategy implements AspectContainer {
private static final int ASPECT_CONSTRUCTION_TYPE_UNKNOWN = 0;
private static final int ASPECT_CONSTRUCTION_TYPE_DEFAULT = 1;
private static final int ASPECT_CONSTRUCTION_TYPE_CROSS_CUTTING_INFO = 2;
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[]{};
/**
* Introduction container containing introduction declared by this aspect, keys by introduction names
*/
private final Map m_introductionContainers = new HashMap();
/**
* The cross-cutting info prototype.
*/
private final CrossCuttingInfo m_infoPrototype;
/**
* An array with the single cross-cutting info, needed to save one array creation per invocation.
*/
private final Object[] arrayWithSingleCrossCuttingInfo = new Object[1];
/**
* The aspect instance prototype.
*/
private final Object m_aspectPrototype;
/**
* The constructor for the aspect.
*/
private final Constructor m_aspectConstructor;
/**
* Holds a reference to the sole per JVM aspect instance.
*/
private Object m_perJvm;
/**
* Holds references to the per class aspect instances.
*/
private final Map m_perClass = new WeakHashMap();
/**
* Holds references to the per instance aspect instances.
*/
private final Map m_perInstance = new WeakHashMap();
/**
* Holds references to the per thread aspect instances.
*/
private final Map m_perThread = new WeakHashMap();
/**
* The advice repository.
*/
private Method[] m_adviceRepository = new Method[0];
/**
* The aspect construction type.
*/
private int m_aspectConstructionType = ASPECT_CONSTRUCTION_TYPE_UNKNOWN;
/**
* Creates a new aspect container strategy.
*
* @param crossCuttingInfo the cross-cutting info
*/
public DefaultAspectContainerStrategy(final CrossCuttingInfo crossCuttingInfo) {
if (crossCuttingInfo == null) {
throw new IllegalArgumentException("cross-cutting info can not be null");
}
m_infoPrototype = crossCuttingInfo;
arrayWithSingleCrossCuttingInfo[0] = m_infoPrototype;
m_aspectConstructor = findConstructor();
m_aspectPrototype = createAspect();
createAdviceRepository();
}
/**
* Invokes an introduced method with the index specified.
*
* @param methodIndex the method index
* @param joinPoint the join point
* @return the result from the invocation
*/
public Object invokeAdvice(final int methodIndex, final JoinPoint joinPoint) {
Object result = null;
switch (m_infoPrototype.getDeploymentModel()) {
case DeploymentModel.PER_JVM:
result = invokeAdvicePerJvm(methodIndex, joinPoint);
break;
case DeploymentModel.PER_CLASS:
result = invokeAdvicePerClass(methodIndex, joinPoint);
break;
case DeploymentModel.PER_INSTANCE:
result = invokeAdvicePerInstance(methodIndex, joinPoint);
break;
case DeploymentModel.PER_THREAD:
result = invokeAdvicePerThread(methodIndex, joinPoint);
break;
default:
throw new RuntimeException("invalid deployment model: " + m_infoPrototype.getDeploymentModel());
}
return result;
}
/**
* Returns a specific advice by index.
*
* @param index the index
* @return the advice
*/
public Method getAdvice(final int index) {
if (index < 0) {
throw new IllegalArgumentException("advice index can not be less than 0");
}
return m_adviceRepository[index];
}
/**
* Returns the cross-cutting info.
*
* @return the cross-cutting info
*/
public CrossCuttingInfo getCrossCuttingInfo() {
return m_infoPrototype;
}
/**
* Invokes the advice method on a per JVM basis.
*
* @param methodIndex the method index
* @param joinPoint the join point
* @return the result from the method invocation
*/
private Object invokeAdvicePerJvm(final int methodIndex, final JoinPoint joinPoint) {
Object result = null;
try {
createPerJvmAspect();
Method method = m_adviceRepository[methodIndex];
result = method.invoke(m_perJvm, new Object[]{joinPoint});
}
catch (InvocationTargetException e) {
throw new WrappedRuntimeException(e.getTargetException());
}
catch (Exception e) {
throw new WrappedRuntimeException(e);
}
return result;
}
/**
* Invokes the advice method on a per class basis.
*
* @param methodIndex the method index
* @param joinPoint the join point
* @return the result from the method invocation
*/
private Object invokeAdvicePerClass(final int methodIndex, final JoinPoint joinPoint) {
final Class targetClass = joinPoint.getTargetClass();
Object result = null;
try {
createPerClassAspect(targetClass);
result = m_adviceRepository[methodIndex].invoke(m_perClass.get(targetClass), new Object[]{joinPoint});
}
catch (InvocationTargetException e) {
throw new WrappedRuntimeException(e.getTargetException());
}
catch (Exception e) {
throw new WrappedRuntimeException(e);
}
return result;
}
/**
* Invokes the advice method on a per instance basis.
*
* @param methodIndex the method index
* @param joinPoint the join point
* @return the result from the method invocation
*/
private Object invokeAdvicePerInstance(final int methodIndex, final JoinPoint joinPoint) {
Object result = null;
Object targetInstance = joinPoint.getTargetInstance();
if (targetInstance == null) { // can be null if f.e. an aspect has deployment model perInstance and has caller side pointcuts defined
return invokeAdvicePerClass(methodIndex, joinPoint);
}
try {
createPerInstanceAspect(targetInstance);
result =
m_adviceRepository[methodIndex].invoke(m_perInstance.get(targetInstance), new Object[]{joinPoint});
}
catch (InvocationTargetException e) {
throw new WrappedRuntimeException(e.getTargetException());
}
catch (Exception e) {
throw new WrappedRuntimeException(e);
}
return result;
}
/**
* Invokes the advice method on a per thread basis.
*
* @param methodIndex the method index
* @param joinPoint the join point
* @return the result from the method invocation
*/
private Object invokeAdvicePerThread(final int methodIndex, final JoinPoint joinPoint) {
Object result;
try {
final Thread currentThread = Thread.currentThread();
createPerThreadAspect(currentThread);
Method method = m_adviceRepository[methodIndex];
result = method.invoke(m_perThread.get(currentThread), new Object[]{joinPoint});
}
catch (InvocationTargetException e) {
throw new WrappedRuntimeException(e.getTargetException());
}
catch (Exception e) {
throw new WrappedRuntimeException(e);
}
return result;
}
/**
* Creates a new perJVM cross-cutting instance, if it already exists then return it.
*
* @return the cross-cutting instance
*/
public Object createPerJvmAspect() {
if (m_perJvm == null) {
m_perJvm = createAspect();
}
return m_perJvm;
}
/**
* Creates a new perClass cross-cutting instance, if it already exists then return it.
*
* @param callingClass
* @return the cross-cutting instance
*/
public Object createPerClassAspect(final Class callingClass) {
if (!m_perClass.containsKey(callingClass)) {
synchronized (m_perClass) {
m_perClass.put(callingClass, createAspect());
}
}
return m_perClass.get(callingClass);
}
/**
* Creates a new perInstance cross-cutting instance, if it already exists then return it.
*
* @param callingInstance
* @return the cross-cutting instance
*/
public Object createPerInstanceAspect(final Object callingInstance) {
if (callingInstance == null) {
return createPerClassAspect(callingInstance.getClass());
}
if (!m_perInstance.containsKey(callingInstance)) {
synchronized (m_perInstance) {
m_perInstance.put(callingInstance, createAspect());
}
}
return m_perInstance.get(callingInstance);
}
/**
* Creates a new perThread cross-cutting instance, if it already exists then return it.
*
* @param thread the thread for the aspect
* @return the cross-cutting instance
*/
public Object createPerThreadAspect(final Thread thread) {
if (!m_perThread.containsKey(thread)) {
synchronized (m_perThread) {
m_perThread.put(thread, createAspect());
}
}
return m_perThread.get(thread);
}
/**
* Grabs the correct constructor for the aspect.
*
* @return the constructor for the aspect
*/
private Constructor findConstructor() {
Constructor aspectConstructor = null;
Class aspectClass = m_infoPrototype.getAspectClass();
Constructor[] constructors = aspectClass.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
Constructor constructor = constructors[i];
Class[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length == 0) {
m_aspectConstructionType = ASPECT_CONSTRUCTION_TYPE_DEFAULT;
aspectConstructor = constructor;
}
else if (parameterTypes.length == 1 && parameterTypes[0].equals(CrossCuttingInfo.class)) {
m_aspectConstructionType = ASPECT_CONSTRUCTION_TYPE_CROSS_CUTTING_INFO;
aspectConstructor = constructor;
break;
}
}
if (m_aspectConstructionType == ASPECT_CONSTRUCTION_TYPE_UNKNOWN) {
throw new RuntimeException(
"aspect [" + aspectClass.getName() +
"] does not have a valid constructor (either default no-arg or one that takes a CrossCuttingInfo type as its only parameter)"
);
}
return aspectConstructor;
}
/**
* Creates a new aspect instance.
*
* @return the new aspect instance
*/
private Object createAspect() {
try {
switch (m_aspectConstructionType) {
case ASPECT_CONSTRUCTION_TYPE_DEFAULT:
return m_aspectConstructor.newInstance(EMPTY_OBJECT_ARRAY);
case ASPECT_CONSTRUCTION_TYPE_CROSS_CUTTING_INFO:
return m_aspectConstructor.newInstance(arrayWithSingleCrossCuttingInfo);
default:
throw new RuntimeException(
"aspect [" + m_aspectPrototype.getClass().getName() +
"] does not have a valid constructor (either default no-arg or one that takes a CrossCuttingInfo type as its only parameter)"
);
}
}
catch (InstantiationException e) {
throw new WrappedRuntimeException(e);
}
catch (IllegalAccessException e) {
throw new WrappedRuntimeException(e);
}
catch (InvocationTargetException e) {
throw new WrappedRuntimeException(e.getTargetException());
}
}
/**
* Creates a repository for the advice methods.
*/
private void createAdviceRepository() {
synchronized (m_adviceRepository) {
List methodList = TransformationUtil.createSortedMethodList(m_infoPrototype.getAspectClass());
m_adviceRepository = new Method[methodList.size()];
for (int i = 0; i < m_adviceRepository.length; i++) {
Method method = (Method)methodList.get(i);
method.setAccessible(true);
m_adviceRepository[i] = method;
}
}
}
/**
* Attach the introduction container to this aspect container to mirror the "aspect contains 0-n introduction"
*
* @param name of the introduction
* @param introContainer introduction container
*/
public void addIntroductionContainer(final String name, final IntroductionContainer introContainer) {
m_introductionContainers.put(name, introContainer);
}
/**
* Returns the introduction container of given name (introduction name) or null if not linked.
*
* @param name of the introduction
* @return introduction container
*/
public IntroductionContainer getIntroductionContainer(final String name) {
return (IntroductionContainer)m_introductionContainers.get(name);
}
}