1
votes

I am sending JMS messages with XML payloads and some custom headers contentType with values text/json and text/xml using Jmeter.

My Spring integration configuration looks like this:

<jms:message-driven-channel-adapter channel="jmsInChannel" destination-name="queue.demo" connection-factory="jmsConnectionFactory1" />
<int:channel id="jmsInChannel" />

<int:header-value-router input-channel="jmsInChannel" header-name="contentType" default-output-channel="nullChannel">
    <int:mapping value="text/json" channel="jsonTransformerChannel" />
    <int:mapping value="text/xml" channel="xmlTransformerChannel" />
</int:header-value-router>

Up to this point, everything works perfectly, the messages are successfully routed to their respective transformers.

My problem is, when it's an XML payload I first used a JAXB Unmarshaller along with <ixml:unmarshalling-transofmer ../> provided by http://www.springframework.org/schema/integration/xml.

I could get the payload, but it couldn't process the message afterwards as a JMS message, it became a pure POJO. So I lost the headers, and I couldn't use <int: ../> components without serializing the POJO, which is not what I wanted to achieve.

I have found a work-around, where I defined my own Unmarshalling bean in java like so:

<int:channel id="xmlTransformerChannel" />
<int:transformer input-channel="xmlTransformerChannel" ref="xmlMsgToCustomerPojoTransformer" output-channel="enrichInChannel" />

method:

@SuppressWarnings("rawtypes")
public Message transform(String message) {

    logger.info("Message Received \r\n" + message);

    try {
        MyModel myModel = (MyModel) unmarshaller.unmarshal(new StreamSource(new StringReader(message)));
        return MessageBuilder.withPayload(myModel).build();

    } catch (XmlMappingException e) {
        return MessageBuilder.withPayload(e).build();
    } catch (Exception e) {
        return MessageBuilder.withPayload(e).build();
    }
}

I could successfully process the message as a Spring integration Message, but I lost the original custom JMS headers.

In contrast, all I had to do to transform the json payloads and keep the Message format AND preserving my custom headers is this xml configuration:

<int:channel id="jsonTransformerChannel" />
<int:json-to-object-transformer input-channel="jsonTransformerChannel" output-channel="enrichInChannel" type="com.alrawas.ig5.MyModel" />

My question is, how to keep the original custom JMS headers after unmarshalling the xml payload?

UPDATE:

I did try to write the xml transformer this way, taking Message as input instead of only string, it did not throw exception in this method, but it did later in the routing phase

public Message<?> transform(Message<String> message) {

    logger.info("Message Received \r\n" + message);

    try {
        MyModel myModel = (MyModel) unmarshaller.unmarshal(new StreamSource(new StringReader(message.getPayload())));
        return (Message<MyModel>) MessageBuilder.withPayload(myModel).copyHeaders(message.getHeaders()).build();

    } catch (XmlMappingException e) {
        return MessageBuilder.withPayload(e).build();
    } catch (Exception e) {
        return MessageBuilder.withPayload(e).build();
    }
}

I ran into a problem in a component I use later in this flow:

<int:object-to-json-transformer input-channel="outr" output-channel="outch" />
<int:router method="route" input-channel="outch" default-output-channel="nullChannel">
    <bean class="com.alrawas.ig5.MyCustomRouter" />
</int:router>

In my route method threw Cannot cast String to com.alrawas.ig5.MyModel Exception:

public class MyCustomRouter {

    public String route(Message<MyModel> myModel) {

    Integer tenNumber = myModel.getPayload().getNumber(); // <-- Cast Exception here
    System.out.println(myModel);
    return (tenNumber % 10 == 0) ? "stayLocal" : "goRemote";

    }
}

This Cast Exception only happens after unmarshalling xml, JSON payloads work fine without losing headers or throwing cast exceptions.

UPDATE:

Check my answer below: contentType was not really a custom header

2

2 Answers

1
votes

When you develop custom transformer, you need to keep in mind that returning a Message<?> leads you to a fully control over its content.

When transformer function returns a Message, it doesn't populate any request headers.

So, your public Message transform(String message) { must expect a Message as an input and you need to copy all the headers from the request message to reply message. There are appropriate methods on the MessageBuilder.

On the other hand it is fully unclear why do you need to return a Message here since everything in the Spring Integration is going to be wrapped to Message before sending to the output channel.

0
votes

The take:

The ClassCastException was happening later in the last router, was because I named my custom header contentType. Which was a default jms header used internally. When I changed its value to text/xml, that last router String route(Message<MyModel> myModel) was trying to cast the json into MyModel, and it failed because the header was no more application/json like it should be, it was text/xml. Which caused the ClassCastException.

So I got rid of the custom xml unmarshalling logic bean. I renamed my custom header. And used <ixml:unmarshalling-transformer ../>.

It worked using xml configuration without additional custom java beans.