INT-2955 Fix Collection/Map Conversion
The fix for INT-2650 (to avoid unnecessary array copying) was too general in that it also prevented types in collections and maps from being converted. Add tests to illustrate that such payloads are not converted. Change code to only short circuit the conversion process if the payload is a primitive array. An existing test ensures that arrays are not copied. Add more tests to complete coverage - discovered another bug - the early exit after using a property editor to convert to a String was never taken - it was testing against the Class of the TypeConverter instead of the Type. If the target type is a String, we don't need to perform conversion after the property editor has done its conversion. Fix MessageHistory test (was testing MessageHeaders).
This commit is contained in:
committed by
Mark Fisher
parent
e003891040
commit
7ab1361231
@@ -27,6 +27,9 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -106,10 +109,15 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware
|
||||
* INT-2630 Spring 3.1 now converts ALL arguments; we know we don't need to convert MessageHeaders
|
||||
* or MessageHistory; the MapToMap converter requires a no-arg constructor.
|
||||
* Also INT-2650 - don't convert large byte[]
|
||||
* This reverts the effective logic to Spring 3.0.
|
||||
*/
|
||||
if (sourceType != null && sourceType.isAssignableTo(targetType)) {
|
||||
return value;
|
||||
if (sourceType != null) {
|
||||
Class<?> sourceClass = sourceType.getType();
|
||||
Class<?> targetClass = targetType.getType();
|
||||
if ((sourceClass == MessageHeaders.class && targetClass == MessageHeaders.class) ||
|
||||
(sourceClass == MessageHistory.class && targetClass == MessageHistory.class) ||
|
||||
(sourceType.isAssignableTo(targetType) && ClassUtils.isPrimitiveArray(sourceClass))) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
if (conversionService.canConvert(sourceType, targetType)) {
|
||||
return conversionService.convert(value, sourceType, targetType);
|
||||
@@ -125,7 +133,7 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware
|
||||
editor.setValue(value);
|
||||
text = editor.getAsText();
|
||||
}
|
||||
if (String.class.isAssignableFrom(targetType.getClass())) {
|
||||
if (String.class.isAssignableFrom(targetType.getType())) {
|
||||
return text;
|
||||
}
|
||||
return convertValue(text, TypeDescriptor.valueOf(String.class), targetType);
|
||||
|
||||
@@ -17,6 +17,8 @@ package org.springframework.integration.util;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
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.mockito.Matchers.any;
|
||||
@@ -28,7 +30,12 @@ import static org.mockito.Mockito.when;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -44,9 +51,14 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.context.NamedComponent;
|
||||
import org.springframework.integration.handler.MethodInvokingMessageProcessor;
|
||||
import org.springframework.integration.handler.ServiceActivatingHandler;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
|
||||
@@ -107,7 +119,7 @@ public class BeanFactoryTypeConverterTests {
|
||||
}
|
||||
});
|
||||
MessageHistory history = MessageHistory.read(message);
|
||||
assertSame(history, typeConverter.convertValue(history, TypeDescriptor.valueOf(MessageHeaders.class), TypeDescriptor.valueOf(MessageHeaders.class)));
|
||||
assertSame(history, typeConverter.convertValue(history, TypeDescriptor.valueOf(MessageHistory.class), TypeDescriptor.valueOf(MessageHistory.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -139,6 +151,128 @@ public class BeanFactoryTypeConverterTests {
|
||||
assertEquals("foo", typeConverter.convertValue(object, TypeDescriptor.valueOf(Object.class), TypeDescriptor.valueOf(String.class)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testMapOfMapOfCollectionIsConverted() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
conversionService.addConverter(new Converter<Foo, Bar>() {
|
||||
public Bar convert(Foo source) {
|
||||
return new Bar();
|
||||
}
|
||||
});
|
||||
BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(conversionService);
|
||||
beanFactory.setConversionService(conversionService);
|
||||
typeConverter.setBeanFactory(beanFactory);
|
||||
Map<String, Map<String, Set<Foo>>> foos;
|
||||
Map<String, Map<String, Set<Bar>>> bars;
|
||||
|
||||
TypeDescriptor sourceType = TypeDescriptor.map(Map.class, null, null);
|
||||
TypeDescriptor targetType = TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(String.class),
|
||||
TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(String.class),
|
||||
TypeDescriptor.collection(Set.class, TypeDescriptor.valueOf(Bar.class))));
|
||||
|
||||
Set<Foo> fooSet = new HashSet<Foo>();
|
||||
fooSet.add(new Foo());
|
||||
Map<String, Set<Foo>> fooMap = new HashMap<String, Set<Foo>>();
|
||||
fooMap.put("foo", fooSet);
|
||||
foos = new HashMap<String, Map<String, Set<Foo>>>();
|
||||
foos.put("foo", fooMap);
|
||||
|
||||
bars = (Map<String, Map<String, Set<Bar>>>) typeConverter.convertValue(foos, sourceType, targetType);
|
||||
assertTrue(bars.get("foo").get("foo").iterator().next() instanceof Bar);
|
||||
|
||||
Service service = new Service();
|
||||
MethodInvokingMessageProcessor<Service> processor = new MethodInvokingMessageProcessor<Service>(service, "handle");
|
||||
processor.setConversionService(conversionService);
|
||||
ServiceActivatingHandler handler = new ServiceActivatingHandler(processor);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
handler.setOutputChannel(replyChannel);
|
||||
handler.handleMessage(new GenericMessage<Map<String, Map<String, Set<Foo>>>>(foos));
|
||||
Message<?> message = replyChannel.receive(0);
|
||||
assertNotNull(message);
|
||||
assertEquals("bar", message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectionIsConverted() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
conversionService.addConverter(new Converter<Foo, Bar>() {
|
||||
public Bar convert(Foo source) {
|
||||
return new Bar();
|
||||
}
|
||||
});
|
||||
BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(conversionService);
|
||||
beanFactory.setConversionService(conversionService);
|
||||
typeConverter.setBeanFactory(beanFactory);
|
||||
|
||||
Service service = new Service();
|
||||
MethodInvokingMessageProcessor<Service> processor = new MethodInvokingMessageProcessor<Service>(service, "handle");
|
||||
processor.setConversionService(conversionService);
|
||||
ServiceActivatingHandler handler = new ServiceActivatingHandler(processor);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
handler.setOutputChannel(replyChannel);
|
||||
handler.handleMessage(new GenericMessage<Collection<Foo>>(Collections.singletonList(new Foo())));
|
||||
Message<?> message = replyChannel.receive(0);
|
||||
assertNotNull(message);
|
||||
assertEquals("baz", message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullArg() {
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(conversionService);
|
||||
Object foo = typeConverter.convertValue(null, null, TypeDescriptor.valueOf(Bar.class));
|
||||
assertNull(foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoidArg() {
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(conversionService);
|
||||
Object foo = typeConverter.convertValue(null, null, TypeDescriptor.valueOf(Void.class));
|
||||
assertNull(foo);
|
||||
foo = typeConverter.convertValue(null, null, TypeDescriptor.valueOf(Void.TYPE));
|
||||
assertNull(foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditorWithTargetString() {
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(conversionService);
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Object foo = typeConverter.convertValue(uuid, TypeDescriptor.valueOf(UUID.class),
|
||||
TypeDescriptor.valueOf(String.class));
|
||||
assertEquals(uuid.toString(), foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditorWithTargetFoo() {
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
final Foo foo = new Foo();
|
||||
conversionService.addConverter(new Converter<String, Foo>() {
|
||||
public Foo convert(String source) {
|
||||
return foo;
|
||||
}
|
||||
});
|
||||
BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(conversionService);
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Object convertedFoo = typeConverter.convertValue(uuid, TypeDescriptor.valueOf(UUID.class),
|
||||
TypeDescriptor.valueOf(Foo.class));
|
||||
assertSame(foo, convertedFoo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelegateWithTargetUUID() {
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(conversionService);
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Object converted = typeConverter.convertValue(uuid.toString(), TypeDescriptor.valueOf(String.class),
|
||||
TypeDescriptor.valueOf(UUID.class));
|
||||
assertEquals(uuid, converted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialConcurrency() throws Exception {
|
||||
ConversionService conversionService = mock(ConversionService.class); // can convert nothing so we drop down to P.E.s
|
||||
@@ -176,4 +310,25 @@ public class BeanFactoryTypeConverterTests {
|
||||
assertEquals(4, count.get());
|
||||
assertFalse(concurrentlyInGetDefaultEditor.get());
|
||||
}
|
||||
|
||||
public static class Foo {
|
||||
|
||||
}
|
||||
|
||||
public static class Bar {
|
||||
|
||||
}
|
||||
|
||||
public static class Service {
|
||||
|
||||
public String handle(Map<String, Map<String, Set<Bar>>> payload) {
|
||||
assertTrue(payload.get("foo").get("foo").iterator().next() instanceof Bar);
|
||||
return "bar";
|
||||
}
|
||||
|
||||
public String handle(Collection<Bar> payload) {
|
||||
assertTrue(payload.iterator().next() instanceof Bar);
|
||||
return "baz";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user