From 1dc4ebe4d1673c9dacb32bd55768e265200c5b92 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Wed, 20 Apr 2016 13:37:30 -0400 Subject: [PATCH] INT-4001 Disable some Jackson features by default JIRA: https://jira.spring.io/browse/INT-4001 Since all other Spring Framework components use the Jackson's `ObjectMapper` configuration by default as: ``` this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); ``` It would be better to be consistent in Spring Integration, too. * Add mention options to the `Jackson2JsonObjectMapper` default's `ObjectMapper` * Add on more constructor into the `Jackson2JsonMessageParser` to allow to inject customized `Jackson2JsonObjectMapper` * Modify a couple tests to reflect the change * Reformat code style in the `AbstractJsonInboundMessageMapperTests` for better readability **The Migration Guide note follows up** --- .../json/Jackson2JsonMessageParser.java | 8 +- .../json/Jackson2JsonObjectMapper.java | 20 +++-- ...AbstractJsonInboundMessageMapperTests.java | 89 +++++++++++++------ .../json/JsonToObjectTransformerTests.java | 10 ++- 4 files changed, 88 insertions(+), 39 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonMessageParser.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonMessageParser.java index 6e712c1109..6526c170f8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonMessageParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonMessageParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -39,7 +39,11 @@ import com.fasterxml.jackson.core.JsonToken; public class Jackson2JsonMessageParser extends AbstractJacksonJsonMessageParser { public Jackson2JsonMessageParser() { - super(new Jackson2JsonObjectMapper()); + this(new Jackson2JsonObjectMapper()); + } + + public Jackson2JsonMessageParser(Jackson2JsonObjectMapper objectMapper) { + super(objectMapper); } @Override diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonObjectMapper.java index f0711574e2..c9ab470933 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonObjectMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonObjectMapper.java @@ -25,18 +25,26 @@ import java.net.URL; import java.util.Collection; import java.util.Map; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.integration.mapping.support.JsonHeaders; import org.springframework.util.Assert; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * Jackson 2 JSON-processor (@link https://github.com/FasterXML) {@linkplain JsonObjectMapper} implementation. * Delegates toJson and fromJson * to the {@linkplain com.fasterxml.jackson.databind.ObjectMapper} + *

+ * It customizes Jackson's default properties with the following ones: + *

