I have a dynamic route creator web application. According to flow design, I produce a camel route. Route may contain multicast, filter, aggregate, processor etc. After designing flow through the UI, my route has been created like this:
from("seda:start").routeId("idx")
.multicast()
.to("direct:a", "direct:b", "direct:c")
.parallelProcessing()
.end();
from("direct:a").transform(constant("A")).delay(1000).to("direct:merge");
from("direct:b").transform(constant("B")).delay(2000).to("direct:merge");
from("direct:c").transform(constant("C")).delay(3000).to("direct:merge");
from("direct:merge")
.aggregate(new MyAggregationStrategy()).constant(true).completionSize(3)
.to("mock:end");
I have an API to give result of this route to the users. When I execute this route with InOut MEP, response is 'C' but mock:end is satisfied with 'ABC':
MockEndpoint mock = getMockEndpoint("mock:end");
mock.expectedBodiesReceived("ABC"); //works as expected
String reply = template.requestBody("seda:start", "", String.class);
assertEquals("ABC", reply); //it returns 'C', but I expect 'ABC'
assertMockEndpointsSatisfied();
How can I change the code to get aggregated result with a synchronous call? Here is the code:
public class ResponseTest extends CamelTestSupport {
#Test
public void testAsyncInOut() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:end");
mock.expectedBodiesReceived("ABC"); //works as expected
String reply = template.requestBody("seda:start", "", String.class);
assertEquals("ABC", reply); //it returns 'C', but I expect 'ABC'
assertMockEndpointsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("seda:start").routeId("idx")
.multicast()
.to("direct:a", "direct:b", "direct:c")
.parallelProcessing()
.end();
from("direct:a").transform(constant("A")).delay(1000).to("direct:merge");
from("direct:b").transform(constant("B")).delay(2000).to("direct:merge");
from("direct:c").transform(constant("C")).delay(3000).to("direct:merge");
from("direct:merge")
.aggregate(new MyAggregationStrategy()).constant(true).completionSize(3)
.to("mock:end");
}
};
}
class MyAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (oldExchange == null) {
// this is the first time so no existing aggregated exchange
return newExchange;
}
// append the new word to the existing
String body = newExchange.getIn().getBody(String.class);
String existing = oldExchange.getIn().getBody(String.class);
oldExchange.getIn().setBody(existing + body);
return oldExchange;
}
}
}
EDIT
Multicasting message to 5 different endpoints doesn't mean that all the messages will be aggregated later. So I cannot use aggregate strategy in multicast definition. Some of them might be used for another kind of job. Flow definition might be like this:
After multicast message to 'a','b','c','d','e' endpoints, 'a' and 'b' can be aggregated together('direct:merge1'), 'c' and 'd' aggregated together('direct:merge2') and 'e' can be used for another thing.
And final aggregator will be aggregate 'direct:merge1' and 'direct:merge2'
to 'direct:merge3'. All these endpoints are created dynamically('direct:a','direct:b','direct:c','direct:d','direct:e','direct:merge1','direct:merge2','direct:merge3'). This scenario will be created like this:
from("seda:start").routeId("idx")
.multicast()
.to("direct:a", "direct:b", "direct:c", "direct:d", "direct:e")
.parallelProcessing()
.end();
from("direct:a").transform(constant("A")).delay(1000).to("direct:merge1");
from("direct:b").transform(constant("B")).delay(2000).to("direct:merge1");
from("direct:c").transform(constant("C")).delay(3000).to("direct:merge2");
from("direct:d").transform(constant("D")).delay(1000).to("direct:merge2");
from("direct:e").transform(constant("E")).delay(1000).to("mock:anywhere");
from("direct:merge1").aggregate(new MyAggregationStrategy()).constant(true).completionSize(2).to("direct:merge3");
from("direct:merge2").aggregate(new MyAggregationStrategy()).constant(true).completionSize(2).to("direct:merge3");
from("direct:merge3").aggregate(new MyAggregationStrategy()).constant(true).completionSize(2).to("mock:end");
When I send message to seda:start, I expect ABDC but I got 'E'. Is there a way to get final aggregated message('ABDC')? Here is the test method:
#Test
public void testAsyncInOut() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:end");
mock.expectedBodiesReceived("ABDC"); //works as expected
String reply = template.requestBody("seda:start", "", String.class);
assertEquals("ABDC", reply); //it returns 'E' because of default multicast behavior, but I expect 'ABDC'
assertMockEndpointsSatisfied();
}
From the multicast documentation:
By default Camel will use the last reply as the outgoing message.
If you want to aggregate the results of the multicast into a single message, you state that in the multicast definition.
#Override
public void configure() throws Exception {
from("seda:start").routeId("idx")
.multicast(new MyAggregationStrategy()) //Put the Aggregation Strategy here!
.to("direct:a", "direct:b", "direct:c")
.parallelProcessing()
.end();
from("direct:a").transform(constant("A")).delay(1000).to("direct:merge");
from("direct:b").transform(constant("B")).delay(2000).to("direct:merge");
from("direct:c").transform(constant("C")).delay(3000).to("direct:merge");
from("direct:merge")
.to("mock:end");
}
Note that your mock endpoint will now be called 3 times, as the aggregation doesn't occur until later. You would need to modify your test accordingly.
Related
I have an aggregation Strategy in my camel split() route.
from("direct:split")
.split()
.method(new SplitBean(), "splitMessage")
.aggregationStrategy(AggregationStrategies.groupedExchange())
.stopOnException()
.to("direct:destination")
.end();
The splitMessage method has split the data into 3 request data. So I am hitting the http destination endpoint 3 times.
Using the aggregation Strategy my http response got aggregated for the first 2 times.
Third time when the http call failed with an exception. The exchange returned to the caller does not contain the first two grouped exchanges.
How can I get the grouped exchanges with (success, exception) this case.
Please tell me if the question is not clear.
Change from .stopOnException() to .stopOnAggregateException()
create an AggregationStrategy strategy class and handle the exception from there
public void configure() throws Exception {
from("direct:split")
.split()
.method(new SplitBean(), "splitMessage")
.aggregationStrategy(new ErrorStrategy())
.stopOnAggregateException()
.to("direct:destination")
.end();
}
public class ErrorStrategy implements CompletionAwareAggregationStrategy {
#Override
public void onCompletion(Exchange exchange) {
}
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (newExchange.getException() != null) {
return oldExchange;
}
if (oldExchange == null) {
....
return newExchange;
}
.....
return oldExchange;
}
}
I am going to do send my DATA toRabbitMq producer(message sender) and get responsible data from RabbitMq consumer(message receiver). producer part is working fine .now my problem is how to implement consumer part (receiver part) in side the Spring boot API. .Below is My spring boot API and i written ProducerAndConsumer one class.
ProducerAndConsumer.class
#Component
public class ProducerAndConsumer {
#Autowired
private RabbitTemplate rabbitTemplate;
//MessageProducer part (send part)
public boolean sendMessage(String message) {
rabbitTemplate.convertAndSend(RobbitMqConfig.ROUTING_KEY, message);
System.out.println("Is listener returned ::: ==========="+rabbitTemplate.isReturnListener());
return rabbitTemplate.isReturnListener();
}
//Consumer part (receiver part)
#RabbitListener(queues = RobbitMqConfig.QUEUE_NAME1)
public void receiveMessage ( final Message message){
System.out.println("Received message====Receiver=====" + message.getPayload());
}
}
API part
#PostMapping(value = {"/sendFilesName"})
public ResponseEntity<?> sendFilesName(#RequestBody SendFileNameRequest sendFileNameRequest, HttpServletRequest request) throws ParseException {
System.out.println("FileNameArray="+sendFileNameRequest.getFileNameArray());
if(sendFileNameRequest.getFileNameArray().size()!=0) {
List<String> message = sendFileNameRequest.getFileNameArray();
**//see here i send my message array data**
if(producerAndConsumer.sendMessage(message.toString())){
**//here i want implement my receiver part how to?**
return ResponseEntity.ok(new ApiResponse(true, "fileName List sent successfully", "",true));
}else {
return ResponseEntity.ok(new ApiResponse(false, "fileName List sent Fails", "",true));
}
}else {
return ResponseEntity.ok(new ApiResponse(false, "fileName List not present ", "",true));
}
}
The routing algorithm behind a direct exchange is simple - a message goes to the queues whose binding key exactly matches the routing key of the message.
spring amqp
Note: Check the routing key and queues binded using rabbitmq admin console to figure out whats going on or share the rabbitmq configuration.
I'm using multicast to send the incoming message to two different endpoints and aggregating the response.I would like to get the response,even if one of the endpoint response timed out.
which method i can use "timeout" or completionTimeout.?
I'm using parallel processing for processing message.
.multicast()
.to("direct:A","direct:B")
.parallelProcessing()
//.timeout(1000L)
.aggregationStrategy(new MyAggregationStrategy())
//.completionTimeout(2000L)
.end()
How can i use TimeoutAwareAggregationStrategy in this case.
public class MyAggregationStrategy implements TimeoutAwareAggregationStrategy {
#Override
public Exchange aggregate(Exchange newExchange, Exchange originalExchange) {
if(newExchange==null){
return originalExchange;
}
else {
ExchangeHelper.copyResults(originalExchange, newExchange);
return originalExchange;
}
}}
Use timeout, and see this unit test for an example: https://github.com/apache/camel/blob/master/camel-core/src/test/java/org/apache/camel/processor/MulticastParallelTimeoutAwareTest.java
I have a route that should process request from CXF endpoint and return results as JSON:
public class MyRoute extends RouteBuilder
{
// ... Autowired:
// msgRequestProcessor - converts json {string,string} to String of "\n" delimited json string: {string}\n{string}
// RangeProcessor, SingleProcessor - create mongodb Criteria object from json string
// msgTypeMapper - adds corresponding header "msg.type"
#Override
public void configure()
{
from("direct:list")
.process(msgRequestProcessor)
.split(body())
.bean(msgTypeMapper.class)
.choice()
.when(header("msg.type").isEqualTo("single"))
.log("Go to direct:single")
.to("direct:single")
.otherwise()
.log("Go to direct:range")
.to("direct:daterange")
.end()
.to("direct:aggregate");
from("direct:range")
.process(new RangeProcessor());
from("direct:single")
.process(new SingleProcessor());
from("direct:aggregate")
.aggregate(new MyAgg()).header("msg.collection").completionSize(2)
.log("RETVAL: ${body}")
.marshal().json(JsonLibrary.Gson).end();
}
public static final class MyAgg implements AggregationStrategy {
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange)
{
if (oldExchange == null) {
return newExchange;
}
Criteria oldCriteria = oldExchange.getIn().getBody(Criteria.class);
Criteria newCriteria = newExchange.getIn().getBody(Criteria.class);
Criteria criteria = new Criteria();
criteria.andOperator(oldCriteria, newCriteria);
oldExchange.getIn().setBody(criteria.getCriteriaObject().toString());
return oldExchange;
}
}
}
Everything works fine, I see correct aggregation results and aggregation completion in the log
but CXF endpoint always returns output of msgRequestProcessor (before split):
{"string"}
{"string"}
while I expect to see Criteria object converted to string (that I can see in the logs).
Any help would be much appreciated! Thanks.
Note first that your indentation is misleading, the end() is really the end of the choice(), and not of the split(); I was puzzled a while by this (as #Ralf was maybe.)
Now, the aggregation works, but its result is discarded because indeed the result of the split is the input message.
For a request/reply usage of the splitter (in-out), you really have to declare the aggregation strategy along with the split() as explained here (same misleading indentation).
In the official documentation you mention, the situation is the other way around (in-only): the result of the splitter is discarded, and the result of the aggregation is routed downstream.
I have a Camel route with an onCompletion() which then hits a Processor. Within this processor it gets a header from the Exchange but this header comes back null.
I know that onCompletion() runs at the end of that particular route but surely the Exchange headers should still be valid and usable. inputLocation below is defined higher up in the class and works for previous routes.
from("file://"+inputLocation+"?initialDelay=5000&delay=2000&recursive=true&delete=true")
.onCompletion()
.process(storedProcProcessor())
.end()
.choice()
.when(appContext.getBean(AppPredicate.class))
.log("Need to check against APP in the database for destination.")
.setHeader(AppConstants.INPUTLOCATION, simple(inputLocation))
.process(databaseProcessor())
.endChoice();
I checked with:
#Override
public void configure() {
from("direct:start")
.onCompletion()
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
LOG.info("Hello, {}", exchange.getIn().getHeader("myHeader"));
}
})
.end()
.setHeader("myHeader").constant("World!");
}
}
This prints
Hello, World!
Thus, the header myHeader is still available in onCompletion. So, I guess that your header is never properly set?