Camel appears to ignore onException instruction - what am I doing wrong? - apache-camel

I have a Camel (2.21.2) route that gets an XML file via FTP and then splits it before processing further, somewhat like...
from("direct-vm:start")
.split()
.xtokenize("*/row", 'w', ns)
.streaming()
...
If an empty file is passed, I am getting a com.ctc.wstx.exc.WstxEOFException raised by the xtokenize step I presume, which seems perfectly reasonable.
However, when I try to put in an onException clause, it doesn't seem to be catching the exception.
I have:
onException(com.ctc.wstx.exc.WstxEOFException.class)
.handled(true)
.log(org.apache.camel.LoggingLevel.INFO,
"myLogger",
"File ${header.CamelFileName} is empty or invalid XML");
and yet the exception is resulting in the route failing with the exception, and my handler log message is never produced.
However, if I change the onException clause to java.lang.Exception.class, it does handle the exception.
I don't want this to handle any exception, just the one caused by an empty or incomplete file.
What I am doing wrong here? How do I get onException to handle only the exception I am interested in?
Thanks for looking!

Related

Setting handled as true in onException block prevents redeliveries

I am reading a file from a directory, and trying to call an API based on the data in the file.
While trying to handle the exceptions, I am facing an issue. I am trying to configure the onException block to redeliver 3 times, with a delay of 5 seconds. The issue occurs, when I am setting handled(true). This configuration does not redeliver, and stops as soon as the exception occurs.
This is my onException block:
onException(HttpOperationFailedException.class)
.log(LoggingLevel.ERROR, logger, "Error occurred while connecting to API for file ${header.CamelFileName} :: ${exception.message}")
.log("redelivery counter :: ${header.CamelRedeliveryCounter}")
.maximumRedeliveries(3)
.redeliveryDelay(5000)
.handled(true);
How do I do both, i.e. handle as well as redeliver?
Unless you use a buggy version of Camel, the redeliveries are made as expected whatever if it is handled or not.
The only difference between handled or not, is the fact that the result sent back to the client once the retries are exhausted will be either the exception (not handled) or the result of your onException (handled).
Your mistake here, is the fact that you assume that the log EIPs that you have defined in your onException are called for each retry while they are actually called only when the retries are exhausted.
If you want to see the retries in your logs, you can use retryAttemptedLogLevel as next:
onException(HttpOperationFailedException.class)
.maximumRedeliveries(3)
.redeliveryDelay(5000)
.retryAttemptedLogLevel(LoggingLevel.WARN);
You will then get warning messages of type:
Failed delivery for (MessageId: X on ExchangeId: Y). On delivery attempt: Z caught: ...

Aggregate after exception from ftp consumer: FatalFallbackErrorHandler