* * @author Artem Bilan * @since 3.0 @@ -47,6 +55,8 @@ public class Jackson2JsonObjectMapper extends AbstractJacksonJsonObjectMapper mapper = JsonObjectMapperProvider.newInstance(); @Factory - public static Matcher> sameExceptImmutableHeaders(Message operand) { - return new MessageMatcher(operand); - } + public static Matcher> sameExceptImmutableHeaders(Message operand) { + return new MessageMatcher(operand); + } @Test public void testToMessageWithHeadersAndStringPayload() throws Exception { UUID id = UUID.randomUUID(); - String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":\"myPayloadStuff\"}"; - Message expected = MessageBuilder.withPayload("myPayloadStuff").setHeader("foo", 123).setHeader("bar", "abc").build(); + String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":\"myPayloadStuff\"}"; + Message expected = MessageBuilder.withPayload("myPayloadStuff") + .setHeader("foo", 123) + .setHeader("bar", "abc") + .build(); JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(String.class, getParser()); Message result = mapper.toMessage(jsonMessage); assertThat(result, sameExceptImmutableHeaders(expected)); @@ -80,8 +85,12 @@ public abstract class AbstractJsonInboundMessageMapperTests { public void testToMessageWithHeadersAndBeanPayload() throws Exception { TestBean bean = new TestBean(); UUID id = UUID.randomUUID(); - String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":" + getBeanAsJson(bean) + "}"; - Message expected = MessageBuilder.withPayload(bean).setHeader("foo", 123).setHeader("bar", "abc").build(); + String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":" + getBeanAsJson(bean) + "}"; + Message expected = MessageBuilder.withPayload(bean) + .setHeader("foo", 123) + .setHeader("bar", "abc") + .build(); JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(TestBean.class, getParser()); Message result = mapper.toMessage(jsonMessage); assertThat(result, sameExceptImmutableHeaders(expected)); @@ -101,8 +110,11 @@ public abstract class AbstractJsonInboundMessageMapperTests { public void testToMessageWithBeanHeaderAndStringPayload() throws Exception { TestBean bean = new TestBean(); UUID id = UUID.randomUUID(); - String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\", \"myHeader\":" + getBeanAsJson(bean) + "},\"payload\":\"myPayloadStuff\"}"; - Message expected = MessageBuilder.withPayload("myPayloadStuff").setHeader("myHeader", bean).build(); + String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\", \"myHeader\":" + + getBeanAsJson(bean) + "},\"payload\":\"myPayloadStuff\"}"; + Message expected = MessageBuilder.withPayload("myPayloadStuff") + .setHeader("myHeader", bean) + .build(); JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(String.class, getParser()); Map> headerTypes = new HashMap>(); headerTypes.put("myHeader", TestBean.class); @@ -114,10 +126,18 @@ public abstract class AbstractJsonInboundMessageMapperTests { @Test public void testToMessageWithHeadersAndListOfStringsPayload() throws Exception { UUID id = UUID.randomUUID(); - String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":[\"myPayloadStuff1\",\"myPayloadStuff2\",\"myPayloadStuff3\"]}"; + String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + + "\",\"foo\":123,\"bar\":\"abc\"}," + + "\"payload\":[\"myPayloadStuff1\",\"myPayloadStuff2\",\"myPayloadStuff3\"]}"; List expectedList = Arrays.asList("myPayloadStuff1", "myPayloadStuff2", "myPayloadStuff3"); - Message> expected = MessageBuilder.withPayload(expectedList).setHeader("foo", 123).setHeader("bar", "abc").build(); - JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new ParameterizedTypeReference>() { }.getType(), getParser()); + Message> expected = MessageBuilder.withPayload(expectedList) + .setHeader("foo", 123) + .setHeader("bar", "abc") + .build(); + Type type = new ParameterizedTypeReference>() { + + }.getType(); + JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(type, getParser()); Message result = mapper.toMessage(jsonMessage); assertThat(result, sameExceptImmutableHeaders(expected)); } @@ -127,10 +147,18 @@ public abstract class AbstractJsonInboundMessageMapperTests { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); UUID id = UUID.randomUUID(); - String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":[" + getBeanAsJson(bean1) + "," + getBeanAsJson(bean2) + "]}"; + String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":[" + getBeanAsJson(bean1) + + "," + getBeanAsJson(bean2) + "]}"; List expectedList = Arrays.asList(bean1, bean2); - Message> expected = MessageBuilder.withPayload(expectedList).setHeader("foo", 123).setHeader("bar", "abc").build(); - JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new ParameterizedTypeReference>() { }.getType(), getParser()); + Message> expected = MessageBuilder.withPayload(expectedList) + .setHeader("foo", 123) + .setHeader("bar", "abc") + .build(); + Type type = new ParameterizedTypeReference>() { + + }.getType(); + JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(type, getParser()); Message result = mapper.toMessage(jsonMessage); assertThat(result, sameExceptImmutableHeaders(expected)); } @@ -138,8 +166,12 @@ public abstract class AbstractJsonInboundMessageMapperTests { @Test public void testToMessageWithPayloadAndHeadersReversed() throws Exception { UUID id = UUID.randomUUID(); - String jsonMessage = "{\"payload\":\"myPayloadStuff\",\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\",\"foo\":123,\"bar\":\"abc\"}}"; - Message expected = MessageBuilder.withPayload("myPayloadStuff").setHeader("foo", 123).setHeader("bar", "abc").build(); + String jsonMessage = "{\"payload\":\"myPayloadStuff\",\"headers\":{\"timestamp\":1,\"id\":\"" + + id + "\",\"foo\":123,\"bar\":\"abc\"}}"; + Message expected = MessageBuilder.withPayload("myPayloadStuff") + .setHeader("foo", 123) + .setHeader("bar", "abc") + .build(); JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(String.class, getParser()); Message result = mapper.toMessage(jsonMessage); assertThat(result, sameExceptImmutableHeaders(expected)); @@ -188,26 +220,23 @@ public abstract class AbstractJsonInboundMessageMapperTests { } @Test - public void testToMessageInvalidFormatHeadersAndBeanPayloadWithMapToPayload() throws Exception { + public void testToMessageInvalidFormatHeadersAndBeanPayloadWithMapToPayloadNotFail() throws Exception { TestBean bean = new TestBean(); UUID id = UUID.randomUUID(); - String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\"},\"payload\":" + getBeanAsJson(bean) + "}"; + String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\"},\"payload\":" + + getBeanAsJson(bean) + "}"; JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(TestBean.class, getParser()); mapper.setMapToPayload(true); - try { - mapper.toMessage(jsonMessage); - fail(); - } - catch (IllegalArgumentException ex) { - //Expected - } + Message message = mapper.toMessage(jsonMessage); + assertEquals(bean, message.getPayload()); } @Test public void testToMessageWithHeadersAndPayloadTypeMappingFailure() throws Exception { TestBean bean = new TestBean(); UUID id = UUID.randomUUID(); - String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\"},\"payload\":" + getBeanAsJson(bean) + "}"; + String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\"},\"payload\":" + + getBeanAsJson(bean) + "}"; JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(Long.class, getParser()); try { mapper.toMessage(jsonMessage); @@ -222,7 +251,8 @@ public abstract class AbstractJsonInboundMessageMapperTests { public void testToMessageWithBeanHeaderTypeMappingFailure() throws Exception { TestBean bean = new TestBean(); UUID id = UUID.randomUUID(); - String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\",\"myHeader\":" + getBeanAsJson(bean) + "},\"payload\":\"myPayloadStuff\"}"; + String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\",\"myHeader\":" + + getBeanAsJson(bean) + "},\"payload\":\"myPayloadStuff\"}"; JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(String.class, getParser()); Map> headerTypes = new HashMap>(); headerTypes.put("myHeader", Long.class); @@ -238,8 +268,9 @@ public abstract class AbstractJsonInboundMessageMapperTests { private String getBeanAsJson(TestBean bean) throws Exception { - return mapper.toJson(bean); + return this.mapper.toJson(bean); } protected abstract JsonMessageParser getParser(); + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java index c78ffd3d16..abd6e78725 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java @@ -39,8 +39,11 @@ public class JsonToObjectTransformerTests { @Test public void objectPayload() throws Exception { JsonToObjectTransformer transformer = new JsonToObjectTransformer(TestPerson.class); - String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42,\"address\":{\"number\":123,\"street\":\"Main Street\"}}"; - Message message = transformer.transform(new GenericMessage(jsonString)); + // Since DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES is disabled by default (see Jackson2JsonObjectMapper) + // the extra "foo" property is ignored. + String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42," + + "\"address\":{\"number\":123,\"street\":\"Main Street\"}, \"foo\":\"bar\"}"; + Message message = transformer.transform(new GenericMessage<>(jsonString)); TestPerson person = (TestPerson) message.getPayload(); assertEquals("John", person.getFirstName()); assertEquals("Doe", person.getLastName()); @@ -68,7 +71,8 @@ public class JsonToObjectTransformerTests { @Test public void testBoonJsonObjectMapper() throws Exception { JsonToObjectTransformer transformer = new JsonToObjectTransformer(TestPerson.class, new BoonJsonObjectMapper()); - String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42,\"address\":{\"number\":123,\"street\":\"Main Street\"}}"; + String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42," + + "\"address\":{\"number\":123,\"street\":\"Main Street\"}}"; Message message = transformer.transform(new GenericMessage(jsonString)); TestPerson person = (TestPerson) message.getPayload(); assertEquals("John", person.getFirstName());