Camel setting exchange headers in a custom dataformat

Apache Camel is a great framework with dozens (hundreds even) components, dataformats and expression languages. However one of the thing that makes Camel even greater is the various ways to provide your own customizations to these items. In this blog we are going to create a custom dataformat used in the marshal and unmarshal statements within a Camel route.

Creating your custom data format is pretty straightforward, all we have to do is provide an implementation of the Dataformat interface which looks like this:


public interface DataFormat {
void marshal(Exchange var1, Object var2, OutputStream var3) throws Exception;
Object unmarshal(Exchange var1, InputStream var2) throws Exception;
}

Within the marshal method you can take whatever is on the Exchange or the body of the exchange, which is put in the second argument and serialize it into an OutputStream object. The unmarshal method however threw me off a bit by also taking the exchange as an argument. Let me explain.
The Object returned from the unmarshal method is, normally, put in the exchange body. But what if you for some reason need to put some parts of the original message into exchange headers instead of the body. I initially thought that I could leverage the exchange object being passed as an argument into the unmarshal method. However this did not seem to work. It seems the exchange object is only used as in imput of the unmarshal method as a parameter but this is not the same reference as the exchange object used in the route downstream.

Message to the rescue!

One elegant way to set both the exchange body and headers is to use the Camel Message interface. Where we can both set the exchange body and headers.


public interface Message {
...
void setHeader(String var1, Object var2);
...
Map<String, Object> getHeaders();
void setHeaders(Map<String, Object> var1);
...
void setBody(Object var1);
<T> void setBody(Object var1, Class<T> var2);
…

Returning the message object from the unmarshal method in the custom dataformat respects the message object and places this on the exchange using the Camel typeconversion method.

Samples

In order to demonstrate this we create two of the silliest of dataformats. The string reverser. One returning the body, the other returning the message.

We begin by implementing the unmarshal method returning a String:


@Override
public Object unmarshal(Exchange exchange, InputStream inputStream) throws Exception {
String originalBody = exchange.getIn().getBody(String.class);
String reversedBody = new StringBuilder(originalBody).reverse().toString();

//The statement below does nothing
exchange.getIn().setHeader("MyAwesomeHeader", reversedBody);

return reversedBody;
}

However the setHeader on the exchange is ignored as we can see in the log:


2018-01-10 17:53:01,656 [main ] INFO route1 - original body: Hello world

2018-01-10 17:53:01,659 [main ] INFO route1 - the body contains: dlrow olleH

2018-01-10 17:53:01,660 [main ] INFO route1 - the headers contain: {breadcrumbId=ID-15-9530-37168-1515603181324-0-1}

Even Modifiying the unmarshal method to return the exchange does not help:


@Override
public Object unmarshal(Exchange exchange, InputStream inputStream) throws Exception {
String originalBody = exchange.getIn().getBody(String.class);
String reversedBody = new StringBuilder(originalBody).reverse().toString();
//The statement below does nothing
exchange.getIn().setHeader("MyAwesomeHeader", reversedBody);
return reversedBody;
}

}

 


2018-01-10 17:50:08,847 [main ] INFO route1 - original body: Hello world

2018-01-10 17:50:08,849 [main ] INFO route1 - the body contains: Hello world

2018-01-10 17:50:08,850 [main ] INFO route1 - the headers contain: {breadcrumbId=ID-15-9530-36027-1515603008530-0-1}

lastly we create a dataformat returning a Camel Message:


@Override
public Object unmarshal(Exchange exchange, InputStream inputStream) throws Exception {
//set the message to the original message of the Exchange. This preserves the body and all headers previously present on the exchange.
Message response = exchange.getIn();

String originalBody = exchange.getIn().getBody(String.class);
String reversedBody = new StringBuilder(originalBody).reverse().toString();

response.setBody(reversedBody, String.class);
response.setHeader("MyAwesomeHeader", reversedBody);

return response;
}

Now when we look at the log we can see our header being set:


2018-01-10 17:56:04,135 [main ] INFO route2 - original body: Hello world
2018-01-10 17:56:04,138 [main ] INFO route2 - the body contains: dlrow olleH
2018-01-10 17:56:04,139 [main ] INFO route2 - the headers contain: {breadcrumbId=ID-15-9530-37739-1515603363797-0-1, MyAwesomeHeader=dlrow olleH}

Example code can be found on GitHub-Mark-120px-plus