INT-3199: Fix Inconsistency for Candidate Methods

JIRA: https://jira.springsource.org/browse/INT-3199

* Set `TypeDescriptor` to `Void.class` as default `targetParameterType` for falling-back
* Change `fall-thru` only to one method:
* Extract generic type from `Massage` parameter, so now
``` m1(Message<String> message);
    m2(String payload);
```
are equal by `payload type` and similar POJO causes ambiguity configuration exception

INT-3199: Polishing and further fixing

* Address PR's comments
* Provide more friendly exception message for `Void.class` method
* Remove the logic around `@ServiceActivator`:
 - one part was fixed within INT-3114
 - another for `RequestReplyExchanger#exchange` doesn't make sense as it is good candidate
* Add fallback to `Iterator.class` method for `Iterable` payloads
* Add tests

Polishing

INT-3199: Add `handlerMessageMethods`

* Introduce separate properties:
 - `handlerMessageMethods` for methods with `Message` parameter
 - `handlerMethod`, if there is only one candidate after configuration
* Polishing several tests according new state of `MessagingMethodInvokerHelper`'s properties

Polishing

Revert `RequestReplyExchanger` fallback

Add `fallbackMessageMethods` processing

INT-3199 Polishing

Only revert to RequestReplyExchanger if there are ambiguous
fallback methods - if the target object is an RRE, it will always
have a candidate fallback (exchange).

Add more RRE tests.

Make ParametersWrapper a public inner class - it's only used
within the context of a process() call. Making it public allows
SpEL to access its properties without reflection.
This commit is contained in:
Artem Bilan
2013-11-08 18:01:54 +02:00
committed by Gary Russell
parent 703b24541f
commit 945cb14407
7 changed files with 570 additions and 155 deletions

View File

@@ -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<T> 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<Class<?>, HandlerMethod> handlerMethods;
private final Map<Class<?>, HandlerMethod> handlerMessageMethods;
private final LinkedList<Map<Class<?>, HandlerMethod>> handlerMethodsList;
private final HandlerMethod handlerMethod;
private final Class<?> expectedType;
private final boolean canProcessMessageList;
@@ -154,11 +166,12 @@ public class MessagingMethodInvokerHelper<T> 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.<Class<?>, 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<T> extends AbstractExpressionEvaluator
this.expectedType = expectedType;
this.targetObject = targetObject;
this.requiresReply = expectedType != null;
this.handlerMethods = this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply);
Map<String, Map<Class<?>, HandlerMethod>> handlerMethodsForTarget =
this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply);
Map<Class<?>, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS);
Map<Class<?>, 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<Map<Class<?>, 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<T> 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<T> 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<T> extends AbstractExpressionEvaluator
}
private T processInternal(ParametersWrapper parameters) throws Exception {
Throwable evaluationException = null;
List<HandlerMethod> 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<Class<?>, HandlerMethod> findHandlerMethodsForTarget(final Object targetObject,
private Map<String, Map<Class<?>, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject,
final Class<? extends Annotation> annotationType, final String methodName, final boolean requiresReply) {
Map<String, Map<Class<?>, HandlerMethod>> handlerMethods = new HashMap<String, Map<Class<?>, HandlerMethod>>();
final Map<Class<?>, HandlerMethod> candidateMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> candidateMessageMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> fallbackMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> fallbackMessageMethods = new HashMap<Class<?>, HandlerMethod>();
final AtomicReference<Class<?>> ambiguousFallbackType = new AtomicReference<Class<?>>();
final AtomicReference<Class<?>> ambiguousFallbackMessageGenericType = new AtomicReference<Class<?>>();
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<T> 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<Method> frameworkMethods = new ArrayList<Method>();
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<T> extends AbstractExpressionEvaluator
}
if (frameworkMethods.size() == 1) {
HandlerMethod handlerMethod = new HandlerMethod(frameworkMethods.get(0), canProcessMessageList);
return Collections.<Class<?>, HandlerMethod>singletonMap(Object.class, handlerMethod);
handlerMethods.put(CANDIDATE_METHODS, Collections.<Class<?>, 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<T> extends AbstractExpressionEvaluator
return targetClass;
}
private List<HandlerMethod> 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<HandlerMethod>(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<Class<?>> candidates = this.handlerMethods.keySet();
Class<?> match = null;
if (candidates != null && !candidates.isEmpty()) {
match = ClassUtils.findClosestMatch(payloadType, candidates, true);
for (Map<Class<?>, HandlerMethod> handlerMethods : handlerMethodsList) {
Set<Class<?>> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> extends AbstractExpressionEvaluator
if (payload != null) {
return payload.getClass();
}
return Collection.class;
return this.messages.getClass();
}
}

View File

@@ -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<Integer> 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);
}

View File

@@ -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));

View File

@@ -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());
}

View File

@@ -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<Object>("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<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("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<String> request) {
return request.getPayload().toUpperCase();
}
};
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
PollableChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
Message<?> test = new GenericMessage<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("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<String> request) {
return request.getPayload().toUpperCase();
}
@SuppressWarnings("unused")
public String bar(Message<String> request) {
return request.getPayload().toUpperCase();
}
};
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
PollableChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
Message<?> test = new GenericMessage<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("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<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("foo");
serviceActivator.handleMessage(test);
assertNotEquals("FOO", outputChannel.receive(10).getPayload());
}
@SuppressWarnings("unused")
private static class SingleAnnotationTestBean {

View File

@@ -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<Number> 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<Object>(2L)));
assertEquals("1", helper.process(new GenericMessage<Object>(1)));
assertEquals("foo", helper.process(new GenericMessage<Object>(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<String> message) {
return message.getPayload();
}
public Integer m2(Message<Integer> 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<Object>("foo")));
assertEquals(1, helper.process(new GenericMessage<Object>(1)));
assertEquals(targetObject, helper.process(new GenericMessage<Object>(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<Object>("foo")));
assertEquals(1, helper.process(new GenericMessage<Object>(1)));
assertEquals(targetObject, helper.process(new GenericMessage<Object>(targetObject)));
}
@Test
public void testInt3199PrecedenceOfCandidates() throws Exception {
class Foo {
public Object m1(Message<String> 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<Object>("foo")));
assertEquals("FOO", helper.process(new GenericMessage<Object>(targetObject)));
}
private static class ExceptionCauseMatcher extends TypeSafeMatcher<Exception> {
private Throwable cause;
@@ -528,5 +662,7 @@ public class MethodInvokingMessageProcessorTests {
this.lastArg = s;
return s;
}
}
}

View File

@@ -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<String, String>();
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);