diff --git a/build.gradle b/build.gradle index bb3c7741..67c4a21c 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ subprojects { subproject -> commonsIoVersion = '1.4' erlangOtpVersion = '1.5.3' jacksonVersion = '1.4.3' + jackson2Version = '2.0.1' junitVersion = '4.8.2' log4jVersion = '1.2.15' mockitoVersion = '1.8.4' @@ -122,7 +123,8 @@ project('spring-amqp') { compile ("org.springframework:spring-context:$springVersion", optional) compile ("org.codehaus.jackson:jackson-core-asl:$jacksonVersion", optional) compile ("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional) - + compile ("com.fasterxml.jackson.core:jackson-core:$jackson2Version", optional) + compile ("com.fasterxml.jackson.core:jackson-databind:$jackson2Version", optional) } } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJavaTypeMapper.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJavaTypeMapper.java new file mode 100644 index 00000000..ede081fb --- /dev/null +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJavaTypeMapper.java @@ -0,0 +1,91 @@ +/* + * 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. 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.amqp.support.converter; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.amqp.core.MessageProperties; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Mark Pollack + * @author Sam Nelson + * @author Andreas Asplund + */ +public abstract class AbstractJavaTypeMapper implements InitializingBean { + public static final String DEFAULT_CLASSID_FIELD_NAME = "__TypeId__"; + public static final String DEFAULT_CONTENT_CLASSID_FIELD_NAME = "__ContentTypeId__"; + public static final String DEFAULT_KEY_CLASSID_FIELD_NAME = "__KeyTypeId__"; + + private Map> idClassMapping = new HashMap>(); + private final Map, String> classIdMapping = new HashMap, String>(); + + public String getClassIdFieldName() { + return DEFAULT_CLASSID_FIELD_NAME; + } + + public String getContentClassIdFieldName() { + return DEFAULT_CONTENT_CLASSID_FIELD_NAME; + } + + public String getKeyClassIdFieldName() { + return DEFAULT_KEY_CLASSID_FIELD_NAME; + } + + public void setIdClassMapping(Map> idClassMapping) { + this.idClassMapping = idClassMapping; + } + + protected void addHeader(MessageProperties properties, String headerName, + Class clazz) { + if (classIdMapping.containsKey(clazz)) { + properties.getHeaders().put(headerName, classIdMapping.get(clazz)); + } + else { + properties.getHeaders().put(headerName, clazz.getName()); + } + } + + protected String retrieveHeader(MessageProperties properties, + String headerName) { + Map headers = properties.getHeaders(); + Object classIdFieldNameValue = headers.get(headerName); + String classId = null; + if (classIdFieldNameValue != null) { + classId = classIdFieldNameValue.toString(); + } + if (classId == null) { + throw new MessageConversionException( + "failed to convert Message content. Could not resolve " + + headerName + " in header"); + } + return classId; + } + + private void validateIdTypeMapping() { + Map> finalIdClassMapping = new HashMap>(); + for (Map.Entry> entry : idClassMapping.entrySet()) { + String id = entry.getKey(); + Class clazz = entry.getValue(); + finalIdClassMapping.put(id, clazz); + classIdMapping.put(clazz, id); + } + this.idClassMapping = finalIdClassMapping; + } + + public Map> getIdClassMapping() { + return Collections.unmodifiableMap(idClassMapping); + } + + public void afterPropertiesSet() throws Exception { + validateIdTypeMapping(); + } +} diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJsonMessageConverter.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJsonMessageConverter.java new file mode 100644 index 00000000..d0569bab --- /dev/null +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJsonMessageConverter.java @@ -0,0 +1,48 @@ +/* + * 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, + * either express or implied. See the License for the specific language governing permissions and limitations under the + * License. + */ + +package org.springframework.amqp.support.converter; + +/** + * + * @author Mark Pollack + * @author James Carr + * @author Dave Syer + * @author Sam Nelson + * @author Andreas Asplund + */ +public abstract class AbstractJsonMessageConverter extends AbstractMessageConverter { + public static final String DEFAULT_CHARSET = "UTF-8"; + + private volatile String defaultCharset = DEFAULT_CHARSET; + + private ClassMapper classMapper = null; + + public ClassMapper getClassMapper() { + return classMapper; + + } + + public void setClassMapper(ClassMapper classMapper) { + this.classMapper = classMapper; + } + + /** + * Specify the default charset to use when converting to or from text-based + * Message body content. If not specified, the charset will be "UTF-8". + */ + public void setDefaultCharset(String defaultCharset) { + this.defaultCharset = (defaultCharset != null) ? defaultCharset + : DEFAULT_CHARSET; + } + + public String getDefaultCharset() { + return defaultCharset; + } +} diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapper.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapper.java new file mode 100644 index 00000000..c450b3f6 --- /dev/null +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapper.java @@ -0,0 +1,91 @@ +/* + * 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. 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.amqp.support.converter; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.MapType; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.util.ClassUtils; + +/** + * @author Mark Pollack + * @author Sam Nelson + * @author Andreas Asplund + */ +public class DefaultJackson2JavaTypeMapper extends AbstractJavaTypeMapper implements Jackson2JavaTypeMapper, ClassMapper { + + public JavaType toJavaType(MessageProperties properties) { + JavaType classType = getClassIdType(retrieveHeader(properties, + getClassIdFieldName())); + if (!classType.isContainerType()) { + return classType; + } + + JavaType contentClassType = getClassIdType(retrieveHeader(properties, + getContentClassIdFieldName())); + if (classType.getKeyType() == null) { + return CollectionType.construct( + classType.getRawClass(), + contentClassType); + } + + JavaType keyClassType = getClassIdType(retrieveHeader(properties, + getKeyClassIdFieldName())); + JavaType mapType = MapType.construct( + classType.getRawClass(), keyClassType, + contentClassType); + return mapType; + + } + + private JavaType getClassIdType(String classId) { + if (getIdClassMapping().containsKey(classId)) { + return TypeFactory.defaultInstance().constructType(getIdClassMapping().get(classId)); + } + + try { + return TypeFactory.defaultInstance().constructType(ClassUtils.forName(classId, getClass() + .getClassLoader())); + } catch (ClassNotFoundException e) { + throw new MessageConversionException( + "failed to resolve class name. Class not found [" + classId + + "]", e); + } catch (LinkageError e) { + throw new MessageConversionException( + "failed to resolve class name. Linkage error [" + classId + + "]", e); + } + } + + public void fromJavaType(JavaType javaType, MessageProperties properties) { + addHeader(properties, getClassIdFieldName(), + (Class) javaType.getRawClass()); + + if (javaType.isContainerType()) { + addHeader(properties, getContentClassIdFieldName(), javaType + .getContentType().getRawClass()); + } + + if (javaType.getKeyType() != null) { + addHeader(properties, getKeyClassIdFieldName(), javaType + .getKeyType().getRawClass()); + } + } + + public void fromClass(Class clazz, MessageProperties properties) { + fromJavaType(TypeFactory.defaultInstance().constructType(clazz), properties); + + } + + public Class toClass(MessageProperties properties) { + return toJavaType(properties).getRawClass(); + } +} diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJavaTypeMapper.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJavaTypeMapper.java index 08041623..2a37b250 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJavaTypeMapper.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJavaTypeMapper.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-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. 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, @@ -8,45 +8,23 @@ */ package org.springframework.amqp.support.converter; -/** - * @author Mark Pollack - * @author Sam Nelson - */ import static org.codehaus.jackson.map.type.TypeFactory.collectionType; import static org.codehaus.jackson.map.type.TypeFactory.mapType; import static org.codehaus.jackson.map.type.TypeFactory.type; import java.util.Collection; -import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import org.codehaus.jackson.type.JavaType; import org.springframework.amqp.core.MessageProperties; -import org.springframework.beans.factory.InitializingBean; import org.springframework.util.ClassUtils; -public class DefaultJavaTypeMapper implements JavaTypeMapper, ClassMapper, - InitializingBean { - - public static final String DEFAULT_CLASSID_FIELD_NAME = "__TypeId__"; - public static final String DEFAULT_CONTENT_CLASSID_FIELD_NAME = "__ContentTypeId__"; - public static final String DEFAULT_KEY_CLASSID_FIELD_NAME = "__KeyTypeId__"; - - private Map> idClassMapping = new HashMap>(); - private Map, String> classIdMapping = new HashMap, String>(); - - public String getClassIdFieldName() { - return DEFAULT_CLASSID_FIELD_NAME; - } - - public String getContentClassIdFieldName() { - return DEFAULT_CONTENT_CLASSID_FIELD_NAME; - } - - public String getKeyClassIdFieldName() { - return DEFAULT_KEY_CLASSID_FIELD_NAME; - } +/** + * @author Mark Pollack + * @author Sam Nelson + * @author Andreas Asplund + */ +public class DefaultJavaTypeMapper extends AbstractJavaTypeMapper implements JavaTypeMapper, ClassMapper { @SuppressWarnings({ "unchecked", "rawtypes" }) public JavaType toJavaType(MessageProperties properties) { @@ -74,47 +52,29 @@ public class DefaultJavaTypeMapper implements JavaTypeMapper, ClassMapper, } private JavaType getClassIdType(String classId) { - if (this.idClassMapping.containsKey(classId)) { - return type(idClassMapping.get(classId)); + if (getIdClassMapping().containsKey(classId)) { + return type(getIdClassMapping().get(classId)); } try { return type(ClassUtils.forName(classId, getClass() .getClassLoader())); - } catch (ClassNotFoundException e) { + } + catch (ClassNotFoundException e) { throw new MessageConversionException( "failed to resolve class name. Class not found [" + classId + "]", e); - } catch (LinkageError e) { + } + catch (LinkageError e) { throw new MessageConversionException( "failed to resolve class name. Linkage error [" + classId + "]", e); } } - private String retrieveHeader(MessageProperties properties, - String headerName) { - Map headers = properties.getHeaders(); - Object classIdFieldNameValue = headers.get(headerName); - String classId = null; - if (classIdFieldNameValue != null) { - classId = classIdFieldNameValue.toString(); - } - if (classId == null) { - throw new MessageConversionException( - "failed to convert Message content. Could not resolve " - + headerName + " in header"); - } - return classId; - } - - public void setIdClassMapping(Map> idClassMapping) { - this.idClassMapping = idClassMapping; - } - public void fromJavaType(JavaType javaType, MessageProperties properties) { addHeader(properties, getClassIdFieldName(), - (Class) javaType.getRawClass()); + javaType.getRawClass()); if (javaType.isContainerType()) { addHeader(properties, getContentClassIdFieldName(), javaType @@ -127,30 +87,6 @@ public class DefaultJavaTypeMapper implements JavaTypeMapper, ClassMapper, } } - public void afterPropertiesSet() throws Exception { - validateIdTypeMapping(); - } - - private void addHeader(MessageProperties properties, String headerName, - Class clazz) { - if (classIdMapping.containsKey(clazz)) { - properties.getHeaders().put(headerName, classIdMapping.get(clazz)); - } else { - properties.getHeaders().put(headerName, clazz.getName()); - } - } - - private void validateIdTypeMapping() { - Map> finalIdClassMapping = new HashMap>(); - for (Entry> entry : idClassMapping.entrySet()) { - String id = entry.getKey(); - Class clazz = entry.getValue(); - finalIdClassMapping.put(id, clazz); - classIdMapping.put(clazz, id); - } - this.idClassMapping = finalIdClassMapping; - } - public void fromClass(Class clazz, MessageProperties properties) { fromJavaType(type(clazz), properties); diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JavaTypeMapper.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JavaTypeMapper.java new file mode 100644 index 00000000..4e854576 --- /dev/null +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JavaTypeMapper.java @@ -0,0 +1,30 @@ +/* + * 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. 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.amqp.support.converter; + +import com.fasterxml.jackson.databind.JavaType; +import org.springframework.amqp.core.MessageProperties; + +/** + * Strategy for setting metadata on messages such that one can create the class that needs to be instantiated when + * receiving a message. + * + * @author Mark Pollack + * @author James Carr + * @author Sam Nelson + * @author Andreas Asplund + */ +public interface Jackson2JavaTypeMapper extends ClassMapper { + + void fromJavaType(JavaType javaType, MessageProperties properties); + + JavaType toJavaType(MessageProperties properties); + +} diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverter.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverter.java new file mode 100644 index 00000000..c47587aa --- /dev/null +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverter.java @@ -0,0 +1,165 @@ +/* + * 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. 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.amqp.support.converter; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * JSON converter that uses the Jackson 2 Json library. + * + * @author Mark Pollack + * @author James Carr + * @author Dave Syer + * @author Sam Nelson + * @author Andreas Asplund + */ +public class Jackson2JsonMessageConverter extends AbstractJsonMessageConverter { + + private static Log log = LogFactory.getLog(Jackson2JsonMessageConverter.class); + + private ObjectMapper jsonObjectMapper = new ObjectMapper(); + + private Jackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper(); + + public Jackson2JsonMessageConverter() { + super(); + initializeJsonObjectMapper(); + } + + public Jackson2JavaTypeMapper getJavaTypeMapper() { + return javaTypeMapper; + } + + public void setJavaTypeMapper(Jackson2JavaTypeMapper javaTypeMapper) { + this.javaTypeMapper = javaTypeMapper; + } + + /** + * The {@link com.fasterxml.jackson.databind.ObjectMapper} to use instead of using the default. An + * alternative to injecting a mapper is to extend this class and override + * {@link #initializeJsonObjectMapper()}. + * + * @param jsonObjectMapper + * the object mapper to set + */ + public void setJsonObjectMapper(ObjectMapper jsonObjectMapper) { + this.jsonObjectMapper = jsonObjectMapper; + } + + /** + * Subclass and override to customize. + */ + protected void initializeJsonObjectMapper() { + jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Override + public Object fromMessage(Message message) + throws MessageConversionException { + Object content = null; + MessageProperties properties = message.getMessageProperties(); + if (properties != null) { + String contentType = properties.getContentType(); + if (contentType != null && contentType.contains("json")) { + String encoding = properties.getContentEncoding(); + if (encoding == null) { + encoding = getDefaultCharset(); + } + try { + + if (getClassMapper() == null) { + JavaType targetJavaType = getJavaTypeMapper() + .toJavaType(message.getMessageProperties()); + content = convertBytesToObject(message.getBody(), + encoding, targetJavaType); + } + else { + Class targetClass = getClassMapper().toClass( + message.getMessageProperties()); + content = convertBytesToObject(message.getBody(), + encoding, targetClass); + } + } + catch (IOException e) { + throw new MessageConversionException( + "Failed to convert Message content", e); + } + } + else { + log.warn("Could not convert incoming message with content-type [" + + contentType + "]"); + } + } + if (content == null) { + content = message.getBody(); + } + return content; + } + + private Object convertBytesToObject(byte[] body, String encoding, + JavaType targetJavaType) throws JsonParseException, + JsonMappingException, IOException { + String contentAsString = new String(body, encoding); + return jsonObjectMapper.readValue(contentAsString, targetJavaType); + } + + private Object convertBytesToObject(byte[] body, String encoding, + Class targetClass) throws JsonParseException, + JsonMappingException, IOException { + String contentAsString = new String(body, encoding); + return jsonObjectMapper.readValue(contentAsString, jsonObjectMapper.constructType(targetClass)); + } + + @Override + protected Message createMessage(Object objectToConvert, + MessageProperties messageProperties) + throws MessageConversionException { + byte[] bytes = null; + try { + String jsonString = jsonObjectMapper + .writeValueAsString(objectToConvert); + bytes = jsonString.getBytes(getDefaultCharset()); + } + catch (IOException e) { + throw new MessageConversionException( + "Failed to convert Message content", e); + } + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); + messageProperties.setContentEncoding(getDefaultCharset()); + if (bytes != null) { + messageProperties.setContentLength(bytes.length); + } + + if (getClassMapper() == null) { + getJavaTypeMapper().fromJavaType(jsonObjectMapper.constructType(objectToConvert.getClass()), + messageProperties); + + } + else { + getClassMapper().fromClass(objectToConvert.getClass(), + messageProperties); + + } + + return new Message(bytes, messageProperties); + + } +} diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/JsonMessageConverter.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/JsonMessageConverter.java index 0f3047e1..a9f1e26d 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/JsonMessageConverter.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/JsonMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); + * 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. 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, @@ -12,11 +12,9 @@ package org.springframework.amqp.support.converter; import static org.codehaus.jackson.map.type.TypeFactory.type; import java.io.IOException; -import java.io.UnsupportedEncodingException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.DeserializationConfig; import org.codehaus.jackson.map.JsonMappingException; @@ -32,44 +30,21 @@ import org.springframework.amqp.core.MessageProperties; * @author James Carr * @author Dave Syer * @author Sam Nelson + * @author Andreas Asplund */ -public class JsonMessageConverter extends AbstractMessageConverter { +public class JsonMessageConverter extends AbstractJsonMessageConverter { private static Log log = LogFactory.getLog(JsonMessageConverter.class); - public static final String DEFAULT_CHARSET = "UTF-8"; - - private volatile String defaultCharset = DEFAULT_CHARSET; - private ObjectMapper jsonObjectMapper = new ObjectMapper(); private JavaTypeMapper javaTypeMapper = new DefaultJavaTypeMapper(); - private ClassMapper classMapper = null; - - public ClassMapper getClassMapper() { - return classMapper; - - } - - public void setClassMapper(ClassMapper classMapper) { - this.classMapper = classMapper; - } - public JsonMessageConverter() { super(); initializeJsonObjectMapper(); } - /** - * Specify the default charset to use when converting to or from text-based - * Message body content. If not specified, the charset will be "UTF-8". - */ - public void setDefaultCharset(String defaultCharset) { - this.defaultCharset = (defaultCharset != null) ? defaultCharset - : DEFAULT_CHARSET; - } - public JavaTypeMapper getJavaTypeMapper() { return javaTypeMapper; } @@ -110,7 +85,7 @@ public class JsonMessageConverter extends AbstractMessageConverter { if (contentType != null && contentType.contains("json")) { String encoding = properties.getContentEncoding(); if (encoding == null) { - encoding = this.defaultCharset; + encoding = getDefaultCharset(); } try { @@ -119,26 +94,20 @@ public class JsonMessageConverter extends AbstractMessageConverter { .toJavaType(message.getMessageProperties()); content = convertBytesToObject(message.getBody(), encoding, targetJavaType); - } else { + } + else { Class targetClass = getClassMapper().toClass( message.getMessageProperties()); content = convertBytesToObject(message.getBody(), encoding, targetClass); } - } catch (UnsupportedEncodingException e) { - throw new MessageConversionException( - "Failed to convert json-based Message content", e); - } catch (JsonParseException e) { - throw new MessageConversionException( - "Failed to convert Message content", e); - } catch (JsonMappingException e) { - throw new MessageConversionException( - "Failed to convert Message content", e); - } catch (IOException e) { + } + catch (IOException e) { throw new MessageConversionException( "Failed to convert Message content", e); } - } else { + } + else { log.warn("Could not convert incoming message with content-type [" + contentType + "]"); } @@ -171,22 +140,14 @@ public class JsonMessageConverter extends AbstractMessageConverter { try { String jsonString = jsonObjectMapper .writeValueAsString(objectToConvert); - bytes = jsonString.getBytes(this.defaultCharset); - } catch (UnsupportedEncodingException e) { - throw new MessageConversionException( - "Failed to convert Message content", e); - } catch (JsonGenerationException e) { - throw new MessageConversionException( - "Failed to convert Message content", e); - } catch (JsonMappingException e) { - throw new MessageConversionException( - "Failed to convert Message content", e); - } catch (IOException e) { + bytes = jsonString.getBytes(getDefaultCharset()); + } + catch (IOException e) { throw new MessageConversionException( "Failed to convert Message content", e); } messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); - messageProperties.setContentEncoding(this.defaultCharset); + messageProperties.setContentEncoding(getDefaultCharset()); if (bytes != null) { messageProperties.setContentLength(bytes.length); } @@ -195,7 +156,8 @@ public class JsonMessageConverter extends AbstractMessageConverter { getJavaTypeMapper().fromJavaType(type(objectToConvert.getClass()), messageProperties); - } else { + } + else { getClassMapper().fromClass(objectToConvert.getClass(), messageProperties); diff --git a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapperTest.java b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapperTest.java new file mode 100644 index 00000000..30dcdf73 --- /dev/null +++ b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapperTest.java @@ -0,0 +1,238 @@ +/* + * 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. 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.amqp.support.converter; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.MapType; +import com.fasterxml.jackson.databind.type.TypeFactory; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import org.junit.Test; +import static org.junit.matchers.JUnitMatchers.containsString; +import org.junit.runner.RunWith; +import static org.mockito.BDDMockito.given; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.amqp.core.MessageProperties; + +/** + * @author James Carr + * @author Sam Nelson + * @author Andreas Asplund + */ + +@RunWith(MockitoJUnitRunner.class) +public class DefaultJackson2JavaTypeMapperTest { + @Spy + DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper(); + private final MessageProperties properties = new MessageProperties(); + + @SuppressWarnings("rawtypes") + private Class containerClass = ArrayList.class; + + @SuppressWarnings("rawtypes") + private Class mapClass = HashMap.class; + + @Test + public void shouldThrowAnExceptionWhenClassIdNotPresent() { + try { + javaTypeMapper.toJavaType(properties); + } + catch (MessageConversionException e) { + String classIdFieldName = javaTypeMapper.getClassIdFieldName(); + assertThat(e.getMessage(), containsString("Could not resolve " + classIdFieldName + " in header")); + return; + } + fail(); + } + + @Test + public void shouldLookInTheClassIdFieldNameToFindTheClassName() { + properties.getHeaders().put("type", "java.lang.String"); + given(javaTypeMapper.getClassIdFieldName()).willReturn("type"); + + JavaType javaType = javaTypeMapper.toJavaType(properties); + + assertThat(javaType, equalTo(TypeFactory.defaultInstance().constructType(String.class))); + } + + @Test + public void shouldUseTheClassProvidedByTheLookupMapIfPresent() { + properties.getHeaders().put("__TypeId__", "trade"); + javaTypeMapper.setIdClassMapping(map("trade", SimpleTrade.class)); + + JavaType javaType = javaTypeMapper.toJavaType(properties); + + assertEquals(javaType, TypeFactory.defaultInstance().constructType(SimpleTrade.class)); + } + + @Test + public void fromJavaTypeShouldPopulateWithJavaTypeNameByDefault() { + javaTypeMapper.fromJavaType(TypeFactory.defaultInstance().constructType(SimpleTrade.class), properties); + + String className = (String) properties.getHeaders().get(javaTypeMapper.getClassIdFieldName()); + assertThat(className, equalTo(SimpleTrade.class.getName())); + } + + @Test + public void shouldUseSpecialNameForClassIfPresent() throws Exception { + javaTypeMapper.setIdClassMapping(map("daytrade", SimpleTrade.class)); + javaTypeMapper.afterPropertiesSet(); + + javaTypeMapper.fromJavaType(TypeFactory.defaultInstance().constructType(SimpleTrade.class), properties); + + String className = (String) properties.getHeaders().get(javaTypeMapper.getClassIdFieldName()); + assertThat(className, equalTo("daytrade")); + } + + @Test + public void shouldThrowAnExceptionWhenContentClassIdIsNotPresentWhenClassIdIsContainerType() { + properties.getHeaders().put(javaTypeMapper.getClassIdFieldName(), ArrayList.class.getName()); + + try { + javaTypeMapper.toJavaType(properties); + } + catch (MessageConversionException e) { + String contentClassIdFieldName = javaTypeMapper.getContentClassIdFieldName(); + assertThat(e.getMessage(), containsString("Could not resolve " + contentClassIdFieldName + " in header")); + return; + } + fail(); + } + + @Test + public void shouldLookInTheContentClassIdFieldNameToFindTheContainerClassIDWhenClassIdIsContainerType() { + properties.getHeaders().put("contentType", "java.lang.String"); + properties.getHeaders().put(javaTypeMapper.getClassIdFieldName(), ArrayList.class.getName()); + given(javaTypeMapper.getContentClassIdFieldName()).willReturn("contentType"); + + JavaType javaType = javaTypeMapper.toJavaType(properties); + + assertThat((CollectionType)javaType, equalTo(TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, String.class))); + } + + @Test + public void shouldUseTheContentClassProvidedByTheLookupMapIfPresent() { + + properties.getHeaders().put(javaTypeMapper.getClassIdFieldName(), containerClass.getName()); + properties.getHeaders().put("__ContentTypeId__", "trade"); + + Map> map = map("trade", SimpleTrade.class); + map.put(javaTypeMapper.getClassIdFieldName(), containerClass); + javaTypeMapper.setIdClassMapping(map); + + JavaType javaType = javaTypeMapper.toJavaType(properties); + + assertThat((CollectionType)javaType, equalTo(TypeFactory.defaultInstance().constructCollectionType(containerClass, TypeFactory.defaultInstance().constructType(SimpleTrade.class)))); + } + + @Test + public void fromJavaTypeShouldPopulateWithContentTypeJavaTypeNameByDefault() { + + javaTypeMapper.fromJavaType(TypeFactory.defaultInstance().constructCollectionType(containerClass, TypeFactory.defaultInstance().constructType(SimpleTrade.class)), + properties); + + String className = (String) properties.getHeaders().get(javaTypeMapper.getClassIdFieldName()); + String contentClassName = (String) properties.getHeaders().get(javaTypeMapper.getContentClassIdFieldName()); + + assertThat(className, equalTo(ArrayList.class.getName())); + assertThat(contentClassName, equalTo(SimpleTrade.class.getName())); + } + + @Test + public void shouldThrowAnExceptionWhenKeyClassIdIsNotPresentWhenClassIdIsAMap() { + properties.getHeaders().put(javaTypeMapper.getClassIdFieldName(), HashMap.class.getName()); + properties.getHeaders().put(javaTypeMapper.getKeyClassIdFieldName(), String.class.getName()); + + try { + javaTypeMapper.toJavaType(properties); + } + catch (MessageConversionException e) { + String contentClassIdFieldName = javaTypeMapper.getContentClassIdFieldName(); + assertThat(e.getMessage(), containsString("Could not resolve " + contentClassIdFieldName + " in header")); + return; + } + fail(); + } + + @Test + public void shouldLookInTheValueClassIdFieldNameToFindTheValueClassIDWhenClassIdIsAMap() { + properties.getHeaders().put("keyType", "java.lang.Integer"); + properties.getHeaders().put(javaTypeMapper.getContentClassIdFieldName(), "java.lang.String"); + properties.getHeaders().put(javaTypeMapper.getClassIdFieldName(), HashMap.class.getName()); + given(javaTypeMapper.getKeyClassIdFieldName()).willReturn("keyType"); + + JavaType javaType = javaTypeMapper.toJavaType(properties); + + assertThat((MapType)javaType, equalTo(TypeFactory.defaultInstance().constructMapType(HashMap.class, Integer.class, String.class))); + } + + @Test + public void shouldUseTheKeyClassProvidedByTheLookupMapIfPresent() { + properties.getHeaders().put(javaTypeMapper.getClassIdFieldName(), mapClass.getName()); + properties.getHeaders().put(javaTypeMapper.getContentClassIdFieldName(), "java.lang.String"); + properties.getHeaders().put("__KeyTypeId__", "trade"); + + Map> map = map("trade", SimpleTrade.class); + map.put(javaTypeMapper.getClassIdFieldName(), mapClass); + map.put(javaTypeMapper.getContentClassIdFieldName(), String.class); + javaTypeMapper.setIdClassMapping(map); + + JavaType javaType = javaTypeMapper.toJavaType(properties); + + assertThat((MapType)javaType, + equalTo(TypeFactory.defaultInstance().constructMapType(mapClass, TypeFactory.defaultInstance().constructType(SimpleTrade.class), + TypeFactory.defaultInstance().constructType(String.class)))); + } + + @Test + public void fromJavaTypeShouldPopulateWithKeyTypeAndContentJavaTypeNameByDefault() { + + javaTypeMapper.fromJavaType(TypeFactory.defaultInstance().constructMapType(mapClass, TypeFactory.defaultInstance().constructType(SimpleTrade.class), + TypeFactory.defaultInstance().constructType(String.class)), properties); + + String className = (String) properties.getHeaders().get(javaTypeMapper.getClassIdFieldName()); + String contentClassName = (String) properties.getHeaders().get(javaTypeMapper.getContentClassIdFieldName()); + String keyClassName = (String) properties.getHeaders().get(javaTypeMapper.getKeyClassIdFieldName()); + + assertThat(className, equalTo(HashMap.class.getName())); + assertThat(contentClassName, equalTo(String.class.getName())); + assertThat(keyClassName, equalTo(SimpleTrade.class.getName())); + } + + @Test + public void fromClassShouldPopulateWithJavaTypeNameByDefault() { + javaTypeMapper.fromClass(SimpleTrade.class, properties); + + String className = (String) properties.getHeaders().get(javaTypeMapper.getClassIdFieldName()); + assertThat(className, equalTo(SimpleTrade.class.getName())); + } + + @Test + public void toClassShouldUseTheClassProvidedByTheLookupMapIfPresent() { + properties.getHeaders().put("__TypeId__", "trade"); + javaTypeMapper.setIdClassMapping(map("trade", SimpleTrade.class)); + + Class clazz = javaTypeMapper.toClass(properties); + + assertEquals(SimpleTrade.class, clazz); + } + + private Map> map(String string, Class clazz) { + Map> map = new HashMap>(); + map.put(string, clazz); + return map; + } +} diff --git a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests.java b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests.java new file mode 100644 index 00000000..d9a469ac --- /dev/null +++ b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests.java @@ -0,0 +1,264 @@ +/* + * 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. 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.amqp.support.converter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; +import java.util.Hashtable; + +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; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; + +/** + * @author Mark Pollack + * @author Dave Syer + * @author Sam Nelson + * @author Gary Russell + * @author Andreas Asplund + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class Jackson2JsonMessageConverterTests { + + private Jackson2JsonMessageConverter converter; + private SimpleTrade trade; + + @Autowired + private Jackson2JsonMessageConverter jsonConverterWithDefaultType; + + @Before + public void before(){ + converter = new Jackson2JsonMessageConverter(); + trade = new SimpleTrade(); + trade.setAccountName("Acct1"); + trade.setBuyRequest(true); + trade.setOrderType("Market"); + trade.setPrice(new BigDecimal(103.30)); + trade.setQuantity(100); + trade.setRequestId("R123"); + trade.setTicker("VMW"); + trade.setUserName("Joe Trader"); + + } + @Test + public void simpleTrade() { + Message message = converter.toMessage(trade, new MessageProperties()); + + SimpleTrade marshalledTrade = (SimpleTrade) converter.fromMessage(message); + assertEquals(trade, marshalledTrade); + } + + @Test + 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); + assertEquals(trade, marshalledTrade); + } + + @Test + public void nestedBean() { + Bar bar = new Bar(); + bar.getFoo().setName("spam"); + + Message message = converter.toMessage(bar, new MessageProperties()); + + Bar marshalled = (Bar) converter.fromMessage(message); + assertEquals(bar, marshalled); + } + + @Test + @SuppressWarnings("unchecked") + public void hashtable() { + 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 shouldUseClassMapperWhenProvidedOutbound() { + converter.setClassMapper(new DefaultClassMapper()); + converter.setJavaTypeMapper(null); + Message message = converter.toMessage(trade, new MessageProperties()); + + 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); + } + + @Test + public void testNoJsonContentType() { + byte[] bytes = "{\"name\" : \"foo\" }".getBytes(); + MessageProperties messageProperties = new MessageProperties(); + Message message = new Message(bytes, messageProperties); + Object foo = jsonConverterWithDefaultType.fromMessage(message); + assertEquals(new String(bytes), new String((byte[]) foo)); + } + + public static class Foo { + private String name = "foo"; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Foo other = (Foo) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } + else if (!name.equals(other.name)) { + return false; + } + return true; + } + } + + public static class Bar { + private String name = "bar"; + private Foo foo = new Foo(); + + public Foo getFoo() { + return foo; + } + + public void setFoo(Foo foo) { + this.foo = foo; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((foo == null) ? 0 : foo.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Bar other = (Bar) obj; + if (foo == null) { + if (other.foo != null) { + return false; + } + } + else if (!foo.equals(other.foo)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } + else if (!name.equals(other.name)) { + return false; + } + return true; + } + } + +} 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 af26b9b8..13ada98c 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-2012 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); + * 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. 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, @@ -112,6 +112,16 @@ public class JsonMessageConverterTests { assertEquals(trade, marshalledTrade); } + @Test + public void shouldUseClassMapperWhenProvidedOutbound() { + converter.setClassMapper(new DefaultClassMapper()); + converter.setJavaTypeMapper(null); + Message message = converter.toMessage(trade, new MessageProperties()); + + SimpleTrade marshalledTrade = (SimpleTrade) converter.fromMessage(message); + assertEquals(trade, marshalledTrade); + } + @Test public void testDefaultType() { byte[] bytes = "{\"name\" : \"foo\" }".getBytes(); @@ -136,6 +146,15 @@ public class JsonMessageConverterTests { assertTrue(foo instanceof Foo); } + @Test + public void testNoJsonContentType() { + byte[] bytes = "{\"name\" : \"foo\" }".getBytes(); + MessageProperties messageProperties = new MessageProperties(); + Message message = new Message(bytes, messageProperties); + Object foo = jsonConverterWithDefaultType.fromMessage(message); + assertEquals(new String(bytes), new String((byte[]) foo)); + } + public static class Foo { private String name = "foo"; @@ -157,14 +176,24 @@ public class JsonMessageConverterTests { @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } Foo other = (Foo) obj; if (name == null) { - if (other.name != null) return false; + if (other.name != null) { + return false; + } } - else if (!name.equals(other.name)) return false; + else if (!name.equals(other.name)) { + return false; + } return true; } } @@ -200,18 +229,32 @@ public class JsonMessageConverterTests { @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } Bar other = (Bar) obj; if (foo == null) { - if (other.foo != null) return false; + if (other.foo != null) { + return false; + } } - else if (!foo.equals(other.foo)) return false; + else if (!foo.equals(other.foo)) { + return false; + } if (name == null) { - if (other.name != null) return false; + if (other.name != null) { + return false; + } } - else if (!name.equals(other.name)) return false; + else if (!name.equals(other.name)) { + return false; + } return true; } } diff --git a/spring-amqp/src/test/resources/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests-context.xml b/spring-amqp/src/test/resources/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests-context.xml new file mode 100644 index 00000000..3a179658 --- /dev/null +++ b/spring-amqp/src/test/resources/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests-context.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/reference/docbook/amqp.xml b/src/reference/docbook/amqp.xml index e33df5a3..fcdedea7 100644 --- a/src/reference/docbook/amqp.xml +++ b/src/reference/docbook/amqp.xml @@ -533,7 +533,7 @@ Object receiveAndConvert(String queueName) throws AmqpException;]]> - Asynchronous ConsumerFor asynchronous Message + Asynchronous ConsumerFor asynchronous Message reception, a dedicated component (not the AmqpTemplate) is involved. That component is a container for a Message consuming callback. We will look at the @@ -666,7 +666,10 @@ void convertAndSend(String exchange, String routingKey, Object message, - + + The MessageListenerAdapter mentioned in + also uses a MessageConverter. + SimpleMessageConverter @@ -723,15 +726,20 @@ Object receiveAndConvert(String queueName) throws AmqpException;]]> - JsonMessageConverter + JsonMessageConverter and Jackson2JsonMessageConverter As mentioned in the previous section, relying on Java serialization is generally not recommended. One rather common alternative that is more flexible and portable across different - languages and platforms is JSON (JavaScript Object Notation). An - implementation is available and can be configured on any + languages and platforms is JSON (JavaScript Object Notation). Two + implementations are available and can be configured on any RabbitTemplate instance to override its usage of - the SimpleMessageConverter default. + the SimpleMessageConverter default. + The JsonMessageConverter which uses the + org.codehaus.jackson 1.x library + and Jackson2JsonMessageConverter which uses the + com.fasterxml.jackson 2.x library. + @@ -743,7 +751,16 @@ Object receiveAndConvert(String queueName) throws AmqpException;]]> ]]> - As shown above, the JsonMessageConverter uses a + + + + + + + + +]]> + As shown above, the JsonMessageConverter and Jackson2JsonMessageConverter 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 @@ -758,6 +775,15 @@ Object receiveAndConvert(String queueName) throws AmqpException;]]> ]]> + + + + + + + +]]>