3

I am using an integration flow to call a RESTful web service as follows:

@Bean
IntegrationFlow flow() throws Exception {
    return IntegrationFlows.from("inputChannel")
            .handle(Http.outboundGateway("http://provider1.com/...")
                    .httpMethod(HttpMethod.GET)
                    .expectedResponseType(ItemDTO[].class))
            .get();
}

In fact, the code above works perfectly. As I understand from the documentation, Spring integration's http outbound-gateway uses an instance of RestTemplate to convert the Http response body to an array of ItemDTOs.

Let us now consider the following code:

@Bean
IntegrationFlow flow() throws Exception {
    return IntegrationFlows.from("inputChannel")
            .handle(Http.outboundGateway("http://provider2.com/...")
                    .httpMethod(HttpMethod.GET)
                    .expectedResponseType(String.class))
            .<String,String>transform(m -> sirenToHal(m))
            .transform(Transformers.fromJson(ItemDTO[].class))
            .get();
}

In this case, the Http response body is converted into a String, which is passed to a transformer (e.g. in my actual project, I use JOLT to convert from a siren document to a HAL -- JSON resource representations). Then, I instantiate a transformer to handle the mapping of JSON to java objects. Suprisingly, the code above fails (e.g. in my project, the transformer throws a UnrecognizedPropertyException).

The reason of the failure seems to be that the Object mapper used by the transformer is not configured in the same way as the RestTemplate. I wonder why the transformer does not use the same ObjectMapper as the instance of RestTemplate or, at least, why they do not use the same configuration (i.e. Spring boot's global configuration). Anyway, is there anyway configure the ObjectMapper to be used by the transformer?

Update

I found out how to configure the tranformer's object mapper.

First, we create and configure an instance of Jackson's ObjectMapper, as follows:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// + any other additional configuration setting

Then, we instantiate the transformer as follows (replacing the corresponding line in the code above):

.transform(Transformers.fromJson(ItemDTO[].class, new Jackson2JsonObjectMapper(mapper)))

I still think the ObjectMapper used by the transformer should take Spring boot's global configuration.

user3329862
  • 83
  • 2
  • 6

1 Answers1

1

That's an interesting point, but since you don't inject RestTemple into the Http.outboundGateway(), neither even MappingJackson2HttpMessageConverter, I only see difference between ObjectToJsonTransformer and MappingJackson2HttpMessageConverter, that Spring Integration uses just new ObjectMapper(), when the Spring Web does this in its code:

public MappingJackson2HttpMessageConverter() {
    this(Jackson2ObjectMapperBuilder.json().build());
}

Even if Spring Boot auto-configure ObjectMapper bean I don't see any reference from this hard code to all those feature.

So, please, confirm that Jackson2ObjectMapperBuilder.json().build() works for you there, too and feel free to raise a JIRA ticket to harmonize the Spring Integration ObjectMapper infrastructure with Spring Web.

UPDATE

OK. Seems for me I found the difference. And I was correct - Jackson2ObjectMapperBuilder:

// Any change to this method should be also applied to spring-jms and spring-messaging
// MappingJackson2MessageConverter default constructors
private void customizeDefaultFeatures(ObjectMapper objectMapper) {
    if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
        configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false);
    }
    if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
        configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }
}

So, we have to apply this options to Spring Integration as well. Also see MappingJackson2MessageConverter.

The JIRA on the matter: https://jira.spring.io/browse/INT-4001

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Again, thank you very much for your help Artem. I have another question tightly connected with my example: is it possible to configure JSON transformer to handle collections? In my example code, I can use `ParameterizedTypeReference` with the outbound-gateway component, but the same cannot be used with the JSON transformer. Can you add this as a improvement for future releases? Let me know if you prefer that I write a new post for this question. – user3329862 Apr 21 '16 at 14:24
  • Well `ParameterizedTypeReference` is a part of Spring Web API, and doesn't relate to Jackson at all. We have the hooks in the `JsonToObjectTransformer` to the possible Jackson's types tricks over `JsonHeaders`. See `JSON Transformers` section in the http://docs.spring.io/spring-integration/reference/html/messaging-transformation-chapter.html#transformer. For collection type you have to use `JsonHeaders.TYPE_ID` and `JsonHeaders.CONTENT_TYPE_ID` headers in the request `Message`. But from other side instead of collection you can specify the array type, e.g. `type="com.my.proj.tPerson[]"`. – Artem Bilan Apr 21 '16 at 14:42
  • What about Jackson's `TypeReference`? I mean, it would be great if we have something like: `Transformers.fromJson(new TypeReference() {})`. I think this would simplify, for instance, the aggregation of message groups as in my example [here](http://stackoverflow.com/questions/36742888/spring-integration-java-dsl-configuration-of-aggregator). – user3329862 Apr 21 '16 at 15:02
  • Yeah, I see your point. But Spring Integration transformers are free from the target JSON engine implementation. We use our own `JsonObjectMapper` abstraction. As I said: feel free to raise a JIRA, and we'll consider what to do with the `ParameterizedTypeReference`. When it appears in the SI core, we may add such a trick into the DSL `Transformers.fromJson()` factory. – Artem Bilan Apr 21 '16 at 15:21