diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultClassMapper.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultClassMapper.java index 70b3724a..d99216f7 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultClassMapper.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultClassMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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 @@ -22,21 +22,37 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.ClassUtils; /** - * + * Maps to/from JSON using type information in the {@link MessageProperties}; + * the default name of the message property containing the type is '__TypeId__'. + * An optional property {@link #setDefaultType(Class)} + * is provided that allows mapping to a statically defined type, if no message property is + * found in the message properties. * @author Mark Pollack - * + * @author Gary Russell + * */ public class DefaultClassMapper implements ClassMapper, InitializingBean { public static final String DEFAULT_CLASSID_FIELD_NAME = "__TypeId__"; - private Map> idClassMapping = new HashMap>(); + private volatile Map> idClassMapping = new HashMap>(); - private Map, String> classIdMapping = new HashMap, String>(); + private volatile Map, String> classIdMapping = new HashMap, String>(); - private String defaultHashtableTypeId = "Hashtable"; + private final String defaultHashtableTypeId = "Hashtable"; - private Class defaultHashtableClass = Hashtable.class; + private volatile Class defaultHashtableClass = Hashtable.class; + + private volatile Class defaultType; + + /** + * The type returned by {@link #toClass(MessageProperties)} if no type information + * is found in the message properties. + * @param defaultType the defaultType to set + */ + public void setDefaultType(Class defaultType) { + this.defaultType = defaultType; + } public void setDefaultHashtableClass(Class defaultHashtableClass) { this.defaultHashtableClass = defaultHashtableClass; @@ -105,9 +121,15 @@ public class DefaultClassMapper implements ClassMapper, InitializingBean { classId = classIdFieldNameValue.toString(); } if (classId == null) { - throw new MessageConversionException( - "failed to convert Message content. Could not resolve " - + getClassIdFieldName() + " in header"); + if (this.defaultType != null) { + return this.defaultType; + } + else { + throw new MessageConversionException( + "failed to convert Message content. Could not resolve " + + getClassIdFieldName() + " in header " + + "and no defaultType provided"); + } } return toClass(classId); } diff --git a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/DefaultClassMapperTest.java b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/DefaultClassMapperTest.java index 8fbc2c13..0c5917b2 100644 --- a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/DefaultClassMapperTest.java +++ b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/DefaultClassMapperTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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 @@ -14,6 +14,7 @@ package org.springframework.amqp.support.converter; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.matchers.JUnitMatchers.containsString; import static org.mockito.BDDMockito.given; @@ -28,15 +29,18 @@ import org.junit.runner.RunWith; import org.mockito.Spy; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.support.converter.JsonMessageConverterTests.Foo; /** * @author James Carr - * + * @author Gary Russell + * */ @RunWith(MockitoJUnitRunner.class) public class DefaultClassMapperTest { @Spy DefaultClassMapper classMapper = new DefaultClassMapper(); + private final MessageProperties props = new MessageProperties(); @Test @@ -115,6 +119,17 @@ public class DefaultClassMapperTest { assertThat(className, equalTo("Hashtable")); } + @SuppressWarnings("unchecked") + @Test + public void shouldUseDefaultType() { + props.getHeaders().clear(); + classMapper.setDefaultType(Foo.class); + Class clazz = (Class) classMapper.toClass(props); + + assertSame(Foo.class, clazz); + classMapper.setDefaultType(null); + } + private Map> map(String string, Class class1) { Map> map = new HashMap>(); map.put(string, class1); diff --git a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/JsonMessageConverterTests.java b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/JsonMessageConverterTests.java index 585f841a..af26b9b8 100644 --- a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/JsonMessageConverterTests.java +++ b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/JsonMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); + * Copyright 2002-2012 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, @@ -10,6 +10,7 @@ package org.springframework.amqp.support.converter; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import java.util.Hashtable; @@ -18,19 +19,29 @@ import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ser.BeanSerializerFactory; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Mark Pollack * @author Dave Syer * @author Sam Nelson + * @author Gary Russell */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) public class JsonMessageConverterTests { private JsonMessageConverter converter; private SimpleTrade trade; + @Autowired + private JsonMessageConverter jsonConverterWithDefaultType; + @Before public void before(){ converter = new JsonMessageConverter(); @@ -43,7 +54,7 @@ public class JsonMessageConverterTests { trade.setRequestId("R123"); trade.setTicker("VMW"); trade.setUserName("Joe Trader"); - + } @Test public void simpleTrade() { @@ -58,7 +69,7 @@ public class JsonMessageConverterTests { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializerFactory(BeanSerializerFactory.instance); converter.setJsonObjectMapper(mapper); - + Message message = converter.toMessage(trade, new MessageProperties()); SimpleTrade marshalledTrade = (SimpleTrade) converter.fromMessage(message); @@ -82,25 +93,49 @@ public class JsonMessageConverterTests { Hashtable hashtable = new Hashtable(); hashtable.put("TICKER", "VMW"); hashtable.put("PRICE", "103.2"); - + Message message = converter.toMessage(hashtable, new MessageProperties()); Hashtable marhsalledHashtable = (Hashtable) converter.fromMessage(message); - + assertEquals("VMW", marhsalledHashtable.get("TICKER")); assertEquals("103.2", marhsalledHashtable.get("PRICE")); } - + @Test public void shouldUseClassMapperWhenProvided() { Message message = converter.toMessage(trade, new MessageProperties()); - + converter.setClassMapper(new DefaultClassMapper()); converter.setJavaTypeMapper(null); - + SimpleTrade marshalledTrade = (SimpleTrade) converter.fromMessage(message); assertEquals(trade, marshalledTrade); } + @Test + public void testDefaultType() { + byte[] bytes = "{\"name\" : \"foo\" }".getBytes(); + MessageProperties messageProperties = new MessageProperties(); + messageProperties.setContentType("application/json"); + Message message = new Message(bytes, messageProperties); + JsonMessageConverter converter = new JsonMessageConverter(); + DefaultClassMapper classMapper = new DefaultClassMapper(); + classMapper.setDefaultType(Foo.class); + converter.setClassMapper(classMapper); + Object foo = converter.fromMessage(message); + assertTrue(foo instanceof Foo); + } + + @Test + public void testDefaultTypeConfig() { + byte[] bytes = "{\"name\" : \"foo\" }".getBytes(); + MessageProperties messageProperties = new MessageProperties(); + messageProperties.setContentType("application/json"); + Message message = new Message(bytes, messageProperties); + Object foo = jsonConverterWithDefaultType.fromMessage(message); + assertTrue(foo instanceof Foo); + } + public static class Foo { private String name = "foo"; diff --git a/spring-amqp/src/test/resources/org/springframework/amqp/support/converter/JsonMessageConverterTests-context.xml b/spring-amqp/src/test/resources/org/springframework/amqp/support/converter/JsonMessageConverterTests-context.xml new file mode 100644 index 00000000..1c8c3263 --- /dev/null +++ b/spring-amqp/src/test/resources/org/springframework/amqp/support/converter/JsonMessageConverterTests-context.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/reference/docbook/amqp.xml b/src/reference/docbook/amqp.xml index 260efc01..9ae7622a 100644 --- a/src/reference/docbook/amqp.xml +++ b/src/reference/docbook/amqp.xml @@ -742,6 +742,22 @@ Object receiveAndConvert(String queueName) throws AmqpException;]]> ]]> + + As shown above, the JsonMessageConverter uses a + DefaultClassMapper by default. Type information is + added to (and retrieved from) the MessageProperties. + If an inbound message does not contain type information in the + MessageProperties, but you know the expected type, + you can configure a static type using the defaultType property + + + + + + + +]]>