Search code examples
spring-integrationspring-integration-dsl

SI Outbound Gateway - unmarshalling the response back to JAXB class


Good afternoon,

I am using an Outbound Gateway to call a service that can consume (non-SOAP) XML and produces (non-SOAP) XML. I can marshall the JAXB class to request XML but the I can't unmarshal the response XML back to JAXB classes, the body of the payload is null.

The flow is as follows

                            .subFlowMapping("SomeRequestType", subflow -> subflow
                                    .transform(someRequestTransformer)
                                    .enrichHeaders(header -> header.header("Content-Type","application/xml"))
                                    .handle(someServiceOutboundGateway)
                                    .transform(someResponseTransformer)
                            )

From someServiceOutboundGateway

@Bean
public HttpMessageConverter m() {
    MarshallingHttpMessageConverter c = new MarshallingHttpMessageConverter();
    c.setMarshaller(someMarshaller());
    c.setUnmarshaller(someUnMarshaller());
    return c;
}

@Bean(name="someServiceOutboundGateway")
public MessageHandler someOutboundGateway() {
    return Http.outboundGateway(someUrl, lnquiHttp())
            .httpMethod(HttpMethod.POST)
            .expectedResponseType(Response.class)
            .get();
}

@Bean
public RestTemplate lnquiHttp() {
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
    connectionManager.setMaxTotal(connections);
    connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);

    RequestConfig requestConfig = RequestConfig
            .custom()
            .setConnectionRequestTimeout(timeout) // timeout to get connection from pool
            .setSocketTimeout(timeout) // standard connection timeout
            .setConnectTimeout(timeout) // standard connection timeout
            .build();

    HttpClient httpClient = HttpClientBuilder.create()
            .setConnectionManager(connectionManager)
            .setDefaultRequestConfig(requestConfig).build();

    ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);

    RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
    RestTemplate restTemplate = restTemplateBuilder
            .requestFactory(requestFactory)
            .basicAuthorization(userName, password)
            .messageConverters(m())
            .build();

    return restTemplate;
}

@Bean
public Marshaller someMarshaller() {
    final Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setContextPath(CONTEXTPATH_REQUEST);
    marshaller.setSchema(responseSchema);
    marshaller.setSupportJaxbElementClass(Boolean.TRUE);
    return  marshaller;
}

@Bean
public Unmarshaller someUnMarshaller() {
    final Jaxb2Marshaller unmarshaller = new Jaxb2Marshaller();
    unmarshaller.setContextPath(CONTEXTPATH_RESPONSE);
    unmarshaller.setSchema(responseSchema);
    unmarshaller.setSupportJaxbElementClass(Boolean.TRUE);
    return  unmarshaller;
}

@Bean(name = "someJAXBContext")
public JAXBContext someJAXBContext() throws JAXBException {
    return JAXBContext.newInstance(Response.class);
}

from someResponseTransformer

@Component
public class SomeResponseTransformer implements GenericHandler<Object> {

    @Override
    public Object handle(final Object payload, final Map<String, Object> headers) {
        ResponseEntity responseEntity = (ResponseEntity)payload;
        Object body = responseEntity.getBody();
        Response lnqiResponse = (Response)body;
        ... = buildHeader(lnqiResponse.getHeader());

When I am trying the get the header (lnqiResponse.getHeader()) I am getting a NullPointerException.

Any idea please how to unmarshall the response to the Response? Any help would be appreciated! Thanks!


Solution

  • After unmarshalling the input payload for your SomeResponseTransformer.handle() method is not going to be a ResponseEntity, but rather that .expectedResponseType(Response.class).

    I'm not sure what is CONTEXTPATH_RESPONSE, but I usually prefer to use setClassesToBeBound() instead. Also, the Jaxb2Marshaller by default expects from us an @XmlRootElement on the class to be bound.

    Then, when response arrives from the server, you need to be sure that Content-Type header is what is supported by that MarshallingHttpMessageConverter:

    /**
     * Protected constructor that sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes}
     * to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}.
     */
    protected AbstractXmlHttpMessageConverter() {
        super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml"));
    }
    

    It might be better from here to start debugging your calls and see what RestTemplate does in its doExecute() around requestCallback.doWithRequest(request); and responseExtractor.extractData(response).