diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationExpressionSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationExpressionSource.java index e409b5a605..3c5957ecf5 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationExpressionSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationExpressionSource.java @@ -152,10 +152,12 @@ public class MethodAnnotationExpressionSource implements ExpressionSource { public String getChannelName(Method method) { String channelName = this.getAnnotationValue(method, this.channelAttributeName, String.class); + if (channelName == null) { + channelName = this.getAnnotationValue(method.getDeclaringClass(), this.channelAttributeName, String.class); + } return (StringUtils.hasText(channelName) ? channelName : null); } - @SuppressWarnings("unchecked") private T getAnnotationValue(Method method, String attributeName, Class expectedType) { T value = null; for (Class annotationType : this.annotationTypes) { @@ -165,18 +167,40 @@ public class MethodAnnotationExpressionSource implements ExpressionSource { throw new IllegalStateException( "method [" + method + "] contains more than one publisher annotation"); } - Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation) - : AnnotationUtils.getValue(annotation, attributeName); - if (valueAsObject != null) { - if (expectedType.isAssignableFrom(valueAsObject.getClass())) { - value = (T) valueAsObject; - } - else { - throw new IllegalArgumentException("expected type [" + expectedType.getName() + - "] for attribute '" + attributeName + "' on publisher annotation [" + - annotationType + "], but actual type was [" + valueAsObject.getClass() + "]"); - } + value = this.getAnnotationValue(annotation, attributeName, expectedType); + } + } + return value; + } + + private T getAnnotationValue(Class clazz, String attributeName, Class expectedType) { + T value = null; + for (Class annotationType : this.annotationTypes) { + Annotation annotation = AnnotationUtils.findAnnotation(clazz, annotationType); + if (annotation != null) { + if (value != null) { + throw new IllegalStateException( + "class [" + clazz + "] contains more than one publisher annotation"); } + value = this.getAnnotationValue(annotation, attributeName, expectedType); + } + } + return value; + } + + @SuppressWarnings("unchecked") + private T getAnnotationValue(Annotation annotation, String attributeName, Class expectedType) { + T value = null; + Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation) + : AnnotationUtils.getValue(annotation, attributeName); + if (valueAsObject != null) { + if (expectedType.isAssignableFrom(valueAsObject.getClass())) { + value = (T) valueAsObject; + } + else { + throw new IllegalArgumentException("expected type [" + expectedType.getName() + + "] for attribute '" + attributeName + "' on publisher annotation [" + + annotation.annotationType() + "], but actual type was [" + valueAsObject.getClass() + "]"); } } return value; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java index d6d7279684..763938402c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java @@ -17,20 +17,27 @@ package org.springframework.integration.aop; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.aopalliance.aop.Advice; +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; import org.springframework.aop.support.AbstractPointcutAdvisor; +import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.ComposablePointcut; -import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.aop.support.annotation.AnnotationClassFilter; +import org.springframework.aop.support.annotation.AnnotationMethodMatcher; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.integration.context.BeanFactoryChannelResolver; import org.springframework.integration.core.MessageChannel; +import org.springframework.util.Assert; /** * An advisor that will apply the {@link MessagePublishingInterceptor} to any @@ -79,8 +86,8 @@ public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor implemen private Pointcut buildPointcut() { ComposablePointcut result = null; for (Class publisherAnnotationType : this.publisherAnnotationTypes) { - Pointcut cpc = new AnnotationMatchingPointcut(publisherAnnotationType, true); - Pointcut mpc = new AnnotationMatchingPointcut(null, publisherAnnotationType); + Pointcut cpc = new MetaAnnotationMatchingPointcut(publisherAnnotationType, true); + Pointcut mpc = new MetaAnnotationMatchingPointcut(null, publisherAnnotationType); if (result == null) { result = new ComposablePointcut(cpc).union(mpc); } @@ -91,4 +98,91 @@ public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor implemen return result; } + + private static class MetaAnnotationMatchingPointcut implements Pointcut { + + private final ClassFilter classFilter; + + private final MethodMatcher methodMatcher; + + + /** + * Create a new MetaAnnotationMatchingPointcut for the given annotation type. + * @param classAnnotationType the annotation type to look for at the class level + * @param checkInherited whether to explicitly check the superclasses and + * interfaces for the annotation type as well (even if the annotation type + * is not marked as inherited itself) + */ + private MetaAnnotationMatchingPointcut(Class classAnnotationType, boolean checkInherited) { + this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited); + this.methodMatcher = MethodMatcher.TRUE; + } + + /** + * Create a new MetaAnnotationMatchingPointcut for the given annotation type. + * @param classAnnotationType the annotation type to look for at the class level + * (can be null) + * @param methodAnnotationType the annotation type to look for at the method level + * (can be null) + */ + private MetaAnnotationMatchingPointcut( + Class classAnnotationType, Class methodAnnotationType) { + + Assert.isTrue((classAnnotationType != null || methodAnnotationType != null), + "Either Class annotation type or Method annotation type needs to be specified (or both)"); + + if (classAnnotationType != null) { + this.classFilter = new AnnotationClassFilter(classAnnotationType); + } + else { + this.classFilter = ClassFilter.TRUE; + } + + if (methodAnnotationType != null) { + this.methodMatcher = new MetaAnnotationMethodMatcher(methodAnnotationType); + } + else { + this.methodMatcher = MethodMatcher.TRUE; + } + } + + + public ClassFilter getClassFilter() { + return this.classFilter; + } + + public MethodMatcher getMethodMatcher() { + return this.methodMatcher; + } + } + + + private static class MetaAnnotationMethodMatcher extends AnnotationMethodMatcher { + + private final Class annotationType; + + + /** + * Create a new AnnotationClassFilter for the given annotation type. + * @param annotationType the annotation type to look for + */ + private MetaAnnotationMethodMatcher(Class annotationType) { + super(annotationType); + this.annotationType = annotationType; + } + + + @Override + @SuppressWarnings("rawtypes") + public boolean matches(Method method, Class targetClass) { + if (AnnotationUtils.getAnnotation(method, this.annotationType) != null) { + return true; + } + // The method may be on an interface, so let's check on the target class as well. + Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); + return (specificMethod != method && + (AnnotationUtils.getAnnotation(specificMethod, this.annotationType) != null)); + } + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java index d99b06c491..f7beac613a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java @@ -19,6 +19,11 @@ package org.springframework.integration.aop; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + import org.junit.Before; import org.junit.Test; @@ -38,16 +43,17 @@ public class PublisherAnnotationAdvisorTests { @Before public void setup() { - context.registerSingleton("testChannel", QueueChannel.class); + context.registerSingleton("testChannel", QueueChannel.class); + context.registerSingleton("testMetaChannel", QueueChannel.class); } + @Test - public void returnValue() { + public void annotationAtMethodLevel() { PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor(); advisor.setBeanFactory(context); QueueChannel testChannel = context.getBean("testChannel", QueueChannel.class); - advisor.setDefaultChannel(testChannel); - ProxyFactory pf = new ProxyFactory(new TestBeanImpl()); + ProxyFactory pf = new ProxyFactory(new AnnotationAtMethodLevelTestBeanImpl()); pf.addAdvisor(advisor); TestBean proxy = (TestBean) pf.getProxy(); proxy.test(); @@ -56,6 +62,48 @@ public class PublisherAnnotationAdvisorTests { assertEquals("foo", message.getPayload()); } + @Test + public void annotationAtClassLevel() { + PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor(); + advisor.setBeanFactory(context); + QueueChannel testChannel = context.getBean("testChannel", QueueChannel.class); + ProxyFactory pf = new ProxyFactory(new AnnotationAtClassLevelTestBeanImpl()); + pf.addAdvisor(advisor); + TestBean proxy = (TestBean) pf.getProxy(); + proxy.test(); + Message message = testChannel.receive(0); + assertNotNull(message); + assertEquals("foo", message.getPayload()); + } + + @Test + public void metaAnnotationAtMethodLevel() { + PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor(); + advisor.setBeanFactory(context); + QueueChannel testMetaChannel = context.getBean("testMetaChannel", QueueChannel.class); + ProxyFactory pf = new ProxyFactory(new MetaAnnotationAtMethodLevelTestBeanImpl()); + pf.addAdvisor(advisor); + TestBean proxy = (TestBean) pf.getProxy(); + proxy.test(); + Message message = testMetaChannel.receive(0); + assertNotNull(message); + assertEquals("foo", message.getPayload()); + } + + @Test + public void metaAnnotationAtClassLevel() { + PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor(); + advisor.setBeanFactory(context); + QueueChannel testMetaChannel = context.getBean("testMetaChannel", QueueChannel.class); + ProxyFactory pf = new ProxyFactory(new MetaAnnotationAtClassLevelTestBeanImpl()); + pf.addAdvisor(advisor); + TestBean proxy = (TestBean) pf.getProxy(); + proxy.test(); + Message message = testMetaChannel.receive(0); + assertNotNull(message); + assertEquals("foo", message.getPayload()); + } + static interface TestBean { @@ -64,13 +112,48 @@ public class PublisherAnnotationAdvisorTests { } - static class TestBeanImpl implements TestBean { + static class AnnotationAtMethodLevelTestBeanImpl implements TestBean { - @Publisher + @Publisher(channel="testChannel") public String test() { return "foo"; } } + + @Publisher(channel="testChannel") + static class AnnotationAtClassLevelTestBeanImpl implements TestBean { + + public String test() { + return "foo"; + } + + } + + + @Target({ElementType.METHOD, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Publisher(channel="testMetaChannel") + public @interface TestMetaPublisher { + } + + + static class MetaAnnotationAtMethodLevelTestBeanImpl implements TestBean { + + @TestMetaPublisher + public String test() { + return "foo"; + } + } + + + @TestMetaPublisher + static class MetaAnnotationAtClassLevelTestBeanImpl implements TestBean { + + public String test() { + return "foo"; + } + } + }