INT-721 PayloadTypeRouter now considers the weight of the match so that an exact match is no longer required. Now interface and superclass types can also be valid candidates.
This commit is contained in:
@@ -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.
|
||||
@@ -27,8 +27,8 @@ import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.util.ClassUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* An implementation of {@link HandlerMethodResolver} that matches the payload
|
||||
@@ -95,7 +95,7 @@ public class PayloadTypeMatchingHandlerMethodResolver implements HandlerMethodRe
|
||||
if (parameterType instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) parameterType;
|
||||
Type rawType = extractRawTypeIfGeneric(parameterizedType.getRawType());
|
||||
if (rawType instanceof Class) {
|
||||
if (rawType instanceof Class<?>) {
|
||||
Class<?> rawTypeClass = (Class<?>) rawType;
|
||||
if (Message.class.isAssignableFrom(rawTypeClass)) {
|
||||
expectedType = this.determineExpectedTypeFromParameterizedMessageType(parameterizedType);
|
||||
@@ -105,7 +105,7 @@ public class PayloadTypeMatchingHandlerMethodResolver implements HandlerMethodRe
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (parameterType instanceof Class) {
|
||||
else if (parameterType instanceof Class<?>) {
|
||||
expectedType = (Class<?>) parameterType;
|
||||
}
|
||||
Assert.notNull(expectedType, "Failed to determine expected type for parameter ["
|
||||
@@ -124,18 +124,14 @@ public class PayloadTypeMatchingHandlerMethodResolver implements HandlerMethodRe
|
||||
|
||||
private Method findClosestMatch(Class<?> payloadType) {
|
||||
Set<Class<?>> expectedTypes = this.methodMap.keySet();
|
||||
int minTypeDiffWeight = Integer.MAX_VALUE;
|
||||
Class<?> match = ClassUtils.findClosestMatch(payloadType, expectedTypes, true);
|
||||
Method matchingMethod = null;
|
||||
for (Class<?> expectedType : expectedTypes) {
|
||||
int typeDiffWeight = getTypeDifferenceWeight(expectedType, payloadType);
|
||||
if (typeDiffWeight < minTypeDiffWeight) {
|
||||
minTypeDiffWeight = typeDiffWeight;
|
||||
matchingMethod = this.methodMap.get(expectedType);
|
||||
if (match != null) {
|
||||
matchingMethod = this.methodMap.get(match);
|
||||
if (matchingMethod != null) {
|
||||
this.methodMap.put(payloadType, matchingMethod);
|
||||
}
|
||||
}
|
||||
if (matchingMethod != null) {
|
||||
this.methodMap.put(payloadType, matchingMethod);
|
||||
}
|
||||
return matchingMethod;
|
||||
}
|
||||
|
||||
@@ -146,41 +142,16 @@ public class PayloadTypeMatchingHandlerMethodResolver implements HandlerMethodRe
|
||||
WildcardType wildcardType = (WildcardType) actualType;
|
||||
if (wildcardType.getUpperBounds().length == 1) {
|
||||
Type upperBound = wildcardType.getUpperBounds()[0];
|
||||
if (upperBound instanceof Class) {
|
||||
if (upperBound instanceof Class<?>) {
|
||||
expectedType = (Class<?>) upperBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (actualType instanceof Class) {
|
||||
else if (actualType instanceof Class<?>) {
|
||||
expectedType = (Class<?>) actualType;
|
||||
}
|
||||
|
||||
return expectedType;
|
||||
}
|
||||
|
||||
private int getTypeDifferenceWeight(Class<?> expectedType, Class<?> payloadType) {
|
||||
int result = 0;
|
||||
if (!ClassUtils.isAssignable(expectedType, payloadType)) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
Class<?> superClass = payloadType.getSuperclass();
|
||||
while (superClass != null) {
|
||||
if (expectedType.equals(superClass)) {
|
||||
result = result + 2;
|
||||
superClass = null;
|
||||
}
|
||||
else if (ClassUtils.isAssignable(expectedType, superClass)) {
|
||||
result = result + 2;
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
else {
|
||||
superClass = null;
|
||||
}
|
||||
}
|
||||
if (expectedType.isInterface()) {
|
||||
result = result + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -21,6 +21,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.util.ClassUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -42,7 +43,12 @@ public class PayloadTypeRouter extends AbstractSingleChannelRouter {
|
||||
|
||||
@Override
|
||||
protected MessageChannel determineTargetChannel(Message<?> message) {
|
||||
return this.payloadTypeChannelMap.get(message.getPayload().getClass());
|
||||
Class<?> closestMatch = ClassUtils.findClosestMatch(
|
||||
message.getPayload().getClass(), this.payloadTypeChannelMap.keySet(), true);
|
||||
if (closestMatch != null) {
|
||||
return this.payloadTypeChannelMap.get(closestMatch);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.util;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class ClassUtils {
|
||||
|
||||
public static Class<?> findClosestMatch(Class<?> type, Set<Class<?>> candidates, boolean failOnTie) {
|
||||
int minTypeDiffWeight = Integer.MAX_VALUE;
|
||||
Class<?> closestMatch = null;
|
||||
for (Class<?> candidate : candidates) {
|
||||
int typeDiffWeight = getTypeDifferenceWeight(candidate, type);
|
||||
if (typeDiffWeight < minTypeDiffWeight) {
|
||||
minTypeDiffWeight = typeDiffWeight;
|
||||
closestMatch = candidate;
|
||||
}
|
||||
else if (failOnTie && typeDiffWeight < Integer.MAX_VALUE && (typeDiffWeight == minTypeDiffWeight)) {
|
||||
throw new IllegalStateException("Unresolvable ambiguity while attempting to find closest match for [" +
|
||||
type.getName() + "]. Candidate types [" + closestMatch.getName() + "] and [" + candidate.getName() +
|
||||
"] have equal weight.");
|
||||
}
|
||||
}
|
||||
return closestMatch;
|
||||
}
|
||||
|
||||
private static int getTypeDifferenceWeight(Class<?> candidate, Class<?> type) {
|
||||
int result = 0;
|
||||
if (!org.springframework.util.ClassUtils.isAssignable(candidate, type)) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
Class<?> superClass = type.getSuperclass();
|
||||
while (superClass != null) {
|
||||
if (type.equals(superClass)) {
|
||||
result = result + 2;
|
||||
superClass = null;
|
||||
}
|
||||
else if (org.springframework.util.ClassUtils.isAssignable(candidate, superClass)) {
|
||||
result = result + 2;
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
else {
|
||||
superClass = null;
|
||||
}
|
||||
}
|
||||
if (candidate.isInterface()) {
|
||||
result = result + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -18,7 +18,9 @@ package org.springframework.integration.router;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -28,6 +30,7 @@ import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.message.MessageHandlingException;
|
||||
import org.springframework.integration.message.StringMessage;
|
||||
|
||||
/**
|
||||
@@ -36,7 +39,7 @@ import org.springframework.integration.message.StringMessage;
|
||||
public class PayloadTypeRouterTests {
|
||||
|
||||
@Test
|
||||
public void resolveByPayloadType() {
|
||||
public void resolveExactMatch() {
|
||||
QueueChannel stringChannel = new QueueChannel();
|
||||
QueueChannel integerChannel = new QueueChannel();
|
||||
Map<Class<?>, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap<Class<?>, MessageChannel>();
|
||||
@@ -52,6 +55,136 @@ public class PayloadTypeRouterTests {
|
||||
assertEquals(integerChannel, result2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveSubclass() {
|
||||
QueueChannel defaultChannel = new QueueChannel();
|
||||
defaultChannel.setBeanName("defaultChannel");
|
||||
QueueChannel numberChannel = new QueueChannel();
|
||||
numberChannel.setBeanName("numberChannel");
|
||||
Map<Class<?>, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap<Class<?>, MessageChannel>();
|
||||
payloadTypeChannelMap.put(Number.class, numberChannel);
|
||||
PayloadTypeRouter router = new PayloadTypeRouter();
|
||||
router.setPayloadTypeChannelMap(payloadTypeChannelMap);
|
||||
router.setDefaultOutputChannel(defaultChannel);
|
||||
Message<Integer> message = new GenericMessage<Integer>(99);
|
||||
router.handleMessage(message);
|
||||
Message<?> result = numberChannel.receive(0);
|
||||
assertNotNull(result);
|
||||
assertEquals(99, result.getPayload());
|
||||
assertNull(defaultChannel.receive(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exactMatchFavoredOverSuperClass() {
|
||||
QueueChannel defaultChannel = new QueueChannel();
|
||||
defaultChannel.setBeanName("defaultChannel");
|
||||
QueueChannel numberChannel = new QueueChannel();
|
||||
numberChannel.setBeanName("numberChannel");
|
||||
QueueChannel integerChannel = new QueueChannel();
|
||||
integerChannel.setBeanName("integerChannel");
|
||||
Map<Class<?>, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap<Class<?>, MessageChannel>();
|
||||
payloadTypeChannelMap.put(Number.class, numberChannel);
|
||||
payloadTypeChannelMap.put(Integer.class, integerChannel);
|
||||
PayloadTypeRouter router = new PayloadTypeRouter();
|
||||
router.setPayloadTypeChannelMap(payloadTypeChannelMap);
|
||||
router.setDefaultOutputChannel(defaultChannel);
|
||||
Message<Integer> message = new GenericMessage<Integer>(99);
|
||||
router.handleMessage(message);
|
||||
Message<?> result = integerChannel.receive(0);
|
||||
assertNotNull(result);
|
||||
assertEquals(99, result.getPayload());
|
||||
assertNull(numberChannel.receive(0));
|
||||
assertNull(defaultChannel.receive(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interfaceMatch() {
|
||||
QueueChannel defaultChannel = new QueueChannel();
|
||||
defaultChannel.setBeanName("defaultChannel");
|
||||
QueueChannel comparableChannel = new QueueChannel();
|
||||
comparableChannel.setBeanName("comparableChannel");
|
||||
Map<Class<?>, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap<Class<?>, MessageChannel>();
|
||||
payloadTypeChannelMap.put(Comparable.class, comparableChannel);
|
||||
PayloadTypeRouter router = new PayloadTypeRouter();
|
||||
router.setPayloadTypeChannelMap(payloadTypeChannelMap);
|
||||
router.setDefaultOutputChannel(defaultChannel);
|
||||
Message<Integer> message = new GenericMessage<Integer>(99);
|
||||
router.handleMessage(message);
|
||||
Message<?> result = comparableChannel.receive(0);
|
||||
assertNotNull(result);
|
||||
assertEquals(99, result.getPayload());
|
||||
assertNull(defaultChannel.receive(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directInterfaceFavoredOverSuperClass() {
|
||||
QueueChannel defaultChannel = new QueueChannel();
|
||||
defaultChannel.setBeanName("defaultChannel");
|
||||
QueueChannel numberChannel = new QueueChannel();
|
||||
numberChannel.setBeanName("numberChannel");
|
||||
QueueChannel comparableChannel = new QueueChannel();
|
||||
comparableChannel.setBeanName("comparableChannel");
|
||||
Map<Class<?>, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap<Class<?>, MessageChannel>();
|
||||
payloadTypeChannelMap.put(Number.class, numberChannel);
|
||||
payloadTypeChannelMap.put(Comparable.class, comparableChannel);
|
||||
PayloadTypeRouter router = new PayloadTypeRouter();
|
||||
router.setPayloadTypeChannelMap(payloadTypeChannelMap);
|
||||
router.setDefaultOutputChannel(defaultChannel);
|
||||
Message<Integer> message = new GenericMessage<Integer>(99);
|
||||
router.handleMessage(message);
|
||||
Message<?> result = comparableChannel.receive(0);
|
||||
assertNotNull(result);
|
||||
assertEquals(99, result.getPayload());
|
||||
assertNull(numberChannel.receive(0));
|
||||
assertNull(defaultChannel.receive(0));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void ambiguityFailure() throws Throwable {
|
||||
QueueChannel defaultChannel = new QueueChannel();
|
||||
defaultChannel.setBeanName("defaultChannel");
|
||||
QueueChannel serializableChannel = new QueueChannel();
|
||||
serializableChannel.setBeanName("serializableChannel");
|
||||
QueueChannel comparableChannel = new QueueChannel();
|
||||
comparableChannel.setBeanName("comparableChannel");
|
||||
Map<Class<?>, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap<Class<?>, MessageChannel>();
|
||||
payloadTypeChannelMap.put(Serializable.class, serializableChannel);
|
||||
payloadTypeChannelMap.put(Comparable.class, comparableChannel);
|
||||
PayloadTypeRouter router = new PayloadTypeRouter();
|
||||
router.setPayloadTypeChannelMap(payloadTypeChannelMap);
|
||||
router.setDefaultOutputChannel(defaultChannel);
|
||||
Message<String> message = new GenericMessage<String>("test");
|
||||
try {
|
||||
router.handleMessage(message);
|
||||
}
|
||||
catch (MessageHandlingException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void superClassFavoredOverIndirectInterface() {
|
||||
QueueChannel defaultChannel = new QueueChannel();
|
||||
defaultChannel.setBeanName("defaultChannel");
|
||||
QueueChannel numberChannel = new QueueChannel();
|
||||
numberChannel.setBeanName("numberChannel");
|
||||
QueueChannel serializableChannel = new QueueChannel();
|
||||
serializableChannel.setBeanName("serializableChannel");
|
||||
Map<Class<?>, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap<Class<?>, MessageChannel>();
|
||||
payloadTypeChannelMap.put(Number.class, numberChannel);
|
||||
payloadTypeChannelMap.put(Serializable.class, serializableChannel);
|
||||
PayloadTypeRouter router = new PayloadTypeRouter();
|
||||
router.setPayloadTypeChannelMap(payloadTypeChannelMap);
|
||||
router.setDefaultOutputChannel(defaultChannel);
|
||||
Message<Integer> message = new GenericMessage<Integer>(99);
|
||||
router.handleMessage(message);
|
||||
Message<?> result = numberChannel.receive(0);
|
||||
assertNotNull(result);
|
||||
assertEquals(99, result.getPayload());
|
||||
assertNull(serializableChannel.receive(0));
|
||||
assertNull(defaultChannel.receive(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveByPayloadTypeWithRouterEndpoint() {
|
||||
QueueChannel stringChannel = new QueueChannel();
|
||||
|
||||
@@ -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.
|
||||
@@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
@@ -40,26 +41,26 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class PayloadTypeRouterParserTests {
|
||||
|
||||
@Autowired
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private TestService testService;
|
||||
|
||||
@Test
|
||||
public void testPayloadTypeRouter() {
|
||||
context.start();
|
||||
MessageBuilder channel1MessageBuilder = MessageBuilder.withPayload("Hello");
|
||||
Message message1 = channel1MessageBuilder.build();
|
||||
MessageBuilder channel2MessageBuilder = MessageBuilder.withPayload(25);
|
||||
Message message2 = channel2MessageBuilder.build();
|
||||
Message<?> message1 = MessageBuilder.withPayload("Hello").build();
|
||||
Message<?> message2 = MessageBuilder.withPayload(25).build();
|
||||
testService.foo(message1);
|
||||
testService.foo(message2);
|
||||
PollableChannel chanel1 = (PollableChannel) context.getBean("channel1");
|
||||
PollableChannel chanel2 = (PollableChannel) context.getBean("channel2");
|
||||
assertTrue(chanel1.receive().getPayload() instanceof String);
|
||||
assertTrue(chanel2.receive().getPayload() instanceof Integer);
|
||||
assertTrue(chanel1.receive(0).getPayload() instanceof String);
|
||||
assertTrue(chanel2.receive(0).getPayload() instanceof Integer);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=BeanDefinitionStoreException.class)
|
||||
public void testFakeTypes(){
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(routerConfigFakeType.getBytes());
|
||||
@@ -68,7 +69,7 @@ public class PayloadTypeRouterParserTests {
|
||||
reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
|
||||
reader.loadBeanDefinitions(new InputStreamResource(stream));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=BeanDefinitionStoreException.class)
|
||||
public void testNoMappingElement(){
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(routerConfigNoMaping.getBytes());
|
||||
@@ -104,8 +105,9 @@ public class PayloadTypeRouterParserTests {
|
||||
" <payload-type-router input-channel=\"routingChannel\"/>" +
|
||||
"</beans:beans>";
|
||||
|
||||
|
||||
|
||||
public static interface TestService{
|
||||
public void foo(Message message);
|
||||
public void foo(Message<?> message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user