Camel, end rest-based route, returning from choice in loop - apache-camel

I'm trying to add error handling to my parallel processing:
...
.multicast(new GroupedMessageAggregationStrategy())
.parallelProcessing()
.to("direct:getAndSaveRoute1")
.to("direct:getAndSaveRoute2")
.end()
.split(body())
.choice()
.when(simple("${body.errorOcurred} == true"))
//TODO:: end route returning current body
.endChoice()
.otherwise()
.log(...)
.endChoice()
.end()
//after split, if no error occurred
.to("direct:nextRoute")
.end()
I can't seem to figure out though how to return/ end the route (and pass back the current body as the rest response body) within the choice in the split. end() and endRest() seem to cause issues...
It is also not clear as how many end()s I need; Adding an end() for the split causes an exception and makes Spring fail to boot.

For those in the future, I ended up making a bean to turn the list into a single message, and then do a choice based on that.
Not very 'camel', but needed to be wrapped up

Related

Camel - onException & redelivery

I am trying to implement an error handler & re-oauth mechanism for a Route which performs a GraphQL API call. However, I seem to be misunderstanding the usage of the onException handler and can't seem to get it to work.
Here's the scenario I am trying to cover:
Execute a Route which performs a GraphQL API call (including the setting of variables etc.). The API call itself will always respond with a HTTP 200.
Once the response is received, check the response payload for any error records
If errors exist with a code 401, throw an AuthorizationException
The onException handler catches the exception and is supposed to:
trigger the oauth route to refresh the token (updating the exchangeProperty)
re-run the Route (re-executing the GraphQL call)
After reaching the configured maximumRedeliveries in the onException handler, fail the execution.
Here's the code I have so far:
onException(AuthorizationException.class)
.handled(true)
.redeliveryDelay(1000)
.maximumRedeliveries(3)
.useOriginalMessage()
.to(ROUTE_OAUTH_URI) // updates the token (?)
.to(ROUTE_QUERY_URI) // re-executes the route (?)
;
from(ROUTE_QUERY_URI)
.process(exchange -> {
...set variables
})
.toD("graphql://...")
.setProperty("graphqlResponseErrors").jsonpath("$.errors.length()", true)
.choice()
.when(exchangeProperty("graphqlResponseErrors").isNotEqualTo(0))
.setProperty("graphqlResponseCode").jsonpath("$.errors[0].code", true)
.choice()
.when(exchangeProperty("graphqlResponseCode").isEqualTo(401))
.throwException(new AuthorizationException(
"Authorization exception"))
.end()
.end();
I have tried a few different combinations, including onRedeliver instead of the oauth route, remove the useOriginalMessage and re-trigger the route again but I couldn't get it to work.
Any help or hints would be much appreciated. Thanks!
Update:
Adding the below code to the onException, does retrieve and set a new token, however now the route seems go into an infinite loop:
.onRedelivery(exchange -> {
ProducerTemplate producerTemplate = exchange.getContext().createProducerTemplate();
Exchange oauthExchange = producerTemplate.send(ROUTE_OAUTH_URI, exchange);
exchange.setProperty("accessToken", oauthExchange.getProperty("accessToken"));
});

Camel - Stop processing a route on exception

I have a camel route which processes hl7 messages. After the processing succeeds I would like to pass it to another endpoint b to do further processing.
But if any exception happens in processHL7, I have a catch block which does some processing. I want to stop processing when I go into the doCatch and encounter end, but this is not happening. Whatever happens the flow is going to endpoint b. How do i stop when I go into the doCatch block?
from("direct:a")
.doTry()
.to("bean:processHL7?method=process")
.doCatch(HL7Exception.class)
.to("direct:ErrorACK")
.transform(ack())
.end()
.transform(ack())
.to("direct:b");
This should work.
from("direct:a")
.doTry()
.to("bean:processHL7?method=process")
.doCatch(HL7Exception.class)
.to("direct:ErrorACK")
.transform(ack())
.stop()
.end()
.transform(ack())
.to("direct:b");
You can use stop() to stop the route from further processing.

Handle exception from load balancer only once (when failover is exhausted)

My code consumes jms queue and through lb redirects to external http client.
I need to log original message for every failed delivery to local directory.
Problem is that onException is caught by each failover.
Is there any way how to achieve this?
Pseudo code:
onException(Exception.class).useOriginalMessage()
.setHeader(...)
.to("file...")
.setHeader(...)
.to("file...")
from("activemq...")
.process(...)
.loadBalance().failover(...)
.to("lb-route1")
.to("lb-route2")
.end()
.process()
.to("file...")
from("lb-route1")
.recipientList("dynamic url")
.end()
from("lb-route2")
.recipientList("dynamic url")
.end()
I have not tested this logic with multiple to statements, but when I have my list of endpoints in an array this logic functions just fine. I will attempt to deliver to the first endpoint, if I cannot I will attempt to deliver this to the second endpoint. If an exception occurs at the second endpoint it will propagate back to the route's error handler. If you need it to round robin instead, change the last false on the failover statement to a true.
String[] endpointList = {"direct:end1", "direct:end2"};
from("direct:start")
.loadbalance().failover(endpointList.length-1, false, false)
.to(endpointList);

