Not able to retry Camel Route continuously in case of specific exception - java

I am very new to Apache camel, I have a situation where I need to perform below action
Whenever I receive specific type of Exception, I need to retry the complete route again,
But I am facing problem of circular-error handling exception and infinite recursion when implementing using onException.
Below is my dummy code
from("direct:updateTheTask")
.to("direct:getWoTaskDetail")
.to("direct:getSoTaskDetail")
.to("direct:updateTaskDetail")
.to("direct:getSoTaskDetail")
.to("direct:getWoTaskDetail")
.to("direct:endRoute");
from("direct:updateTaskDetail").routeId("updateTaskDetail")
.bean(BEAN, Constants.SET_PARAMS)
.to("direct:restUpdate")
.to(getGetResponseBeanUrl(BEAN));
I have a call to "direct:updateTheTask" route and "direct:updateTaskDetail" is giving exception in my case when exception is received I want to retry again from "direct:updateTheTask" maintaining/persisit the data in exchange.
When no such exception is thrown by "direct:updateTaskDetail" during this recursion the route should be completed normally.
I have used below 2 approach but seems it is not working for me.
onException(TaskException.class)
.handled(true)
.maximumRedeliveries(-1)
.redeliveryDelay(20);
The other approach is
onException(TaskException.class)
.handled(true)
.delay(20)
.to("direct:updateTheTroubleTicket");

Turn off the error handler in the task route so when calling this route, Camel will retry the entire route, instead of the route error handler will retry at the point off the error.
from("direct:updateTheTask")
.errorHandler(noErrorHandler());
.to("direct:getWoTaskDetail")

Related

Camel maximumRedeliveries is ignored

I try to use Camel to deliver files from one folder to a rest call and Im trying to achieve that on Error it's tried to redeliver twice and then moved to an error folder if the second redelivery fails as well. My code in the RouteBuilder's configure method looks like this:
errorHandler(deadLetterChannel("file:///home/camelerror").useOriginalMessage());
from("file:///home/camelefiles")
.onException(RetryableException.class)
.log("RetryableException handled")
.maximumRedeliveries(2)
.end()
.routeId(port.id())
.throwException(new RetryableException());
I get the "RetryableException handled" logs so I guess the exception is handled correctly but it redelivers the message an infinite number of times.
What am I doing wrong and how can I achieve that the message is only redelivered twice and then the deadLetterChannel is used?

How to retry a camel direct route once?

