From e6b508ca87e9f48a47be90dfe01df6625ddd6e26 Mon Sep 17 00:00:00 2001 From: David Syer Date: Tue, 10 Aug 2010 10:48:25 +0000 Subject: [PATCH] INT-538, INT-665, INT-818: @Header, @Headers and (new) @Payloads recognised in message list POJOs * All method scanning and expression building is consolidated in one place * MessageProcessor and MessageListProcessor implementations share commmon delegate helper --- ...tractAggregatingMessageGroupProcessor.java | 152 +---- ...faultAggregatingMessageGroupProcessor.java | 3 +- ...essionEvaluatingMessageGroupProcessor.java | 6 +- ...ressionEvaluatingMessageListProcessor.java | 63 +- .../aggregator/MessageGroupProcessor.java | 8 +- .../MethodInvokingMessageGroupProcessor.java | 25 +- .../MethodInvokingMessageListProcessor.java | 125 ++-- .../MethodInvokingReleaseStrategy.java | 29 +- .../PassThroughMessageGroupProcessor.java | 4 +- .../ResequencingMessageGroupProcessor.java | 4 +- .../integration/annotation/Payloads.java | 45 ++ .../AbstractIntegrationNamespaceHandler.java | 2 - .../config/xml/IntegrationNamespaceUtils.java | 1 - .../handler/AbstractMessageProcessor.java | 50 +- .../ExpressionEvaluatingMessageProcessor.java | 15 +- .../MethodInvokingMessageProcessor.java | 447 +------------ .../util/AbstractExpressionEvaluator.java | 84 +++ .../AnnotatedMethodFilter.java} | 6 +- .../FixedMethodFilter.java} | 6 +- .../util/MessagingMethodInvokerHelper.java | 621 ++++++++++++++++++ .../integration/util/UniqueMethodFilter.java | 28 + .../aggregator/AggregatorTests.java | 6 +- .../aggregator/ConcurrentAggregatorTests.java | 6 +- ...va => CorrelatingMessageBarrierTests.java} | 2 +- ...hodInvokingMessageGroupProcessorTests.java | 474 +++++++++---- .../MethodInvokingReleaseStrategyTests.java | 287 ++++++++ .../ReleaseStrategyAdapterTests.java | 226 ------- .../AggregatorIntegrationTests.java | 2 +- .../config/AggregatorParserTests.java | 35 +- .../config/TestAggregatorBean.java | 4 +- .../annotation/AggregatorAnnotationTests.java | 25 +- .../config/xml/PNamespaceTests.java | 2 +- ...essionEvaluatingMessageProcessorTests.java | 37 +- .../MethodInvokingMessageProcessorTests.java | 187 +++--- 34 files changed, 1705 insertions(+), 1312 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/annotation/Payloads.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/util/AbstractExpressionEvaluator.java rename spring-integration-core/src/main/java/org/springframework/integration/{handler/HandlerMethodFilter.java => util/AnnotatedMethodFilter.java} (90%) rename spring-integration-core/src/main/java/org/springframework/integration/{handler/FixedHandlerMethodFilter.java => util/FixedMethodFilter.java} (90%) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/util/UniqueMethodFilter.java rename spring-integration-core/src/test/java/org/springframework/integration/aggregator/{CorrelatingMessageBarrierTest.java => CorrelatingMessageBarrierTests.java} (99%) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategyTests.java delete mode 100644 spring-integration-core/src/test/java/org/springframework/integration/aggregator/ReleaseStrategyAdapterTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java index 5ee54b247c..3e3aa8e7cb 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java @@ -13,33 +13,24 @@ package org.springframework.integration.aggregator; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.aop.support.AopUtils; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.integration.Message; import org.springframework.integration.MessageHeaders; -import org.springframework.integration.annotation.Header; import org.springframework.integration.core.MessageBuilder; import org.springframework.integration.core.MessageChannel; -import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.core.MessagingOperations; import org.springframework.integration.splitter.AbstractMessageSplitter; import org.springframework.integration.store.MessageGroup; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; /** * Base class for MessageGroupProcessor implementations that aggregate the group of Messages into a single Message. @@ -47,22 +38,28 @@ import org.springframework.util.ReflectionUtils; * @author Iwein Fuld * @author Alexander Peters * @author Mark Fisher + * @author Dave Syer + * * @since 2.0 */ public abstract class AbstractAggregatingMessageGroupProcessor implements MessageGroupProcessor { private final Log logger = LogFactory.getLog(this.getClass()); - @SuppressWarnings("unchecked") - public final void processAndSend(MessageGroup group, MessagingTemplate channelTemplate, MessageChannel outputChannel) { + public final void processAndSend(MessageGroup group, MessagingOperations messagingTemplate, MessageChannel outputChannel) { Assert.notNull(group, "MessageGroup must not be null"); Assert.notNull(outputChannel, "'outputChannel' must not be null"); - Object payload = this.aggregatePayloads(group); Map headers = this.aggregateHeaders(group); - MessageBuilder builder = (payload instanceof Message) ? MessageBuilder.fromMessage((Message) payload) - : MessageBuilder.withPayload(payload); - Message message = builder.copyHeadersIfAbsent(headers).build(); - channelTemplate.send(outputChannel, message); + Object payload = this.aggregatePayloads(group, headers); + MessageBuilder builder; + if (payload instanceof Message) { + builder = MessageBuilder.fromMessage((Message) payload); + } + else { + builder = MessageBuilder.withPayload(payload).copyHeadersIfAbsent(headers); + } + Message message = builder.build(); + messagingTemplate.send(outputChannel, message); } /** @@ -120,125 +117,6 @@ public abstract class AbstractAggregatingMessageGroupProcessor implements Messag return aggregatedHeaders; } - protected abstract Object aggregatePayloads(MessageGroup group); - - protected MessageListProcessor getAdapter(Object candidate, Class annotationType) { - Method method = findAggregatorMethod(candidate, annotationType); - if (method == null) { - return null; - } - return new MethodInvokingMessageListProcessor(candidate, method); - } - - private Method findAggregatorMethod(Object candidate, Class annotationType) { - Class targetClass = AopUtils.getTargetClass(candidate); - if (targetClass == null) { - targetClass = candidate.getClass(); - } - Method method = this.findAnnotatedMethod(targetClass, annotationType); - if (method == null) { - method = this.findSinglePublicMethod(targetClass); - } - return method; - } - - private Method findAnnotatedMethod(final Class targetClass, final Class annotationType) { - final AtomicReference annotatedMethod = new AtomicReference(); - ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() { - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType); - if (annotation != null) { - Assert.isNull(annotatedMethod.get(), "found more than one method on target class [" + targetClass - + "] with the annotation type [" + annotationType.getName() + "]"); - annotatedMethod.set(method); - } - } - }); - return annotatedMethod.get(); - } - - private Method findSinglePublicMethod(Class targetClass) { - Set methods = new HashSet(); - for (Method method : targetClass.getMethods()) { - if (!method.getDeclaringClass().equals(Object.class)) { - methods.add(method); - } - } - removeListIncompatibleMethodsFrom(methods); - removeVoidMethodsFrom(methods); - removeUnfittingFrom(methods); - if (methods.size() > 1) { - throw new IllegalArgumentException("Class [" + targetClass + "] contains more than one public Method."); - } - return methods.isEmpty() ? null : methods.iterator().next(); - } - - private void removeListIncompatibleMethodsFrom(Set candidates) { - removeMethodsMatchingSelector(candidates, new MethodSelector() { - public boolean select(Method method) { - int found = 0; - for (Class parameterClass : method.getParameterTypes()) { - if (Collection.class.isAssignableFrom(parameterClass)) { - found++; - } - } - return found != 1; - } - }); - } - - private void removeVoidMethodsFrom(Set candidates) { - removeMethodsMatchingSelector(candidates, new MethodSelector() { - public boolean select(Method method) { - return method.getReturnType().getName().equals("void"); - } - }); - } - - private Set removeUnfittingFrom(Set candidates) { - return removeMethodsMatchingSelector(candidates, new MethodSelector() { - public boolean select(Method method) { - Annotation[][] parameterAnnotations = method.getParameterAnnotations(); - Class[] parameterTypes = method.getParameterTypes(); - return (!isFittinglyAnnotated(parameterTypes, parameterAnnotations)); - } - }); - } - - private boolean isFittinglyAnnotated(Class[] parameterTypes, Annotation[][] parameterAnnotations) { - int candidateParametersFound = 0; - for (int i = 0; i < parameterTypes.length; i++) { - Class parameterType = parameterTypes[i]; - if (Collection.class.isAssignableFrom(parameterType)) { - boolean headerAnnotationFound = false; - for (Annotation annotation : parameterAnnotations[i]) { - if (annotation instanceof Header) { - headerAnnotationFound = true; - } - } - if (!headerAnnotationFound) { - candidateParametersFound++; - } - } - } - return candidateParametersFound == 1; - } - - private Set removeMethodsMatchingSelector(Set candidates, MethodSelector selector) { - Set removed = new HashSet(); - Iterator iterator = candidates.iterator(); - while (iterator.hasNext()) { - Method method = iterator.next(); - if (selector.select(method)) { - iterator.remove(); - removed.add(method); - } - } - return removed; - } - - private interface MethodSelector { - boolean select(Method method); - } + protected abstract Object aggregatePayloads(MessageGroup group, Map defaultHeaders); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java index 2304f9a059..677944f108 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java @@ -19,6 +19,7 @@ package org.springframework.integration.aggregator; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import org.springframework.integration.Message; import org.springframework.integration.store.MessageGroup; @@ -36,7 +37,7 @@ import org.springframework.util.Assert; public class DefaultAggregatingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor { @Override - protected final Object aggregatePayloads(MessageGroup group) { + protected final Object aggregatePayloads(MessageGroup group, Map headers) { Collection> messages = group.getUnmarked(); Assert.notEmpty(messages, this.getClass().getSimpleName() + " cannot process empty message groups"); List payloads = new ArrayList(messages.size()); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java index 5e0c17e5ad..240f43d196 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java @@ -1,5 +1,7 @@ package org.springframework.integration.aggregator; +import java.util.Map; + import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.core.convert.ConversionService; @@ -37,10 +39,10 @@ public class ExpressionEvaluatingMessageGroupProcessor extends AbstractAggregati /** * Evaluate the expression provided on the unmarked messages (a collection) in the group, and delegate to the - * {@link MessagingTemplate} to send dowstream. + * {@link MessagingTemplate} to send downstream. */ @Override - protected Object aggregatePayloads(MessageGroup group) { + protected Object aggregatePayloads(MessageGroup group, Map headers) { return processor.process(group.getUnmarked()); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageListProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageListProcessor.java index 46b43c6724..3e976e09d2 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageListProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageListProcessor.java @@ -18,22 +18,14 @@ package org.springframework.integration.aggregator; import java.util.Collection; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.context.expression.MapAccessor; -import org.springframework.core.convert.ConversionService; -import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParseException; import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.expression.spel.support.StandardTypeConverter; import org.springframework.integration.Message; -import org.springframework.integration.context.SimpleBeanResolver; -import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; -import org.springframework.integration.transformer.MessageTransformationException; +import org.springframework.integration.util.AbstractExpressionEvaluator; /** * A base class for aggregators that evaluates a SpEL expression with the message list as the root object within the @@ -42,29 +34,14 @@ import org.springframework.integration.transformer.MessageTransformationExceptio * @author Dave Syer * @since 2.0 */ -public class ExpressionEvaluatingMessageListProcessor implements BeanFactoryAware, MessageListProcessor { +public class ExpressionEvaluatingMessageListProcessor extends AbstractExpressionEvaluator implements MessageListProcessor { private final ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); private final Expression expression; private volatile Class expectedType = null; - - private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); - - /** - * Create an {@link ExpressionEvaluatingMessageProcessor} for the given expression String. - */ - public ExpressionEvaluatingMessageListProcessor(String expression) { - try { - this.expression = parser.parseExpression(expression); - this.getEvaluationContext().addPropertyAccessor(new MapAccessor()); - } - catch (ParseException e) { - throw new IllegalArgumentException("Failed to parse expression.", e); - } - } - + /** * Set the result type expected from evaluation of the expression. */ @@ -72,37 +49,13 @@ public class ExpressionEvaluatingMessageListProcessor implements BeanFactoryAwar this.expectedType = expectedType; } - /** - * Specify a BeanFactory in order to enable resolution via @beanName in the expression. - */ - public void setBeanFactory(final BeanFactory beanFactory) { - if (beanFactory != null) { - this.getEvaluationContext().setBeanResolver(new SimpleBeanResolver(beanFactory)); - } - } - - public void setConversionService(ConversionService conversionService) { - if (conversionService != null) { - this.evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService)); - } - } - - protected StandardEvaluationContext getEvaluationContext() { - return this.evaluationContext; - } - - protected Object evaluateExpression(Expression expression, Collection> messages, - Class expectedType) { + public ExpressionEvaluatingMessageListProcessor(String expression) { try { - return (expectedType != null) ? expression.getValue(this.evaluationContext, messages, expectedType) - : expression.getValue(this.evaluationContext, messages); + this.expression = parser.parseExpression(expression); + this.getEvaluationContext().addPropertyAccessor(new MapAccessor()); } - catch (EvaluationException e) { - Throwable cause = e.getCause(); - throw new MessageTransformationException("Expression evaluation failed.", cause == null ? e : cause); - } - catch (Exception e) { - throw new MessageTransformationException("Expression evaluation failed.", e); + catch (ParseException e) { + throw new IllegalArgumentException("Failed to parse expression.", e); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupProcessor.java index ba69b3810d..61202fb9bb 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupProcessor.java @@ -14,7 +14,7 @@ package org.springframework.integration.aggregator; import org.springframework.integration.core.MessageChannel; -import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.core.MessagingOperations; import org.springframework.integration.store.MessageGroup; /** @@ -26,11 +26,11 @@ import org.springframework.integration.store.MessageGroup; public interface MessageGroupProcessor { /** - * Process the given group and send the resulting message(s) to the output channel using the messaging template. - * Implementations are free to send as little or as many messages based on the invocation as needed. For example an + * Process the given group and send the resulting message(s) to the output channel using the messaging operations. + * Implementations are free to send as few or as many messages based on the invocation as needed. For example an * aggregating processor will send only a single message representing the group, where a resequencing strategy will * send all messages in the group individually. */ - void processAndSend(MessageGroup group, MessagingTemplate messagingTemplate, MessageChannel outputChannel); + void processAndSend(MessageGroup group, MessagingOperations messagingTemplate, MessageChannel outputChannel); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java index 55c91d5879..77133b319d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java @@ -18,11 +18,13 @@ package org.springframework.integration.aggregator; import java.lang.reflect.Method; import java.util.Collection; +import java.util.Map; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.convert.ConversionService; import org.springframework.integration.Message; import org.springframework.integration.annotation.Aggregator; import org.springframework.integration.store.MessageGroup; -import org.springframework.util.Assert; /** * MessageGroupProcessor that serves as an adapter for the invocation of a POJO method. @@ -34,7 +36,7 @@ import org.springframework.util.Assert; */ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor { - private final MessageListProcessor adapter; + private final MethodInvokingMessageListProcessor processor; /** * Creates a wrapper around the object passed in. This constructor will look for a method that can process @@ -43,8 +45,7 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess * @param target the object to wrap */ public MethodInvokingMessageGroupProcessor(Object target) { - this.adapter = getAdapter(target, Aggregator.class); - Assert.notNull(this.adapter, "No aggregator method could be found for object of type: "+target.getClass()); + this.processor = new MethodInvokingMessageListProcessor(target, Aggregator.class); } /** @@ -55,7 +56,7 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess * @param methodName the name of the method to invoke */ public MethodInvokingMessageGroupProcessor(Object target, String methodName) { - this.adapter = new MethodInvokingMessageListProcessor(target, methodName); + this.processor = new MethodInvokingMessageListProcessor(target, methodName); } /** @@ -65,13 +66,21 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess * @param method the method to invoke */ public MethodInvokingMessageGroupProcessor(Object target, Method method) { - this.adapter = new MethodInvokingMessageListProcessor(target, method); + this.processor = new MethodInvokingMessageListProcessor(target, method); + } + + public void setConversionService(ConversionService conversionService) { + processor.setConversionService(conversionService); + } + + public void setBeanFactory(BeanFactory beanFactory) { + processor.setBeanFactory(beanFactory); } @Override - protected final Object aggregatePayloads(MessageGroup group) { + protected final Object aggregatePayloads(MessageGroup group, Map headers) { final Collection> messagesUpForProcessing = group.getUnmarked(); - Object result = this.adapter.process(messagesUpForProcessing); + Object result = this.processor.process(messagesUpForProcessing, headers); return result; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageListProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageListProcessor.java index 58f006881a..eb241706f7 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageListProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageListProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2010 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. @@ -16,106 +16,61 @@ package org.springframework.integration.aggregator; -import java.lang.reflect.InvocationTargetException; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; -import java.util.List; +import java.util.Map; import org.springframework.integration.Message; -import org.springframework.integration.MessagingException; -import org.springframework.integration.util.DefaultMethodInvoker; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; +import org.springframework.integration.util.AbstractExpressionEvaluator; +import org.springframework.integration.util.MessagingMethodInvokerHelper; /** - * Base class for implementing adapters for methods which take as an argument a - * list of {@link Message Message} instances or payloads. - * - * @author Marius Bogoevici - * @author Iwein Fuld + * A MessageListProcessor implementation that invokes a method on a target POJO. + * * @author Dave Syer + * @since 2.0 */ -public class MethodInvokingMessageListProcessor implements MessageListProcessor { +public class MethodInvokingMessageListProcessor extends AbstractExpressionEvaluator { - private final DefaultMethodInvoker invoker; + private final MessagingMethodInvokerHelper delegate; - protected final Method method; + public MethodInvokingMessageListProcessor(Object targetObject, Method method, Class expectedType) { + delegate = new MessagingMethodInvokerHelper(targetObject, method, expectedType, true); + } - public MethodInvokingMessageListProcessor(Object object, String methodName) { - Assert.notNull(object, "'object' must not be null"); - Assert.notNull(methodName, "'methodName' must not be null"); - this.method = ReflectionUtils.findMethod(object.getClass(), methodName, new Class[]{List.class}); - Assert.notNull(this.method, "Method '" + methodName + - "(List args)' not found on '" + object.getClass().getName() + "'."); - this.invoker = new DefaultMethodInvoker(object, this.method); - } + public MethodInvokingMessageListProcessor(Object targetObject, Method method) { + delegate = new MessagingMethodInvokerHelper(targetObject, method, true); + } - public MethodInvokingMessageListProcessor(Object object, Method method) { - Assert.notNull(object, "'object' must not be null"); - Assert.notNull(method, "'method' must not be null"); - Assert.isTrue(method.getParameterTypes().length == 1 - && method.getParameterTypes()[0].equals(List.class), - "Method " + method + " does not accept exactly one parameter, of type List."); - this.method = method; - this.invoker = new DefaultMethodInvoker(object, this.method); - } + public MethodInvokingMessageListProcessor(Object targetObject, String methodName, Class expectedType) { + delegate = new MessagingMethodInvokerHelper(targetObject, methodName, + expectedType, true); + } + public MethodInvokingMessageListProcessor(Object targetObject, String methodName) { + delegate = new MessagingMethodInvokerHelper(targetObject, methodName, true); + } - public Method getMethod() { - return method; - } + public MethodInvokingMessageListProcessor(Object targetObject, Class annotationType) { + delegate = new MessagingMethodInvokerHelper(targetObject, annotationType, Object.class, true); + } - /* (non-Javadoc) - * @see org.springframework.integration.aggregator.MessageListProcessor#executeMethod(java.util.Collection) - */ - public final Object process(Collection> messages) { - try { - if (isMethodParameterParameterized(this.method) && isHavingActualTypeArguments(this.method) - && (isActualTypeRawMessage(this.method) || isActualTypeParameterizedMessage(this.method))) { - return this.invoker.invokeMethod(messages); - } - return this.invoker.invokeMethod(extractPayloadsFromMessages(messages)); - } - catch (InvocationTargetException e) { - throw new MessagingException( - "Method '" + this.method + "' threw an Exception.", e.getTargetException()); - } - catch (Exception e) { - throw new MessagingException("Failed to invoke method '" + this.method + "'."); - } - } + public String toString() { + return delegate.toString(); + } - private static boolean isActualTypeParameterizedMessage(Method method) { - return (getCollectionActualType(method) instanceof ParameterizedType) - && Message.class.isAssignableFrom((Class) ((ParameterizedType) getCollectionActualType(method)).getRawType()); - } - - private List extractPayloadsFromMessages(Collection> messages) { - List payloadList = new ArrayList(); - for (Message message : messages) { - payloadList.add(message.getPayload()); - } - return payloadList; - } - - private static boolean isActualTypeRawMessage(Method method) { - return getCollectionActualType(method).equals(Message.class); - } - - private static Type getCollectionActualType(Method method) { - return ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0]; - } - - private static boolean isHavingActualTypeArguments(Method method) { - return ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments().length == 1; - } - - private static boolean isMethodParameterParameterized(Method method) { - return method.getGenericParameterTypes().length == 1 - && method.getGenericParameterTypes()[0] instanceof ParameterizedType; - } + public Object process(Collection> messages, Map aggregateHeaders) { + try { + return delegate.process(new ArrayList>(messages), aggregateHeaders); + } + catch (RuntimeException e) { + throw e; + } + catch (Exception e) { + throw new IllegalStateException("Failed to process message list", e); + } + } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategy.java index 2699aa998c..aeacf90272 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategy.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategy.java @@ -18,14 +18,12 @@ package org.springframework.integration.aggregator; import java.lang.reflect.Method; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.convert.ConversionService; import org.springframework.integration.store.MessageGroup; -import org.springframework.util.Assert; /** - * Adapter for methods annotated with - * {@link org.springframework.integration.annotation.ReleaseStrategy @ReleaseStrategy} - * and for 'release-strategy' elements that include a 'method' - * attribute (e.g. <release-strategy ref="beanReference" method="methodName"/>). + * A {@link ReleaseStrategy} that invokes a method on a plain old Java object. * * @author Marius Bogoevici * @author Dave Syer @@ -35,24 +33,23 @@ public class MethodInvokingReleaseStrategy implements ReleaseStrategy { private final MethodInvokingMessageListProcessor adapter; public MethodInvokingReleaseStrategy(Object object, Method method) { - adapter = new MethodInvokingMessageListProcessor(object, method); - this.assertMethodReturnsBoolean(); + adapter = new MethodInvokingMessageListProcessor(object, method, Boolean.class); } public MethodInvokingReleaseStrategy(Object object, String methodName) { - adapter = new MethodInvokingMessageListProcessor(object, methodName); - this.assertMethodReturnsBoolean(); + adapter = new MethodInvokingMessageListProcessor(object, methodName, Boolean.class); } + public void setBeanFactory(BeanFactory beanFactory) { + adapter.setBeanFactory(beanFactory); + } + + public void setConversionService(ConversionService conversionService) { + adapter.setConversionService(conversionService); + } public boolean canRelease(MessageGroup messages) { - return ((Boolean) adapter.process(messages.getUnmarked())).booleanValue(); - } - - private void assertMethodReturnsBoolean() { - Assert.isTrue(Boolean.class.equals(adapter.getMethod().getReturnType()) - || boolean.class.equals(adapter.getMethod().getReturnType()), - "Method '" + adapter.getMethod().getName() + "' does not return a boolean value"); + return (Boolean) adapter.process(messages.getUnmarked(), null); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/PassThroughMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/PassThroughMessageGroupProcessor.java index a5234ebd65..32d5c8bf60 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/PassThroughMessageGroupProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/PassThroughMessageGroupProcessor.java @@ -15,7 +15,7 @@ package org.springframework.integration.aggregator; import org.springframework.integration.Message; import org.springframework.integration.core.MessageChannel; -import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.core.MessagingOperations; import org.springframework.integration.store.MessageGroup; /** @@ -28,7 +28,7 @@ import org.springframework.integration.store.MessageGroup; */ public class PassThroughMessageGroupProcessor implements MessageGroupProcessor { - public void processAndSend(MessageGroup group, MessagingTemplate messagingTemplate, MessageChannel outputChannel) { + public void processAndSend(MessageGroup group, MessagingOperations messagingTemplate, MessageChannel outputChannel) { for (Message message : group.getUnmarked()) { messagingTemplate.send(outputChannel, message); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageGroupProcessor.java index 527231acf4..724caf448f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageGroupProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageGroupProcessor.java @@ -21,7 +21,7 @@ import java.util.List; import org.springframework.integration.Message; import org.springframework.integration.core.MessageChannel; -import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.core.MessagingOperations; import org.springframework.integration.store.MessageGroup; /** @@ -45,7 +45,7 @@ public class ResequencingMessageGroupProcessor implements MessageGroupProcessor this.comparator = comparator; } - public void processAndSend(MessageGroup group, MessagingTemplate messagingTemplate, MessageChannel outputChannel) { + public void processAndSend(MessageGroup group, MessagingOperations messagingTemplate, MessageChannel outputChannel) { Collection> messages = group.getUnmarked(); if (messages.size() > 0) { List> sorted = new ArrayList>(messages); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Payloads.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Payloads.java new file mode 100644 index 0000000000..41622d2b50 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Payloads.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2009 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation marks a method parameter as being a list of message payloads, for POJO handlers that deal with lists + * of messages (e.g. aggregators and release strategies). + *

+ * Example: void foo(@Payloads("city.name") List cityName) - will map the value of the 'name' property of the 'city' + * property of all the payload objects in the input list. + * + * @author Dave Syer + * @since 2.0 + */ +@Target( { ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Payloads { + + /** + * Expression for matching against nested properties of the payloads. + */ + String value() default ""; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java index 34249028fc..63901734fe 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java @@ -19,7 +19,6 @@ package org.springframework.integration.config.xml; import org.w3c.dom.Element; import org.w3c.dom.Node; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -30,7 +29,6 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.NamespaceHandler; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.ObjectUtils; /** * Base class for NamespaceHandlers that registers a BeanFactoryPostProcessor diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java index b090414ec4..2a7c0c3d13 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java @@ -22,7 +22,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.core.Conventions; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageProcessor.java index 862f71fffc..04ce36f98e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageProcessor.java @@ -16,58 +16,16 @@ package org.springframework.integration.handler; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.core.convert.ConversionService; -import org.springframework.expression.EvaluationException; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.Message; -import org.springframework.integration.MessageHandlingException; -import org.springframework.integration.util.BeanFactoryTypeConverter; +import org.springframework.integration.util.AbstractExpressionEvaluator; /** * @author Mark Fisher + * @author Dave Syer * @since 2.0 */ -public abstract class AbstractMessageProcessor implements MessageProcessor, BeanFactoryAware { +public abstract class AbstractMessageProcessor extends AbstractExpressionEvaluator implements MessageProcessor { - private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); - - private final BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(); - - public AbstractMessageProcessor() { - evaluationContext.setTypeConverter(typeConverter); - } - - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - typeConverter.setBeanFactory(beanFactory); - } - - public void setConversionService(ConversionService conversionService) { - if (conversionService != null) { - typeConverter.setConversionService(conversionService); - } - } - - protected StandardEvaluationContext getEvaluationContext() { - return this.evaluationContext; - } - - protected Object evaluateExpression(Expression expression, Message message, Class expectedType) { - try { - return (expectedType != null) - ? expression.getValue(this.evaluationContext, message, expectedType) - : expression.getValue(this.evaluationContext, message); - } - catch (EvaluationException e) { - Throwable cause = e.getCause(); - throw new MessageHandlingException(message, "Expression evaluation failed: "+expression.getExpressionString(), cause==null ? e : cause); - } - catch (Exception e) { - throw new MessageHandlingException(message, "Expression evaluation failed: "+expression.getExpressionString(), e); - } - } + abstract public Object processMessage(Message message); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessor.java index 689e70df2c..43928feda7 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessor.java @@ -16,8 +16,6 @@ package org.springframework.integration.handler; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.context.expression.MapAccessor; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; @@ -25,7 +23,6 @@ import org.springframework.expression.ParseException; import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.Message; -import org.springframework.integration.context.SimpleBeanResolver; import org.springframework.util.Assert; /** @@ -35,7 +32,7 @@ import org.springframework.util.Assert; * @author Mark Fisher * @since 2.0 */ -public class ExpressionEvaluatingMessageProcessor extends AbstractMessageProcessor implements BeanFactoryAware { +public class ExpressionEvaluatingMessageProcessor extends AbstractMessageProcessor { private final ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); @@ -66,16 +63,6 @@ public class ExpressionEvaluatingMessageProcessor extends AbstractMessageProcess this.expectedType = expectedType; } - /** - * Specify a BeanFactory in order to enable resolution via @beanName in the expression. - */ - public void setBeanFactory(final BeanFactory beanFactory) { - super.setBeanFactory(beanFactory); - if (beanFactory != null) { - this.getEvaluationContext().setBeanResolver(new SimpleBeanResolver(beanFactory)); - } - } - /** * Processes the Message by evaluating the expression with that Message as the * root object. The expression evaluation result Object will be returned. diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java index 43a4d7c575..dcb2771f3b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java @@ -18,452 +18,61 @@ package org.springframework.integration.handler; import java.lang.annotation.Annotation; 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.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.aop.support.AopUtils; -import org.springframework.context.expression.MapAccessor; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; -import org.springframework.core.MethodParameter; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.convert.ConversionService; import org.springframework.integration.Message; import org.springframework.integration.MessageHandlingException; -import org.springframework.integration.MessagingException; -import org.springframework.integration.annotation.Header; -import org.springframework.integration.annotation.Headers; -import org.springframework.integration.annotation.Payload; -import org.springframework.integration.util.ClassUtils; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.util.ReflectionUtils.MethodCallback; -import org.springframework.util.ReflectionUtils.MethodFilter; +import org.springframework.integration.util.MessagingMethodInvokerHelper; /** - * A MessageProcessor implementation that invokes a method on a target Object. - * The Method instance or method name may be provided as a constructor argument. - * If a method name is provided, and more than one declared method has that name, - * the method-selection will be dynamic, based on the underlying SpEL method - * resolution. Alternatively, an annotation type may be provided so that the - * candidates for SpEL's method resolution are determined by the presence of that + * A MessageProcessor implementation that invokes a method on a target Object. The Method instance or method name may be + * provided as a constructor argument. If a method name is provided, and more than one declared method has that name, + * the method-selection will be dynamic, based on the underlying SpEL method resolution. Alternatively, an annotation + * type may be provided so that the candidates for SpEL's method resolution are determined by the presence of that * annotation rather than the method name. * - * @author Mark Fisher - * @author Oleg Zhurakousky + * @author Dave Syer + * * @since 2.0 */ public class MethodInvokingMessageProcessor extends AbstractMessageProcessor { - private final Log logger = LogFactory.getLog(this.getClass()); - - private final Object targetObject; - - private volatile String displayString; - - private volatile boolean requiresReply; - - private final Map, HandlerMethod> handlerMethods; - + private final MessagingMethodInvokerHelper delegate; public MethodInvokingMessageProcessor(Object targetObject, Method method) { - this(targetObject, null, method); + delegate = new MessagingMethodInvokerHelper(targetObject, method, false); } public MethodInvokingMessageProcessor(Object targetObject, String methodName) { - this(targetObject, null, methodName); + delegate = new MessagingMethodInvokerHelper(targetObject, methodName, false); } public MethodInvokingMessageProcessor(Object targetObject, String methodName, boolean requiresReply) { - this(targetObject, null, methodName, requiresReply); + delegate = new MessagingMethodInvokerHelper(targetObject, methodName, Object.class, false); } public MethodInvokingMessageProcessor(Object targetObject, Class annotationType) { - this(targetObject, annotationType, (String) null); + delegate = new MessagingMethodInvokerHelper(targetObject, annotationType, false); } - - - /* - * Private constructors for internal use - */ - - private MethodInvokingMessageProcessor(Object targetObject, Class annotationType, Method method) { - Assert.notNull(method, "method must not be null"); - HandlerMethod handlerMethod = new HandlerMethod(method); - Assert.notNull(targetObject, "targetObject must not be null"); - this.targetObject = targetObject; - this.handlerMethods = Collections., HandlerMethod>singletonMap(handlerMethod.getTargetParameterType(), handlerMethod); - this.prepareEvaluationContext(this.getEvaluationContext(), method, annotationType); - this.setDisplayString(targetObject, method); + + @Override + public void setConversionService(ConversionService conversionService) { + super.setConversionService(conversionService); + delegate.setConversionService(conversionService); } - - private MethodInvokingMessageProcessor(Object targetObject, Class annotationType, String methodName) { - this(targetObject, annotationType, methodName, false); - } - - private MethodInvokingMessageProcessor(Object targetObject, Class annotationType, String methodName, boolean requiresReply) { - Assert.notNull(targetObject, "targetObject must not be null"); - this.targetObject = targetObject; - this.requiresReply = requiresReply; - this.handlerMethods = this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply); - this.prepareEvaluationContext(this.getEvaluationContext(), methodName, annotationType); - this.setDisplayString(targetObject, methodName); - } - - - private void setDisplayString(Object targetObject, Object targetMethod) { - StringBuilder sb = new StringBuilder(targetObject.getClass().getName()); - if (targetMethod instanceof Method) { - sb.append("." + ((Method) targetMethod).getName()); - } - else if (targetMethod instanceof String) { - sb.append("." + (String) targetMethod); - } - this.displayString = sb.toString() + "]"; - } - - private void prepareEvaluationContext(StandardEvaluationContext context, Object method, Class annotationType) { - Class targetType = AopUtils.getTargetClass(this.targetObject); - if (method instanceof Method) { - context.registerMethodFilter(targetType, new FixedHandlerMethodFilter((Method) method)); - } - else if (method == null || method instanceof String) { - context.registerMethodFilter(targetType, - new HandlerMethodFilter(annotationType, (String) method, this.requiresReply)); - } - context.addPropertyAccessor(new MapAccessor()); - context.setVariable("target", targetObject); - } - - - public String toString() { - return this.displayString; + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + super.setBeanFactory(beanFactory); + delegate.setBeanFactory(beanFactory); } public Object processMessage(Message message) { - Throwable evaluationException = null; - List candidates = this.findHandlerMethodsForMessage(message); - for (HandlerMethod candidate : candidates) { - try { - Expression expression = candidate.getExpression(); - Class expectedType = candidate.method.getReturnType(); - Object result = this.evaluateExpression(expression, message, expectedType); - if (this.requiresReply) { - Assert.notNull(result, "Expression evaluation result was null, but this processor requires a reply."); - } - return result; - } - catch (MessageHandlingException e) { - if (evaluationException == null) { - // keep the first exception - evaluationException = e.getCause(); - } - } - } - throw new MessageHandlingException(message, "Failed to process Message.", evaluationException); - } - - private Map, HandlerMethod> findHandlerMethodsForTarget(final Object targetObject, - final Class annotationType, final String methodName, final boolean requiresReply) { - - final Map, HandlerMethod> candidateMethods = new HashMap, HandlerMethod>(); - final Map, HandlerMethod> fallbackMethods = new HashMap, HandlerMethod>(); - final AtomicReference> ambiguousFallbackType = new AtomicReference>(); - final Class targetClass = this.getTargetClass(targetObject); - MethodFilter methodFilter = new UniqueMethodFilter(targetClass); - ReflectionUtils.doWithMethods(targetClass, new MethodCallback() { - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - boolean matchesAnnotation = false; - if (method.isBridge()) { - return; - } - if (isMethodDefinedOnObjectClass(method)) { - return; - } - if (!Modifier.isPublic(method.getModifiers())) { - return; - } - if (requiresReply && void.class.equals(method.getReturnType())) { - return; - } - if (methodName != null && !methodName.equals(method.getName())) { - return; - } - if (annotationType != null && AnnotationUtils.findAnnotation(method, annotationType) != null) { - matchesAnnotation = true; - } - HandlerMethod handlerMethod = null; - try { - handlerMethod = new HandlerMethod(method); - } - catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.debug("Method [" + method + "] is not eligible for Message handling.", e); - } - return; - } - Class targetParameterType = handlerMethod.getTargetParameterType(); - if (matchesAnnotation || annotationType == null) { - Assert.isTrue(!candidateMethods.containsKey(targetParameterType), - "Found more than one method match for type [" + targetParameterType + "]"); - candidateMethods.put(targetParameterType, handlerMethod); - } - else { - if (fallbackMethods.containsKey(targetParameterType)) { - // we need to check for duplicate type matches, - // but only if we end up falling back - // and we'll only keep track of the first one - ambiguousFallbackType.compareAndSet(null, targetParameterType); - } - fallbackMethods.put(targetParameterType, handlerMethod); - } - } - }, methodFilter); - if (!candidateMethods.isEmpty()) { - return candidateMethods; - } - Assert.notEmpty(fallbackMethods, "Target object of type [" + this.targetObject.getClass() + - "] has no eligible methods for handling Messages."); - Assert.isNull(ambiguousFallbackType.get(), - "Found more than one method match for type [" + ambiguousFallbackType + "]"); - return fallbackMethods; - } - - private Class getTargetClass(Object targetObject) { - Class targetClass = targetObject.getClass(); - if (AopUtils.isAopProxy(targetObject)) { - targetClass = AopUtils.getTargetClass(targetObject); - } - else if(AopUtils.isCglibProxyClass(targetClass)) { - Class superClass = targetObject.getClass().getSuperclass(); - if (!Object.class.equals(superClass)) { - targetClass = superClass; - } - } - return targetClass; - } - - private List findHandlerMethodsForMessage(Message message) { - final Class payloadType = message.getPayload().getClass(); - HandlerMethod closestMatch = this.findClosestMatch(payloadType); - if (closestMatch != null) { - return Collections.singletonList(closestMatch); - } - return new ArrayList(this.handlerMethods.values()); - } - - private HandlerMethod findClosestMatch(Class payloadType) { - Set> candidates = this.handlerMethods.keySet(); - Class match = null; - if (candidates != null && !candidates.isEmpty()) { - match = ClassUtils.findClosestMatch(payloadType, candidates, true); - } - return (match != null) ? this.handlerMethods.get(match) : null; - } - - private static boolean isMethodDefinedOnObjectClass(Method method) { - if (method == null) { - return false; - } - if (method.getDeclaringClass().equals(Object.class)) { - return true; - } - if (ReflectionUtils.isEqualsMethod(method) || - ReflectionUtils.isHashCodeMethod(method) || - ReflectionUtils.isToStringMethod(method) || - AopUtils.isFinalizeMethod(method)) { - return true; - } - return (method.getName().equals("clone") && method.getParameterTypes().length == 0); - } - - - /** - * Helper class for generating and exposing metadata for a candidate handler method. - * The metadata includes the SpEL expression and the expected payload type. - */ - private static class HandlerMethod { - - private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); - - private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = - new LocalVariableTableParameterNameDiscoverer(); - - - private final Method method; - - private final Expression expression; - - private volatile Class targetParameterType; - - - HandlerMethod(Method method) { - this.method = method; - this.expression = this.generateExpression(method); - } - - Expression getExpression() { - return this.expression; - } - - Class getTargetParameterType() { - return this.targetParameterType; - } - - public String toString() { - return this.method.toString(); - } - - private Expression generateExpression(Method method) { - StringBuilder sb = new StringBuilder("#target." + method.getName() + "("); - Class[] parameterTypes = method.getParameterTypes(); - Annotation[][] parameterAnnotations = method.getParameterAnnotations(); - boolean hasUnqualifiedMapParameter = false; - for (int i = 0; i < parameterTypes.length; i++) { - if (i != 0) { - sb.append(", "); - } - Class parameterType = parameterTypes[i]; - Annotation mappingAnnotation = findMappingAnnotation(parameterAnnotations[i]); - if (mappingAnnotation != null) { - Class annotationType = mappingAnnotation.annotationType(); - if (annotationType.equals(Payload.class)) { - sb.append("payload"); - String qualifierExpression = ((Payload) mappingAnnotation).value(); - if (StringUtils.hasText(qualifierExpression)) { - sb.append("." + qualifierExpression); - } - if (!StringUtils.hasText(qualifierExpression)) { - this.setExclusiveTargetParameterType(parameterType); - } - } - else if (annotationType.equals(Headers.class)) { - Assert.isTrue(Map.class.isAssignableFrom(parameterType), - "The @Headers annotation can only be applied to a Map-typed parameter."); - sb.append("headers"); - } - else if (annotationType.equals(Header.class)) { - Header headerAnnotation = (Header) mappingAnnotation; - sb.append(this.determineHeaderExpression(headerAnnotation, new MethodParameter(method, i))); - } - } - else if (Message.class.isAssignableFrom(parameterType)) { - sb.append("#root"); - this.setExclusiveTargetParameterType(Message.class); - } - else if (Map.class.isAssignableFrom(parameterType)) { - if (Properties.class.isAssignableFrom(parameterType)) { - sb.append("payload instanceof T(java.util.Map) or " + - "(payload instanceof T(String) and payload.contains('=')) ? payload : headers"); - } - else { - sb.append("(payload instanceof T(java.util.Map) ? payload : headers)"); - } - Assert.isTrue(!hasUnqualifiedMapParameter, - "Found more than one Map typed parameter without any qualification. " + - "Consider using @Payload or @Headers on at least one of the parameters."); - hasUnqualifiedMapParameter = true; - } - else { - sb.append("payload"); - this.setExclusiveTargetParameterType(parameterType); - } - } - if (hasUnqualifiedMapParameter) { - if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType)) { - throw new IllegalArgumentException( - "Unable to determine payload matching parameter due to ambiguous Map typed parameters. " + - "Consider adding the @Payload and or @Headers annotations as appropriate."); - } - } - sb.append(")"); - if (this.targetParameterType == null) { - this.targetParameterType = Message.class; - } - return EXPRESSION_PARSER.parseExpression(sb.toString()); - } - - private Annotation findMappingAnnotation(Annotation[] annotations) { - if (annotations == null || annotations.length == 0) { - return null; - } - Annotation match = null; - for (Annotation annotation : annotations) { - Class type = annotation.annotationType(); - if (type.equals(Payload.class) || type.equals(Header.class) || type.equals(Headers.class)) { - if (match != null) { - throw new MessagingException("At most one parameter annotation can be provided for message mapping, " + - "but found two: [" + match.annotationType().getName() + "] and [" + annotation.annotationType().getName() + "]"); - } - match = annotation; - } - } - return match; - } - - private String determineHeaderExpression(Header headerAnnotation, MethodParameter methodParameter) { - methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); - String headerName = null; - String relativeExpression = ""; - String valueAttribute = headerAnnotation.value(); - if (!StringUtils.hasText(valueAttribute)) { - headerName = methodParameter.getParameterName(); - } - else if (valueAttribute.indexOf('.') != -1) { - String tokens[] = valueAttribute.split("\\.", 2); - headerName = tokens[0]; - if (StringUtils.hasText(tokens[1])) { - relativeExpression = "." + tokens[1]; - } - } - else { - headerName = valueAttribute; - } - Assert.notNull(headerName, "Cannot determine header name. Possible reasons: -debug is " + - "disabled or header name is not explicitly provided via @Header annotation."); - String headerExpression = "headers." + headerName + relativeExpression; - return (headerAnnotation.required()) ? headerExpression - : "headers['" + headerName + "'] != null ? " + headerExpression + " : null"; - } - - private synchronized void setExclusiveTargetParameterType(Class targetParameterType) { - Assert.isNull(this.targetParameterType, "Found more than one parameter type candidate: [" + - this.targetParameterType + "] and [" + targetParameterType + "]"); - this.targetParameterType = targetParameterType; - } - } - - - /** - * @author Oleg Zhurakousky - * @since 2.0 - */ - private static class UniqueMethodFilter implements MethodFilter { - - private List uniqueMethods = new ArrayList(); - - public UniqueMethodFilter(Class targetClass) { - ArrayList allMethods = new ArrayList(Arrays.asList(targetClass.getMethods())); - for (Method method : allMethods) { - uniqueMethods.add(org.springframework.util.ClassUtils.getMostSpecificMethod(method, targetClass)); - } - } - - public boolean matches(Method method) { - return uniqueMethods.contains(method); + try { + return delegate.process(message); + } catch (Exception e) { + throw new MessageHandlingException(message, e); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/AbstractExpressionEvaluator.java b/spring-integration-core/src/main/java/org/springframework/integration/util/AbstractExpressionEvaluator.java new file mode 100644 index 0000000000..0a4f1e0549 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/AbstractExpressionEvaluator.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2010 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.util; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.core.convert.ConversionService; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.context.SimpleBeanResolver; + +/** + * @author Mark Fisher + * @since 2.0 + */ +public abstract class AbstractExpressionEvaluator implements BeanFactoryAware { + + private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + + private final BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(); + + public AbstractExpressionEvaluator() { + evaluationContext.setTypeConverter(typeConverter); + } + + /** + * Specify a BeanFactory in order to enable resolution via @beanName in the expression. + */ + public void setBeanFactory(final BeanFactory beanFactory) { + if (beanFactory != null) { + typeConverter.setBeanFactory(beanFactory); + this.getEvaluationContext().setBeanResolver(new SimpleBeanResolver(beanFactory)); + } + } + + public void setConversionService(ConversionService conversionService) { + if (conversionService != null) { + typeConverter.setConversionService(conversionService); + } + } + + // TODO: does this need to be public? + public StandardEvaluationContext getEvaluationContext() { + return this.evaluationContext; + } + + protected Object evaluateExpression(Expression expression, Message message, Class expectedType) { + try { + return evaluateExpression(expression, (Object) message, expectedType); + } + catch (EvaluationException e) { + Throwable cause = e.getCause(); + throw new MessageHandlingException(message, "Expression evaluation failed: " + + expression.getExpressionString(), cause == null ? e : cause); + } + catch (Exception e) { + throw new MessageHandlingException(message, "Expression evaluation failed: " + + expression.getExpressionString(), e); + } + } + + protected Object evaluateExpression(Expression expression, Object message, Class expectedType) { + return (expectedType != null) ? expression.getValue(this.evaluationContext, message, expectedType) : expression + .getValue(this.evaluationContext, message); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/HandlerMethodFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/util/AnnotatedMethodFilter.java similarity index 90% rename from spring-integration-core/src/main/java/org/springframework/integration/handler/HandlerMethodFilter.java rename to spring-integration-core/src/main/java/org/springframework/integration/util/AnnotatedMethodFilter.java index 5dcd282ce7..7620f416c1 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/HandlerMethodFilter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/AnnotatedMethodFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.handler; +package org.springframework.integration.util; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -37,7 +37,7 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @since 2.0 */ -class HandlerMethodFilter implements MethodFilter { +public class AnnotatedMethodFilter implements MethodFilter { private final Class annotationType; @@ -46,7 +46,7 @@ class HandlerMethodFilter implements MethodFilter { private final boolean requiresReply; - public HandlerMethodFilter(Class annotationType, String methodName, boolean requiresReply) { + public AnnotatedMethodFilter(Class annotationType, String methodName, boolean requiresReply) { this.annotationType = annotationType; this.methodName = methodName; this.requiresReply = requiresReply; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/FixedHandlerMethodFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/util/FixedMethodFilter.java similarity index 90% rename from spring-integration-core/src/main/java/org/springframework/integration/handler/FixedHandlerMethodFilter.java rename to spring-integration-core/src/main/java/org/springframework/integration/util/FixedMethodFilter.java index 7bdfa9e014..ee3455270e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/FixedHandlerMethodFilter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/FixedMethodFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.handler; +package org.springframework.integration.util; import java.lang.reflect.Method; import java.util.Collections; @@ -32,12 +32,12 @@ import org.springframework.util.Assert; * @author Mark Fisher * @since 2.0 */ -class FixedHandlerMethodFilter implements MethodFilter { +public class FixedMethodFilter implements MethodFilter { private final Method method; - public FixedHandlerMethodFilter(Method method) { + public FixedMethodFilter(Method method) { Assert.notNull(method, "method must not be null"); this.method = method; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java new file mode 100644 index 0000000000..6d133136a0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java @@ -0,0 +1,621 @@ +/* + * Copyright 2002-2010 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.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.context.expression.MapAccessor; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.TypeConverter; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.MessagingException; +import org.springframework.integration.annotation.Header; +import org.springframework.integration.annotation.Headers; +import org.springframework.integration.annotation.Payload; +import org.springframework.integration.annotation.Payloads; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.util.ReflectionUtils.MethodFilter; + +/** + * A helper class for processors that invoke a method on a target Object using a combination of message payload(s) and + * headers as arguments. The Method instance or method name may be provided as a constructor argument. If a method name + * is provided, and more than one declared method has that name, the method-selection will be dynamic, based on the + * underlying SpEL method resolution. Alternatively, an annotation type may be provided so that the candidates for + * SpEL's method resolution are determined by the presence of that annotation rather than the method name. + * + * @author Mark Fisher + * @author Oleg Zhurakousky + * @author Dave Syer + * + * @since 2.0 + */ +public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator { + + private final Log logger = LogFactory.getLog(this.getClass()); + + private final Object targetObject; + + private volatile String displayString; + + private volatile boolean requiresReply; + + private final Map, HandlerMethod> handlerMethods; + + private final Class expectedType; + + private final boolean canProcessMessageList; + + public MessagingMethodInvokerHelper(Object targetObject, Method method, Class expectedType, + boolean canProcessMessageList) { + this(targetObject, null, method, expectedType, canProcessMessageList); + } + + public MessagingMethodInvokerHelper(Object targetObject, Method method, boolean canProcessMessageList) { + this(targetObject, method, null, canProcessMessageList); + } + + public MessagingMethodInvokerHelper(Object targetObject, String methodName, Class expectedType, + boolean canProcessMessageList) { + this(targetObject, null, methodName, expectedType, canProcessMessageList); + } + + public MessagingMethodInvokerHelper(Object targetObject, String methodName, boolean canProcessMessageList) { + this(targetObject, methodName, null, canProcessMessageList); + } + + public MessagingMethodInvokerHelper(Object targetObject, Class annotationType, + boolean canProcessMessageList) { + this(targetObject, annotationType, null, canProcessMessageList); + } + + public MessagingMethodInvokerHelper(Object targetObject, Class annotationType, Class expectedType, + boolean canProcessMessageList) { + this(targetObject, annotationType, (String) null, expectedType, canProcessMessageList); + } + + public Object process(Message message) throws Exception { + ParametersWrapper parameters = new ParametersWrapper(message); + return processInternal(parameters); + } + + public Object process(Collection> messages, Map headers) throws Exception { + ParametersWrapper parameters = new ParametersWrapper(messages, headers); + return processInternal(parameters); + } + + public String toString() { + return this.displayString; + } + + /* + * Private constructors for internal use + */ + + private MessagingMethodInvokerHelper(Object targetObject, Class annotationType, + Method method, Class expectedType, boolean canProcessMessageList) { + this.canProcessMessageList = canProcessMessageList; + Assert.notNull(method, "method must not be null"); + this.expectedType = expectedType; + this.requiresReply = expectedType != null; + if (expectedType != null) { + Assert.isTrue(method.getReturnType() != Void.class && method.getReturnType() != Void.TYPE, + "method must have a return type"); + } + HandlerMethod handlerMethod = new HandlerMethod(method, canProcessMessageList); + Assert.notNull(targetObject, "targetObject must not be null"); + this.targetObject = targetObject; + this.handlerMethods = Collections., HandlerMethod> singletonMap(handlerMethod.getTargetParameterType() + .getObjectType(), handlerMethod); + this.prepareEvaluationContext(this.getEvaluationContext(), method, annotationType); + this.setDisplayString(targetObject, method); + } + + private MessagingMethodInvokerHelper(Object targetObject, Class annotationType, + String methodName, Class expectedType, boolean canProcessMessageList) { + this.canProcessMessageList = canProcessMessageList; + Assert.notNull(targetObject, "targetObject must not be null"); + this.expectedType = expectedType; + this.targetObject = targetObject; + this.requiresReply = expectedType != null; + this.handlerMethods = this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply); + this.prepareEvaluationContext(this.getEvaluationContext(), methodName, annotationType); + this.setDisplayString(targetObject, methodName); + } + + private void setDisplayString(Object targetObject, Object targetMethod) { + StringBuilder sb = new StringBuilder(targetObject.getClass().getName()); + if (targetMethod instanceof Method) { + sb.append("." + ((Method) targetMethod).getName()); + } + else if (targetMethod instanceof String) { + sb.append("." + (String) targetMethod); + } + this.displayString = sb.toString() + "]"; + } + + private void prepareEvaluationContext(StandardEvaluationContext context, Object method, + Class annotationType) { + Class targetType = AopUtils.getTargetClass(this.targetObject); + if (method instanceof Method) { + context.registerMethodFilter(targetType, new FixedMethodFilter((Method) method)); + if (expectedType != null) { + Assert.state(context.getTypeConverter().canConvert(((Method) method).getReturnType(), expectedType), + "Cannot convert to expected type (" + expectedType + ") from " + method); + } + } + else if (method == null || method instanceof String) { + AnnotatedMethodFilter filter = new AnnotatedMethodFilter(annotationType, (String) method, + this.requiresReply); + Assert.state(canReturnExpectedType(filter, targetType, context.getTypeConverter()), + "Cannot convert to expected type (" + expectedType + ") from " + method); + context.registerMethodFilter(targetType, filter); + } + context.addPropertyAccessor(new MapAccessor()); + context.setVariable("target", targetObject); + } + + private boolean canReturnExpectedType(AnnotatedMethodFilter filter, Class targetType, TypeConverter typeConverter) { + if (expectedType == null) { + return true; + } + List methods = filter.filter(Arrays.asList(ReflectionUtils.getAllDeclaredMethods(targetType))); + for (Method method : methods) { + if (typeConverter.canConvert(method.getReturnType(), expectedType)) { + return true; + } + } + return false; + } + + private Object processInternal(ParametersWrapper parameters) throws Exception { + Throwable evaluationException = null; + List candidates = this.findHandlerMethodsForParameters(parameters); + Assert.state(!candidates.isEmpty(), "No candidate methods found for messages."); + for (HandlerMethod candidate : candidates) { + try { + Expression expression = candidate.getExpression(); + Class expectedType = this.expectedType != null ? this.expectedType : candidate.method + .getReturnType(); + Object result = this.evaluateExpression(expression, parameters, expectedType); + if (this.requiresReply) { + Assert.notNull(result, + "Expression evaluation result was null, but this processor requires a reply."); + } + return result; + } + // keep the first exception + catch (EvaluationException e) { + if (evaluationException == null) { + evaluationException = e.getCause(); + } + if (evaluationException == null) { + evaluationException = e; + } + } + catch (MessageHandlingException e) { + if (evaluationException == null) { + evaluationException = e.getCause(); + } + if (evaluationException == null) { + evaluationException = e; + } + } + catch (Exception e) { + if (evaluationException == null) { + evaluationException = e; + } + } + } + if (evaluationException instanceof Exception) { + throw (Exception) evaluationException; + } + else if (evaluationException instanceof Error) { + throw (Error) evaluationException; + } + else { + throw new IllegalStateException("Cannot process message", evaluationException); + } + } + + private Map, HandlerMethod> findHandlerMethodsForTarget(final Object targetObject, + final Class annotationType, final String methodName, final boolean requiresReply) { + + final Map, HandlerMethod> candidateMethods = new HashMap, HandlerMethod>(); + final Map, HandlerMethod> fallbackMethods = new HashMap, HandlerMethod>(); + final AtomicReference> ambiguousFallbackType = new AtomicReference>(); + final Class targetClass = this.getTargetClass(targetObject); + MethodFilter methodFilter = new UniqueMethodFilter(targetClass); + ReflectionUtils.doWithMethods(targetClass, new MethodCallback() { + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + boolean matchesAnnotation = false; + if (method.isBridge()) { + return; + } + if (isMethodDefinedOnObjectClass(method)) { + return; + } + if (!Modifier.isPublic(method.getModifiers())) { + return; + } + if (requiresReply && void.class.equals(method.getReturnType())) { + return; + } + if (methodName != null && !methodName.equals(method.getName())) { + return; + } + if (annotationType != null && AnnotationUtils.findAnnotation(method, annotationType) != null) { + matchesAnnotation = true; + } + HandlerMethod handlerMethod = null; + try { + handlerMethod = new HandlerMethod(method, canProcessMessageList); + } + catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Method [" + method + "] is not eligible for Message handling.", e); + } + return; + } + Class targetParameterType = handlerMethod.getTargetParameterType().getObjectType(); + if (matchesAnnotation || annotationType == null) { + Assert.isTrue(!candidateMethods.containsKey(targetParameterType), + "Found more than one method match for type [" + targetParameterType + "]"); + candidateMethods.put(targetParameterType, handlerMethod); + } + else { + if (fallbackMethods.containsKey(targetParameterType)) { + // we need to check for duplicate type matches, + // but only if we end up falling back + // and we'll only keep track of the first one + ambiguousFallbackType.compareAndSet(null, targetParameterType); + } + fallbackMethods.put(targetParameterType, handlerMethod); + } + } + }, methodFilter); + if (!candidateMethods.isEmpty()) { + return candidateMethods; + } + Assert.notEmpty(fallbackMethods, "Target object of type [" + this.targetObject.getClass() + + "] has no eligible methods for handling Messages."); + Assert.isNull(ambiguousFallbackType.get(), "Found ambiguous parameter type [" + ambiguousFallbackType + + "] for method match: " + fallbackMethods.values()); + return fallbackMethods; + } + + private Class getTargetClass(Object targetObject) { + Class targetClass = targetObject.getClass(); + if (AopUtils.isAopProxy(targetObject)) { + targetClass = AopUtils.getTargetClass(targetObject); + } + else if (AopUtils.isCglibProxyClass(targetClass)) { + Class superClass = targetObject.getClass().getSuperclass(); + if (!Object.class.equals(superClass)) { + targetClass = superClass; + } + } + return targetClass; + } + + private List findHandlerMethodsForParameters(ParametersWrapper parameters) { + final Class payloadType = parameters.getFirstParameterType(); + HandlerMethod closestMatch = this.findClosestMatch(payloadType); + if (closestMatch != null) { + return Collections.singletonList(closestMatch); + } + return new ArrayList(this.handlerMethods.values()); + } + + private HandlerMethod findClosestMatch(Class payloadType) { + Set> candidates = this.handlerMethods.keySet(); + Class match = null; + if (candidates != null && !candidates.isEmpty()) { + match = ClassUtils.findClosestMatch(payloadType, candidates, true); + } + return (match != null) ? this.handlerMethods.get(match) : null; + } + + private static boolean isMethodDefinedOnObjectClass(Method method) { + if (method == null) { + return false; + } + if (method.getDeclaringClass().equals(Object.class)) { + return true; + } + if (ReflectionUtils.isEqualsMethod(method) || ReflectionUtils.isHashCodeMethod(method) + || ReflectionUtils.isToStringMethod(method) || AopUtils.isFinalizeMethod(method)) { + return true; + } + return (method.getName().equals("clone") && method.getParameterTypes().length == 0); + } + + /** + * Helper class for generating and exposing metadata for a candidate handler method. The metadata includes the SpEL + * expression and the expected payload type. + */ + private static class HandlerMethod { + + private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); + + private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); + + private final Method method; + + private final Expression expression; + + private volatile TypeDescriptor targetParameterType; + + private static final TypeDescriptor messageTypeDescriptor = TypeDescriptor.valueOf(Message.class); + + private static final TypeDescriptor messageListTypeDescriptor = new TypeDescriptor(ReflectionUtils.findField( + HandlerMethod.class, "dummyMessages")); + + private static final TypeDescriptor messageArrayTypeDescriptor = TypeDescriptor.valueOf(Message[].class); + + @SuppressWarnings("unused") + private static final List> dummyMessages = Collections.emptyList(); + + private final boolean canProcessMessageList; + + HandlerMethod(Method method, boolean canProcessMessageList) { + this.method = method; + this.canProcessMessageList = canProcessMessageList; + this.expression = this.generateExpression(method); + } + + Expression getExpression() { + return this.expression; + } + + TypeDescriptor getTargetParameterType() { + return this.targetParameterType; + } + + public String toString() { + return this.method.toString(); + } + + private Expression generateExpression(Method method) { + StringBuilder sb = new StringBuilder("#target." + method.getName() + "("); + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + boolean hasUnqualifiedMapParameter = false; + TypeDescriptor defaultParameterTypeDescriptor = TypeDescriptor.valueOf(List.class); + for (int i = 0; i < parameterTypes.length; i++) { + if (i != 0) { + sb.append(", "); + } + TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(new MethodParameter(method, i)); + defaultParameterTypeDescriptor = parameterTypeDescriptor; + Class parameterType = parameterTypeDescriptor.getObjectType(); + Annotation mappingAnnotation = findMappingAnnotation(parameterAnnotations[i]); + if (mappingAnnotation != null) { + Class annotationType = mappingAnnotation.annotationType(); + if (annotationType.equals(Payload.class)) { + sb.append("payload"); + String qualifierExpression = ((Payload) mappingAnnotation).value(); + if (StringUtils.hasText(qualifierExpression)) { + sb.append("." + qualifierExpression); + } + if (!StringUtils.hasText(qualifierExpression)) { + this.setExclusiveTargetParameterType(parameterTypeDescriptor); + } + } + if (annotationType.equals(Payloads.class)) { + sb.append("messages.![payload"); + String qualifierExpression = ((Payloads) mappingAnnotation).value(); + if (StringUtils.hasText(qualifierExpression)) { + sb.append("." + qualifierExpression); + } + sb.append("]"); + if (!StringUtils.hasText(qualifierExpression)) { + this.setExclusiveTargetParameterType(parameterTypeDescriptor); + } + } + else if (annotationType.equals(Headers.class)) { + Assert.isTrue(Map.class.isAssignableFrom(parameterType), + "The @Headers annotation can only be applied to a Map-typed parameter."); + sb.append("headers"); + } + else if (annotationType.equals(Header.class)) { + Header headerAnnotation = (Header) mappingAnnotation; + sb.append(this.determineHeaderExpression(headerAnnotation, new MethodParameter(method, i))); + } + } + else if (parameterTypeDescriptor != null + && parameterTypeDescriptor.isAssignableTo(messageTypeDescriptor)) { + sb.append("message"); + this.setExclusiveTargetParameterType(parameterTypeDescriptor); + } + else if (parameterTypeDescriptor != null + && (parameterTypeDescriptor.isAssignableTo(messageListTypeDescriptor) || parameterTypeDescriptor + .isAssignableTo(messageArrayTypeDescriptor))) { + sb.append("messages"); + this.setExclusiveTargetParameterType(parameterTypeDescriptor); + } + else if (Collection.class.isAssignableFrom(parameterType) || parameterType.isArray()) { + if (canProcessMessageList) { + sb.append("messages.![payload]"); + } else { + sb.append("payload"); + } + this.setExclusiveTargetParameterType(parameterTypeDescriptor); + } + else if (Map.class.isAssignableFrom(parameterType)) { + if (Properties.class.isAssignableFrom(parameterType)) { + sb.append("payload instanceof T(java.util.Map) or " + + "(payload instanceof T(String) and payload.contains('=')) ? payload : headers"); + } + else { + sb.append("(payload instanceof T(java.util.Map) ? payload : headers)"); + } + Assert.isTrue(!hasUnqualifiedMapParameter, + "Found more than one Map typed parameter without any qualification. " + + "Consider using @Payload or @Headers on at least one of the parameters."); + hasUnqualifiedMapParameter = true; + } + else { + sb.append("payload"); + this.setExclusiveTargetParameterType(parameterTypeDescriptor); + } + } + if (hasUnqualifiedMapParameter) { + if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType.getObjectType())) { + throw new IllegalArgumentException( + "Unable to determine payload matching parameter due to ambiguous Map typed parameters. " + + "Consider adding the @Payload and or @Headers annotations as appropriate."); + } + } + sb.append(")"); + if (this.targetParameterType == null) { + this.targetParameterType = defaultParameterTypeDescriptor; + } + return EXPRESSION_PARSER.parseExpression(sb.toString()); + } + + private Annotation findMappingAnnotation(Annotation[] annotations) { + if (annotations == null || annotations.length == 0) { + return null; + } + Annotation match = null; + for (Annotation annotation : annotations) { + Class type = annotation.annotationType(); + if (type.equals(Payload.class) || type.equals(Header.class) || type.equals(Headers.class)) { + if (match != null) { + throw new MessagingException( + "At most one parameter annotation can be provided for message mapping, " + + "but found two: [" + match.annotationType().getName() + "] and [" + + annotation.annotationType().getName() + "]"); + } + match = annotation; + } + } + return match; + } + + private String determineHeaderExpression(Header headerAnnotation, MethodParameter methodParameter) { + methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); + String headerName = null; + String relativeExpression = ""; + String valueAttribute = headerAnnotation.value(); + if (!StringUtils.hasText(valueAttribute)) { + headerName = methodParameter.getParameterName(); + } + else if (valueAttribute.indexOf('.') != -1) { + String tokens[] = valueAttribute.split("\\.", 2); + headerName = tokens[0]; + if (StringUtils.hasText(tokens[1])) { + relativeExpression = "." + tokens[1]; + } + } + else { + headerName = valueAttribute; + } + Assert.notNull(headerName, "Cannot determine header name. Possible reasons: -debug is " + + "disabled or header name is not explicitly provided via @Header annotation."); + String headerExpression = "headers." + headerName + relativeExpression; + return (headerAnnotation.required()) ? headerExpression : "headers['" + headerName + "'] != null ? " + + headerExpression + " : null"; + } + + private synchronized void setExclusiveTargetParameterType(TypeDescriptor targetParameterType) { + Assert.isNull(this.targetParameterType, "Found more than one parameter type candidate: [" + + this.targetParameterType + "] and [" + targetParameterType + "]"); + this.targetParameterType = targetParameterType; + } + } + + @SuppressWarnings("unused") + private static class ParametersWrapper { + + private final Object payload; + + private final Collection> messages; + + private final Map headers; + + private final Message message; + + public ParametersWrapper(Message message) { + this.message = message; + this.payload = message.getPayload(); + this.headers = message.getHeaders(); + this.messages = null; + } + + public ParametersWrapper(Collection> messages, Map headers) { + this.payload = null; + this.messages = messages; + this.headers = headers; + this.message = null; + } + + public Object getPayload() { + Assert.state(payload != null, "Invalid method parameter for payload: was expecting collection."); + return payload; + } + + public Collection> getMessages() { + Assert.state(messages != null, "Invalid method parameter for messages: was expecting a single payload."); + return messages; + } + + public Map getHeaders() { + return headers; + } + + public Message getMessage() { + return message; + } + + public Class getFirstParameterType() { + if (payload != null) { + return payload.getClass(); + } + return Collection.class; + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/UniqueMethodFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/util/UniqueMethodFilter.java new file mode 100644 index 0000000000..86248e7281 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/UniqueMethodFilter.java @@ -0,0 +1,28 @@ +package org.springframework.integration.util; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.util.ReflectionUtils.MethodFilter; + +/** + * @author Oleg Zhurakousky + * @since 2.0 + */ +public class UniqueMethodFilter implements MethodFilter { + + private List uniqueMethods = new ArrayList(); + + public UniqueMethodFilter(Class targetClass) { + ArrayList allMethods = new ArrayList(Arrays.asList(targetClass.getMethods())); + for (Method method : allMethods) { + uniqueMethods.add(org.springframework.util.ClassUtils.getMostSpecificMethod(method, targetClass)); + } + } + + public boolean matches(Method method) { + return uniqueMethods.contains(method); + } +} \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AggregatorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AggregatorTests.java index c94ea0bdd1..9094e61f77 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AggregatorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AggregatorTests.java @@ -31,7 +31,7 @@ import org.springframework.integration.MessageHeaders; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.MessageBuilder; import org.springframework.integration.core.MessageChannel; -import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.core.MessagingOperations; import org.springframework.integration.store.MessageGroup; import org.springframework.integration.store.SimpleMessageStore; @@ -231,7 +231,7 @@ public class AggregatorTests { } private class MultiplyingProcessor implements MessageGroupProcessor { - public void processAndSend(MessageGroup group, MessagingTemplate messagingTemplate, + public void processAndSend(MessageGroup group, MessagingOperations messagingTemplate, MessageChannel outputChannel) { Integer product = 1; for (Message message : group.getUnmarked()) { @@ -242,7 +242,7 @@ public class AggregatorTests { } private class NullReturningMessageProcessor implements MessageGroupProcessor { - public void processAndSend(MessageGroup group, MessagingTemplate messagingTemplate, + public void processAndSend(MessageGroup group, MessagingOperations messagingTemplate, MessageChannel outputChannel) { // noop } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ConcurrentAggregatorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ConcurrentAggregatorTests.java index bf6feff390..3bb8ab5752 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ConcurrentAggregatorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ConcurrentAggregatorTests.java @@ -37,7 +37,7 @@ import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.MessageBuilder; import org.springframework.integration.core.MessageChannel; import org.springframework.integration.core.MessageHandler; -import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.core.MessagingOperations; import org.springframework.integration.store.MessageGroup; import org.springframework.integration.store.MessageGroupStore; import org.springframework.integration.store.SimpleMessageStore; @@ -344,7 +344,7 @@ public class ConcurrentAggregatorTests { private class MultiplyingProcessor implements MessageGroupProcessor { public void processAndSend(MessageGroup group, - MessagingTemplate messagingTemplate, + MessagingOperations messagingTemplate, MessageChannel outputChannel) { Integer product = 1; for (Message message : group.getUnmarked()) { @@ -356,7 +356,7 @@ public class ConcurrentAggregatorTests { private class NullReturningMessageProcessor implements MessageGroupProcessor { public void processAndSend(MessageGroup group, - MessagingTemplate messagingTemplate, + MessagingOperations messagingTemplate, MessageChannel outputChannel) { // noop } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageBarrierTest.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageBarrierTests.java similarity index 99% rename from spring-integration-core/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageBarrierTest.java rename to spring-integration-core/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageBarrierTests.java index a9f43f0d97..e00176a2c7 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageBarrierTest.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageBarrierTests.java @@ -34,7 +34,7 @@ import static org.mockito.Mockito.*; * @author Iwein Fuld */ @RunWith(MockitoJUnitRunner.class) -public class CorrelatingMessageBarrierTest { +public class CorrelatingMessageBarrierTests { private CorrelatingMessageBarrier barrier; @Mock diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java index b41eb78d18..c6118c9534 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java @@ -25,29 +25,38 @@ import static org.mockito.Matchers.isA; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.Map; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; - import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.DirectFieldAccessor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.core.convert.support.GenericConversionService; import org.springframework.integration.Message; import org.springframework.integration.annotation.Aggregator; import org.springframework.integration.annotation.Header; +import org.springframework.integration.annotation.Headers; +import org.springframework.integration.annotation.Payloads; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.MessageBuilder; import org.springframework.integration.core.MessageChannel; import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.core.StringMessage; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.store.MessageGroup; +import org.springframework.integration.store.SimpleMessageGroup; @RunWith(MockitoJUnitRunner.class) public class MethodInvokingMessageGroupProcessorTests { @@ -70,27 +79,28 @@ public class MethodInvokingMessageGroupProcessorTests { messagesUpForProcessing.add(MessageBuilder.withPayload(4).build()); } - @SuppressWarnings("unused") - private class AnnotatedAggregatorMethod { - - @Aggregator - public Integer and(List flags) { - int result = 0; - for (Integer flag : flags) { - result = result | flag; - } - return result; - } - - public String know(List flags) { - return "I'm not the one "; - } - } - + @SuppressWarnings("unchecked") @Test public void shouldFindAnnotatedAggregatorMethod() throws Exception { + + @SuppressWarnings("unused") + class AnnotatedAggregatorMethod { + + @Aggregator + public Integer and(List flags) { + int result = 0; + for (Integer flag : flags) { + result = result | flag; + } + return result; + } + + public String know(List flags) { + return "I'm not the one "; + } + } + MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedAggregatorMethod()); - @SuppressWarnings("unchecked") ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); when(outputChannel.send(isA(Message.class))).thenReturn(true); when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing); @@ -100,21 +110,22 @@ public class MethodInvokingMessageGroupProcessorTests { assertThat((Integer) messageCaptor.getValue().getPayload(), is(7)); } - @SuppressWarnings("unused") - private class SimpleAggregator { - public Integer and(List flags) { - int result = 0; - for (Integer flag : flags) { - result = result | flag; - } - return result; - } - } - @Test + @SuppressWarnings("unchecked") public void shouldFindSimpleAggregatorMethod() throws Exception { + + @SuppressWarnings("unused") + class SimpleAggregator { + public Integer and(List flags) { + int result = 0; + for (Integer flag : flags) { + result = result | flag; + } + return result; + } + } + MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new SimpleAggregator()); - @SuppressWarnings("unchecked") ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); when(outputChannel.send(isA(Message.class))).thenReturn(true); when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing); @@ -124,31 +135,169 @@ public class MethodInvokingMessageGroupProcessorTests { assertThat((Integer) messageCaptor.getValue().getPayload(), is(7)); } - @SuppressWarnings("unused") - private class UnannotatedAggregator { - public Integer and(List flags) { - int result = 0; - for (Integer flag : flags) { - result = result | flag; + @Test + @SuppressWarnings("unchecked") + public void shouldFindSimpleAggregatorMethodForMessages() throws Exception { + + @SuppressWarnings("unused") + class SimpleAggregator { + public Integer and(List> flags) { + int result = 0; + for (Message flag : flags) { + result = result | flag.getPayload(); + } + return result; } - return result; } - public void voidMethodShouldBeIgnored(List flags) { - fail("this method should not be invoked"); - } - - public String methodAcceptingNoCollectionShouldBeIgnored(@Header String irrelevant) { - fail("this method should not be invoked"); - return null; - } + MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new SimpleAggregator()); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + when(outputChannel.send(isA(Message.class))).thenReturn(true); + when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing); + processor.processAndSend(messageGroupMock, messagingTemplate, outputChannel); + // verify + verify(messagingTemplate).send(eq(outputChannel), messageCaptor.capture()); + assertThat((Integer) messageCaptor.getValue().getPayload(), is(7)); } @Test + @SuppressWarnings("unchecked") + public void shouldFindAnnotatedPayloads() throws Exception { + + @SuppressWarnings("unused") + class SimpleAggregator { + public String and(@Payloads List flags, @Header("foo") List header) { + List result = new ArrayList(); + for (int flag : flags) { + result.add(flag); + } + for (int flag : header) { + result.add(flag); + } + return result.toString(); + } + } + + MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new SimpleAggregator()); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + when(outputChannel.send(isA(Message.class))).thenReturn(true); + messagesUpForProcessing.add(MessageBuilder.withPayload(3).setHeader("foo", Arrays.asList(101, 102)).build()); + when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing); + processor.processAndSend(messageGroupMock, messagingTemplate, outputChannel); + // verify + verify(messagingTemplate).send(eq(outputChannel), messageCaptor.capture()); + assertThat((String) messageCaptor.getValue().getPayload(), is("[1, 2, 4, 3, 101, 102]")); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldFindSimpleAggregatorMethodWithCollection() throws Exception { + + @SuppressWarnings("unused") + class SimpleAggregator { + public Integer and(Collection flags) { + int result = 0; + for (Integer flag : flags) { + result = result | flag; + } + return result; + } + } + + MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new SimpleAggregator()); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + when(outputChannel.send(isA(Message.class))).thenReturn(true); + when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing); + processor.processAndSend(messageGroupMock, messagingTemplate, outputChannel); + // verify + verify(messagingTemplate).send(eq(outputChannel), messageCaptor.capture()); + assertThat((Integer) messageCaptor.getValue().getPayload(), is(7)); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldFindSimpleAggregatorMethodWithArray() throws Exception { + + @SuppressWarnings("unused") + class SimpleAggregator { + public Integer and(int[] flags) { + int result = 0; + for (int flag : flags) { + result = result | flag; + } + return result; + } + } + + MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new SimpleAggregator()); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + when(outputChannel.send(isA(Message.class))).thenReturn(true); + when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing); + processor.processAndSend(messageGroupMock, messagingTemplate, outputChannel); + // verify + verify(messagingTemplate).send(eq(outputChannel), messageCaptor.capture()); + assertThat((Integer) messageCaptor.getValue().getPayload(), is(7)); + } + + @Ignore("INT-938: it probably should work if there is a converter registered, but maybe a SpEL bug?") + @Test + @SuppressWarnings("unchecked") + public void shouldFindSimpleAggregatorMethodWithIterator() throws Exception { + + @SuppressWarnings("unused") + class SimpleAggregator { + public Integer and(Iterator flags) { + int result = 0; + for (int flag = flags.next(); flags.hasNext();) { + result = result | flag; + } + return result; + } + } + + MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new SimpleAggregator()); + GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); + conversionService.addConverter(new Converter, Iterator>() { + public Iterator convert(ArrayList source) { + return source.iterator(); + } + }); + processor.setConversionService(conversionService); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + when(outputChannel.send(isA(Message.class))).thenReturn(true); + when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing); + processor.processAndSend(messageGroupMock, messagingTemplate, outputChannel); + // verify + verify(messagingTemplate).send(eq(outputChannel), messageCaptor.capture()); + assertThat((Integer) messageCaptor.getValue().getPayload(), is(7)); + } + + @Test + @SuppressWarnings("unchecked") public void shouldFindFittingMethodAmongMultipleUnannotated() { + + @SuppressWarnings("unused") + class UnannotatedAggregator { + public Integer and(List flags) { + int result = 0; + for (Integer flag : flags) { + result = result | flag; + } + return result; + } + + public void voidMethodShouldBeIgnored(List flags) { + fail("this method should not be invoked"); + } + + public String methodAcceptingNoCollectionShouldBeIgnored(String irrelevant) { + fail("this method should not be invoked"); + return null; + } + } + MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new UnannotatedAggregator()); - @SuppressWarnings("unchecked") ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); when(outputChannel.send(isA(Message.class))).thenReturn(true); @@ -159,69 +308,168 @@ public class MethodInvokingMessageGroupProcessorTests { assertThat((Integer) messageCaptor.getValue().getPayload(), is(7)); } - @SuppressWarnings("unused") - private class AnnotatedParametersAggregator { - public Integer and(List flags) { - int result = 0; - for (Integer flag : flags) { - result = result | flag; + @Test(expected = IllegalArgumentException.class) + public void testTwoMethodsWithSameParameterTypesAmbiguous() { + + @SuppressWarnings("unused") + class AnnotatedParametersAggregator { + public Integer and(List flags) { + int result = 0; + for (Integer flag : flags) { + result = result | flag; + } + return result; + } + + public String listHeaderShouldBeIgnored(@Header List flags) { + fail("this method should not be invoked"); + return ""; } - return result; } - public String listHeaderShouldBeIgnored(@Header List flags) { - fail("this method should not be invoked"); - return ""; - } - } + new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator()); - @Test - public void shouldFindFittingMethodAmongMultipleWithAnnotatedParameters() { - MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator()); - - @SuppressWarnings("unchecked") - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - - when(outputChannel.send(isA(Message.class))).thenReturn(true); - when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing); - processor.processAndSend(messageGroupMock, messagingTemplate, outputChannel); - // verify - verify(messagingTemplate).send(eq(outputChannel), messageCaptor.capture()); - assertThat((Integer) messageCaptor.getValue().getPayload(), is(7)); } @Test public void singleAnnotation() throws Exception { + + @SuppressWarnings("unused") + class SingleAnnotationTestBean { + + @Aggregator + public String method1(List input) { + return input.get(0); + } + + public String method2(List input) { + return input.get(1); + } + } + SingleAnnotationTestBean bean = new SingleAnnotationTestBean(); MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(bean); - Method method = this.getMethod(aggregator); - Method expected = SingleAnnotationTestBean.class.getMethod("method1", new Class[] { List.class }); - assertEquals(expected, method); + SimpleMessageGroup group = new SimpleMessageGroup("FOO"); + group.add(new StringMessage("foo")); + group.add(new StringMessage("bar")); + assertEquals("foo", aggregator.aggregatePayloads(group, null)); + + } + + @Test + public void testHeaderParameters() throws Exception { + + @SuppressWarnings("unused") + class SingleAnnotationTestBean { + @Aggregator + public String method1(List input, @Header("foo") String foo) { + return input.get(0) + foo; + } + } + + SingleAnnotationTestBean bean = new SingleAnnotationTestBean(); + MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(bean); + SimpleMessageGroup group = new SimpleMessageGroup("FOO"); + group.add(MessageBuilder.withPayload("foo").setHeader("foo", "bar").build()); + group.add(MessageBuilder.withPayload("bar").setHeader("foo", "bar").build()); + assertEquals("foobar", aggregator.aggregatePayloads(group, aggregator.aggregateHeaders(group))); + + } + + @Test + public void testHeadersParameters() throws Exception { + + @SuppressWarnings("unused") + class SingleAnnotationTestBean { + @Aggregator + public String method1(List input, @Headers Map map) { + return input.get(0) + map.get("foo"); + } + } + + SingleAnnotationTestBean bean = new SingleAnnotationTestBean(); + MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(bean); + SimpleMessageGroup group = new SimpleMessageGroup("FOO"); + group.add(MessageBuilder.withPayload("foo").setHeader("foo", "bar").build()); + group.add(MessageBuilder.withPayload("bar").setHeader("foo", "bar").build()); + assertEquals("foobar", aggregator.aggregatePayloads(group, aggregator.aggregateHeaders(group))); + } @Test(expected = IllegalArgumentException.class) public void multipleAnnotations() { + + @SuppressWarnings("unused") + class MultipleAnnotationTestBean { + + @Aggregator + public String method1(List input) { + return input.get(0); + } + + @Aggregator + public String method2(List input) { + return input.get(0); + } + } + MultipleAnnotationTestBean bean = new MultipleAnnotationTestBean(); new MethodInvokingMessageGroupProcessor(bean); } @Test public void noAnnotations() throws Exception { + + @SuppressWarnings("unused") + class NoAnnotationTestBean { + + public String method1(List input) { + return input.get(0); + } + + String method2(List input) { + return input.get(1); + } + } + NoAnnotationTestBean bean = new NoAnnotationTestBean(); MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(bean); - Method method = this.getMethod(aggregator); - Method expected = NoAnnotationTestBean.class.getMethod("method1", new Class[] { List.class }); - assertEquals(expected, method); + SimpleMessageGroup group = new SimpleMessageGroup("FOO"); + group.add(new StringMessage("foo")); + group.add(new StringMessage("bar")); + assertEquals("foo", aggregator.aggregatePayloads(group, null)); } @Test(expected = IllegalArgumentException.class) public void multiplePublicMethods() { + + @SuppressWarnings("unused") + class MultiplePublicMethodTestBean { + + public String upperCase(String s) { + return s.toUpperCase(); + } + + public String lowerCase(String s) { + return s.toLowerCase(); + } + } + MultiplePublicMethodTestBean bean = new MultiplePublicMethodTestBean(); new MethodInvokingMessageGroupProcessor(bean); } @Test(expected = IllegalArgumentException.class) public void noPublicMethods() { + + @SuppressWarnings("unused") + class NoPublicMethodTestBean { + + String lowerCase(String s) { + return s.toLowerCase(); + } + } + NoPublicMethodTestBean bean = new NoPublicMethodTestBean(); new MethodInvokingMessageGroupProcessor(bean); } @@ -264,74 +512,8 @@ public class MethodInvokingMessageGroupProcessorTests { assertEquals("hello proxy", output.receive(0).getPayload()); } - private Method getMethod(MethodInvokingMessageGroupProcessor aggregator) { - Object invoker = new DirectFieldAccessor(aggregator).getPropertyValue("adapter"); - return (Method) new DirectFieldAccessor(invoker).getPropertyValue("method"); - } - - @SuppressWarnings("unused") - private static class SingleAnnotationTestBean { - - @Aggregator - public String method1(List input) { - return input.get(0); - } - - public String method2(List input) { - return input.get(0); - } - } - - @SuppressWarnings("unused") - private static class MultipleAnnotationTestBean { - - @Aggregator - public String method1(List input) { - return input.get(0); - } - - @Aggregator - public String method2(List input) { - return input.get(0); - } - } - - @SuppressWarnings("unused") - private static class NoAnnotationTestBean { - - public String method1(List input) { - return input.get(0); - } - - String method2(List input) { - return input.get(0); - } - } - - @SuppressWarnings("unused") - private static class MultiplePublicMethodTestBean { - - public String upperCase(String s) { - return s.toUpperCase(); - } - - public String lowerCase(String s) { - return s.toLowerCase(); - } - } - - @SuppressWarnings("unused") - private static class NoPublicMethodTestBean { - - String lowerCase(String s) { - return s.toLowerCase(); - } - } - public interface GreetingService { - String sayHello(List names); - } public static class GreetingBean implements GreetingService { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategyTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategyTests.java new file mode 100644 index 0000000000..d1ba657708 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategyTests.java @@ -0,0 +1,287 @@ +/* + * 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.aggregator; + +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.integration.Message; +import org.springframework.integration.core.GenericMessage; +import org.springframework.integration.store.MessageGroup; +import org.springframework.integration.store.SimpleMessageGroup; + +/** + * @author Marius Bogoevici + * @author Dave Syer + */ +public class MethodInvokingReleaseStrategyTests { + + @Test + public void testTrueConvertedProperly() { + MethodInvokingReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new AlwaysTrueReleaseStrategy(), + "checkCompleteness"); + Assert.assertTrue(adapter.canRelease(createListOfMessages(0))); + } + + @Test + public void testFalseConvertedProperly() { + MethodInvokingReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new AlwaysFalseReleaseStrategy(), + "checkCompleteness"); + Assert.assertTrue(!adapter.canRelease(createListOfMessages(0))); + } + + @Test + public void testAdapterWithNonParameterizedMessageListBasedMethod() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean checkCompletenessOnNonParameterizedListOfMessages(List> messages) { + Assert.assertTrue(messages.size() > 0); + return messages.size() > messages.iterator().next().getHeaders().getSequenceSize(); + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), + "checkCompletenessOnNonParameterizedListOfMessages"); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + @Test + public void testAdapterWithWildcardParametrizedMessageBasedMethod() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean checkCompletenessOnListOfMessagesParametrizedWithWildcard(List> messages) { + Assert.assertTrue(messages.size() > 0); + return messages.size() > messages.iterator().next().getHeaders().getSequenceSize(); + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), + "checkCompletenessOnListOfMessagesParametrizedWithWildcard"); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + @Test + public void testAdapterWithTypeParametrizedMessageBasedMethod() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean checkCompletenessOnListOfMessagesParametrizedWithString(List> messages) { + Assert.assertTrue(messages.size() > 0); + return messages.size() > messages.iterator().next().getHeaders().getSequenceSize(); + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), + "checkCompletenessOnListOfMessagesParametrizedWithString"); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + @Test + public void testAdapterWithPojoBasedMethod() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + // Example for the case when completeness is checked on the structure of + // the data + public boolean checkCompletenessOnListOfStrings(List messages) { + StringBuffer buffer = new StringBuffer(); + for (String content : messages) { + buffer.append(content); + } + return buffer.length() >= 9; + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), + "checkCompletenessOnListOfStrings"); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + @Test + public void testAdapterWithPojoBasedMethodReturningObject() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + // Example for the case when completeness is checked on the structure of + // the data + public boolean checkCompletenessOnListOfStrings(List messages) { + StringBuffer buffer = new StringBuffer(); + for (String content : messages) { + buffer.append(content); + } + return buffer.length() >= 9; + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), + "checkCompletenessOnListOfStrings"); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + @Test(expected = IllegalArgumentException.class) + public void testAdapterWithWrongMethodName() { + class TestReleaseStrategy { + } + new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), "methodThatDoesNotExist"); + } + + @Test(expected = IllegalStateException.class) + public void testInvalidParameterTypeUsingMethodName() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean invalidParameterType(Date invalid) { + return true; + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), "invalidParameterType"); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + @Test(expected = IllegalArgumentException.class) + public void testTooManyParametersUsingMethodName() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean tooManyParameters(List c1, List c2) { + return false; + } + } + new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), "tooManyParameters"); + } + + @Test + public void testNotEnoughParametersUsingMethodName() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean notEnoughParameters() { + return false; + } + } + // TODO: this is stupid, but maybe it should be illegal? + new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), "notEnoughParameters"); + } + + @Test + public void testNotEnoughParametersUsingMethodObject() throws SecurityException, NoSuchMethodException { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean notEnoughParameters() { + return false; + } + } + // TODO: this is stupid, but maybe it should be illegal? + new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), TestReleaseStrategy.class.getMethod( + "notEnoughParameters", new Class[] {})); + } + + @Test + public void testListSubclassParameterUsingMethodName() { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean listSubclassParameter(LinkedList l1) { + return true; + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), "listSubclassParameter"); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + // TODO: should this be MessageHandlingException? + @Test(expected = ConversionFailedException.class) + public void testWrongReturnType() throws SecurityException, NoSuchMethodError { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public String wrongReturnType(List> messages) { + return "foo"; + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), "wrongReturnType"); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + @Test(expected = IllegalArgumentException.class) + public void testTooManyParametersUsingMethodObject() throws SecurityException, NoSuchMethodException { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean tooManyParameters(List c1, List c2) { + return false; + } + } + new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), TestReleaseStrategy.class.getMethod( + "tooManyParameters", List.class, List.class)); + } + + @Test + public void testListSubclassParameterUsingMethodObject() throws SecurityException, NoSuchMethodException { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public boolean listSubclassParameter(LinkedList l1) { + return true; + } + } + ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), + TestReleaseStrategy.class.getMethod("listSubclassParameter", new Class[] { LinkedList.class })); + MessageGroup messages = createListOfMessages(3); + Assert.assertTrue(adapter.canRelease(messages)); + } + + // TODO: review exception type here + @Test(expected = IllegalStateException.class) + public void testWrongReturnTypeUsingMethodObject() throws SecurityException, NoSuchMethodException { + class TestReleaseStrategy { + @SuppressWarnings("unused") + public int wrongReturnType(List> message) { + return 0; + } + } + new MethodInvokingReleaseStrategy(new TestReleaseStrategy(), TestReleaseStrategy.class.getMethod( + "wrongReturnType", new Class[] { List.class })); + } + + private static MessageGroup createListOfMessages(int size) { + List> messages = new ArrayList>(); + if (size > 0) { + messages.add(new GenericMessage("123")); + } + if (size > 1) { + messages.add(new GenericMessage("456")); + } + if (size > 2) { + messages.add(new GenericMessage("789")); + } + return new SimpleMessageGroup(messages, "ABC"); + } + + @SuppressWarnings("unused") + private static class AlwaysTrueReleaseStrategy { + public boolean checkCompleteness(List> messages) { + return true; + } + } + + @SuppressWarnings("unused") + private static class AlwaysFalseReleaseStrategy { + public boolean checkCompleteness(List> messages) { + return false; + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ReleaseStrategyAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ReleaseStrategyAdapterTests.java deleted file mode 100644 index 6b52f5eada..0000000000 --- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ReleaseStrategyAdapterTests.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * 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.aggregator; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.springframework.integration.Message; -import org.springframework.integration.core.GenericMessage; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.SimpleMessageGroup; - -/** - * @author Marius Bogoevici - */ -public class ReleaseStrategyAdapterTests { - - private SimpleReleaseStrategy simpleReleaseStrategy; - - @Before - public void setUp() { - simpleReleaseStrategy = new SimpleReleaseStrategy(); - } - - @Test - public void testTrueConvertedProperly() { - MethodInvokingReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new AlwaysTrueReleaseStrategy(), - "checkCompleteness"); - Assert.assertTrue(adapter.canRelease(createListOfMessages(0))); - } - - @Test - public void testFalseConvertedProperly() { - MethodInvokingReleaseStrategy adapter = new MethodInvokingReleaseStrategy(new AlwaysFalseReleaseStrategy(), - "checkCompleteness"); - Assert.assertTrue(!adapter.canRelease(createListOfMessages(0))); - } - - @Test - public void testAdapterWithNonParameterizedMessageListBasedMethod() { - ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(simpleReleaseStrategy, - "checkCompletenessOnNonParameterizedListOfMessages"); - MessageGroup messages = createListOfMessages(3); - Assert.assertTrue(adapter.canRelease(messages)); - } - - @Test - public void testAdapterWithWildcardParametrizedMessageBasedMethod() { - ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(simpleReleaseStrategy, - "checkCompletenessOnListOfMessagesParametrizedWithWildcard"); - MessageGroup messages = createListOfMessages(3); - Assert.assertTrue(adapter.canRelease(messages)); - } - - @Test - public void testAdapterWithTypeParametrizedMessageBasedMethod() { - ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(simpleReleaseStrategy, - "checkCompletenessOnListOfMessagesParametrizedWithString"); - MessageGroup messages = createListOfMessages(3); - Assert.assertTrue(adapter.canRelease(messages)); - } - - @Test - public void testAdapterWithPojoBasedMethod() { - ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(simpleReleaseStrategy, "checkCompletenessOnListOfStrings"); - MessageGroup messages = createListOfMessages(3); - Assert.assertTrue(adapter.canRelease(messages)); - } - - @Test - public void testAdapterWithPojoBasedMethodReturningObject() { - ReleaseStrategy adapter = new MethodInvokingReleaseStrategy(simpleReleaseStrategy, "checkCompletenessOnListOfStrings"); - MessageGroup messages = createListOfMessages(3); - Assert.assertTrue(adapter.canRelease(messages)); - } - - @Test(expected = IllegalArgumentException.class) - public void testAdapterWithWrongMethodName() { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, "methodThatDoesNotExist"); - } - - @Test(expected = IllegalArgumentException.class) - public void testInvalidParameterTypeUsingMethodName() { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, "invalidParameterType"); - } - - @Test(expected = IllegalArgumentException.class) - public void testTooManyParametersUsingMethodName() { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, "tooManyParameters"); - } - - @Test(expected = IllegalArgumentException.class) - public void testNotEnoughParametersUsingMethodName() { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, "notEnoughParameters"); - } - - @Test(expected = IllegalArgumentException.class) - public void testListSubclassParameterUsingMethodName() { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, "ListSubclassParameter"); - } - - @Test(expected = IllegalArgumentException.class) - public void testWrongReturnType() throws SecurityException, NoSuchMethodError { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, "wrongReturnType"); - } - - @Test(expected = IllegalArgumentException.class) - public void testTooManyParametersUsingMethodObject() throws SecurityException, NoSuchMethodException { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod( - "tooManyParameters", List.class, List.class)); - } - - @Test(expected = IllegalArgumentException.class) - public void testNotEnoughParametersUsingMethodObject() throws SecurityException, NoSuchMethodException { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod( - "notEnoughParameters", new Class[] {})); - } - - @Test(expected = IllegalArgumentException.class) - public void testListSubclassParameterUsingMethodObject() throws SecurityException, NoSuchMethodException { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod( - "ListSubclassParameter", new Class[] { LinkedList.class })); - } - - @Test(expected = IllegalArgumentException.class) - public void testWrongReturnTypeUsingMethodObject() throws SecurityException, NoSuchMethodException { - new MethodInvokingReleaseStrategy(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod("wrongReturnType", - new Class[] { List.class })); - } - - private static MessageGroup createListOfMessages(int size) { - List> messages = new ArrayList>(); - if (size > 0) { - messages.add(new GenericMessage("123")); - } - if (size > 1) { - messages.add(new GenericMessage("456")); - } - if (size > 2) { - messages.add(new GenericMessage("789")); - } - return new SimpleMessageGroup(messages, "ABC"); - } - - @SuppressWarnings("unused") - private static class AlwaysTrueReleaseStrategy { - public boolean checkCompleteness(List> messages) { - return true; - } - } - - @SuppressWarnings("unused") - private static class AlwaysFalseReleaseStrategy { - public boolean checkCompleteness(List> messages) { - return false; - } - } - - @SuppressWarnings("unused") - private static class SimpleReleaseStrategy { - - public boolean checkCompletenessOnNonParameterizedListOfMessages(List> messages) { - Assert.assertTrue(messages.size() > 0); - return messages.size() > messages.iterator().next().getHeaders().getSequenceSize(); - } - - public boolean checkCompletenessOnListOfMessagesParametrizedWithWildcard(List> messages) { - Assert.assertTrue(messages.size() > 0); - return messages.size() > messages.iterator().next().getHeaders().getSequenceSize(); - } - - public boolean checkCompletenessOnListOfMessagesParametrizedWithString(List> messages) { - Assert.assertTrue(messages.size() > 0); - return messages.size() > messages.iterator().next().getHeaders().getSequenceSize(); - } - - // Example for the case when completeness is checked on the structure of - // the data - public boolean checkCompletenessOnListOfStrings(List messages) { - StringBuffer buffer = new StringBuffer(); - for (String content : messages) { - buffer.append(content); - } - return buffer.length() >= 9; - } - - public String wrongReturnType(List> message) { - return ""; - } - - public boolean invalidParameterType(String invalid) { - return false; - } - - public boolean tooManyParameters(List c1, List c2) { - return false; - } - - public boolean notEnoughParameters() { - return false; - } - - public boolean ListSubclassParameter(LinkedList l1) { - return false; - } - } - -} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/integration/AggregatorIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/integration/AggregatorIntegrationTests.java index 2ad38d36ea..b2be2377dc 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/integration/AggregatorIntegrationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/integration/AggregatorIntegrationTests.java @@ -50,7 +50,7 @@ public class AggregatorIntegrationTests { @Qualifier("output") private PollableChannel output; - @Test(timeout=5000) + @Test//(timeout=5000) public void testVanillaAggregation() throws Exception { for (int i = 0; i < 5; i++) { Map headers = stubHeaders(i, 5, 1); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java index 3820222290..b9618b148a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java @@ -19,10 +19,11 @@ package org.springframework.integration.config; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.junit.Assert; @@ -38,9 +39,8 @@ import org.springframework.integration.MessageHandlingException; import org.springframework.integration.MessageRejectedException; import org.springframework.integration.aggregator.CorrelatingMessageHandler; import org.springframework.integration.aggregator.CorrelationStrategy; -import org.springframework.integration.aggregator.MethodInvokingMessageListProcessor; -import org.springframework.integration.aggregator.ReleaseStrategy; import org.springframework.integration.aggregator.MethodInvokingReleaseStrategy; +import org.springframework.integration.aggregator.ReleaseStrategy; import org.springframework.integration.core.MessageBuilder; import org.springframework.integration.core.MessageChannel; import org.springframework.integration.core.MessageHandler; @@ -48,7 +48,6 @@ import org.springframework.integration.core.PollableChannel; import org.springframework.integration.core.SubscribableChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.test.util.TestUtils; -import org.springframework.integration.util.MethodInvoker; /** * @author Marius Bogoevici @@ -99,7 +98,8 @@ public class AggregatorParserTests { for (Message message : outboundMessages) { input.send(message); } - assertEquals("The aggregated message payload is not correct", "[123]", aggregatedMessage.get().getPayload().toString()); + assertEquals("The aggregated message payload is not correct", "[123]", aggregatedMessage.get().getPayload() + .toString()); } @Test @@ -112,10 +112,14 @@ public class AggregatorParserTests { Object consumer = new DirectFieldAccessor(endpoint).getPropertyValue("handler"); assertThat(consumer, is(CorrelatingMessageHandler.class)); DirectFieldAccessor accessor = new DirectFieldAccessor(consumer); - Method expectedMethod = TestAggregatorBean.class.getMethod("createSingleMessageFromGroup", List.class); - assertEquals("The MethodInvokingAggregator is not injected with the appropriate aggregation method", - expectedMethod, ((MethodInvokingMessageListProcessor) new DirectFieldAccessor(accessor - .getPropertyValue("outputProcessor")).getPropertyValue("adapter")).getMethod()); + Map map = (Map) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor + .getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate")) + .getPropertyValue("handlerMethods"); + assertEquals("The MethodInvokingAggregator is not injected with the appropriate aggregation method", 1, map + .size()); + assertEquals("The release strategy is not injected with the appropriate method", 1, map.size()); + assertTrue("Handler methods do not contain correct method: " + map, map.toString().contains( + "createSingleMessageFromGroup")); assertEquals("The AggregatorEndpoint is not injected with the appropriate ReleaseStrategy instance", releaseStrategy, accessor.getPropertyValue("releaseStrategy")); assertEquals("The AggregatorEndpoint is not injected with the appropriate CorrelationStrategy instance", @@ -163,13 +167,12 @@ public class AggregatorParserTests { ReleaseStrategy releaseStrategy = (ReleaseStrategy) new DirectFieldAccessor(new DirectFieldAccessor(endpoint) .getPropertyValue("handler")).getPropertyValue("releaseStrategy"); Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy); - DirectFieldAccessor releaseStrategyAccessor = new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategy) - .getPropertyValue("adapter")); - MethodInvoker invoker = (MethodInvoker) releaseStrategyAccessor.getPropertyValue("invoker"); - Assert - .assertTrue(new DirectFieldAccessor(invoker).getPropertyValue("object") instanceof MaxValueReleaseStrategy); - Assert.assertTrue(((Method) releaseStrategyAccessor.getPropertyValue("method")).getName().equals( - "checkCompleteness")); + DirectFieldAccessor releaseStrategyAccessor = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategy) + .getPropertyValue("adapter")).getPropertyValue("delegate")); + Map map = (Map) releaseStrategyAccessor.getPropertyValue("handlerMethods"); + assertEquals("The release strategy is not injected with the appropriate method", 1, map.size()); + assertTrue("Handler methods do not contain correct method: " + map, map.toString() + .contains("checkCompleteness")); input.send(createMessage(1l, "correllationId", 4, 0, null)); input.send(createMessage(2l, "correllationId", 4, 1, null)); input.send(createMessage(3l, "correllationId", 4, 2, null)); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/TestAggregatorBean.java b/spring-integration-core/src/test/java/org/springframework/integration/config/TestAggregatorBean.java index b09fe2e727..01ec050a1a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/TestAggregatorBean.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/TestAggregatorBean.java @@ -48,7 +48,9 @@ public class TestAggregatorBean { } } Message returnedMessage = new StringMessage(buffer.toString()); - aggregatedMessages.put(correlationId, returnedMessage); + if (correlationId!=null) { + aggregatedMessages.put(correlationId, returnedMessage); + } return returnedMessage; } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java index 31341fe38c..2a21028811 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java @@ -82,15 +82,16 @@ public class AggregatorAnnotationTests { new String[] { "classpath:/org/springframework/integration/config/annotation/testAnnotatedAggregator.xml" }); final String endpointName = "endpointWithDefaultAnnotationAndCustomReleaseStrategy"; MessageHandler aggregator = this.getAggregator(context, endpointName); - Object ReleaseStrategy = getPropertyValue(aggregator, "releaseStrategy"); - Assert.assertTrue(ReleaseStrategy instanceof MethodInvokingReleaseStrategy); - MethodInvokingReleaseStrategy releaseStrategyAdapter = (MethodInvokingReleaseStrategy) ReleaseStrategy; - DirectFieldAccessor invokerAccessor = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor( - releaseStrategyAdapter).getPropertyValue("adapter")).getPropertyValue("invoker")); - Object targetObject = invokerAccessor.getPropertyValue("object"); - assertSame(context.getBean(endpointName), targetObject); - Method completionCheckerMethod = (Method) invokerAccessor.getPropertyValue("method"); - assertEquals("completionChecker", completionCheckerMethod.getName()); + Object releaseStrategy = getPropertyValue(aggregator, "releaseStrategy"); + Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy); + MethodInvokingReleaseStrategy releaseStrategyAdapter = (MethodInvokingReleaseStrategy) releaseStrategy; + Map map = (Map) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter) + .getPropertyValue("adapter")).getPropertyValue("delegate")).getPropertyValue("handlerMethods"); + assertEquals("The MethodInvokingAggregator is not injected with the appropriate aggregation method", 1, map + .size()); + assertEquals("The release strategy is not injected with the appropriate method", 1, map.size()); + assertTrue("Handler methods do not contain correct method: " + map, map.toString() + .contains("completionChecker")); } @Test @@ -101,9 +102,9 @@ public class AggregatorAnnotationTests { MessageHandler aggregator = this.getAggregator(context, endpointName); Object correlationStrategy = getPropertyValue(aggregator, "correlationStrategy"); Assert.assertTrue(correlationStrategy instanceof MethodInvokingCorrelationStrategy); - MethodInvokingCorrelationStrategy ReleaseStrategyAdapter = (MethodInvokingCorrelationStrategy) correlationStrategy; - DirectFieldAccessor processorAccessor = new DirectFieldAccessor(new DirectFieldAccessor(ReleaseStrategyAdapter) - .getPropertyValue("processor")); + MethodInvokingCorrelationStrategy releaseStrategyAdapter = (MethodInvokingCorrelationStrategy) correlationStrategy; + DirectFieldAccessor processorAccessor = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter) + .getPropertyValue("processor")).getPropertyValue("delegate")); Object targetObject = processorAccessor.getPropertyValue("targetObject"); assertSame(context.getBean(endpointName), targetObject); Map handlerMethods = (Map) processorAccessor.getPropertyValue("handlerMethods"); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PNamespaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PNamespaceTests.java index 0f0062f95a..ef5456281e 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PNamespaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PNamespaceTests.java @@ -85,7 +85,7 @@ public class PNamespaceTests { private TestBean prepare(EventDrivenConsumer edc) { return TestUtils.getPropertyValue(serviceActivator, - "handler.processor.targetObject", TestBean.class); + "handler.processor.delegate.targetObject", TestBean.class); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java index af43d78bcc..7179b6f007 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java @@ -27,6 +27,7 @@ import org.junit.internal.matchers.TypeSafeMatcher; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.io.Resource; import org.springframework.expression.EvaluationException; @@ -55,6 +56,12 @@ public class ExpressionEvaluatingMessageProcessorTests { @Test public void testProcessMessageWithParameterCoercion() { + @SuppressWarnings("unused") + class TestTarget { + public String stringify(int number) { + return number+""; + } + } ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("#target.stringify(payload)"); processor.getEvaluationContext().setVariable("target", new TestTarget()); assertEquals("2", processor.processMessage(new StringMessage("2"))); @@ -62,6 +69,11 @@ public class ExpressionEvaluatingMessageProcessorTests { @Test public void testProcessMessageWithVoidResult() { + @SuppressWarnings("unused") + class TestTarget { + public void ping(String input) { + } + } ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("#target.ping(payload)"); processor.getEvaluationContext().setVariable("target", new TestTarget()); assertEquals(null, processor.processMessage(new StringMessage("2"))); @@ -69,7 +81,15 @@ public class ExpressionEvaluatingMessageProcessorTests { @Test public void testProcessMessageWithParameterCoercionToNonPrimitive() { + class TestTarget { + @SuppressWarnings("unused") + public String find(Resource[] resources) { + return Arrays.asList(resources).toString(); + } + + } ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("#target.find(payload)"); + processor.setBeanFactory(new GenericApplicationContext().getBeanFactory()); processor.getEvaluationContext().setVariable("target", new TestTarget()); String result = (String) processor.processMessage(new StringMessage("classpath:*.properties")); assertTrue("Wrong result: "+result, result.contains("log4j.properties")); @@ -187,23 +207,6 @@ public class ExpressionEvaluatingMessageProcessorTests { } } - @SuppressWarnings("unused") - private static class TestTarget { - - public String stringify(int number) { - return number+""; - } - - public String find(Resource[] resources) { - return Arrays.asList(resources).toString(); - } - - public void ping(String input) { - } - - } - - @SuppressWarnings("serial") private static final class CheckedException extends Exception { public CheckedException(String string) { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java index fd102911c6..1cedc0cce8 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java @@ -49,7 +49,7 @@ import org.springframework.integration.core.StringMessage; public class MethodInvokingMessageProcessorTests { private static final Log logger = LogFactory.getLog(MethodInvokingMessageProcessorTests.class); - + @Rule public ExpectedException expected = ExpectedException.none(); @@ -58,14 +58,16 @@ public class MethodInvokingMessageProcessorTests { class A { @SuppressWarnings("unused") public Message myMethod(final Message msg) { - return MessageBuilder.fromMessage(msg).setHeader("A", "A").build(); - } + return MessageBuilder.fromMessage(msg).setHeader("A", "A").build(); + } } - class B extends A {} + class B extends A { + } @SuppressWarnings("unused") - class C extends B {} + class C extends B { + } MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new B(), "myMethod"); Message message = (Message) processor.processMessage(new StringMessage("")); @@ -77,18 +79,19 @@ public class MethodInvokingMessageProcessorTests { class A { @SuppressWarnings("unused") public Message myMethod(Message msg) { - return MessageBuilder.fromMessage(msg).setHeader("A", "A").build(); - } + return MessageBuilder.fromMessage(msg).setHeader("A", "A").build(); + } } class B extends A { public Message myMethod(Message msg) { - return MessageBuilder.fromMessage(msg).setHeader("B", "B").build(); - } + return MessageBuilder.fromMessage(msg).setHeader("B", "B").build(); + } } @SuppressWarnings("unused") - class C extends B {} + class C extends B { + } MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new B(), "myMethod"); Message message = (Message) processor.processMessage(new StringMessage("")); @@ -99,20 +102,20 @@ public class MethodInvokingMessageProcessorTests { class A { @SuppressWarnings("unused") public Message myMethod(Message msg) { - return MessageBuilder.fromMessage(msg).setHeader("A", "A").build(); - } + return MessageBuilder.fromMessage(msg).setHeader("A", "A").build(); + } } class B extends A { public Message myMethod(Message msg) { - return MessageBuilder.fromMessage(msg).setHeader("B", "B").build(); - } + return MessageBuilder.fromMessage(msg).setHeader("B", "B").build(); + } } class C extends B { public Message myMethod(Message msg) { - return MessageBuilder.fromMessage(msg).setHeader("C", "C").build(); - } + return MessageBuilder.fromMessage(msg).setHeader("C", "C").build(); + } } MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new C(), "myMethod"); @@ -123,17 +126,18 @@ public class MethodInvokingMessageProcessorTests { public void testHandlerInheritanceMethodImplInSubClassAndSuper() { class A { @SuppressWarnings("unused") - public Message myMethod(Message msg){ - return MessageBuilder.fromMessage(msg).setHeader("A", "A").build(); - } + public Message myMethod(Message msg) { + return MessageBuilder.fromMessage(msg).setHeader("A", "A").build(); + } } - class B extends A {} + class B extends A { + } class C extends B { public Message myMethod(Message msg) { - return MessageBuilder.fromMessage(msg).setHeader("C", "C").build(); - } + return MessageBuilder.fromMessage(msg).setHeader("C", "C").build(); + } } MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new C(), "myMethod"); @@ -143,92 +147,102 @@ public class MethodInvokingMessageProcessorTests { @Test public void payloadAsMethodParameterAndObjectAsReturnValue() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor( - new TestBean(), "acceptPayloadAndReturnObject"); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "acceptPayloadAndReturnObject"); Object result = processor.processMessage(new StringMessage("testing")); assertEquals("testing-1", result); } @Test public void testPayloadCoercedToString() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor( - new TestBean(), "acceptPayloadAndReturnObject"); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "acceptPayloadAndReturnObject"); Object result = processor.processMessage(new GenericMessage(123456789)); assertEquals("123456789-1", result); } @Test public void payloadAsMethodParameterAndMessageAsReturnValue() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor( - new TestBean(), "acceptPayloadAndReturnMessage"); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "acceptPayloadAndReturnMessage"); Message result = (Message) processor.processMessage(new StringMessage("testing")); assertEquals("testing-2", result.getPayload()); } @Test public void messageAsMethodParameterAndObjectAsReturnValue() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor( - new TestBean(), "acceptMessageAndReturnObject"); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "acceptMessageAndReturnObject"); Object result = processor.processMessage(new StringMessage("testing")); assertEquals("testing-3", result); } @Test public void messageAsMethodParameterAndMessageAsReturnValue() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor( - new TestBean(), "acceptMessageAndReturnMessage"); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "acceptMessageAndReturnMessage"); Message result = (Message) processor.processMessage(new StringMessage("testing")); assertEquals("testing-4", result.getPayload()); } @Test public void messageSubclassAsMethodParameterAndMessageAsReturnValue() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor( - new TestBean(), "acceptMessageSubclassAndReturnMessage"); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "acceptMessageSubclassAndReturnMessage"); Message result = (Message) processor.processMessage(new StringMessage("testing")); assertEquals("testing-5", result.getPayload()); } @Test public void messageSubclassAsMethodParameterAndMessageSubclassAsReturnValue() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor( - new TestBean(), "acceptMessageSubclassAndReturnMessageSubclass"); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "acceptMessageSubclassAndReturnMessageSubclass"); Message result = (Message) processor.processMessage(new StringMessage("testing")); assertEquals("testing-6", result.getPayload()); } @Test public void payloadAndHeaderAnnotationMethodParametersAndObjectAsReturnValue() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor( - new TestBean(), "acceptPayloadAndHeaderAndReturnObject"); - Message request = MessageBuilder.withPayload("testing") - .setHeader("number", new Integer(123)).build(); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "acceptPayloadAndHeaderAndReturnObject"); + Message request = MessageBuilder.withPayload("testing").setHeader("number", new Integer(123)).build(); Object result = processor.processMessage(request); assertEquals("testing-123", result); } - @Test - public void testVoidMethodsIncludedbyDefault() { - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), "testVoidReturningMethods"); - assertNull(processor.processMessage(MessageBuilder.withPayload("Something").build())); - assertEquals(12, processor.processMessage(MessageBuilder.withPayload(12).build())); - } + @Test + public void testVoidMethodsIncludedbyDefault() { + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), + "testVoidReturningMethods"); + assertNull(processor.processMessage(MessageBuilder.withPayload("Something").build())); + assertEquals(12, processor.processMessage(MessageBuilder.withPayload(12).build())); + } - @Test - public void testVoidMethodsExcludedByFlag() { - Exception exception = null; - MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), "testVoidReturningMethods", true); - assertEquals(12, processor.processMessage(MessageBuilder.withPayload(12).build())); - try { - processor.processMessage(MessageBuilder.withPayload("not_a_number").build()); - fail(); - } - catch(MessageHandlingException ex) { - // the only void method expects a number - exception = ex; - } - assertNotNull(exception); - } + @Test + public void testVoidMethodsExcludedByFlag() { + @SuppressWarnings("unused") + class VoidMethodsBean { + public void testVoidReturningMethods(String s) { + // do nothing + } + public int testVoidReturningMethods(int i) { + return i; + } + } + Exception exception = null; + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new VoidMethodsBean(), + "testVoidReturningMethods", true); + assertEquals(12, processor.processMessage(MessageBuilder.withPayload(12).build())); + try { + processor.processMessage(MessageBuilder.withPayload("not_a_number").build()); + fail(); + } + catch (MessageHandlingException ex) { + // the only void method expects a number + exception = ex; + } + assertNotNull(exception); + } @Test public void messageOnlyWithAnnotatedMethod() throws Exception { @@ -267,6 +281,7 @@ public class MethodInvokingMessageProcessorTests { @Test public void testProcessMessageBadExpression() throws Exception { + // TODO: should this be MessageHandlingException or NumberFormatException? expected.expect(new ExceptionCauseMatcher(NumberFormatException.class)); AnnotatedTestService service = new AnnotatedTestService(); Method method = service.getClass().getMethod("integerMethod", Integer.class); @@ -297,8 +312,7 @@ public class MethodInvokingMessageProcessorTests { AnnotatedTestService service = new AnnotatedTestService(); Method method = service.getClass().getMethod("messageAndHeader", Message.class, Integer.class); MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method); - Message message = MessageBuilder.withPayload("foo") - .setHeader("number", 42).build(); + Message message = MessageBuilder.withPayload("foo").setHeader("number", 42).build(); Object result = processor.processMessage(message); assertEquals("foo-42", result); } @@ -308,9 +322,8 @@ public class MethodInvokingMessageProcessorTests { AnnotatedTestService service = new AnnotatedTestService(); Method method = service.getClass().getMethod("twoHeaders", String.class, Integer.class); MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method); - Message message = MessageBuilder.withPayload("foo") - .setHeader("prop", "bar") - .setHeader("number", 42).build(); + Message message = MessageBuilder.withPayload("foo").setHeader("prop", "bar").setHeader("number", 42) + .build(); Object result = processor.processMessage(message); assertEquals("bar-42", result); } @@ -334,7 +347,7 @@ public class MethodInvokingMessageProcessorTests { assertEquals(String.class, bean.lastArg.getClass()); assertEquals("true", bean.lastArg); } - + @Test public void testOverloadedNonVoidReturningMethodsWithExactMatchForType() { AmbiguousMethodBean bean = new AmbiguousMethodBean(); @@ -344,20 +357,24 @@ public class MethodInvokingMessageProcessorTests { assertEquals(String.class, bean.lastArg.getClass()); assertEquals("true", bean.lastArg); } - + private static class ExceptionCauseMatcher extends TypeSafeMatcher { private Throwable cause; + private Class type; + public ExceptionCauseMatcher(Class type) { this.type = type; } + @Override public boolean matchesSafely(Exception item) { logger.debug(item); cause = item.getCause(); - assertNotNull("There is no cause for "+item, cause); + assertNotNull("There is no cause for " + item, cause); return type.isAssignableFrom(cause.getClass()); } + public void describeTo(Description description) { description.appendText("cause to be ").appendValue(type).appendText("but was ").appendValue(cause); } @@ -368,11 +385,12 @@ public class MethodInvokingMessageProcessorTests { public String error(String input) { throw new UnsupportedOperationException("Expected test exception"); } + public String checked(String input) throws Exception { throw new CheckedException("Expected test exception"); } } - + @SuppressWarnings("serial") public static final class CheckedException extends Exception { public CheckedException(String string) { @@ -411,17 +429,16 @@ public class MethodInvokingMessageProcessorTests { return s + "-" + n; } - public void testVoidReturningMethods(String s) { - // do nothing - } + public void testVoidReturningMethods(String s) { + // do nothing + } - public int testVoidReturningMethods(int i) { - return i; - } + public int testVoidReturningMethods(int i) { + return i; + } } - @SuppressWarnings("unused") private static class AnnotatedTestService { @@ -437,15 +454,16 @@ public class MethodInvokingMessageProcessorTests { return prop + "-" + num.toString(); } - public Integer optionalHeader(@Header(required=false) Integer num) { + public Integer optionalHeader(@Header(required = false) Integer num) { return num; } - public Integer requiredHeader(@Header(value="num", required=true) Integer num) { + public Integer requiredHeader(@Header(value = "num", required = true) Integer num) { return num; } - public String optionalAndRequiredHeader(@Header(required=false) String prop, @Header(value="num", required=true) Integer num) { + public String optionalAndRequiredHeader(@Header(required = false) String prop, + @Header(value = "num", required = true) Integer num) { return prop + num; } @@ -464,10 +482,9 @@ public class MethodInvokingMessageProcessorTests { } - /** - * Method names create ambiguities, but the MethodResolver implementation - * should filter out based on the annotation or the 'requiresReply' flag. + * Method names create ambiguities, but the MethodResolver implementation should filter out based on the annotation + * or the 'requiresReply' flag. */ @SuppressWarnings("unused") private static class AmbiguousMethodBean { @@ -491,8 +508,8 @@ public class MethodInvokingMessageProcessorTests { } /** - * Method names create ambiguities, but the MethodResolver implementation - * should filter out based on the annotation or the 'requiresReply' flag. + * Method names create ambiguities, but the MethodResolver implementation should filter out based on the annotation + * or the 'requiresReply' flag. */ @SuppressWarnings("unused") private static class OverloadedMethodBean {