Permalink
Browse files

AMQP-277 Add DefaultType to DefaultClassMapper

Permit the use of a JsonMessageConverter when no type information
is provided in the MessageProperties and the type is known.
  • Loading branch information...
1 parent 9c29a74 commit 8f533c86f56c618403e0bee7550d79119575d0b0 @garyrussell garyrussell committed Nov 6, 2012
@@ -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.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<String, Class<?>> idClassMapping = new HashMap<String, Class<?>>();
+ private volatile Map<String, Class<?>> idClassMapping = new HashMap<String, Class<?>>();
+
+ private volatile Map<Class<?>, String> classIdMapping = new HashMap<Class<?>, String>();
- private Map<Class<?>, String> classIdMapping = new HashMap<Class<?>, String>();
+ private final String defaultHashtableTypeId = "Hashtable";
- private String defaultHashtableTypeId = "Hashtable";
+ private volatile Class<?> defaultHashtableClass = Hashtable.class;
- private 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 void fromClass(Class<?> clazz, MessageProperties properties) {
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);
}
@@ -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 @@
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.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 void shouldConvertAnyMapToUseHashtables() {
assertThat(className, equalTo("Hashtable"));
}
+ @SuppressWarnings("unchecked")
+ @Test
+ public void shouldUseDefaultType() {
+ props.getHeaders().clear();
+ classMapper.setDefaultType(Foo.class);
+ Class<Foo> clazz = (Class<Foo>) classMapper.toClass(props);
+
+ assertSame(Foo.class, clazz);
+ classMapper.setDefaultType(null);
+ }
+
private Map<String, Class<?>> map(String string, Class<?> class1) {
Map<String, Class<?>> map = new HashMap<String, Class<?>>();
map.put(string, class1);
@@ -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.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 void before(){
trade.setRequestId("R123");
trade.setTicker("VMW");
trade.setUserName("Joe Trader");
-
+
}
@Test
public void simpleTrade() {
@@ -58,7 +69,7 @@ public void simpleTradeOverrideMapper() {
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 void hashtable() {
Hashtable<String, String> hashtable = new Hashtable<String, String>();
hashtable.put("TICKER", "VMW");
hashtable.put("PRICE", "103.2");
-
+
Message message = converter.toMessage(hashtable, new MessageProperties());
Hashtable<String, String> marhsalledHashtable = (Hashtable<String, String>) 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";
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="jsonConverterWithDefaultType" class="org.springframework.amqp.support.converter.JsonMessageConverter">
+ <property name="classMapper">
+ <bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
+ <property name="defaultType"
+ value="org.springframework.amqp.support.converter.JsonMessageConverterTests$Foo" />
+ </bean>
+ </property>
+ </bean>
+
+</beans>
@@ -742,6 +742,22 @@ Object receiveAndConvert(String queueName) throws AmqpException;]]></programlist
</bean>
</property>
</bean>]]></programlisting>
+
+ <para>As shown above, the <classname>JsonMessageConverter</classname> uses a
+ <classname>DefaultClassMapper</classname> by default. Type information is
+ added to (and retrieved from) the <classname>MessageProperties</classname>.
+ If an inbound message does not contain type information in the
+ <classname>MessageProperties</classname>, but you know the expected type,
+ you can configure a static type using the <code>defaultType</code> property</para>
+
+ <programlisting language="xml"><![CDATA[<bean id="jsonConverterWithDefaultType" class="org.springframework.amqp.support.converter.JsonMessageConverter">
+ <property name="classMapper">
+ <bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
+ <property name="defaultType"
+ value="foo.PurchaseOrder" />
+ </bean>
+ </property>
+</bean>]]></programlisting>
</sect2>
<sect2>

0 comments on commit 8f533c8

Please sign in to comment.