With camel, I want to send an HTTP request and, if it fails, take some corrective action and try it once more.
Sending the request is wrapped in a direct route and requires the generation of a unique token, apart from that the request should be exactly the same. If the request fails the second time, it should fail the "normal" way.
I want the sending logic in it's own route because sometimes I want to call it without retry.
How can I set up this scenario with the camel DSL? I tried various combinations of onException and errorHandler but it doesn't even seem to catch the exception.
Example (that doesn't work):
from("direct:send-request-no-retry")
.setHeader("token").exchange(this::generateToken)
.to("http://example.com");
from("direct:fix-the-error")
// ...
;
from("direct:send-request")
.onException(HttpOperationFailedException.class)
.to("direct:fix-the-error")
.maximumRedeliveries(1)
.useOriginalMessage()
.end()
.to("direct:send-request-no-retry");
I have mainly used onException() clause as a global exception handler. With quick testing it does not indeed catch the exceptions when put directly into a route. Couple of options that come to my mind are:
1st option do-try clauses:
from("direct:send-request")
.doTry()
.to("direct:send-request-no-retry")
.doCatch(HttpOperationFailedException.class)
.to("direct:fix-the-error")
.to("direct:send-request-no-retry")
.doFinally()
.whatevs()
.endDoTry();
But this is a bit janky since you would have to handle the exception again from the doCatch clause. Another one is to throw custom exception from the "send-request-no-retry".
from("direct:send-request-no-retry")
.setHeader("token").exchange(this::generateToken)
.doTry()
.to("http://example.com")
.doCatch(HttpOperationFailedException.class)
.throwException(new CustomExceptionThatExtendsRunTimeException())
.doFinally();
onException(CustomExceptionThatExtendsRunTimeException.class)
.handled(true)
.to("direct:fix-the-error")
.maximumRedeliveries(1)
.useOriginalMessage()
.to("direct:send-request-no-retry")
.end();
I didn't test this so I'm not sure if this creates an infinite feedback loop or if the maximumRedeliveries will cut it off. If it does you can implement another "direct:send-request-no-retry" route that does not throw the customException but just fails silently or throws the HttpOperationFailedException and pass the request there from onException(). Or set the retryAmount to a header and check that header in onException handler to see if we want to try again.
The customException is for the purposes that the onException() clause catches all exceptions from every route of the specified type. And I think you don't want to pass every failed http request to your retryroute.

Camel SFTP Producer retry for Specific type of errors

We had a requirement to retry certain number of times with delay for camel SFTP Producer in case of errors. By using maximumReconnectAttempts along with reconnectDelay, camel attempts to retry for all type of errors but want to retry only for certain type of errors like socketException or connectionexception or jschExceptions and avoid retry for authentication exceptions. Here is the sample code we are using for this. How can we configure to retry only certain type of errors and not all?
from("file:///test/dummy/moveFailed=error&antInclude=*.txt&initialDelay=60000").routeId("test")
.log(LoggingLevel.DEBUG, " Message sending to Destination")
.setHeader(Exchange.FILE_NAME, simple("Test${date:now:yyyyMMdd}.CSV"))
.to("sftp://username#host/destinationpassword=password123&reconnectDelay=3000&maximumReconnectAttempts=5")
.log(LoggingLevel.INFO,"event=${in.header.event} || File successfully transmitted to Destination")
.end();
if you want to control the behavior per Exception you can do as follow:
onException(MyRetryableServiceException.class)
.useOriginalMessage()
.redeliveryDelay(1000)
.backOffMultiplier(1.5)
.maximumRedeliveries(3)
.retryAttemptedLogLevel(LoggingLevel.WARN)
.useExponentialBackOff();
When a MyRetryableServiceException is thrown the message will be redelivered as per maximumRedeliveries.
You can define multiple onException or wrap the ones you want to retry in a single Exception...
This has precedence over the default error handler, see exception-clause and camel error handling
Everything else will goes to the default error handled that will retry all kind of exception (prior camel 2.x, no retry from version >= 2.0). Unless you override it.
So, one possible solution is to define the OnException to skip retry for authentication errors and leave the default error handler to retry the others if you are using camel < 2.0
You can disable the default error handler with noErrorHandler(); or customize it as well, for example:
errorHandler(
deadLetterChannel("seda:errors")
.logNewException(true)
.log("Unable to process ${body}")
.maximumRedeliveries(1)
.retryAttemptedLogLevel(LoggingLevel.WARN)
.useExponentialBackOff()
);

Apache camel muticast with parallel processing does not propagate exceptions to dead letter handler

I have a camel route.
from(errorMultiDirect).routeId("errorMulticastTest")
.errorHandler(deadLetterChannel(mock)
.onPrepareFailure(errorProcessor).maximumRedeliveries(0))
.log(LoggingLevel.INFO, "Testing Error route")
.setHeader(OrderMessageConstants.WIMS_MSG_TYPE, simple("body[messageType]"))
.setHeader(OrderMessageConstants.SAP_MESSAGE_ID, simple("body[messageID]"))
.setHeader(OrderMessageConstants.ORDER_NUMBER, simple("body[orderHeader][order]"))
.multicast().parallelProcessing().shareUnitOfWork().stopOnException().to("direct:materialsTest", "direct:qmDocTest", "direct:sdsTest").end()
.to("log:com.sial.NotifyStatusLogger?level=INFO");
If an exception occurs in any of the multicast routes, like materaialsTest, the exception is caught in the multicast code, but the exception never gets sent to the dead letter channel. According to the documentation, setting shareUnitOfWork should make this happen, but it does not, nor does setting stopOnException.
Am I missing something?
I have a second test route that doesn't use multicast, and it works as expected.
from(errorDirect).routeId("errorMaterialTest")
.errorHandler(deadLetterChannel(mock)
.onPrepareFailure(errorProcessor).maximumRedeliveries(0))
.log(LoggingLevel.INFO, "Testing Error route")
.setHeader(OrderMessageConstants.WIMS_MSG_TYPE, simple("body[messageType]"))
.setHeader(OrderMessageConstants.SAP_MESSAGE_ID, simple("body[messageID]"))
.setHeader(OrderMessageConstants.ORDER_NUMBER, simple("body[orderHeader][order]"))
.bean(materialsEnrichment)
.to("log:com.sial.NotifyStatusLogger?level=INFO");
This second route does send the message to the dead letter channel, when the materialsEnrichment bean throws an exception. This is the same bean that is called by the multicast route via "direct:materialsTest".
You need to turn off error handling in the direct routes, as the dead letter channel is configured as route scoped error handler only in your route. Then failures form the direct routes should be handled by your route with the multicast.
from("direct:xxx")
.errorHandler(noErrorHandler())

spring rabbit exception in consumer issue

I have a spring rabbit consumer:
public class SlackIdle1Consumer extends AbstractMessageConsumer {
#Override public void process(Message amqpMessage, Channel channel)
throws Exception {
/*very bad exception goes here.
it causes amqp message to be rejected and if no other consumer is available and error
still persists, the message begins looping over and over.
And when the error is fixed,
those messages are being processed but the result of this procession may be harmful.
*/
}
}
}
And somewhere inside an exception happens. Lets imagine this is a bad exception - development logic error. So amqp message begins to spin indefinitely, and when error is fixed and consumer restarted, all old messages are being processed, and it's bad, because logic and data may change since those messages were sent. How to handle it properly?
So the question is: how to fix this situation properly? Should I wrap all my code to try-catch clause or will I have to develop 'checks' in each consumer to prevent consistency issues in my app?
There are several options:
Set the container's defaultRequeueRejected property to false so failed messages are always rejected (discarded or sent to a dead letter exchange depending on queue configuration).
If you want some exceptions to be retried and others not, then add a try catch and throw an AmqpRejectAndDontRequeueException to reject those you don't want retried.
Add a custom ErrorHandler to the container, to do the same thing as #2 - determine which exceptions you want retried - documentation here.
Add a retry advice with a recoverer - the default recoverer simply logs the error, the RejectAndDontRequeueRecoverer causes the message to be rejected after retries are exhausted, the RepublishMessageRecoverer is used to write to a queue with additional diagnostics in headers - documentation here.

Categories