Merge pull request #699 from garyrussell/INT-2874
* INT-2874: Fix JSON/JMS Incompatibility
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* 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.
|
||||
@@ -18,13 +18,13 @@ package org.springframework.integration.json;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.transformer.AbstractTransformer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Transformer implementation that converts a payload instance into a JSON string representation.
|
||||
@@ -32,6 +32,7 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
* @author Mark Fisher
|
||||
* @author James Carr
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ObjectToJsonTransformer extends AbstractTransformer {
|
||||
@@ -61,7 +62,7 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
|
||||
// only null assertion is needed since "" is a valid value
|
||||
Assert.notNull(contentType, "'contentType' must not be null");
|
||||
this.contentTypeExplicitlySet = true;
|
||||
this.contentType = contentType;
|
||||
this.contentType = contentType.trim();
|
||||
}
|
||||
|
||||
private String transformPayload(Object payload) throws Exception {
|
||||
@@ -80,11 +81,13 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
|
||||
|
||||
if (headers.containsKey(MessageHeaders.CONTENT_TYPE)) {
|
||||
if (this.contentTypeExplicitlySet){
|
||||
// override
|
||||
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
|
||||
// override, unless empty
|
||||
if (StringUtils.hasLength(this.contentType)) {
|
||||
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (StringUtils.hasLength(this.contentType)) {
|
||||
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
|
||||
}
|
||||
messageBuilder.copyHeaders(headers);
|
||||
|
||||
@@ -2106,11 +2106,17 @@
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="content-type" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to set 'content-type' Message header. In the case that a content-type header is already present,
|
||||
we will only override that value IF this attribute is explicitely set with
|
||||
content-type value (e.g., content-type="text/xml")
|
||||
</xsd:documentation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Allows you to set the 'content-type' Message header. When a 'content-type' header is already present
|
||||
on the input message, the transformer will only override that value IF this attribute is explicitly set
|
||||
(e.g., content-type="text/x-json").
|
||||
If this attribute is omitted, the transformer will set the 'content-type' header to "application/json",
|
||||
when there is no existing 'content-type' header on the input message.
|
||||
Setting the attribute to an empty string ("")
|
||||
will suppress setting the header to the default value,
|
||||
but will not remove the header, if present on the input message. If you wish to remove an existing
|
||||
header, use a <header-filter/> before or after the transformer.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="object-mapper" use="optional">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -31,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
@@ -40,12 +42,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class ObjectToJsonTransformerParserTests {
|
||||
|
||||
|
||||
@Autowired
|
||||
private volatile ApplicationContext context;
|
||||
|
||||
@@ -56,11 +59,15 @@ public class ObjectToJsonTransformerParserTests {
|
||||
private volatile MessageChannel customObjectMapperInput;
|
||||
|
||||
@Test
|
||||
public void testContextType(){
|
||||
ObjectToJsonTransformer transformer =
|
||||
public void testContentType(){
|
||||
ObjectToJsonTransformer transformer =
|
||||
TestUtils.getPropertyValue(context.getBean("defaultTransformer"), "handler.transformer", ObjectToJsonTransformer.class);
|
||||
assertEquals("application/json", TestUtils.getPropertyValue(transformer, "contentType"));
|
||||
|
||||
Message<?> transformed = transformer.transform(MessageBuilder.withPayload("foo").build());
|
||||
assertTrue(transformed.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE));
|
||||
assertEquals("application/json", transformed.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
|
||||
transformer =
|
||||
TestUtils.getPropertyValue(context.getBean("customTransformer"), "handler.transformer", ObjectToJsonTransformer.class);
|
||||
assertEquals("application/json", TestUtils.getPropertyValue(transformer, "contentType"));
|
||||
@@ -69,6 +76,13 @@ public class ObjectToJsonTransformerParserTests {
|
||||
TestUtils.getPropertyValue(context.getBean("emptyContentTypeTransformer"), "handler.transformer", ObjectToJsonTransformer.class);
|
||||
assertEquals("", TestUtils.getPropertyValue(transformer, "contentType"));
|
||||
|
||||
transformed = transformer.transform(MessageBuilder.withPayload("foo").build());
|
||||
assertFalse(transformed.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE));
|
||||
|
||||
transformed = transformer.transform(MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE, "foo").build());
|
||||
assertNotNull(transformed.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
assertEquals("foo", transformed.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
|
||||
transformer =
|
||||
TestUtils.getPropertyValue(context.getBean("overridenContentTypeTransformer"), "handler.transformer", ObjectToJsonTransformer.class);
|
||||
assertEquals("text/xml", TestUtils.getPropertyValue(transformer, "contentType"));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,24 +16,25 @@
|
||||
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.codehaus.jackson.JsonGenerator.Feature;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ObjectToJsonTransformerTests {
|
||||
@@ -71,13 +72,22 @@ public class ObjectToJsonTransformerTests {
|
||||
|
||||
@Test
|
||||
public void withProvidedContentTypeAsEmptyString() throws Exception {
|
||||
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer();
|
||||
transformer.setContentType("");
|
||||
Message<?> message = MessageBuilder.withPayload("foo").build();
|
||||
Message<?> result = transformer.transform(message);
|
||||
assertFalse(result.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withProvidedContentTypeAsEmptyStringDoesNotOverride() throws Exception {
|
||||
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer();
|
||||
transformer.setContentType("");
|
||||
Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE, "text/xml").build();
|
||||
Message<?> result = transformer.transform(message);
|
||||
assertEquals("", result.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
assertEquals("text/xml", result.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withProvidedContentTypeAsNull() throws Exception {
|
||||
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* 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.
|
||||
@@ -28,7 +28,6 @@ import javax.jms.JMSException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -47,8 +46,9 @@ import org.springframework.util.StringUtils;
|
||||
* Note that the JMSMessageID and JMSRedelivered flag are only copied
|
||||
* <em>from</em> a JMS Message. Those values will <em>not</em> be passed
|
||||
* along from a Spring Integration Message to an outbound JMS Message.
|
||||
*
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class DefaultJmsHeaderMapper implements JmsHeaderMapper {
|
||||
|
||||
@@ -68,9 +68,9 @@ public class DefaultJmsHeaderMapper implements JmsHeaderMapper {
|
||||
* for any JMS property that is being mapped into the MessageHeaders.
|
||||
* The Default is an empty string (no prefix).
|
||||
* <p/>
|
||||
* This does not affect the JMS properties covered by the specification/API,
|
||||
* This does not affect the JMS properties covered by the specification/API,
|
||||
* such as JMSCorrelationID, etc. The header names used for mapping such
|
||||
* properties are all defined in our {@link JmsHeaders}.
|
||||
* properties are all defined in our {@link JmsHeaders}.
|
||||
*/
|
||||
public void setInboundPrefix(String inboundPrefix) {
|
||||
this.inboundPrefix = (inboundPrefix != null) ? inboundPrefix : "";
|
||||
@@ -81,9 +81,9 @@ public class DefaultJmsHeaderMapper implements JmsHeaderMapper {
|
||||
* integration message header that is being mapped into the JMS Message.
|
||||
* The Default is an empty string (no prefix).
|
||||
* <p/>
|
||||
* This does not affect the JMS properties covered by the specification/API,
|
||||
* This does not affect the JMS properties covered by the specification/API,
|
||||
* such as JMSCorrelationID, etc. The header names used for mapping such
|
||||
* properties are all defined in our {@link JmsHeaders}.
|
||||
* properties are all defined in our {@link JmsHeaders}.
|
||||
*/
|
||||
public void setOutboundPrefix(String outboundPrefix) {
|
||||
this.outboundPrefix = (outboundPrefix != null) ? outboundPrefix : "";
|
||||
@@ -228,23 +228,31 @@ public class DefaultJmsHeaderMapper implements JmsHeaderMapper {
|
||||
|
||||
/**
|
||||
* Adds the outbound prefix if necessary.
|
||||
* Converts {@link MessageHeaders#CONTENT_TYPE} to content_type for JMS compliance.
|
||||
*/
|
||||
private String fromHeaderName(String headerName) {
|
||||
String propertyName = headerName;
|
||||
if (StringUtils.hasText(this.outboundPrefix) && !propertyName.startsWith(this.outboundPrefix)) {
|
||||
propertyName = this.outboundPrefix + headerName;
|
||||
}
|
||||
else if (MessageHeaders.CONTENT_TYPE.equals(headerName)) {
|
||||
propertyName = CONTENT_TYPE_PROPERTY;
|
||||
}
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the inbound prefix if necessary.
|
||||
* Converts content_type to {@link MessageHeaders#CONTENT_TYPE}.
|
||||
*/
|
||||
private String toHeaderName(String propertyName) {
|
||||
String headerName = propertyName;
|
||||
if (StringUtils.hasText(this.inboundPrefix) && !headerName.startsWith(this.inboundPrefix)) {
|
||||
headerName = this.inboundPrefix + propertyName;
|
||||
}
|
||||
else if (CONTENT_TYPE_PROPERTY.equals(propertyName)) {
|
||||
headerName = MessageHeaders.CONTENT_TYPE;
|
||||
}
|
||||
return headerName;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* 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.
|
||||
@@ -24,9 +24,14 @@ import org.springframework.integration.mapping.HeaderMapper;
|
||||
* Strategy interface for mapping integration Message headers to an outbound
|
||||
* JMS Message (e.g. to configure JMS properties) or extracting integration
|
||||
* header values from an inbound JMS Message.
|
||||
*
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public interface JmsHeaderMapper extends HeaderMapper<Message> {}
|
||||
public interface JmsHeaderMapper extends HeaderMapper<Message> {
|
||||
|
||||
static final String CONTENT_TYPE_PROPERTY = "content_type";
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* 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.
|
||||
@@ -28,14 +28,13 @@ import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.jms.DefaultJmsHeaderMapper;
|
||||
import org.springframework.integration.jms.JmsHeaders;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class DefaultJmsHeaderMapperTests {
|
||||
|
||||
@@ -115,6 +114,19 @@ public class DefaultJmsHeaderMapperTests {
|
||||
assertNull(jmsMessage.getJMSType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContentTypePropertyMappedFromHeader() throws JMSException {
|
||||
Message<String> message = MessageBuilder.withPayload("test")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, "foo")
|
||||
.build();
|
||||
DefaultJmsHeaderMapper mapper = new DefaultJmsHeaderMapper();
|
||||
javax.jms.Message jmsMessage = new StubTextMessage();
|
||||
mapper.fromHeaders(message.getHeaders(), jmsMessage);
|
||||
Object value = jmsMessage.getObjectProperty("content_type");
|
||||
assertNotNull(value);
|
||||
assertEquals("foo", value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserDefinedPropertyMappedFromHeader() throws JMSException {
|
||||
Message<String> message = MessageBuilder.withPayload("test")
|
||||
@@ -217,6 +229,17 @@ public class DefaultJmsHeaderMapperTests {
|
||||
assertEquals(timestamp, attrib);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContentTypePropertyMappedToHeader() throws JMSException {
|
||||
javax.jms.Message jmsMessage = new StubTextMessage();
|
||||
jmsMessage.setStringProperty("content_type", "foo");
|
||||
DefaultJmsHeaderMapper mapper = new DefaultJmsHeaderMapper();
|
||||
Map<String, Object> headers = mapper.toHeaders(jmsMessage);
|
||||
Object attrib = headers.get(MessageHeaders.CONTENT_TYPE);
|
||||
assertNotNull(attrib);
|
||||
assertEquals("foo", attrib);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserDefinedPropertyMappedToHeader() throws JMSException {
|
||||
javax.jms.Message jmsMessage = new StubTextMessage();
|
||||
|
||||
@@ -230,8 +230,18 @@ public class Kid {
|
||||
<important>
|
||||
<para>
|
||||
Beginning with version 2.2, the <code>object-to-json-transformer</code> sets the <emphasis>content-type</emphasis>
|
||||
header to <emphasis>application/json</emphasis>, by default. It can be overridden using the <code>content-type</code>
|
||||
attribute. The default behavior has a side affect - causing applications with the following
|
||||
header to <code>application/json</code>, by default, if the input message does not already have that header
|
||||
present.
|
||||
</para>
|
||||
<para>
|
||||
It you wish to set the <emphasis>content type</emphasis> header to some other value, or explicitly overwrite any existing header
|
||||
with some value (including <code>application/json</code>), use the <code>content-type</code>
|
||||
attribute. If you wish to suppress the setting of the header, set the <code>content-type</code>
|
||||
attribute to an empty string (<code>""</code>). This will result in a message with no <code>content-type</code>
|
||||
header, unless such a header was present on the input message.
|
||||
</para>
|
||||
<para>
|
||||
The behavior of adding the default header has a side affect - causing applications with the following
|
||||
sequence to fail:
|
||||
</para>
|
||||
<para>
|
||||
|
||||
Reference in New Issue
Block a user