Duplication of Apache Camel messages - java

How can I create duplicates of some Camel messages by some header condition? For example, I have a message with header key=value1,value2,value3, and I want to have three copies of the same messages with keys key=value1, key=value2, key=value3.
I tried to find some components here https://camel.apache.org/components/3.20.x/, but haven't found any suitable example.

Try with multicast
Example code:
from("direct:input")
.choice()
.when(header("myHeader").isEqualTo("duplicate"))
.to("direct:duplicate")
.end()
.to("someOtherEndpoint");
from("direct:duplicate")
.multicast()
.to("direct:output1")
.to("direct:output2")
.end();

Related

Apache Camel - consume messages only if queue size greater than

I'm trying to find the way of consuming the messages in a ActiveMQ queue only if the count of messages is greater than a certain number.
Any ideas?
Usually if you use the syntax:
from("jms:start-queue")
.process(new SampleProcessor())
.to("jms:end-queue")
.end();
the messages are consumed as soon they are "passed to the processor".
Thanks
Is it possible to aggregate the messages with a completionSize ?
from("...")
.aggregate(constant(true), new MyListAggregator())
.completionSize(x)
.to("...")
Please see this articale about aggregation

How exactly is JMSReplyTo handled by Apache Camel? When does camel implicitly utilises the destination?

Using spring-camel, I have built a route that consumes from a JMS topic (with JMSReplyTo expected to be set for each input message), splits the message into smaller chunks, sends them to a REST processsor, then aggregates the answers and should produce an output message to the destination pointed by JMSReplyTo. Unfortunately, camel implicitly utilises the JMSReplyTo destination in one of the intermediate steps (producing an unmarshalled POJO).
We have a functional requirement to adapt JMSReplyTo in order to provide a request-reply messaging service.
I am able to read the JMSReplyTo header before ending the route and I am explicitly converting it to CamelJmsDestinationName, which successfully overrides the destination for JMS component and produces the message on the output topic. I am not sure if this is the best approach and the problem is that camel still utilises the JMSReplyTo on its own.
My RouteBuilder configuration is as follows:
from("jms:topic:T.INPUT")
.process(requestProcessor)
.unmarshal().json(JsonLibrary.Jackson, MyRequest.class)
.split(messageSplitter)
.process(restProcessor)
.aggregate(messagesAggregator)
.unmarshal().json(JsonLibrary.Jackson, BulkResponses.class)
.process(responseProcessor)
.to("jms:topic:recipientTopic");
T.INPUT is the name of the input topic, while recipientTopic is just a placeholder that will be replaced by CamelJmsDestinationName.
I'm not keen on using CamelJmsDestinationName and a sort of a mocked up topic name in route configuration so I'm open to find a better solution. It would be great if camel utilised the JMSReplyTo automatically to produce the output message to the output topic.
Currently, the problem is that camel produces an intermediate output on the JMSReplyTo topic BUT the output is an unmarshalled MyRequest object, which results in an exception saying "ClassNotFoundException: (package name).MyRequest", which is obvious since this is only a class used in my internal processing - I don't want to produce this to the output topic. It seems like Camel does implicitly use the JMSReplyTo destination between requestProcessor and messageSplitter processing... Why? What am I doing wrong? What are the best practices?
Use "disableReplyTo=true" in Endpoint. Camel will not try to use any reply option.
Refer: https://camel.apache.org/jms.html for more details
I have found the answer... this is absurdly easy but I haven't seen it anywhere in the documentation.
You just need to call .stop() to mark the route as completed, and Camel will reply the body you configured in the last step to the destination configured in ${header.JMSReplyTo}. It's that simple.
So you can do:
from("jms:my-queue")
.unmarshall().json(JsonLibrary.Jsonb, InboundMessage.class)
.bean(SomeProcessingBean.class)
....
.log(LoggingLevel.INFO, "Sending reply to: " + simple("${header.JMSReplyTo}").getExpression().toString())
.marshall().json(JsonLibrary.Jsonb, ReplyMessage.class)
.stop();
And you will receive reply.
I wonder why no one has found this before... Nothing when I search the doc or here.... I must be dumb, or the doc is incomplete...but I am not dumb, so.

Camel recipientList not iterating all recipients

