Apache Camel: can message have multiple objects in body (with different classes)? - apache-camel

I have almost ready application in java that use jms with Camel. Pop up that we I have to add additional infomations in exchange/message. Lets say that those additional infomations are in fact new java object. What is the best way to add my new object to exchange?
I have a lot of Camel processors processing the message that look like this:
public class MyProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
String s = exchange.getIn().getBody(String.class);
s = magicalTransform(s);
exchange.getIn().setBody(s, String.class);
//Now I have to add object of some Info.cass:
Info info = new Info( new Date() );
//Can I add it like this? :
exchange.getIn().setBody(info, Info.class);
}
}
The problem is that I can't find information if I can add many objects to Message. The Message method: setBody(Object body, Class type) suggest that it is possible, but there is also method: getBody() that sugesst that there is only one body class.
If I can't do it in this way, then what's the best way? I could try to wrap String that I transform and info in to one class, and put that new class in to message, but It will cause change the way obtaining String in every Processor. I want to avoid that.

The body of an Exchange is a single Object. If you want to add multiple objects to the body of your exchange you need to make the body of the exchange a map, list, or pojo with fields that you set all of your objects within.

Related

Apache Camel route to pojo

I have an Account Instance that is not in the format I need. I am trying Camel
but just not getting
the basic understanding. I have the un-formatted Instance successfully coming in and I display it in the log.
I have tried to extract the data from the body using a processor and directly to a POJO.
The POJO uses fixed length definitions. Which is needed do to passing data to a Mainframe DB. Please point me in a direction.
Got message:12 xZZ200ZZZZZZZ
public class AccountAddRoute extends RouteBuilder{
public void configure() throws Exception {
DataFormat bindy = new BindyFixedLengthDataFormat(AccountAddData.class);
from("direct:input")
.log(" In AccountAddRoute ")
.marshal(bindy)
.log("Got message:${body}");
.to(???????_)
My Apologies. Not a good explanation of what I am trying to do.
I have An Account POJO that is being built thru FORM. I need to save
it in 2 different formats. Incoming just the way it is and in a
fixed format to be sent to Mainframe. This is what I get from the form
Code1=12 x , Code2=200
I initiate rout by sending body of form in accountAddData Instance
producerTemplate.sendBody("direct:input", accountAddData);
public class AccountAddRoute extends RouteBuilder{
public void configure() throws Exception {
DataFormat bindy = new BindyFixedLengthDataFormat(AccountAddData.class);
from("direct:input")
.log(" In AccountAddRoute ")
.marshal(bindy)
.log("Got message:${body}");
.to(???????_)
Just for Testing purposes my Account Bindy Pojo is below
#DataField(pos =1, length=15, paddingChar='Z', trim=true, align="L")
private String Code1;
#DataField(pos =2, length=10, paddingChar='Z', align="L")
private String Code2;
When I log Body, I get >>
Got message:12 xZZ200ZZZZZZZ
However, my question is how do I create the new Instance in the
BINDY Format SO I can use it
==========
I did add processor and when I do display of body in processor >>
log.info(" In MyProcessor = " + exchange.getIn().getBody());
In MyProcessor = [B#3338b2b9
I was expecting to have the getter/setters available after getBody for the
accoountAddData instance. But obviously I don’t have a good understanding
of camel
exchange.getIn().getBody().????
Are you trying to unmarshal the incoming data? If thats the case, then you would need to use the following code.
from("direct:input")
.log(" In AccountAddRoute ")
.unmarshal(new BindyFixedLengthDataFormat(AccountAddData.class))
.log("Got message:${body}");
.to(???????_)

How to pass parameters to a Camel route?

It is possible to pass parameters to a Camel route?, for instance, in the next code snippet:
public class MyRoute extends RouteBuilder {
public void configure() throws Exception {
from("direct:start")
.to("cxf:bean:inventoryEndpoint?dataFormat=PAYLOAD");
}
}
The value for dataFormat is in hard code, but, what if I want set it dynamically?, passing a value from the code where route is called. I know this is possible adding a constructor and passing parameters in it, like this:
public class MyRoute extends RouteBuilder {
private String type;
public MyRoute(String type){
this.type = type;
}
public void configure() throws Exception {
from("direct:start")
.to("cxf:bean:inventoryEndpoint?dataFormat=" + type);
}
}
There is another way?
Thanks so much!
As you mentioned, you can use a constructor (or setters or any other Java/Framework instruments) if the parameters are static from a Camel point of view.
The parameters are configurable in the application, but after the application is started they do no more change. So, every message processed by the Camel route uses the same value.
In contrast, when the parameters are dynamic - i.e. they can change for every processed message, you can use the dynamic endpoint toD() of Camel. These endpoint addresses can contain expressions that are computed on runtime. For example the route
from("direct:start")
.toD("${header.foo}");
sends messages to a dynamic endpoint and takes the value from the message header named foo.
Or to use your example
.toD("cxf:bean:inventoryEndpoint?dataFormat=${header.dataFormat}");
This way you can set the dataformat for every message individually through a header.
You can find more about dynamic endpoints on this Camel documentation page

how to set an exchange property globally in apache camel

For example:
from("direct:test")
.multicast()
.to("direct:req1","direct:req2");
from("direct:req1")
.to(cxf:bean:endpoint1)
.process("response1");
from("direct:req2")
.process("requestProcessor2")
.to(cxf:bean:endpoint2)
.process(response2);
I am new to apache camel, i just wanna know is there any way to use the response which i get from the endpoint1 in "requestProcessor2" .
You could do something like this
from("direct:test")
.setProperty("test.body", body())
.to(cxf:bean:endpoint1)
.setProperty("endpoint1.body", body())
.process("response1")
.setBody(exchangeProperty("test.body"))
.to("direct:req2")
from("direct:req2")
.process("requestProcessor2")
.to(cxf:bean:endpoint2)
.process(response2);
You save the original body in an property and also the body from endpoint1. You then send the exchange to direct:req2 with the original body in the exhcnage body and the body form endpoint1 in a property which you then can access (in you processor or else where).
To access the the property in your processor:
public void process(final Exchange exchange) throws Exception {
Object body = exchange.getProperty("endpoint1.body");
}
You question already has the answer , use and you can get the property from the exchange whichever route you want. Also consider removing the property at the final route.

Camel doesn't retrieve SQS messages attributes

Here is the route:
from("aws-sqs://myQueue?accessKey=RAW(xxx)&secretKey=RAW(yyy)&deleteAfterRead=false")
.log("Attributes: ${header.CamelAwsSqsAttributes}")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Map<String, String> messageAttributes = (Map<String, String>) exchange.getIn().getHeader("CamelAwsSqsAttributes");
...
}
});
The .log() shows an empty map as well as if I print messageAttributes from the processor.
I also tried with the header "CamelAwsSqsMessageAttributes" instead of "CamelAwsSqsAttributes" but still nothing.
I see the attributes from the AWS console though.
By the way I get the message body, and I use Camel 2.15
I figured it out, here is an example to get queue attributes and message attributes:
main.bind("sqsAttributeNames", Collections.singletonList("All"));
main.bind("sqsMessageAttributeNames", Collections.singletonList("All"));
Or add those objects to the registry if you don't use org.apache.camel.main.Main
Then:
from("aws-sqs://myQueue?accessKey=RAW(xxx)&secretKey=RAW(yyy)&deleteAfterRead=false&attributeNames=#sqsAttributeNames&messageAttributeNames=#sqsMessageAttributeNames")
Of course you can replace Collections.singletonList("All") with the list of attributes you need if you don't want all of them.
I faced the same issue. When I am using camel-aws 2.16.x and I have my endpoint configured as follow
from("aws-sqs://myQueue?...&messageAttributeNames=#sqsMsgAttributeNames")
.to(...)
Then I have defined a Collection of String in my spring configuration file
#Bean
public Collection<String> sqsMsgAttributeNames() {
return Arrays.asList("Attr1", "Attr2");
}
Above settings work fine but ever since I upgraded to camel-aws 2.17.3. It no longer works. As mentioned in Camel SQS Component, collection of string no longer will be supported for messageAttributeNames and it should be a String with attributes separated by comma.
Note: The string containing attributes should not contain any white
spaces otherwise camel-aws component will only read the first
attribute. I went through the pain to debug on this. Besides, setting the
attribute value to be "All" does not work for me, none of the message
attributes will be read.
Below is the changes I made that allowed camel-aws's SqsConsumer to work again:
#Bean
public String sqsMsgAttributeNames() {
return String.format("%s,%s", "Attr1", "Attr2");
}
It is not an issue of Camel. It can be the default behavior of SQS or aws-java-sdk-core library.
As a quick solution this aws-sqs URL can be used
aws-sqs://myQueue?<other attributes here>&attributeNames=All
Keep in mind that localstack can work well without attributeNames parameter, unlike SQS.

Put modified xml back into the message?

I'm using CXF to send messages with SOAP over JMS.
I'm trying to write a CXF Interceptor in the POST_MARSHALL phase.
I want to change some attributes when the xml is generated.
I know i can get the content from the message via
message.getContent(java.io.Writer.class).
This happens to be in the form of JMSConduit$1. Which - I think - is a StringWriter (if I debug my code I can see a buf field).
I can get the xml in String format and make my changes, but the problems is putting it back in the message.
I can not change the JMSConduit$1 to something else, otherwise CXF won't send it to the JMS Endpoint. (it must be a JMSConduit).
I can't find a way to put the modified xml back in a JMSConduit, which i can get through
message.getExchange().getConduit();
So, how can I put my modified xml back into the message/JMSConduit?
Finally found an answer. I used a FilterWriter.
public void handleMessage(Message message) throws Fault {
final Writer writer = message.getContent(Writer.class);
message.setContent(Writer.class, new OutWriter(message, writer));
}
class OutWriter extends FilterWriter {
#Override
public void close() throws IOException {
// Modify String (in xml form).
message.setContent(Writer.class, out);
}
}

Resources