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 index 0067fab170..7673c6b892 100644 --- 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 @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; @@ -58,6 +59,7 @@ import org.springframework.integration.annotation.Payload; import org.springframework.integration.annotation.Payloads; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; import org.springframework.util.ReflectionUtils.MethodFilter; @@ -76,21 +78,31 @@ import org.springframework.util.StringUtils; * @author Gunnar Hillert * @author Soby Chacko * @author Gary Russell + * @author Artem Bilan * * @since 2.0 */ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator { + private static final String CANDIDATE_METHODS = "CANDIDATE_METHODS"; + + private static final String CANDIDATE_MESSAGE_METHODS = "CANDIDATE_MESSAGE_METHODS"; + 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 Map, HandlerMethod> handlerMessageMethods; + + private final LinkedList, HandlerMethod>> handlerMethodsList; + + private final HandlerMethod handlerMethod; + private final Class expectedType; private final boolean canProcessMessageList; @@ -154,11 +166,12 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator 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.handlerMethod = new HandlerMethod(method, canProcessMessageList); + this.handlerMethods = null; + this.handlerMessageMethods = null; + this.handlerMethodsList = null; this.prepareEvaluationContext(this.getEvaluationContext(false), method, annotationType); this.setDisplayString(targetObject, method); } @@ -170,7 +183,32 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator this.expectedType = expectedType; this.targetObject = targetObject; this.requiresReply = expectedType != null; - this.handlerMethods = this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply); + Map, HandlerMethod>> handlerMethodsForTarget = + this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply); + Map, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS); + Map, HandlerMethod> handlerMessageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS); + if ((handlerMethods.size() == 1 && handlerMessageMethods.isEmpty()) || + (handlerMessageMethods.size() == 1 && handlerMethods.isEmpty())) { + if (handlerMethods.size() == 1) { + this.handlerMethod = handlerMethods.values().iterator().next(); + } + else { + this.handlerMethod = handlerMessageMethods.values().iterator().next(); + } + this.handlerMethods = null; + this.handlerMessageMethods = null; + this.handlerMethodsList = null; + } + else { + this.handlerMethod = null; + this.handlerMethods = handlerMethods; + this.handlerMessageMethods = handlerMessageMethods; + this.handlerMethodsList = new LinkedList, HandlerMethod>>(); + + //TODO Consider to use global option to determine a precedence of methods + this.handlerMethodsList.add(this.handlerMethods); + this.handlerMethodsList.add(this.handlerMessageMethods); + } this.prepareEvaluationContext(this.getEvaluationContext(false), methodName, annotationType); this.setDisplayString(targetObject, methodName); } @@ -181,7 +219,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator sb.append("." + ((Method) targetMethod).getName()); } else if (targetMethod instanceof String) { - sb.append("." + (String) targetMethod); + sb.append("." + targetMethod); } this.displayString = sb.toString() + "]"; } @@ -192,7 +230,8 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator if (method instanceof Method) { context.registerMethodFilter(targetType, new FixedMethodFilter((Method) method)); if (expectedType != null) { - Assert.state(context.getTypeConverter().canConvert(TypeDescriptor.valueOf(((Method) method).getReturnType()), TypeDescriptor.valueOf(expectedType)), + Assert.state(context.getTypeConverter().canConvert(TypeDescriptor.valueOf(((Method) method).getReturnType()), + TypeDescriptor.valueOf(expectedType)), "Cannot convert to expected type (" + expectedType + ") from " + method); } } @@ -220,64 +259,48 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } private T 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(); - @SuppressWarnings("unchecked") - T result = (T) 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; - } + HandlerMethod candidate = this.findHandlerMethodForParameters(parameters); + Assert.notNull(candidate, "No candidate methods found for messages."); + Expression expression = candidate.getExpression(); + Class expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType(); + try { + @SuppressWarnings("unchecked") + T result = (T) this.evaluateExpression(expression, parameters, expectedType); + if (this.requiresReply) { + Assert.notNull(result, + "Expression evaluation result was null, but this processor requires a reply."); } + return result; } - if (evaluationException instanceof Exception) { - throw (Exception) evaluationException; - } - else if (evaluationException instanceof Error) { - throw (Error) evaluationException; - } - else { - throw new IllegalStateException("Cannot process message", evaluationException); + catch (Exception e) { + Throwable evaluationException = e; + if ((e instanceof EvaluationException || e instanceof MessageHandlingException) && e.getCause() != null) { + evaluationException = e.getCause(); + } + if (evaluationException instanceof Exception) { + throw (Exception) evaluationException; + } + else { + throw new IllegalStateException("Cannot process message", evaluationException); + } } } - private Map, HandlerMethod> findHandlerMethodsForTarget(final Object targetObject, + private Map, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject, final Class annotationType, final String methodName, final boolean requiresReply) { + Map, HandlerMethod>> handlerMethods = new HashMap, HandlerMethod>>(); + final Map, HandlerMethod> candidateMethods = new HashMap, HandlerMethod>(); + final Map, HandlerMethod> candidateMessageMethods = new HashMap, HandlerMethod>(); final Map, HandlerMethod> fallbackMethods = new HashMap, HandlerMethod>(); + final Map, HandlerMethod> fallbackMessageMethods = new HashMap, HandlerMethod>(); final AtomicReference> ambiguousFallbackType = new AtomicReference>(); + final AtomicReference> ambiguousFallbackMessageGenericType = new AtomicReference>(); final Class targetClass = this.getTargetClass(targetObject); MethodFilter methodFilter = new UniqueMethodFilter(targetClass); ReflectionUtils.doWithMethods(targetClass, new MethodCallback() { + @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { boolean matchesAnnotation = false; if (method.isBridge()) { @@ -311,37 +334,75 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } return; } - Class targetParameterType = handlerMethod.getTargetParameterType().getObjectType(); + 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); + if (handlerMethod.isMessageMethod()) { + if (candidateMessageMethods.containsKey(targetParameterType)) { + throw new IllegalArgumentException("Found more than one method match for type " + + "[Message<" + targetParameterType + ">]"); + } + candidateMessageMethods.put(targetParameterType, handlerMethod); + } + else { + if (candidateMethods.containsKey(targetParameterType)) { + String exceptionMessage = "Found more than one method match for "; + if (Void.class.equals(targetParameterType)) { + exceptionMessage += "empty parameter for 'payload'"; + } + else { + exceptionMessage += "type [" + targetParameterType + "]"; + } + throw new IllegalArgumentException(exceptionMessage); + } + 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); + if (handlerMethod.isMessageMethod()) { + if (fallbackMessageMethods.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 + ambiguousFallbackMessageGenericType.compareAndSet(null, targetParameterType); + } + fallbackMessageMethods.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); } - fallbackMethods.put(targetParameterType, handlerMethod); } } }, methodFilter); - if (!candidateMethods.isEmpty()) { - return candidateMethods; + + if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) { + handlerMethods.put(CANDIDATE_METHODS, candidateMethods); + handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods); + return handlerMethods; } - if ((fallbackMethods.isEmpty() || ambiguousFallbackType.get() != null) && ServiceActivator.class.equals(annotationType)) { - // a Service Activator can fallback to either MessageHandler.handleMessage(m) or RequestReplyExchanger.exchange(m) + if ((ambiguousFallbackType.get() != null + || ambiguousFallbackMessageGenericType.get() != null) + && ServiceActivator.class.equals(annotationType)) { + /* + * When there are ambiguous fallback methods, + * a Service Activator can finally fallback to RequestReplyExchanger.exchange(m). + * Ambiguous means > 1 method that takes the same payload type, or > 1 method + * that takes a Message with the same generic type. + */ List frameworkMethods = new ArrayList(); Class[] allInterfaces = org.springframework.util.ClassUtils.getAllInterfacesForClass(targetClass); for (Class iface : allInterfaces) { try { if ("org.springframework.integration.gateway.RequestReplyExchanger".equals(iface.getName())) { frameworkMethods.add(targetClass.getMethod("exchange", Message.class)); - } - else if ("org.springframework.integration.core.MessageHandler".equals(iface.getName()) && !requiresReply) { - frameworkMethods.add(targetClass.getMethod("handleMessage", Message.class)); + if (logger.isDebugEnabled()) { + logger.debug(targetObject.getClass() + ": Ambiguous fallback methods; using RequestReplyExchanger.exchange()"); + } } } catch (Exception e) { @@ -350,14 +411,30 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } if (frameworkMethods.size() == 1) { HandlerMethod handlerMethod = new HandlerMethod(frameworkMethods.get(0), canProcessMessageList); - return Collections., HandlerMethod>singletonMap(Object.class, handlerMethod); + handlerMethods.put(CANDIDATE_METHODS, Collections., HandlerMethod>singletonMap(Object.class, handlerMethod)); + handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods); + return handlerMethods; } } - Assert.notEmpty(fallbackMethods, "Target object of type [" + this.targetObject.getClass() - + "] has no eligible methods for handling Messages."); + + try { + Assert.state(!fallbackMethods.isEmpty() || !fallbackMessageMethods.isEmpty(), + "Target object of type [" + this.targetObject.getClass() + "] has no eligible methods for handling Messages."); + } + catch (Exception e) { + //TODO backward compatibility + throw new IllegalArgumentException(e.getMessage()); + } + Assert.isNull(ambiguousFallbackType.get(), "Found ambiguous parameter type [" + ambiguousFallbackType + "] for method match: " + fallbackMethods.values()); - return fallbackMethods; + Assert.isNull(ambiguousFallbackMessageGenericType.get(), + "Found ambiguous parameter type [" + ambiguousFallbackMessageGenericType + "] for method match: " + + fallbackMethods.values()); + + handlerMethods.put(CANDIDATE_METHODS, fallbackMethods); + handlerMethods.put(CANDIDATE_MESSAGE_METHODS, fallbackMessageMethods); + return handlerMethods; } private Class getTargetClass(Object targetObject) { @@ -388,22 +465,40 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator return targetClass; } - private List findHandlerMethodsForParameters(ParametersWrapper parameters) { + private HandlerMethod findHandlerMethodForParameters(ParametersWrapper parameters) { + if (this.handlerMethod != null) { + return this.handlerMethod; + } + final Class payloadType = parameters.getFirstParameterType(); + HandlerMethod closestMatch = this.findClosestMatch(payloadType); if (closestMatch != null) { - return Collections.singletonList(closestMatch); + return closestMatch; + } - return new ArrayList(this.handlerMethods.values()); + + if (Iterable.class.isAssignableFrom(payloadType) && this.handlerMethods.containsKey(Iterator.class)) { + return this.handlerMethods.get(Iterator.class); + } + else { + return this.handlerMethods.get(Void.class); + } + } private HandlerMethod findClosestMatch(Class payloadType) { - Set> candidates = this.handlerMethods.keySet(); - Class match = null; - if (candidates != null && !candidates.isEmpty()) { - match = ClassUtils.findClosestMatch(payloadType, candidates, true); + for (Map, HandlerMethod> handlerMethods : handlerMethodsList) { + Set> candidates = handlerMethods.keySet(); + Class match = null; + if (!CollectionUtils.isEmpty(candidates)) { + match = ClassUtils.findClosestMatch(payloadType, candidates, true); + } + if (match != null) { + return handlerMethods.get(match); + } } - return (match != null) ? this.handlerMethods.get(match) : null; + return null; } private static boolean isMethodDefinedOnObjectClass(Method method) { @@ -446,10 +541,13 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator private final Expression expression; - private volatile TypeDescriptor targetParameterType; - private final boolean canProcessMessageList; + private volatile TypeDescriptor targetParameterTypeDescriptor; + + private volatile Class targetParameterType = Void.class; + + private volatile boolean messageMethod; HandlerMethod(Method method, boolean canProcessMessageList) { this.method = method; @@ -462,10 +560,14 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator return this.expression; } - TypeDescriptor getTargetParameterType() { + Class getTargetParameterType() { return this.targetParameterType; } + private boolean isMessageMethod() { + return messageMethod; + } + @Override public String toString() { return this.method.toString(); @@ -476,13 +578,12 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator 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; + MethodParameter methodParameter = new MethodParameter(method, i); + TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(methodParameter); Class parameterType = parameterTypeDescriptor.getObjectType(); Annotation mappingAnnotation = findMappingAnnotation(parameterAnnotations[i]); if (mappingAnnotation != null) { @@ -494,7 +595,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator sb.append("." + qualifierExpression); } if (!StringUtils.hasText(qualifierExpression)) { - this.setExclusiveTargetParameterType(parameterTypeDescriptor); + this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter); } } if (annotationType.equals(Payloads.class)) { @@ -505,7 +606,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } sb.append("]"); if (!StringUtils.hasText(qualifierExpression)) { - this.setExclusiveTargetParameterType(parameterTypeDescriptor); + this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter); } } else if (annotationType.equals(Headers.class)) { @@ -515,17 +616,18 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } else if (annotationType.equals(Header.class)) { Header headerAnnotation = (Header) mappingAnnotation; - sb.append(this.determineHeaderExpression(headerAnnotation, new MethodParameter(method, i))); + sb.append(this.determineHeaderExpression(headerAnnotation, methodParameter)); } } else if (parameterTypeDescriptor.isAssignableTo(messageTypeDescriptor)) { + this.messageMethod = true; sb.append("message"); - this.setExclusiveTargetParameterType(parameterTypeDescriptor); + this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter); } else if ((parameterTypeDescriptor.isAssignableTo(messageListTypeDescriptor) || parameterTypeDescriptor .isAssignableTo(messageArrayTypeDescriptor))) { sb.append("messages"); - this.setExclusiveTargetParameterType(parameterTypeDescriptor); + this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter); } else if (Collection.class.isAssignableFrom(parameterType) || parameterType.isArray()) { if (canProcessMessageList) { @@ -534,11 +636,11 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator else { sb.append("payload"); } - this.setExclusiveTargetParameterType(parameterTypeDescriptor); + this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter); } else if (Iterator.class.isAssignableFrom(parameterType)) { if (canProcessMessageList) { - Type type = method.getGenericParameterTypes()[0]; + Type type = method.getGenericParameterTypes()[i]; Type parameterizedType = null; if (type instanceof ParameterizedType){ parameterizedType = ((ParameterizedType)type).getActualTypeArguments()[0]; @@ -546,7 +648,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator parameterizedType = ((ParameterizedType) parameterizedType).getRawType(); } } - if (parameterizedType != null && Message.class.isAssignableFrom((Class)parameterizedType)){ + if (parameterizedType != null && Message.class.isAssignableFrom((Class) parameterizedType)){ sb.append("messages.iterator()"); } else { @@ -556,7 +658,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator else { sb.append("payload.iterator()"); } - this.setExclusiveTargetParameterType(parameterTypeDescriptor); + this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter); } else if (Map.class.isAssignableFrom(parameterType)) { if (Properties.class.isAssignableFrom(parameterType)) { @@ -573,19 +675,19 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } else { sb.append("payload"); - this.setExclusiveTargetParameterType(parameterTypeDescriptor); + this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter); } } if (hasUnqualifiedMapParameter) { - if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType.getObjectType())) { + 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 = defaultParameterTypeDescriptor; + if (this.targetParameterTypeDescriptor == null) { + this.targetParameterTypeDescriptor = TypeDescriptor.valueOf(Void.class); } return EXPRESSION_PARSER.parseExpression(sb.toString()); } @@ -638,15 +740,22 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression; } - private synchronized void setExclusiveTargetParameterType(TypeDescriptor targetParameterType) { - Assert.isNull(this.targetParameterType, "Found more than one parameter type candidate: [" - + this.targetParameterType + "] and [" + targetParameterType + "]"); - this.targetParameterType = targetParameterType; + private synchronized void setExclusiveTargetParameterType(TypeDescriptor targetParameterType, MethodParameter methodParameter) { + Assert.isNull(this.targetParameterTypeDescriptor, "Found more than one parameter type candidate: [" + + this.targetParameterTypeDescriptor + "] and [" + targetParameterType + "]"); + this.targetParameterTypeDescriptor = targetParameterType; + if (Message.class.isAssignableFrom(targetParameterType.getObjectType())) { + methodParameter.increaseNestingLevel(); + this.targetParameterType = methodParameter.getNestedParameterType(); + methodParameter.decreaseNestingLevel(); + } + else { + this.targetParameterType = targetParameterType.getObjectType(); + } } } - @SuppressWarnings("unused") - private static class ParametersWrapper { + public class ParametersWrapper { private final Object payload; @@ -692,7 +801,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator if (payload != null) { return payload.getClass(); } - return Collection.class; + return this.messages.getClass(); } } 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 9d38922e11..4bef9675d9 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 @@ -308,10 +308,9 @@ public class MethodInvokingMessageGroupProcessorTests { assertTrue(((Message)result).getPayload() instanceof Iterator); } - @Test(expected = IllegalArgumentException.class) + @Test public void testTwoMethodsWithSameParameterTypesAmbiguous() { - @SuppressWarnings("unused") class AnnotatedParametersAggregator { public Integer and(List flags) { int result = 0; @@ -327,7 +326,12 @@ public class MethodInvokingMessageGroupProcessorTests { } } - new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator()); + MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator()); + when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing); + Object result = processor.processMessageGroup(messageGroupMock); + Object payload = ((Message) result).getPayload(); + assertTrue(payload instanceof Integer); + assertEquals(7, payload); } 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 91123e4d11..dbebc3bc07 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 @@ -16,9 +16,16 @@ package org.springframework.integration.config; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.junit.Assert; @@ -49,14 +56,6 @@ import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.instanceOf; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - /** * @author Marius Bogoevici * @author Mark Fisher @@ -123,14 +122,14 @@ public class AggregatorParserTests { Object consumer = new DirectFieldAccessor(endpoint).getPropertyValue("handler"); assertThat(consumer, is(instanceOf(AggregatingMessageHandler.class))); DirectFieldAccessor accessor = new DirectFieldAccessor(consumer); - Map map = (Map) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor + Object handlerMethods = 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")); + assertNull(handlerMethods); + Object handlerMethod = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor + .getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate")) + .getPropertyValue("handlerMethod"); + assertTrue(handlerMethod.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", @@ -180,10 +179,10 @@ public class AggregatorParserTests { Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy); 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")); + Object handlerMethods = releaseStrategyAccessor.getPropertyValue("handlerMethods"); + assertNull(handlerMethods); + Object handlerMethod = releaseStrategyAccessor.getPropertyValue("handlerMethod"); + assertTrue(handlerMethod.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)); @@ -205,10 +204,10 @@ public class AggregatorParserTests { Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy); 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")); + Object handlerMethods = releaseStrategyAccessor.getPropertyValue("handlerMethods"); + assertNull(handlerMethods); + Object handlerMethod = releaseStrategyAccessor.getPropertyValue("handlerMethod"); + assertTrue(handlerMethod.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/annotation/AggregatorAnnotationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java index bc7b2f92b5..9a03544136 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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,11 +16,18 @@ package org.springframework.integration.config.annotation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.springframework.integration.test.util.TestUtils.getPropertyValue; + import java.lang.reflect.Method; -import java.util.Map; import org.junit.Assert; import org.junit.Test; + import org.springframework.beans.DirectFieldAccessor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -35,16 +42,10 @@ import org.springframework.integration.support.channel.BeanFactoryChannelResolve import org.springframework.integration.support.channel.ChannelResolver; import org.springframework.integration.test.util.TestUtils; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import static org.springframework.integration.test.util.TestUtils.getPropertyValue; - /** * @author Marius Bogoevici * @author Mark Fisher + * @author Artem Bilan */ public class AggregatorAnnotationTests { @@ -86,13 +87,12 @@ public class AggregatorAnnotationTests { Object releaseStrategy = getPropertyValue(aggregator, "releaseStrategy"); Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy); MethodInvokingReleaseStrategy releaseStrategyAdapter = (MethodInvokingReleaseStrategy) releaseStrategy; - Map map = (Map) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter) + Object handlerMethods = 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")); + assertNull(handlerMethods); + Object handlerMethod = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter) + .getPropertyValue("adapter")).getPropertyValue("delegate")).getPropertyValue("handlerMethod"); + assertTrue(handlerMethod.toString().contains("completionChecker")); } @Test @@ -108,9 +108,10 @@ public class AggregatorAnnotationTests { .getPropertyValue("processor")).getPropertyValue("delegate")); Object targetObject = processorAccessor.getPropertyValue("targetObject"); assertSame(context.getBean(endpointName), targetObject); - Map handlerMethods = (Map) processorAccessor.getPropertyValue("handlerMethods"); - assertEquals(1, handlerMethods.size()); - DirectFieldAccessor handlerMethodAccessor = new DirectFieldAccessor(handlerMethods.values().iterator().next()); + assertNull(processorAccessor.getPropertyValue("handlerMethods")); + Object handlerMethod = processorAccessor.getPropertyValue("handlerMethod"); + assertNotNull(handlerMethod); + DirectFieldAccessor handlerMethodAccessor = new DirectFieldAccessor(handlerMethod); Method completionCheckerMethod = (Method) handlerMethodAccessor.getPropertyValue("method"); assertEquals("correlate", completionCheckerMethod.getName()); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ServiceActivatorMethodResolutionTests.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ServiceActivatorMethodResolutionTests.java index f2969be31c..117e4d6274 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ServiceActivatorMethodResolutionTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ServiceActivatorMethodResolutionTests.java @@ -17,12 +17,18 @@ package org.springframework.integration.endpoint; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; + +import java.util.Date; import org.junit.Test; import org.springframework.integration.Message; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.gateway.RequestReplyExchanger; import org.springframework.integration.handler.ServiceActivatingHandler; import org.springframework.integration.message.GenericMessage; @@ -66,6 +72,166 @@ public class ServiceActivatorMethodResolutionTests { } + @Test + public void testRequestReplyExchanger() { + RequestReplyExchanger testBean = new RequestReplyExchanger() { + + @Override + public Message exchange(Message request) { + return request; + } + }; + + final Message test = new GenericMessage("foo"); + + ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean) { + + @Override + protected Object handleRequestMessage(Message message) { + Object o = super.handleRequestMessage(message); + assertSame(test, o); + return null; + } + }; + + serviceActivator.handleMessage(test); + } + + @Test + /* + * A handler and message handler fallback (RRE); don't force RRE + */ + public void testRequestReplyExchangerSeveralMethods() { + RequestReplyExchanger testBean = new RequestReplyExchanger() { + + @Override + public Message exchange(Message request) { + return request; + } + + @SuppressWarnings("unused") + public String foo(String request) { + return request.toUpperCase(); + } + + }; + ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean); + PollableChannel outputChannel = new QueueChannel(); + serviceActivator.setOutputChannel(outputChannel); + + Message test = new GenericMessage(new Date()); + serviceActivator.handleMessage(test); + assertEquals(test, outputChannel.receive(10)); + + test = new GenericMessage("foo"); + serviceActivator.handleMessage(test); + assertEquals("FOO", outputChannel.receive(10).getPayload()); + } + + @Test + /* + * No handler fallback methods; don't force RRE + */ + public void testRequestReplyExchangerWithGenericMessageMethod() { + RequestReplyExchanger testBean = new RequestReplyExchanger() { + + @Override + public Message exchange(Message request) { + return request; + } + + @SuppressWarnings("unused") + public String foo(Message request) { + return request.getPayload().toUpperCase(); + } + + }; + ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean); + PollableChannel outputChannel = new QueueChannel(); + serviceActivator.setOutputChannel(outputChannel); + + Message test = new GenericMessage(new Date()); + serviceActivator.handleMessage(test); + assertEquals(test, outputChannel.receive(10)); + + test = new GenericMessage("foo"); + serviceActivator.handleMessage(test); + assertEquals("FOO", outputChannel.receive(10).getPayload()); + } + + @Test + /* + * No handler fallback methods; ambiguous message handler fallbacks; force RRE + */ + public void testRequestReplyExchangerWithAmbiguousGenericMessageMethod() { + RequestReplyExchanger testBean = new RequestReplyExchanger() { + + @Override + public Message exchange(Message request) { + return request; + } + + @SuppressWarnings("unused") + public String foo(Message request) { + return request.getPayload().toUpperCase(); + } + + @SuppressWarnings("unused") + public String bar(Message request) { + return request.getPayload().toUpperCase(); + } + + }; + ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean); + PollableChannel outputChannel = new QueueChannel(); + serviceActivator.setOutputChannel(outputChannel); + + Message test = new GenericMessage(new Date()); + serviceActivator.handleMessage(test); + assertEquals(test, outputChannel.receive(10)); + + test = new GenericMessage("foo"); + serviceActivator.handleMessage(test); + assertNotEquals("FOO", outputChannel.receive(10).getPayload()); + } + + @Test + /* + * One message handler fallback method (RRE); ambiguous handler fallbacks; force RRE + */ + public void testRequestReplyExchangerWithAmbiguousMethod() { + RequestReplyExchanger testBean = new RequestReplyExchanger() { + + @Override + public Message exchange(Message request) { + return request; + } + + @SuppressWarnings("unused") + public String foo(String request) { + return request.toUpperCase(); + } + + @SuppressWarnings("unused") + public String bar(String request) { + return request.toUpperCase(); + } + + }; + ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean); + PollableChannel outputChannel = new QueueChannel(); + serviceActivator.setOutputChannel(outputChannel); + + Message test = new GenericMessage(new Date()); + serviceActivator.handleMessage(test); + assertEquals(test, outputChannel.receive(10)); + + test = new GenericMessage("foo"); + serviceActivator.handleMessage(test); + assertNotEquals("FOO", outputChannel.receive(10).getPayload()); + } + + @SuppressWarnings("unused") private static class SingleAnnotationTestBean { 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 6b078bf311..9cd7b57052 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 @@ -20,18 +20,23 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import java.lang.reflect.Method; +import java.util.Date; import java.util.Map; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hamcrest.Description; +import org.hamcrest.Matchers; import org.hamcrest.TypeSafeMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.integration.Message; import org.springframework.integration.MessageHandlingException; @@ -355,6 +360,135 @@ public class MethodInvokingMessageProcessorTests { assertSame(RequestReplyExchanger.class, result); } + @Test + public void testInt3199GenericTypeResolvingAndObjectMethod() throws Exception { + + class Foo { + + public String handleMessage(Message message) { + return "" + (message.getPayload().intValue() * 2); + } + + public String objectMethod(Integer foo) { + return foo.toString(); + } + + public String voidMethod() { + return "foo"; + } + + } + + MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(new Foo(), (String) null, false); + assertEquals("4", helper.process(new GenericMessage(2L))); + assertEquals("1", helper.process(new GenericMessage(1))); + assertEquals("foo", helper.process(new GenericMessage(new Date()))); + } + + @Test + public void testInt3199GettersAmbiguity() throws Exception { + + class Foo { + + public String getFoo() { + return "foo"; + } + + public String getBar() { + return "foo"; + } + } + + try { + new MessagingMethodInvokerHelper(new Foo(), (String) null, false); + fail("IllegalArgumentException expected"); + } + catch (Exception e) { + assertThat(e, Matchers.instanceOf(IllegalArgumentException.class)); + assertEquals("Found more than one method match for empty parameter for 'payload'", e.getMessage()); + } + } + + @Test + public void testInt3199MessageMethods() throws Exception { + + class Foo { + + public String m1(Message message) { + return message.getPayload(); + } + + public Integer m2(Message message) { + return message.getPayload(); + } + + public Object m3(Message message) { + return message.getPayload(); + } + + } + + Foo targetObject = new Foo(); + + MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false); + assertEquals("foo", helper.process(new GenericMessage("foo"))); + assertEquals(1, helper.process(new GenericMessage(1))); + assertEquals(targetObject, helper.process(new GenericMessage(targetObject))); + } + + @Test + public void testInt3199TypedMethods() throws Exception { + + class Foo { + + public String m1(String payload) { + return payload; + } + + public Integer m2(Integer payload) { + return payload; + } + + public Object m3(Object payload) { + return payload; + } + + } + + Foo targetObject = new Foo(); + + MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false); + assertEquals("foo", helper.process(new GenericMessage("foo"))); + assertEquals(1, helper.process(new GenericMessage(1))); + assertEquals(targetObject, helper.process(new GenericMessage(targetObject))); + } + + @Test + public void testInt3199PrecedenceOfCandidates() throws Exception { + + class Foo { + + public Object m1(Message message) { + fail("This method must not be invoked"); + return message; + } + + public Object m2(String payload) { + return payload; + } + + public Object m3() { + return "FOO"; + } + } + + Foo targetObject = new Foo(); + + MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false); + assertEquals("foo", helper.process(new GenericMessage("foo"))); + assertEquals("FOO", helper.process(new GenericMessage(targetObject))); + } + private static class ExceptionCauseMatcher extends TypeSafeMatcher { private Throwable cause; @@ -528,5 +662,7 @@ public class MethodInvokingMessageProcessorTests { this.lastArg = s; return s; } + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java b/spring-integration-core/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java index cffa393060..92910f5749 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java @@ -749,14 +749,14 @@ public class PayloadAndHeaderMappingTests { this.lastHeaders.put("foo", header); this.lastPayload = payload; } - + public void payloadMapAndHeaderStrings(Map payload, @Header("foo") String header1, @Header("bar") String header2) { this.lastHeaders = new HashMap(); this.lastHeaders.put("foo", header1); this.lastHeaders.put("bar", header2); - this.lastPayload = payload; + this.lastPayload = payload; } - + public void payloadMapAndHeaderMap(Map payload, @Headers Map headers) { this.lastHeaders = headers; this.lastPayload = payload; @@ -771,7 +771,7 @@ public class PayloadAndHeaderMappingTests { this.lastHeaders = headers; this.lastPayload = payload; } - + public void headerPropertiesPayloadMapAndStringHeader(@Headers Properties headers, Map payload, @Header("foo") String header) { this.lastHeaders = headers; this.lastHeaders.put("foo2", header);