/*******************************************************************************
* Copyright (c) 2007, 2013 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.aop.core.internal.model.builder;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.aopalliance.aop.Advice;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.ide.eclipse.aop.core.model.IAopReference.ADVICE_TYPE;
import org.springframework.ide.eclipse.aop.core.model.IAspectDefinition;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.model.IBean;
import org.springframework.ide.eclipse.core.SpringCoreUtils;
import org.springframework.ide.eclipse.core.java.ClassUtils;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Utility class that tries to locate matches of Spring AOP configurations given by {@link IAspectDefinition}.
* <p>
* Uses Spring AOP's {@link AspectJExpressionPointcut} infrastructure to determine matches.
* <p>
* With Spring 2.5 this class supports the bean pointcut primitive as well.
* @author Christian Dupuis
* @since 2.0
*/
public class AspectDefinitionMatcher {
/** Internal cache to used with {@link AspectJExpressionPointcut} */
private Map<IAspectDefinition, Object> pointcutExpressionCache = new HashMap<IAspectDefinition, Object>();
/**
* Returns all matches on {@link Method} in form of the corresponding {@link IMethod}.
* @param targetClass the target class to check for a match
* @param targetBean the target bean to check for a match
* @param info the {@link IAspectDefinition}
* @param project the current {@link IProject}
* @return the set of {@link IMethod} that match the given {@link IAspectDefinition}
* @throws Throwable any exception occurred during reflective invocation
*/
public Set<IMethod> matches(final Class<?> targetClass, final IBean targetBean, final IAspectDefinition info,
final IProject project) throws Throwable {
Set<IMethod> matches = new LinkedHashSet<IMethod>();
// check aspect definition
if (SpringCoreUtils.hasPlaceHolder(info.getPointcutExpression())) {
return Collections.emptySet();
}
// expose bean name on thread local
Class<?> proxyCreationContextClass = ClassUtils
.loadClass("org.springframework.ide.eclipse.springframework.aop.framework.autoproxy.ProxyCreationContext");
List<String> beanNames = new ArrayList<String>();
beanNames.add(targetBean.getElementName());
if (targetBean.getAliases() != null && targetBean.getAliases().length > 0) {
beanNames.addAll(Arrays.asList(targetBean.getAliases()));
}
for (String beanName : beanNames) {
ClassUtils.invokeMethod(proxyCreationContextClass, "setCurrentProxiedBeanName", new Object[] { beanName },
new Class[] { String.class });
try {
matches.addAll(internalMatches(targetClass, targetBean, info, project));
}
finally {
// reset bean name on thread local
ClassUtils.invokeMethod(proxyCreationContextClass, "setCurrentProxiedBeanName", new Object[] { null },
new Class[] { String.class });
}
}
return matches;
}
public void close() {
for (Object pce : pointcutExpressionCache.values()) {
Field field = ReflectionUtils.findField(pce.getClass(), "shadowMatchCache");
field.setAccessible(true);
Map<?, ?> shadowMatchCache = (Map<?, ?>) ReflectionUtils.getField(field, pce);
try {
Class<?> resolvedTypeClass = pce.getClass().getClassLoader().loadClass(
"org.aspectj.weaver.ResolvedType");
Method resetPrimitivesMethod = resolvedTypeClass.getMethod("resetPrimitives");
resetPrimitivesMethod.invoke(resolvedTypeClass);
}
catch (Exception e) {
}
shadowMatchCache.clear();
}
pointcutExpressionCache.clear();
}
/**
* Checks if the given matching candidate method is a legal match for Spring AOP.
* <p>
* Legal matches need to be public and either defined on the class and/or interface depending on the
* <code>isProxyTargetClass</code>.
*/
private boolean checkMethod(Class targetClass, Method targetMethod, boolean isProxyTargetClass) {
Assert.notNull(targetClass);
Assert.notNull(targetMethod);
if (!Modifier.isPublic(targetMethod.getModifiers())) {
return false;
}
else if (isProxyTargetClass) {
return true;
}
else {
Class[] targetInterfaces = org.springframework.util.ClassUtils.getAllInterfacesForClass(targetClass);
// if target class does not implement any interface allow match
if (targetInterfaces == null || targetInterfaces.length == 0) {
return true;
}
for (Class targetInterface : targetInterfaces) {
Method[] targetInterfaceMethods = targetInterface.getMethods();
for (Method targetInterfaceMethod : targetInterfaceMethods) {
Method targetMethodGuess = AopUtils.getMostSpecificMethod(targetInterfaceMethod, targetClass);
if (targetMethod.equals(targetMethodGuess)) {
return true;
}
}
}
return false;
}
}
/**
* Checks if the given <code>targetClass</code> can be proxied in Spring AOP.
*/
private boolean checkClass(Class targetClass, boolean isProxyTargetClass) throws ClassNotFoundException {
// check if bean is an infrastructure class
if (isInfrastructureClass(targetClass)) {
return false;
}
// Check if proxy-target-class=true is being used and CGLIB can't subclass the class
if (Modifier.isFinal(targetClass.getModifiers()) && isProxyTargetClass) {
return false;
}
// all fine; proceed with the given class
return true;
}
/**
* Creates {@link AspectJExpressionPointcut} instances based on {@link IAspectDefinition}.
*/
private Object createAspectJPointcutExpression(IAspectDefinition info) throws Throwable {
try {
if (pointcutExpressionCache.containsKey(info)) {
return pointcutExpressionCache.get(info);
}
Object pc = initAspectJExpressionPointcut(info);
pointcutExpressionCache.put(info, pc);
Class<?> aspectJAdviceClass = AspectJAdviceClassFactory.getAspectJAdviceClass(info);
Class<?> aspectInstanceFactoryClass = ClassUtils
.loadClass("org.springframework.ide.eclipse.springframework.aop.aspectj.SimpleAspectInstanceFactory");
if (aspectJAdviceClass != null && aspectInstanceFactoryClass != null) {
Constructor<?> ctor = aspectInstanceFactoryClass.getConstructors()[0];
Object aspectInstanceFactory = ctor.newInstance(aspectJAdviceClass);
ctor = aspectJAdviceClass.getConstructors()[0];
Object aspectJAdvice = ctor.newInstance(new Object[] { info.getAdviceMethod(), pc,
aspectInstanceFactory });
if (info.getType() == ADVICE_TYPE.AFTER_RETURNING) {
if (info.getReturning() != null) {
ClassUtils.invokeMethod(aspectJAdvice, "setReturningName", info.getReturning());
}
}
else if (info.getType() == ADVICE_TYPE.AFTER_THROWING) {
if (info.getThrowing() != null) {
ClassUtils.invokeMethod(aspectJAdvice, "setThrowingName", info.getThrowing());
}
}
if (info.getArgNames() != null && info.getArgNames().length > 0) {
ClassUtils.invokeMethod(aspectJAdvice, "setArgumentNamesFromStringArray", new Object[] { info
.getArgNames() });
}
return ClassUtils.invokeMethod(aspectJAdvice, "getPointcut");
}
else {
return pc;
}
}
catch (InvocationTargetException e) {
throw e.getCause();
}
}
private Object initAspectJExpressionPointcut(IAspectDefinition info) throws Throwable {
Class<?> expressionPointcutClass = ClassUtils
.loadClass("org.springframework.ide.eclipse.springframework.aop.aspectj.AspectJExpressionPointcut");
Object pc = expressionPointcutClass.newInstance();
for (Method m : expressionPointcutClass.getMethods()) {
if (m.getName().equals("setExpression")) {
m.invoke(pc, info.getPointcutExpression());
}
}
// don't set the declaration scope if no aspect class is yet given
if (info.getAspectClassName() != null) {
ClassUtils.invokeMethod(pc, "setPointcutDeclarationScope", ClassUtils.loadClass(info.getAspectClassName()));
}
return pc;
}
private Set<IMethod> internalMatches(final Class<?> targetClass, final IBean targetBean,
final IAspectDefinition info, final IProject project) throws Throwable {
// check if bean class can be processed
if (!checkClass(targetClass, info.isProxyTargetClass())) {
return Collections.emptySet();
}
// check if bean is synthetic as this would mean that the BeanPostProcessor would not load
BeanDefinition beanDefinition = BeansModelUtils.getMergedBeanDefinition(targetBean, null);
if (beanDefinition instanceof RootBeanDefinition && ((RootBeanDefinition) beanDefinition).isSynthetic()) {
return Collections.emptySet();
}
// check if pointcut expression has been set
if (info.getPointcutExpression() == null) {
return Collections.emptySet();
}
final Set<IMethod> matchingMethods = new HashSet<IMethod>();
final Object aspectJExpressionPointcut = createAspectJPointcutExpression(info);
if (!((Boolean) ClassUtils.invokeMethod(aspectJExpressionPointcut, "matches", targetClass))) {
return matchingMethods;
}
final IType jdtTargetType = JdtUtils.getJavaType(project, targetClass.getName());
// TODO CD here is room for speed improvements by collecting all valid
// methods in one go and then ask for
// matches
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (checkMethod(targetClass, method, info.isProxyTargetClass()) && !matchingMethods.contains(method)) {
try {
boolean matches = (Boolean) ClassUtils.invokeMethod(aspectJExpressionPointcut, "matches", method, targetClass);
if (matches) {
addMatchingJdtMethod(matchingMethods, jdtTargetType, method);
}
// If in proxy interface mode we can match on methods
// from the interface rather then the actual
// class
else if (!info.isProxyTargetClass()) {
Class[] targetInterfaces = org.springframework.util.ClassUtils.getAllInterfacesForClass(targetClass);
if (targetInterfaces != null) {
for (Class targetInterface : targetInterfaces) {
Method[] targetInterfaceMethods = targetInterface.getMethods();
for (Method targetInterfaceMethod : targetInterfaceMethods) {
Method targetMethodGuess = AopUtils.getMostSpecificMethod(targetInterfaceMethod, targetClass);
if (method.equals(targetMethodGuess)) {
matches = (Boolean) ClassUtils.invokeMethod(aspectJExpressionPointcut, "matches", targetInterfaceMethod,
targetInterface);
if (matches) {
addMatchingJdtMethod(matchingMethods, jdtTargetType, method);
}
}
}
}
}
}
} catch (Throwable e) {
if (e instanceof IllegalArgumentException) {
throw (IllegalArgumentException) e;
} else if (e instanceof IllegalAccessException) {
throw (IllegalAccessException) e;
} else {
// get the original exception out
throw new RuntimeException(e);
}
}
}
}
private void addMatchingJdtMethod(final Set<IMethod> matchingMethods, final IType jdtTargetType, Method method) {
IMethod jdtMethod = JdtUtils.getMethod(jdtTargetType, method.getName(), method.getParameterTypes());
if (jdtMethod != null) {
matchingMethods.add(jdtMethod);
}
}
});
return matchingMethods;
}
private boolean isInfrastructureClass(Class<?> beanClass) throws ClassNotFoundException {
Class<?> advisorClass = ClassUtils.loadClass(Advisor.class);
Class<?> adviceClass = ClassUtils.loadClass(Advice.class);
Class<?> aopInfrastructureBeanClass = ClassUtils.loadClass(AopInfrastructureBean.class);
return advisorClass.isAssignableFrom(beanClass) || adviceClass.isAssignableFrom(beanClass)
|| aopInfrastructureBeanClass.isAssignableFrom(beanClass);
}
}