From 7c6e207adcb22014a6060510b0ba38aa612c490e Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Tue, 22 Dec 2009 12:51:45 +0000 Subject: [PATCH] INT-828, INT-829 Message mapping and method resolution are now both managed with SpEL support. INT-925 is resolved by this commit as well, since the EvaluationContext is reused for all invocations within a handler. This also appears to make the problem in INT-915 obsolete, and it lays the groundwork for INT-174 at the message-handling level. --- .../MethodInvokingMessageProcessor.java | 477 ++++++++++++------ .../annotation/AggregatorAnnotationTests.java | 19 +- .../config/xml/PNamespaceTest.java | 10 +- .../MethodInvokingMessageProcessorTests.java | 16 +- .../message/PayloadAndHeaderMappingTests.java | 115 ++--- .../MethodInvokingTransformerTests.java | 9 +- 6 files changed, 394 insertions(+), 252 deletions(-) diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java index 82d834c4b2..722c20734e 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java @@ -17,215 +17,386 @@ package org.springframework.integration.handler; import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +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.ConversionService; +import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +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.annotation.Header; +import org.springframework.integration.annotation.Headers; +import org.springframework.integration.annotation.Payload; import org.springframework.integration.core.Message; +import org.springframework.integration.core.MessagingException; import org.springframework.integration.message.MessageHandlingException; -import org.springframework.integration.util.DefaultMethodInvoker; -import org.springframework.integration.util.MethodInvoker; +import org.springframework.integration.util.ClassUtils; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; +import org.springframework.util.ReflectionUtils.MethodCallback; /** - * A base or helper class for any Messaging component that acts as an adapter - * by invoking a "plain" (not Message-aware) method on a given target object. - * The target Object is mandatory, and either a {@link Method} reference, a - * 'methodName', or an Annotation type must be provided. - * * @author Mark Fisher - * @author Marius Bogoevici - * @see ArgumentArrayMessageMapper + * @since 2.0 */ public class MethodInvokingMessageProcessor implements MessageProcessor { - protected static final Log logger = LogFactory.getLog(MethodInvokingMessageProcessor.class); + private final Log logger = LogFactory.getLog(this.getClass()); - private volatile Object object; + private final Object targetObject; - private final HandlerMethodResolver methodResolver; + private volatile boolean requiresReply; - private final ConcurrentMap messageMappers = - new ConcurrentHashMap(); + private final Map, HandlerMethod> handlerMethods; - private final Map invokers = new HashMap(); - - private final Set methodsExpectingMessage = new HashSet(); + private final EvaluationContext evaluationContext; - public MethodInvokingMessageProcessor(Object object, Method method) { - Assert.notNull(object, "object must not be null"); + public MethodInvokingMessageProcessor(Object targetObject, Method method) { + this(targetObject, null, method); + } + + public MethodInvokingMessageProcessor(Object targetObject, String methodName) { + this(targetObject, null, methodName); + } + + public MethodInvokingMessageProcessor(Object targetObject, String methodName, boolean requiresReply) { + this(targetObject, null, methodName, requiresReply); + } + + public MethodInvokingMessageProcessor(Object targetObject, Class annotationType) { + this(targetObject, annotationType, (String) null); + } + + + /* + * Private constructors for internal use + */ + + private MethodInvokingMessageProcessor(Object targetObject, Class annotationType, Method method) { Assert.notNull(method, "method must not be null"); - this.object = object; - this.methodResolver = new StaticHandlerMethodResolver(method); + HandlerMethod handlerMethod = new HandlerMethod(method); + this.targetObject = targetObject; + this.handlerMethods = Collections., HandlerMethod>singletonMap(handlerMethod.getTargetParameterType(), handlerMethod); + this.evaluationContext = this.createEvaluationContext(targetObject); } - public MethodInvokingMessageProcessor(Object object, Class annotationType) { - Assert.notNull(object, "object must not be null"); - Assert.notNull(annotationType, "annotation type must not be null"); - this.object = object; - this.methodResolver = this.createResolverForAnnotation(annotationType); + private MethodInvokingMessageProcessor(Object targetObject, Class annotationType, String methodName) { + this(targetObject, annotationType, methodName, false); } - public MethodInvokingMessageProcessor(Object object, String methodName) { - this(object, methodName, false); - } - - public MethodInvokingMessageProcessor(Object object, String methodName, boolean requiresReturnValue) { - Assert.notNull(object, "object must not be null"); - Assert.notNull(methodName, "methodName must not be null"); - this.object = object; - this.methodResolver = this.createResolverForMethodName(methodName, requiresReturnValue); + private MethodInvokingMessageProcessor(Object targetObject, Class annotationType, String methodName, boolean requiresReply) { + this.targetObject = targetObject; + this.requiresReply = requiresReply; + this.handlerMethods = this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply); + this.evaluationContext = this.createEvaluationContext(targetObject); } + + private EvaluationContext createEvaluationContext(Object targetObject) { + StandardEvaluationContext context = new StandardEvaluationContext(); + // TODO: Add a filtering MethodResolver (may soon be supported by SpEL) to enable: + // 1) exclusion of void-returning methods if requiresReply is true + // 2) limiting to annotated methods if at least one is present + context.addPropertyAccessor(new MapAccessor()); + // TODO: Enable configuration of an integration ConversionService bean to be used here, + // but then fallback to this same default if no such bean has been defined. + ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); + context.setTypeConverter(new StandardTypeConverter(conversionService)); + context.setVariable("target", targetObject); + return context; + } + + public Object processMessage(Message message) { - Assert.notNull(message, "message must not be null"); - if (message.getPayload() == null) { - if (logger.isDebugEnabled()) { - logger.debug("received null payload"); + EvaluationException evaluationException = null; + List candidates = this.findHandlerMethodsForMessage(message); + for (HandlerMethod candidate : candidates) { + try { + Object result = candidate.getExpression().getValue(this.evaluationContext, message); + if (this.requiresReply) { + // TODO: remove this if SpEL is modified to throw an EvaluationException instead + // e.g. we can invoke getValue(this.evaluationContext, message, candidate.getReturnType); + Assert.notNull(result, "Expression evaluation result was null, but this processor requires a reply."); + } + return result; + } + catch (EvaluationException e) { + if (evaluationException == null) { + // keep the first exception + evaluationException = e; + } } - return null; } - Method method = this.methodResolver.resolveHandlerMethod(message); - Object[] args = null; - try { - if (this.methodsExpectingMessage.contains(method)) { - args = new Object[] { message }; - } - else { - args = this.resolveMessageMapper(method).fromMessage(message); - } - return this.invokeMethod(method, args, message); - } - catch (InvocationTargetException e) { - if (e.getCause() != null && e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); - } - throw new MessageHandlingException(message, - "method '" + method + "' threw an Exception.", e.getCause()); - } - catch (Exception e) { - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } - throw new MessageHandlingException(message, "Failed to invoke method '" - + method + "' with arguments: " + ObjectUtils.nullSafeToString(args), e); + if (evaluationException == null) { + throw new MessageHandlingException(message, "Failed to find a suitable Message-handling " + + "method on target of type [" + targetObject.getClass() + "]."); } + throw new MessageHandlingException(message, "Failed to process Message.", evaluationException); } - private Object invokeMethod(Method method, Object[] args, Message message) throws Exception { - Object result = null; - MethodInvoker invoker = null; - try { - invoker = this.invokers.get(method); - if (invoker == null) { - invoker = new DefaultMethodInvoker(this.object, method); - this.invokers.put(method, invoker); - } - try { - result = invoker.invokeMethod(args); - } - catch (NoSuchMethodException e) { - // fallback to replace the payload argument with headers if possible - boolean foundFallbackCandidate = false; - for (int i = 0; i < args.length; i++) { - if (message != null && message.getPayload().equals(args[i]) - && Map.class.isAssignableFrom(method.getParameterTypes()[i])) { - if (foundFallbackCandidate) { - // more than one, throw an Exception - throw new MessageHandlingException(message, "Failed to resolve ambiguity " + - "amongst multiple non-annotated candidates for matching Message headers.", e); - } - args[i] = message.getHeaders(); - foundFallbackCandidate = true; + 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>(); + ReflectionUtils.doWithMethods(targetObject.getClass(), 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); } } - if (foundFallbackCandidate) { - result = invoker.invokeMethod(args); + 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); } } + }); + if (!candidateMethods.isEmpty()) { + return candidateMethods; } - catch (IllegalArgumentException e) { - try { - if (message != null) { - result = invoker.invokeMethod(message); - this.methodsExpectingMessage.add(method); + Assert.notEmpty(fallbackMethods, "Target object [" + this.targetObject + + "] has no eligible methods for handling Messages."); + Assert.isNull(ambiguousFallbackType.get(), + "Found more than one method match for type [" + ambiguousFallbackType + "]"); + return fallbackMethods; + } + + 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); + } + 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; + String headerName = this.determineHeaderName(headerAnnotation, new MethodParameter(method, i)); + String headerExpression = "headers." + headerName; + if (headerAnnotation.required()) { + sb.append(headerExpression); + } + else { + sb.append("headers[" + headerName + "] != null ? " + headerExpression + " : null"); + } + } + } + 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); } } - catch (NoSuchMethodException e2) { - throw new MessageHandlingException(message, "unable to resolve method for args: " - + StringUtils.arrayToCommaDelimitedString(args)); + 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()); } - return result; - } - private HandlerMethodResolver createResolverForMethodName(String methodName, boolean requiresReturnValue) { - List methodsWithName = new ArrayList(); - Method[] defaultCandidateMethods = HandlerMethodUtils.getCandidateHandlerMethods(this.object); - for (Method method : defaultCandidateMethods) { - if (method.getName().equals(methodName) - && (!requiresReturnValue || !Void.TYPE.equals(method.getReturnType()))) { - methodsWithName.add(method); + 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; } - Assert.notEmpty(methodsWithName, "Failed to find any valid Message-handling methods named '" - + methodName + "' on target class [" + this.object.getClass() + "]."); - if (methodsWithName.size() == 1) { - return new StaticHandlerMethodResolver(methodsWithName.get(0)); - } - return new PayloadTypeMatchingHandlerMethodResolver(methodsWithName.toArray(new Method[methodsWithName.size()])); - } - private HandlerMethodResolver createResolverForAnnotation(Class annotationType) { - List methodsWithAnnotation = new ArrayList(); - Method[] defaultCandidateMethods = HandlerMethodUtils.getCandidateHandlerMethods(this.object); - for (Method method : defaultCandidateMethods) { - Annotation annotation = AnnotationUtils.getAnnotation(method, annotationType); - if (annotation != null) { - methodsWithAnnotation.add(method); - } + private String determineHeaderName(Header headerAnnotation, MethodParameter methodParameter) { + methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); + String valueAttribute = headerAnnotation.value(); + String headerName = StringUtils.hasText(valueAttribute) ? valueAttribute : methodParameter.getParameterName(); + Assert.notNull(headerName, "Cannot determine header name. Possible reasons: -debug is " + + "disabled or header name is not explicitly provided via @Header annotation."); + return headerName; } - Method[] candidateMethods = (methodsWithAnnotation.size() == 0) ? null - : methodsWithAnnotation.toArray(new Method[methodsWithAnnotation.size()]); - if (candidateMethods == null) { - if (logger.isInfoEnabled()) { - logger.info("Failed to find any valid Message-handling methods with annotation [" - + annotationType + "] on target class [" + this.object.getClass() + "]. " - + "Method-resolution will be applied to all eligible methods."); - } - candidateMethods = defaultCandidateMethods; - } - if (candidateMethods.length == 1) { - return new StaticHandlerMethodResolver(candidateMethods[0]); - } - return new PayloadTypeMatchingHandlerMethodResolver(candidateMethods); - } - private ArgumentArrayMessageMapper resolveMessageMapper(Method method) { - ArgumentArrayMessageMapper mapper = this.messageMappers.get(method); - if (mapper == null) { - mapper = new ArgumentArrayMessageMapper(method); - ArgumentArrayMessageMapper existingMapper = this.messageMappers.putIfAbsent(method, mapper); - if (existingMapper != null) { - // throw away the one just created, since one was created in the meantime - mapper = existingMapper; - } + private synchronized void setExclusiveTargetParameterType(Class targetParameterType) { + Assert.isNull(this.targetParameterType, "Found more than one parameter type candidate: [" + + this.targetParameterType + "] and [" + targetParameterType + "]"); + this.targetParameterType = targetParameterType; } - return mapper; } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java index 10391a3af1..fb8a8ef612 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * 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. @@ -24,6 +24,7 @@ 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; @@ -33,13 +34,11 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.aggregator.AbstractMessageAggregator; import org.springframework.integration.aggregator.CompletionStrategyAdapter; -import org.springframework.integration.aggregator.SequenceSizeCompletionStrategy; import org.springframework.integration.aggregator.CorrelationStrategyAdapter; +import org.springframework.integration.aggregator.SequenceSizeCompletionStrategy; import org.springframework.integration.channel.BeanFactoryChannelResolver; import org.springframework.integration.channel.ChannelResolver; import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.handler.HandlerMethodResolver; -import org.springframework.integration.handler.StaticHandlerMethodResolver; /** * @author Marius Bogoevici @@ -112,14 +111,14 @@ public class AggregatorAnnotationTests { Object correlationStrategy = getPropertyValue(aggregator, "correlationStrategy"); Assert.assertTrue(correlationStrategy instanceof CorrelationStrategyAdapter); CorrelationStrategyAdapter completionStrategyAdapter = (CorrelationStrategyAdapter) correlationStrategy; - DirectFieldAccessor invokerAccessor = new DirectFieldAccessor( + DirectFieldAccessor processorAccessor = new DirectFieldAccessor( new DirectFieldAccessor(completionStrategyAdapter).getPropertyValue("processor")); - Object targetObject = invokerAccessor.getPropertyValue("object"); + Object targetObject = processorAccessor.getPropertyValue("targetObject"); assertSame(context.getBean(endpointName), targetObject); - HandlerMethodResolver completionCheckerMethodResolver = (HandlerMethodResolver) invokerAccessor.getPropertyValue("methodResolver"); - assertTrue(completionCheckerMethodResolver instanceof StaticHandlerMethodResolver); - DirectFieldAccessor resolverAccessor = new DirectFieldAccessor(completionCheckerMethodResolver); - Method completionCheckerMethod = (Method) resolverAccessor.getPropertyValue("method"); + Map handlerMethods = (Map) processorAccessor.getPropertyValue("handlerMethods"); + assertEquals(1, handlerMethods.size()); + DirectFieldAccessor handlerMethodAccessor = new DirectFieldAccessor(handlerMethods.values().iterator().next()); + Method completionCheckerMethod = (Method) handlerMethodAccessor.getPropertyValue("method"); assertEquals("correlate", completionCheckerMethod.getName()); } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/PNamespaceTest.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/PNamespaceTest.java index f2035c39de..7e57e1ba94 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/PNamespaceTest.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/PNamespaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * 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. @@ -19,6 +19,7 @@ import junit.framework.Assert; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -30,7 +31,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * Validates the "p:namespace" is working for inner "bean" definition within SI components. * * @author Oleg Zhurakousky - * */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) @@ -81,9 +81,9 @@ public class PNamespaceTest { DirectFieldAccessor saAccessor = new DirectFieldAccessor(serviceActivator); Object handler = saAccessor.getPropertyValue("handler"); DirectFieldAccessor hAccessor = new DirectFieldAccessor(handler); - Object invoker = hAccessor.getPropertyValue("processor"); - DirectFieldAccessor iAccessor = new DirectFieldAccessor(invoker); - return (TestBean) iAccessor.getPropertyValue("object"); + Object processor = hAccessor.getPropertyValue("processor"); + DirectFieldAccessor pAccessor = new DirectFieldAccessor(processor); + return (TestBean) pAccessor.getPropertyValue("targetObject"); } public interface InboundGateway{ diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java index b7f979cc4f..42a0d5fee8 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java @@ -17,6 +17,7 @@ package org.springframework.integration.handler; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; @@ -30,6 +31,7 @@ import org.springframework.integration.annotation.Header; import org.springframework.integration.core.Message; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.message.MessageBuilder; +import org.springframework.integration.message.MessageHandlingException; import org.springframework.integration.message.StringMessage; /** @@ -105,14 +107,17 @@ public class MethodInvokingMessageProcessorTests { @Test public void testVoidMethodsExcludedByFlag() { + Exception exception = null; MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(), "testVoidReturningMethods", true); assertEquals(12, processor.processMessage(MessageBuilder.withPayload(12).build())); try { - assertNull(processor.processMessage(MessageBuilder.withPayload("Something").build())); + processor.processMessage(MessageBuilder.withPayload("Something").build()); fail(); - } catch(IllegalArgumentException ex){ - } + catch(IllegalArgumentException ex) { + exception = ex; + } + assertNotNull(exception); } @Test @@ -142,13 +147,12 @@ public class MethodInvokingMessageProcessorTests { assertEquals(new Integer(456), result); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = MessageHandlingException.class) public void conversionFailureWithAnnotatedMethod() throws Exception { AnnotatedTestService service = new AnnotatedTestService(); Method method = service.getClass().getMethod("integerMethod", Integer.class); MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method); - Object result = processor.processMessage(new StringMessage("foo")); - assertEquals(new Integer(123), result); + processor.processMessage(new StringMessage("foo")); } @Test diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java index 96bc80a089..3755823600 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java @@ -433,31 +433,9 @@ public class PayloadAndHeaderMappingTests { //assertFalse(bean.lastHeaders.containsKey("baz")); } - @Test - public void twoMapsNoAnnotationsWithStringPayload() throws Exception { - MessageHandler handler = this.getHandler("twoMapsNoAnnotations", Map.class, Map.class); - Map headers = new HashMap(); - headers.put("foo", "1"); - headers.put("bar", "2"); - Message message = MessageBuilder.withPayload("test").copyHeaders(headers).build(); - handler.handleMessage(message); - assertNull(bean.lastPayload); - assertEquals("1", bean.lastHeaders.get("foo")); - assertEquals("2", bean.lastHeaders.get("bar")); - assertEquals("1", bean.lastHeaders.get("foo2")); - assertEquals("2", bean.lastHeaders.get("bar2")); - } - - @Test(expected = MessageHandlingException.class) - public void twoMapsNoAnnotationsWithMapPayload() throws Exception { - MessageHandler handler = this.getHandler("twoMapsNoAnnotations", Map.class, Map.class); - Map headers = new HashMap(); - headers.put("foo", "1"); - headers.put("bar", "2"); - Map payloadMap = new HashMap(); - payloadMap.put("baz", "99"); - Message message = MessageBuilder.withPayload(payloadMap).copyHeaders(headers).build(); - handler.handleMessage(message); + @Test(expected = IllegalArgumentException.class) + public void twoMapsNoAnnotations() throws Exception { + this.getHandler("twoMapsNoAnnotations", Map.class, Map.class); } @Test @@ -475,7 +453,6 @@ public class PayloadAndHeaderMappingTests { assertEquals("2", bean.lastHeaders.get("bar2")); } - //@Test(expected = MessageHandlingException.class) @Test public void twoMapsWithAnnotationsWithMapPayload() throws Exception { MessageHandler handler = this.getHandler("twoMapsWithAnnotations", Map.class, Map.class); @@ -493,39 +470,15 @@ public class PayloadAndHeaderMappingTests { assertEquals(null, bean.lastHeaders.get("baz")); } - @Test + @Test(expected = IllegalArgumentException.class) public void twoMapsNoAnnotationsAndObject() throws Exception { - MessageHandler handler = this.getHandler("twoMapsNoAnnotationsAndObject", - Map.class, Object.class, Map.class); - Map headers = new HashMap(); - headers.put("foo", "1"); - headers.put("bar", "2"); - Message message = MessageBuilder.withPayload("test").copyHeaders(headers).build(); - handler.handleMessage(message); - assertEquals("test", bean.lastPayload); - assertEquals("1", bean.lastHeaders.get("foo")); - assertEquals("2", bean.lastHeaders.get("bar")); - assertEquals("1", bean.lastHeaders.get("foo2")); - assertEquals("2", bean.lastHeaders.get("bar2")); - } - - @Test(expected = MessageHandlingException.class) - public void twoMapsNoAnnotationsAndObjectWithMapPayload() throws Exception { - MessageHandler handler = this.getHandler("twoMapsNoAnnotationsAndObject", - Map.class, Object.class, Map.class); - Map payloadMap = new HashMap(); - Map headers = new HashMap(); - headers.put("foo", "1"); - headers.put("bar", "2"); - Message message = MessageBuilder.withPayload(payloadMap) - .copyHeaders(headers).build(); - handler.handleMessage(message); + this.getHandler("twoMapsNoAnnotationsAndObject", Map.class, Object.class, Map.class); } @Test - public void twoMapsNoAnnotationsAndAnnotatedString() throws Exception { - MessageHandler handler = this.getHandler("twoMapsNoAnnotationsAndAnnotatedString", - Map.class, Map.class, String.class); + public void mapAndAnnotatedStringHeaderWithStringPayload() throws Exception { + MessageHandler handler = this.getHandler( + "mapAndAnnotatedStringHeaderExpectingMapAsHeaders", Map.class, String.class); Map headers = new HashMap(); headers.put("foo", "1"); headers.put("bar", "2"); @@ -536,8 +489,24 @@ public class PayloadAndHeaderMappingTests { assertEquals("1", bean.lastHeaders.get("foo")); assertEquals("2", bean.lastHeaders.get("bar")); assertEquals("1", bean.lastHeaders.get("foo2")); - assertEquals("2", bean.lastHeaders.get("bar2")); - assertEquals("1", bean.lastHeaders.get("foo3")); + } + + @Test + public void mapAndAnnotatedStringHeaderWithMapPayload() throws Exception { + MessageHandler handler = this.getHandler( + "mapAndAnnotatedStringHeaderExpectingMapAsPayload", Map.class, String.class); + Map payload = new HashMap(); + payload.put("test", "0"); + Map headers = new HashMap(); + headers.put("foo", "1"); + headers.put("bar", "2"); + Message message = MessageBuilder.withPayload(payload) + .copyHeaders(headers).build(); + handler.handleMessage(message); + assertNotNull(bean.lastPayload); + assertEquals(payload, bean.lastPayload); + assertEquals("1", bean.lastHeaders.get("foo")); + assertNull(bean.lastHeaders.get("bar")); } @Test @@ -690,7 +659,7 @@ public class PayloadAndHeaderMappingTests { } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "unused"}) private static class TestBean { private volatile Map lastHeaders; @@ -708,6 +677,11 @@ public class PayloadAndHeaderMappingTests { this.lastPayload = payload; } + public void stringPayloadAndHeaderMap(String payload, Map headers) { + this.lastHeaders = headers; + this.lastPayload = payload; + } + public void headerMapAndObjectPayload(Map headers, Object payload) { this.lastHeaders = headers; this.lastPayload = payload; @@ -789,10 +763,7 @@ public class PayloadAndHeaderMappingTests { } public void twoMapsNoAnnotations(Map map1, Map map2) { - this.lastHeaders = new HashMap(map1); - for (Map.Entry entry : map2.entrySet()) { - this.lastHeaders.put(entry.getKey() + "2", entry.getValue()); - } + // invalid due to ambiguity (no @Payload or @Headers) } public void twoMapsWithAnnotations(@Headers Map map1, @Headers Map map2) { @@ -811,19 +782,17 @@ public class PayloadAndHeaderMappingTests { } public void twoMapsNoAnnotationsAndObject(Map map1, Object o, Map map2) { - this.lastPayload = o; - this.lastHeaders = new HashMap(map1); - for (Map.Entry entry : map2.entrySet()) { - this.lastHeaders.put(entry.getKey() + "2", entry.getValue()); - } + // invalid due to ambiguity of Map parameters (no @Payload or @Headers) } - public void twoMapsNoAnnotationsAndAnnotatedString(Map map1, Map map2, @Header("foo") String s) { - this.lastHeaders = new HashMap(map1); - for (Map.Entry entry : map2.entrySet()) { - this.lastHeaders.put(entry.getKey() + "2", entry.getValue()); - } - this.lastHeaders.put("foo3", s); + public void mapAndAnnotatedStringHeaderExpectingMapAsHeaders(Map map, @Header("foo") String s) { + this.lastHeaders = new HashMap(map); + this.lastHeaders.put("foo2", s); + } + + public void mapAndAnnotatedStringHeaderExpectingMapAsPayload(Map map, @Header("foo") String s) { + this.lastPayload = map; + this.lastHeaders = Collections.singletonMap("foo", s); } public void singleStringHeaderOnly(@Header("foo") String s) { diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MethodInvokingTransformerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MethodInvokingTransformerTests.java index 5519cf6f9a..2142d5b863 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MethodInvokingTransformerTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MethodInvokingTransformerTests.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import java.lang.reflect.Method; -import java.util.Date; import java.util.Properties; import org.junit.Test; @@ -76,20 +75,20 @@ public class MethodInvokingTransformerTests { assertEquals("123!", result.getPayload()); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = MessageHandlingException.class) public void typeConversionFailureConfiguredWithMethodReference() throws Exception { TestBean testBean = new TestBean(); Method testMethod = testBean.getClass().getMethod("exclaim", String.class); MethodInvokingTransformer transformer = new MethodInvokingTransformer(testBean, testMethod); - Message message = new GenericMessage(new Date()); + Message message = new GenericMessage(new TestBean()); transformer.transform(message); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = MessageHandlingException.class) public void typeConversionFailureConfiguredWithMethodName() throws Exception { TestBean testBean = new TestBean(); MethodInvokingTransformer transformer = new MethodInvokingTransformer(testBean, "exclaim"); - Message message = new GenericMessage(new Date()); + Message message = new GenericMessage(new TestBean()); transformer.transform(message); }