I am writing a Camel integration that can consume an arbitrary number of queries and execute those against an arbitrary number of databases.
The route starts by getting all queries located in a folder and then uses a splitter to iterate over them in order:
from("quartz2:quartzInitializer?cron={{sync.cron}}")
.routeId("quartzInitializer")
.bean(QueryHandler.class, "getQueries")
.split(exchangeProperty(QueryHandler.Properties.QUERIES))
.setProperty(Properties.CURRENT_QUERY, simple("body"))
.to("direct:executeSingleQuery")
.end();
In the above snippet, the property QueryHandler.Properties.QUERIES contains two query file locations:
config/sql/1__select_stat_machine.sql
config/sql/2__select_stat_session.sql
Next, I send the location of the iterated query and construct a recipient list from it:
from("direct:executeSingleQuery")
.routeId("executeSingleQuery")
.bean(DataSourceHandler.class, "createEndpointsWithQuery")
.recipientList(exchangeProperty(DataSourceHandler.Properties.QUERY_RECIPIENTS))
.parallelProcessing()
.log(LoggingLevel.INFO, String.format("Calling ${in.header.%s}", Exchange.RECIPIENT_LIST_ENDPOINT));
In the above snippet, the parameter DataSourceHandler.Properties.QUERY_RECIPIENTS contains two recipients:
sql:file:config/sql/1__select_stat_machine.sql?dataSource=datasource3&outputHeader=resultset
sql:file:config/sql/1__select_stat_machine.sql?dataSource=datasource2&outputHeader=resultset
However, when I run this, only one of the recipients are called, in this case only datasource2, which was at index 1 in the list passed to the recipientList:
Calling sql://file:config/sql/1__select_stat_machine.sql?dataSource=datasource2&outputHeader=resultset
Calling sql://file:config/sql/2__select_stat_session.sql?dataSource=datasource2&outputHeader=resultset
I can't for the life of me figure out what I'm doing wrong. Am I missing an end() somewhere? Is my splitter at fault, or is it my recipient list?
.recipientList(exchangeProperty(...))
.log(LoggingLevel.INFO, String.format("Calling ${in.header.%s}", Exchange.RECIPIENT_LIST_ENDPOINT));
Your are putting the log statement in the wrong place.
Basically the way you have modelled your route is:
"Please send the messages to all recipients, and AFTER this, print a message". The fact is that after looping through the list of recipients, Camel variable holds the URI of the LAST recipient.
It is more obvious in Spring DSL:
What your Camel route is doing:
<recipientList>
<header>...</header>
</recipientList>
<log message="Done"/>
versus what you think Camel is doing:
<recipientList>
<header>...</header>
<log message="Done"/>
</recipientList>

Camel Exchange on different routes in the same route builder

My REST application will post data to a queue (Q1) on rabbitMQ. There's another separate application that will read from Q1, process the data and post the result back to Q2. My application will read the data from Q2 and return the result. Many clients will use these 2 queues so I generate a UUID and set it in the header so that I can listen on Q2 (the response topic). I will then query each incoming message and match the incoming UUID in the header to the one I generated when I posted to Q1.
from("direct:test")
.choice().when(isValid)
.bean(FOOProcessor.class, "setFooQuery")
.to(FOO_REQUEST_QUEUE).log(LoggingLevel.INFO, "body=${in.body}")
.otherwise()
.setBody(constant("error"))
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400)).log(LoggingLevel.INFO, "body=${in.body}")
.to("direct:error");
from(FOO_RESPONSE_QUEUE)
.unmarshal(new JacksonDataFormat(JsonNode.class))
.bean(FooProcessor.class, "setFooResponse")
.to("direct:end");
from("direct:error").log(LoggingLevel.DEBUG, "end");
from("direct:end").log(LoggingLevel.DEBUG, "end");
The trouble is the 2 "from" statements - they create to separate Camel exchanges/contexts and I can't get the original UUID. Any suggestions?
I solved this by using a processor that had a route builder embedded in it (with its own producer and consumer).
The processor provided a reference to the main exchange from this
process(final Exchange exchange)

Camel pattern for multiple sources specific messages aggregation and redirect to destination

I have one problem, and don't know how to solve it using camel. I searched for related EIP in camel documentation, but without results.
Now I have simple route:
<route id="routeId">
<from uri="Source1"/>
<from uri="Source2"/>
<to uri="Destination"/>
</route>
Both sources sends JMS messages to Destination and at some point when Source finish its job it send specific end message, with some flag. What I need to do is to collect or count those end messages and send single end message to destination when I receive end messages from both sources. Only when i receive two end messages (imagine that its just simple message with some header flag) then i should send single one to destination.
Sorry if problem explanation isn't clear enough.
Thanks in advance.
the Camel aggregator and filter patterns can be used for this scenario...
use a filter to detect "end" messages and route them through an aggregator
use a custom aggregation strategy to build up the single end message with a count
use a custom completion predicate to trigger the completion message
something like this...
from("source1").to("direct:aggregateRoute");
from("source2").to("direct:aggregateRoute");
from("direct:aggregateRoute")
.filter(header("isEndMessage").isEqualTo("true"))
.aggregate(constant(true), new MyAggregationStrategy())
.completionPredicate(new MyCompletionStrategy())
.to("destination");
If you just want to pick from multiple inputs and does not want to perform any modification on the incoming message,
you can do something like this:
from("URI1", "URI2", "URI3").to("DestinationUri");
for more info check this link it helped me

Categories

Resources