From 5c16131ce12385adf9bdc53ec7a3586be441ca30 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Sun, 5 Oct 2008 14:57:15 +0000 Subject: [PATCH] Added annotation-type method resolution to MessageMappingMethodInvoker. --- .../message/MessageMappingMethodInvoker.java | 48 ++++++++--- ...geMappingMethodInvokerAnnotationTests.java | 80 +++++++++++++++++++ .../message/MethodInvokingConsumerTests.java | 14 ++-- 3 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 org.springframework.integration/src/test/java/org/springframework/integration/message/MessageMappingMethodInvokerAnnotationTests.java diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/message/MessageMappingMethodInvoker.java b/org.springframework.integration/src/main/java/org/springframework/integration/message/MessageMappingMethodInvoker.java index 830505ad55..e7a6664d26 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/message/MessageMappingMethodInvoker.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/message/MessageMappingMethodInvoker.java @@ -16,6 +16,7 @@ package org.springframework.integration.message; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -25,7 +26,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.integration.ConfigurationException; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.integration.util.DefaultMethodInvoker; import org.springframework.integration.util.MethodInvoker; import org.springframework.integration.util.NameResolvingMethodInvoker; @@ -36,10 +37,9 @@ import org.springframework.util.StringUtils; /** * A base or helper class for any Messaging component that acts as an adapter - * by invoking a "plain" (not Message-aware) method for a given target object. - * The target Object is mandatory, and either a {@link Method} reference or a - * 'methodName' must be provided. If no Object and Method are provided, the - * handler will simply process the request Message's payload. + * by invoking a "plain" (not Message-aware) method on a given target object. + * The target Object is mandatory, and either a {@link Method} reference, a + * 'methodName', or an Annotation type must be provided. * * @author Mark Fisher */ @@ -55,6 +55,8 @@ public class MessageMappingMethodInvoker implements MethodInvoker, InitializingB private volatile String methodName; + private volatile Class annotationType; + private volatile OutboundMessageMapper messageMapper; private volatile MethodInvoker invoker; @@ -79,6 +81,12 @@ public class MessageMappingMethodInvoker implements MethodInvoker, InitializingB this.methodName = methodName; } + public MessageMappingMethodInvoker(Object object, Class annotationType) { + Assert.notNull(object, "object must not be null"); + this.object = object; + this.annotationType = annotationType; + } + public void afterPropertiesSet() { synchronized (this.initializationMonitor) { @@ -89,18 +97,38 @@ public class MessageMappingMethodInvoker implements MethodInvoker, InitializingB final List candidates = new ArrayList(); ReflectionUtils.doWithMethods(this.object.getClass(), new ReflectionUtils.MethodCallback() { public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - if (method.getName().equals(MessageMappingMethodInvoker.this.methodName)) { - candidates.add(method); + if (MessageMappingMethodInvoker.this.methodName != null) { + if (method.getName().equals(MessageMappingMethodInvoker.this.methodName)) { + candidates.add(method); + } + } + else if (MessageMappingMethodInvoker.this.annotationType != null) { + if (AnnotationUtils.findAnnotation(method, annotationType) != null) { + candidates.add(method); + } } } }); if (candidates.size() == 0) { - throw new ConfigurationException("no such method '" + this.methodName - + "' on target class [" + this.object.getClass() + "]"); + String clause = ""; + if (this.methodName != null) { + clause = " matching method name '" + this.methodName + "'"; + } + else if (this.annotationType != null) { + clause = " matching annotation type '" + this.annotationType + "'"; + } + throw new IllegalArgumentException("unable to find a candidate method" + + clause + " on target class [" + this.object.getClass() + "]"); } - if (candidates.size() == 1) { + else if (candidates.size() == 1) { this.method = candidates.get(0); } + else if (this.annotationType != null) { + throw new IllegalArgumentException("unable to resolve method for annotation [" + + this.annotationType + "], found " + candidates.size() + + " candidates on target class [" + this.object.getClass() + "]: " + + candidates); + } } if (this.method != null) { Class[] parameterTypes = this.method.getParameterTypes(); diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/message/MessageMappingMethodInvokerAnnotationTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/message/MessageMappingMethodInvokerAnnotationTests.java new file mode 100644 index 0000000000..357f95260e --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/message/MessageMappingMethodInvokerAnnotationTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed 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. + */ + +package org.springframework.integration.message; + +import static org.junit.Assert.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.Test; + +/** + * @author Mark Fisher + */ +public class MessageMappingMethodInvokerAnnotationTests { + + @Test + public void singleAnnotationMatches() { + SingleAnnotationTestBean testBean = new SingleAnnotationTestBean(); + MessageMappingMethodInvoker invoker = + new MessageMappingMethodInvoker(testBean, TestAnnotation.class); + invoker.afterPropertiesSet(); + String result = (String) invoker.invokeMethod("foo"); + assertEquals("FOO", result); + } + + @Test(expected = IllegalArgumentException.class) + public void multipleAnnotationMatches() { + MultipleAnnotationTestBean testBean = new MultipleAnnotationTestBean(); + MessageMappingMethodInvoker invoker = + new MessageMappingMethodInvoker(testBean, TestAnnotation.class); + invoker.afterPropertiesSet(); + } + + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + private static @interface TestAnnotation { + } + + + private static class SingleAnnotationTestBean { + + @TestAnnotation + public String upperCase(String s) { + return s.toUpperCase(); + } + } + + + private static class MultipleAnnotationTestBean { + + @TestAnnotation + public String upperCase(String s) { + return s.toUpperCase(); + } + + @TestAnnotation + public String lowerCase(String s) { + return s.toLowerCase(); + } + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodInvokingConsumerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodInvokingConsumerTests.java index 96629bcda0..0e3c2ce9b2 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodInvokingConsumerTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodInvokingConsumerTests.java @@ -27,7 +27,6 @@ import java.util.concurrent.TimeUnit; import org.junit.Test; import org.springframework.context.support.GenericApplicationContext; -import org.springframework.integration.ConfigurationException; import org.springframework.integration.bus.DefaultMessageBus; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.endpoint.ServiceActivatorEndpoint; @@ -38,20 +37,20 @@ import org.springframework.integration.endpoint.ServiceActivatorEndpoint; public class MethodInvokingConsumerTests { @Test - public void testValidMethod() { + public void validMethod() { MethodInvokingConsumer consumer = new MethodInvokingConsumer(new TestSink(), "validMethod"); consumer.afterPropertiesSet(); consumer.onMessage(new GenericMessage("test")); } @Test(expected = IllegalArgumentException.class) - public void testInvalidMethodWithNoArgs() { + public void invalidMethodWithNoArgs() { MethodInvokingConsumer consumer = new MethodInvokingConsumer(new TestSink(), "invalidMethodWithNoArgs"); consumer.afterPropertiesSet(); } @Test(expected = MessagingException.class) - public void testMethodWithReturnValue() { + public void methodWithReturnValue() { Message message = new StringMessage("test"); try { MethodInvokingConsumer consumer = new MethodInvokingConsumer(new TestSink(), "methodWithReturnValue"); @@ -64,14 +63,14 @@ public class MethodInvokingConsumerTests { } } - @Test(expected = ConfigurationException.class) - public void testNoMatchingMethodName() { + @Test(expected = IllegalArgumentException.class) + public void noMatchingMethodName() { MethodInvokingConsumer consumer = new MethodInvokingConsumer(new TestSink(), "noSuchMethod"); consumer.afterPropertiesSet(); } @Test - public void testSubscription() throws Exception { + public void subscription() throws Exception { GenericApplicationContext context = new GenericApplicationContext(); SynchronousQueue queue = new SynchronousQueue(); TestBean testBean = new TestBean(queue); @@ -138,7 +137,6 @@ public class MethodInvokingConsumerTests { public String get() { return this.result; } - } }