INT-2874 Fix JSON/JMS Incompatibility

2.2.0 changed ObjectToJsonTransformr to add content-type to
simplify AMQP applications.

However, in a JMS environment, the DefaultJmsHeaderMapper attempted
to map (and failed) to map the header, emitting a warning log entry.

JMS does not allow '-' in property names.

Add code such that:

1. If the ObjectToJsonTransformer is configured to use a
content-type of an empty String (after trimming), suppress the
addition of the header to the output message. For consistency, if
the inbound message already has a content type header, and the transformer
is configured with an empty String, remove the header.

2. Change the DefaultJmsHeaderMapper to map the MessageHeaders.CONTENT_TYPE
header (content-type) to/from a JMS compliant property name (content_type).

INT-2874 PR Comments

Move constant to JmsHeaderMapper interface and rename.

Fix failing test (explicit set content-type to "").

INT-2874 Documentation Updates

Add docbook and schema documentation clarifying the behavior
of the transformer with respect to setting the `content-type`
header.

INT-2874 Polishing - Remove Header Removal

After further discussion, we decided to not remove an
existing header, if 'content-type' is set to "".

This is because there was no way to handle the case of
NOT adding a header when none present, while retaining a
header if it was already present.

added test for empty content-type attrib and no existing header (while merging)
This commit is contained in:
Gary Russell
2013-01-08 15:28:42 -05:00
committed by Mark Fisher
parent ed3cfe0271
commit 3902c96a20
8 changed files with 117 additions and 38 deletions

View File

@@ -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);

View File

@@ -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">

View File

@@ -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"));

View File

@@ -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();