My camel route tries to pick up some files from sftp, transfer them to network, and delete them from sftp. If the sftp is unreachable after 3 attempts, I want the route to send an email warning the admin about the problem.
For this reason my sftp address has the following parameters:
maximumReconnectAttempts=2&throwExceptionOnConnectFailed=true&consumer.bridgeErrorHandler=true
In case the network location is not available, i want the route to notify the admin and not delete the files from sftp.
For this reason i have set .handled(false) in onException.
However, when connecting to sftp fails, aggregation also fails and no emails are coming. I have made a minimalist example below:
/configure
onException(Throwable.class)
.retryAttemptedLogLevel(LoggingLevel.WARN)
.redeliveryDelay(1000)
.handled(false)
.log(LoggingLevel.ERROR, LOG, "XXX - Error moving files")
.to(AGGREGATEROUTE)
.end();
from(downloadFrom)
.to(to)
.log(LoggingLevel.INFO, LOG, "XXX - Moving file OK")
.to(AGGREGATEROUTE);
from(AGGREGATEROUTE)
.log(LoggingLevel.INFO, LOG, "XXX - Starting aggregation.")
.aggregate(constant(true), new GroupedExchangeAggregationStrategy())
.completionFromBatchConsumer()
.completionTimeout(10000)
.log(LoggingLevel.INFO, LOG, "XXX - Aggregation completed, sending mail.");
In the logs i see:
16:02| ERROR | CamelLogger.java 156 | XXX - Error moving files
Then the logs for the Exception occurring during connection.
And then this:
16:02| ERROR | FatalFallbackErrorHandler.java 174 | Exception occurred while trying to handle previously thrown exception on exchangeId: ID-LP0641-1552662095664-0-2 using: [Pipeline[[Channel[Log(proefjes.camel_cursus.routebuilders.MoveWithPickupExceptions)[XXX - Error moving files]], Channel[sendTo(direct://aggregate)]]]].
16:02| ERROR | FatalFallbackErrorHandler.java 172 | \--> New exception on exchangeId: ID-LP0641-1552662095664-0-2
org.apache.camel.component.file.GenericFileOperationFailedException: Cannot connect to sftp://user#mycompany.nl:22
at org.apache.camel.component.file.remote.SftpOperations.connect(SftpOperations.java:149)
I do not see "XXX - Starting aggregation." which i would expect to see in the log. Does some kind of error occur befor aggregation? The breakpoint on aggregate(*, *) is never reached.
First, I just want to clarify something. You write "In case the network location is not available, i want the route to notify the admin and not delete the files from sftp", but shouldn't that be obvious anyhow? I mean, if the network location is not available, wouldn't deleting the files from sftp be impossible?
It's a little confusing that your exception handler is also routing .to(AGGREGATEROUTE). Given that you want to email an admin, shouldn't that be in the exception handler, not in the happy path? Why would you and how would you "aggregate" a connection failure?
Finally, and here I think is a real problem with your implementation, you may have misunderstood what handled(false) does. Setting this to false means routing should stop and propagate the exception returned to the caller. I'm not sure what having to the .to(AGGREGATEROUTE) would do in this case, but I'm not surprised it's not being called.
I suggest trying a few things. I don't have your code so I'm not sure which will work best. These are all related and any might work:
Change handled(false) to handled(true).
Replace handled with continued(true).
Use a Dead Letter Channel.
Reference:
Handle and Continue Exceptions
Dead Letter Channel
Since errorhandling is different depending on which endpoint causes the error, i have solved this by having two different versions of onException:
//configure exception on sft end
onException(Throwable.class)
.maximumRedeliveries(2)
.retryAttemptedLogLevel(LoggingLevel.WARN)
.redeliveryDelay(1000)
.onWhen(new hasSFTPErrorPredicate())
// .continued(true) // tries to connect once, mails and continues to aggregation with empty exchange
//.handled(false) // tries to connect twice but does not reach mail
.handled(true) // tries to connect once, does reach mail
// handled not defined: tries to connect twice but does not reach mail
.log(LoggingLevel.INFO, LOG, "XXX - SFTP exception")
.to(MAIL_ROUTE)
.end();
// exception anywhere else
onException(Throwable.class)
.maximumRedeliveries(2)
.retryAttemptedLogLevel(LoggingLevel.WARN)
.redeliveryDelay(1000)
.log(LoggingLevel.ERROR, LOG, "XXX - Error moving file ${file:name}: ${exception}")
.to(AGGREGATEROUTE)
.handled(false)
.end();
Exceptions occuring at the sftp end are handled in the first onException, because there the hasSFTPErrorPredicate returns 'true'. All this predicate does is check if any exception or their cause has "Cannot connect to sftp:" in the message.
No rollback is required in this case because nothing has happened yet.
Any other exception is handled by the second onException.

Having access to entire soap:body when service call returns soap:fault

I'm building a route that sends a SOAP request to a webservice. For achieving that, I wrote this code.
.doTry()
.inOut(getEndpointDocumentWS())
.log("Response WHEN OKAY: ${body}")
.process(Document_WS_REPLY_PROCESSOR)
.endDoTry()
.doCatch(Exception.class)
.log(LoggingLevel.INFO, "SOAP REPLY WITH FAULTMESSAGE")
.log("Response ON ERROR FAULT: ${body}")
.process(Document_WS_REPLY_ERROR_PROCESSOR)
.end();
Everything goes as planned when the service response is "okay". Otherwise, when the service response is a soap:Fault, I'm not having access to all of the response (I am using soapUI to mock the soap:Fault response).
I can access a tiny fraction of the soap:fault by getting the EXCEPTION_CAUGHT property.
The instruction
.log("Response ON ERROR FAULT: ${body}")
Has no data at all.
What can I do differently to have access to all the instead of only the faultstring?
Exception exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT,
Exception.class);
According to this answer, Camel's CXF component not catching onException(Exception.class):
Camel's onException only triggeres if there is an exception. A SOAP
Fault is represented as a Message with the fault flag = true.
What you can do is to set handleFault=true on CamelContext, then it
will turn SOAP fault messages into an exception that the onException
can react upon.
Assuming you have not configured handleFault=true, then it's odd that your exception handler is running at all. It could be that some other exception, not the Fault you're looking for, is occurring which causes the exception handler to run.
If you have already configured handleFault=true, I don't have any advice except inspect the data objects in a debugger to see if you can find out what's going on. (If you don't know, to do this you can add a Processor to your exception handler and insert a break point inside the Processor. Break points don't work in route definition because route definitions are only run on initialization.)

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 SFTP Route fails to continue onException

I've fairly simple looking route
sftp://hostname:22//incoming/folder/location/?username=username&password=xxxxx
&localWorkDirectory=/tmp&readLock=changed&readLockCheckInterval=2000
&move=processed/$simple{date:now:yyyy}/$simple{date:now:MM}/$simple{date:now:dd}${file:name}
&consumer.delay=450000&stepwise=false&streamDownload=true&disconnect=true
I also have an onException clause
onException(ValidationException.class)
.handled(true)
.logStackTrace(true)
.filter(header("VALIDATION_ERROR").isEqualTo(true))
.choice()
.when(header("CamelFileName").contains("Param1"))
.to(sftp://hostname:22//One/error/folder?password=xxxxxx&username=username)
.when(header("CamelFileName").contains("Param2"))
.to(sftp://hostname:22//Two/error/folder?password=xxxxxx&username=username)
.endChoice();
When I have single file, the route seems to work as expected. When more than one file and exception occurs, I get many different exceptions like
org.apache.camel.component.file.GenericFileOperationFailedException: Cannot list directory: incoming/folder/location
Caused by: java.lang.IndexOutOfBoundsException
I tried using all the attributes mentioned in the route viz. streamDownload, stepwise, readLock, localWorkDirectory et al. However, the error handling while multiple files is not working. I see the first file getting processed. However, it doesn't move to the processed folder once the exception occurs and then incoming/folder/location becomes non listable. I tried using continued(true) as well instead of handled(true)
The problem was with multiple files being handled in the same exchange. On exception, the route was trying to FTP back the error file on the same server. The solution was to split body into multiple exchanges so that each file has its own exchange and process them separately.
from(sftp://hostname:22//incoming/folder/location/?username=username&password=xxxxx
&localWorkDirectory=/tmp&readLock=changed&disconnect=true&stepwise=false
&move=processed/$simple{date:now:yyyy}/$simple{date:now:MM}/$simple{date:now:dd}${file:name}
&consumer.delay=450000).split(body()).processRef("incomingProcessor").end();

Resources