The default is {@link org.springframework.jms.support.converter.MessageType#BYTES}, i.e. this converter marshals to + * a {@link javax.jms.BytesMessage}. Note that the default version of this converter + * supports {@link org.springframework.jms.support.converter.MessageType#BYTES} and {@link org.springframework.jms.support.converter.MessageType#TEXT} only. + * @see org.springframework.jms.support.converter.MessageType#BYTES + * @see org.springframework.jms.support.converter.MessageType#TEXT + */ + public void setTargetType(MessageType targetType) { + Assert.notNull(targetType, "MessageType must not be null"); + this.targetType = targetType; + } + + /** + * Specify the encoding to use when converting to and from text-based + * message body content. The default encoding will be "UTF-8". + *
When reading from a a text-based message, an encoding may have been + * suggested through a special JMS property which will then be preferred + * over the encoding set on this MessageConverter instance. + * @see #setEncodingPropertyName + */ + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + /** + * Specify the name of the JMS message property that carries the encoding from + * bytes to String and back is BytesMessage is used during the conversion process. + *
Default is none. Setting this property is optional; if not set, UTF-8 will + * be used for decoding any incoming bytes message. + * @see #setEncoding + */ + public void setEncodingPropertyName(String encodingPropertyName) { + this.encodingPropertyName = encodingPropertyName; + } + + /** + * Specify the name of the JMS message property that carries the type id for the + * contained object: either a mapped id value or a raw Java class name. + *
Default is none. NOTE: This property needs to be set in order to allow + * for converting from an incoming message to a Java object. + * @see #setTypeIdMappings + */ + public void setTypeIdPropertyName(String typeIdPropertyName) { + this.typeIdPropertyName = typeIdPropertyName; + } + + /** + * Specify mappings from type ids to Java classes, if desired. + * This allows for synthetic ids in the type id message property, + * instead of transferring Java class names. + *
Default is no custom mappings, i.e. transferring raw Java class names.
+ * @param typeIdMappings a Map with type id values as keys and Java classes as values
+ */
+ public void setTypeIdMappings(Map The default implementation throws an {@link IllegalArgumentException}.
+ * @param object the object to marshal
+ * @param session the JMS Session
+ * @param objectMapper the mapper to use
+ * @param targetType the target message type (other than TEXT or BYTES)
+ * @return the resulting message
+ * @throws javax.jms.JMSException if thrown by JMS methods
+ * @throws java.io.IOException in case of I/O errors
+ */
+ protected Message mapToMessage(Object object, Session session, ObjectMapper objectMapper, MessageType targetType)
+ throws JMSException, IOException {
+
+ throw new IllegalArgumentException("Unsupported message type [" + targetType +
+ "]. MappingJacksonMessageConverter by default only supports TextMessages and BytesMessages.");
+ }
+
+ /**
+ * Set a type id for the given payload object on the given JMS Message.
+ * The default implementation consults the configured type id mapping and
+ * sets the resulting value (either a mapped id or the raw Java class name)
+ * into the configured type id message property.
+ * @param object the payload object to set a type id for
+ * @param message the JMS Message to set the type id on
+ * @throws javax.jms.JMSException if thrown by JMS methods
+ * @see #getJavaTypeForMessage(javax.jms.Message)
+ * @see #setTypeIdPropertyName(String)
+ * @see #setTypeIdMappings(java.util.Map)
+ */
+ protected void setTypeIdOnMessage(Object object, Message message) throws JMSException {
+ if (this.typeIdPropertyName != null) {
+ String typeId = this.classIdMappings.get(object.getClass());
+ if (typeId == null) {
+ typeId = object.getClass().getName();
+ }
+ message.setStringProperty(this.typeIdPropertyName, typeId);
+ }
+ }
+
+
+ /**
+ * Convenience method to dispatch to converters for individual message types.
+ */
+ private Object convertToObject(Message message, JavaType targetJavaType) throws JMSException, IOException {
+ if (message instanceof TextMessage) {
+ return convertFromTextMessage((TextMessage) message, targetJavaType);
+ }
+ else if (message instanceof BytesMessage) {
+ return convertFromBytesMessage((BytesMessage) message, targetJavaType);
+ }
+ else {
+ return convertFromMessage(message, targetJavaType);
+ }
+ }
+
+ /**
+ * Convert a TextMessage to a Java Object with the specified type.
+ * @param message the input message
+ * @param targetJavaType the target type
+ * @return the message converted to an object
+ * @throws javax.jms.JMSException if thrown by JMS
+ * @throws java.io.IOException in case of I/O errors
+ */
+ protected Object convertFromTextMessage(TextMessage message, JavaType targetJavaType)
+ throws JMSException, IOException {
+
+ String body = message.getText();
+ return this.objectMapper.readValue(body, targetJavaType);
+ }
+
+ /**
+ * Convert a BytesMessage to a Java Object with the specified type.
+ * @param message the input message
+ * @param targetJavaType the target type
+ * @return the message converted to an object
+ * @throws javax.jms.JMSException if thrown by JMS
+ * @throws java.io.IOException in case of I/O errors
+ */
+ protected Object convertFromBytesMessage(BytesMessage message, JavaType targetJavaType)
+ throws JMSException, IOException {
+
+ String encoding = this.encoding;
+ if (this.encodingPropertyName != null && message.propertyExists(this.encodingPropertyName)) {
+ encoding = message.getStringProperty(this.encodingPropertyName);
+ }
+ byte[] bytes = new byte[(int) message.getBodyLength()];
+ message.readBytes(bytes);
+ try {
+ String body = new String(bytes, encoding);
+ return this.objectMapper.readValue(body, targetJavaType);
+ }
+ catch (UnsupportedEncodingException ex) {
+ throw new MessageConversionException("Cannot convert bytes to String", ex);
+ }
+ }
+
+ /**
+ * Template method that allows for custom message mapping.
+ * Invoked when {@link #setTargetType} is not {@link org.springframework.jms.support.converter.MessageType#TEXT} or
+ * {@link org.springframework.jms.support.converter.MessageType#BYTES}.
+ * The default implementation throws an {@link IllegalArgumentException}.
+ * @param message the input message
+ * @param targetJavaType the target type
+ * @return the message converted to an object
+ * @throws javax.jms.JMSException if thrown by JMS
+ * @throws java.io.IOException in case of I/O errors
+ */
+ protected Object convertFromMessage(Message message, JavaType targetJavaType)
+ throws JMSException, IOException {
+
+ throw new IllegalArgumentException("Unsupported message type [" + message.getClass() +
+ "]. MappingJacksonMessageConverter by default only supports TextMessages and BytesMessages.");
+ }
+
+ /**
+ * Determine a Jackson JavaType for the given JMS Message,
+ * typically parsing a type id message property.
+ * The default implementation parses the configured type id property name
+ * and consults the configured type id mapping. This can be overridden with
+ * a different strategy, e.g. doing some heuristics based on message origin.
+ * @param message the JMS Message to set the type id on
+ * @throws javax.jms.JMSException if thrown by JMS methods
+ * @see #setTypeIdOnMessage(Object, javax.jms.Message)
+ * @see #setTypeIdPropertyName(String)
+ * @see #setTypeIdMappings(java.util.Map)
+ */
+ protected JavaType getJavaTypeForMessage(Message message) throws JMSException {
+ String typeId = message.getStringProperty(this.typeIdPropertyName);
+ if (typeId == null) {
+ throw new MessageConversionException("Could not find type id property [" + this.typeIdPropertyName + "]");
+ }
+ Class mappedClass = this.idClassMappings.get(typeId);
+ if (mappedClass != null) {
+ return this.objectMapper.getTypeFactory().constructType(mappedClass);
+ }
+ try {
+ return this.objectMapper.getTypeFactory().constructType(
+ ClassUtils.forName(typeId, getClass().getClassLoader()));
+ }
+ catch (Throwable ex) {
+ throw new MessageConversionException("Failed to resolve type id [" + typeId + "]", ex);
+ }
+ }
+
+}
diff --git a/org.springframework.jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java b/org.springframework.jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java
new file mode 100644
index 0000000000..9416822af0
--- /dev/null
+++ b/org.springframework.jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java
@@ -0,0 +1,175 @@
+/*
+ * 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.jms.support.converter;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import javax.jms.BytesMessage;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Arjen Poutsma
+ * @author Dave Syer
+ */
+public class MappingJackson2MessageConverterTests {
+
+ private MappingJackson2MessageConverter converter;
+
+ private Session sessionMock;
+
+ @Before
+ public void setUp() throws Exception {
+ sessionMock = createMock(Session.class);
+ converter = new MappingJackson2MessageConverter();
+ converter.setEncodingPropertyName("__encoding__");
+ converter.setTypeIdPropertyName("__typeid__");
+ }
+
+ @Test
+ public void toBytesMessage() throws Exception {
+ BytesMessage bytesMessageMock = createMock(BytesMessage.class);
+ Date toBeMarshalled = new Date();
+
+ expect(sessionMock.createBytesMessage()).andReturn(bytesMessageMock);
+ bytesMessageMock.setStringProperty("__encoding__", "UTF-8");
+ bytesMessageMock.setStringProperty("__typeid__", Date.class.getName());
+ bytesMessageMock.writeBytes(isA(byte[].class));
+
+ replay(sessionMock, bytesMessageMock);
+
+ converter.toMessage(toBeMarshalled, sessionMock);
+
+ verify(sessionMock, bytesMessageMock);
+ }
+
+ @Test
+ public void fromBytesMessage() throws Exception {
+ BytesMessage bytesMessageMock = createMock(BytesMessage.class);
+ Map