onCompletion in Camel 2.14

I'd like to wrap the result of a processed message into some reply-object to answer a webservice. This is my test-route:
this.from("cxf:someEndpoint")
.process(new SomeProcessorThatMightThrowAnException())
.process(new SomeOtherProcessorThatMightThrowAnException())
.log("normal end of route");
Nevermind if there was an exception or not, the result should be wrapped in some object, that is given back to the caller of my ws.
In camel 2.13.x I did this by adding an other processor to the end of the route and to do the same in 'onException'.
Now I tried to simplify this (technical thing and handle it outside of the 'functional route') in camel 2.14 (2.14 because of 'modeBeforeConsumer'), and added this to my routebuilder:
onCompletion()
.modeBeforeConsumer()
.process(new ConvertToWsReplyProcessor());
This ConvertToWsReplyProcessor should handle an Exception, but I found no way to see, if there was an Exception, because exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class) is allways null.
Questions:
1) Is there a way to find out if there was an excetion in onCompletion()?
2) The only way I found to prevent camel from dumping a stacktrace is to use onException(Ex...).handled(true), are there others?
3) How are these onXY processed? Do they get a copy of the exchange? And is onCompletion called last?
OnCompletionProcessor just remove some exchange properties before processing the exchange, that could explain why you cannot fine the exception here.
As camel use onException to handle the exception, I'm afraid you have to do it that way.

Camel - Split() and doCatch(....) does not work

I'm trying to build a route that tries to validate an xml and if everything is correct then has to split this file otherwise an exception is thrown and it has to do something else. So I did the following:
from("file:"+fileOutboxTransformed+"?preMove=inprogress&move="+backupFolderTransformed+"/"+labelMessageType+"_${date:now:yyyyMMddHHmmssSSS}-${file:name.noext}.${file:ext}")
.log(LoggingLevel.INFO, "Got transformed file and sending it to jms queue: "+queue)
.doTry()
.to("validator:classpath:"+validator)
.split(xPathMessageTypeSplit)
.to("jms:"+queue+"?jmsMessageType=Text")
.doCatch(ValidationException.class)
.log(LoggingLevel.INFO, "Validation Exception for message ${body}")
.to("xslt:classpath:"+transformationsErrorAfter)
.split(xPathNotificationSplit)
.to("file:"+fileOutboxInvalid+"?fileName=${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.err2")
.end();
But it does not compile (if I do not use the split then it compiles and works) and the error is:
The method doCatch(Class<ValidationException>) is undefined for the type ExpressionNode
So I tried the following
from("file:"+fileOutboxTransformed+"?preMove=inprogress&move="+backupFolderTransformed+"/"+labelMessageType+"_${date:now:yyyyMMddHHmmssSSS}-${file:name.noext}.${file:ext}")
.log(LoggingLevel.INFO, "Got transformed file and sending it to jms queue: "+queue)
.doTry()
.to("direct:validate")
.doCatch(ValidationException.class)
.log(LoggingLevel.INFO, "Validation Exception for message ${body}")
.to("xslt:classpath:"+transformationsErrorAfter)
.split(xPathNotificationSplit)
.to("file:"+fileOutboxInvalid+"?fileName=${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.err2")
.end();
from("direct:validate")
.to("validator:classpath:"+validator)
.to("direct:split_message");
from("direct:split_message")
.split(xPathMessageTypeSplit)
.to("jms:"+queue+"?jmsMessageType=Text");
This time I get the error of duplicate endpoint
org.apache.camel.FailedToStartRouteException: Failed to start route route312 because of Multiple consumers for the same endpoint is not allowed: Endpoint[direct://validate]
do you have any idea on how to solve this problem?
In order to get back to the doTry() block from a split() block (or choice or other nested type), you need to use the endDoTry(). Contrary to its name, this method will end the nested split block and return back to the doTry() DSL.
I'd use the first route you posted with these changes:
from("file:"+fileOutboxTransformed+"?preMove=inprogress&move="+backupFolderTransformed+"/"+labelMessageType+"_${date:now:yyyyMMddHHmmssSSS}-${file:name.noext}.${file:ext}")
.log(LoggingLevel.INFO, "Got transformed file and sending it to jms queue: "+queue)
.doTry()
.to("validator:classpath:"+validator)
.split(xPathMessageTypeSplit)
.to("jms:"+queue+"?jmsMessageType=Text")
.endDoTry()
.doCatch(ValidationException.class)
.log(LoggingLevel.INFO, "Validation Exception for message ${body}")
.to("xslt:classpath:"+transformationsErrorAfter)
.split(xPathNotificationSplit)
.to("file:"+fileOutboxInvalid+"?fileName=${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.err2")
.endDoTry()
.end();
Your second try seems fine. The error you're getting is caused by two routes beginning with from("direct:validate")
Don't you have an other route in your application consuming from the same endpoint?
Edit : Try to name it differently, maybe validate exists already (in your app or inside camel)

Resources