Need to end Apache camel split while processing - java

My intention is to send a list of messages one by one (messages are generate using values from db).
What I do is query and get the message list attach it to the exchange and use split to slipt the messages and it sends
Below is my route
.process(this::analyseSettlement)
.choice()
.when()
.simple("${header.error_reason} == 001")
.log("Settlement Completed")
.otherwise()
.log("BatchUpload process")
.split(body(), flexible().accumulateInCollection(ArrayList.class))
.setHeader(Exchange.HTTP_METHOD).constant(HttpMethod.POST)
.removeHeader(Exchange.HTTP_PATH)
.marshal().json(JsonLibrary.Jackson)
.to("http://localhost:8087/channel/tcp?bridgeEndpoint=true")
// analyze response if success then update the table
// if not return error
.unmarshal(new JacksonDataFormat(InternalTransactionBean.class))
.process(this::analyseBatchUploadResponse)
.end()
.log("${body}")
.process(this::processPostSettlement)
What I require is if I find an error in one of the response need to stop
sending all un send messages and end the split and go to postProcesssettlement function
flow required->
Message list->
send message 1 by one
error occurred while processing one message
stop processing the rest of the messages and exit the route
How to achieve this or
If my process of sending batch of messages is not correct please advice me regarding that.

One way to implement what you try to achieve in a Camel route is to use the EIPs doTry, doCatch and doFinally pretty much like the famous try-catch-finally blocks in Java associated with stopOnException to interrupt the execution of the split in case of error.
In the example below, I simulate a per line validation of my file content which fails, I then execute something when the exception is caught in my doCatch block and finally execute something else whatever happened in my doFinally block
from("file:src/data")
.doTry()
.split(body().tokenize("\n")).stopOnException()
.log("In split ${body}")
.throwException(new RuntimeException("Not good"))
.end()
.endDoTry()
.doCatch(RuntimeException.class)
.log("In catch: ${body}")
.doFinally()
.log("In finally: ${body}")
.end();
Assuming that the content of my file is:
line 1
line 2
line 3
The result of my route is then:
In split line 1
In catch: line 1
line 2
line 3
In finally: line 1
line 2
line 3
More details about the EIPs doTry, doCatch and doFinally.
More details about the option stopOnException.

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?

Configure Redelivery for a file when Exception occures in camel route

I have a pretty easy route where I pickup files from a directory and send it to a bean:
from("file:/mydir?delete=true").bean(MyProcessor.class);
It can happen that an exception occures in MyProcessor.class and so I want to delay the processing of that file again. How can I setup a redelivery for that as I tried already different things with
onException().redeliveryDelay(10000);
but it didn't work and right after the exception the same file gets processed again.
Did you do onException() before Processing?
Example:
errorHandler(defaultErrorHandler()
.maximumRedeliveries(2)
.redeliveryDelay(5000)
.retryAttemptedLogLevel(LoggingLevel.WARN));
// exception handler for specific exceptions
onException(IOException.class).maximumRedeliveries(1).redeliveryDelay(5000);
//only the failed record send write to error folder
onException(CsvRecordException.class)
.to("file:/app/dev/dataland/error");
onCompletion()
.log("global thread: ${threadName}")
.to("file:/app/dev/dataland/archive");
from("file:/path?noop=true?delay=3000")
.startupOrder(1)
.log("start to process file: ${header.CamelFileName}")
.bean(CsvFilePreLoadChecker.class, "validateMetaData")
.end()
.log("Done processing file: ${header.CamelFileName}");
When an error occurs in MyProcessor.class the route processing is failed and therefore the file consumer does not delete the file.
Since the route processing is completed, the file consumer simply reads the (still present) file again.
If you want to move files with processing errors out of your way, you can use the moveFailed option of the file consumer. You would then have to move them back periodically to retry.
If you want to decouple file reading and MyProcessor.class you need to split the route into 2 routes. One that reads read the files and sends its messages to a queue or similar. The other consumes that queue and processes the messages.

Camel onException - Route with same exception class but different actions

Within a single camel route I have two url calls, making calls to two different applications.
to("http://datasource1/data)
//some process
to("http://datasource2/data)
//some process
Both are capable of throwing UnKnowHostException.
So, if the URL1 throws the exception i have to handled and set the exchange body as "Datasource 1 not available" and if URL2 throws the same exception , I want to show a different message.
How to handle this using onException
You can use onWhen. Set some header (in my example "httpDatasource") before each request, and after use different handlers.
onException(UnKnowHostException.class).onWhen(header("httpDatasource").isEqualTo("1")).to("...");
onException(UnKnowHostException.class).onWhen(header("httpDatasource").isEqualTo("2")).to("...");
.....
setHeader("httpDatasource").constant("1")
to("http://datasource1/data)
//some process
setHeader("httpDatasource").constant("2")
to("http://datasource2/data)
//some process
I would use the camel try catch blocks (as suggested by #soilworker).
.doTry()
.to("http://datasource1/data")
.doCatch(UnknownHostException.class)
// Add message 1 here
.end()
//process
.doTry()
.to("http://datasource2/data")
.doCatch(UnknownHostException.class)
// Add message 2 here
.end()
// process
It's more verbose, but it's easy to understand and clearly associates the message with the exception. And in the event you wish to make the to calls asynchronous, you can.
You can use a route specific onException but you would need to split your route into multiple routes:
from("somewhere")
.to("direct:datasource1")
//process
.to("direct:datasource2")
//process
from("direct:datasource1")
.onException(UnknownHostException.class)
// add message 1 here
.end()
.to("http://datasource/data")
from("direct:datasource2")
.onException(UnknownHostException.class)
// add message 2 here
.end()
.to("http://datasource2/data")
I don't believe there's a way of using onException with the one route but applied to different to calls (other than using #Alexeys or #Ewouts suggestion). Would love to hear about it if there is.

continue behavior in camel route execution

I want to put continue behaviour in route, my route is like following
from("file:D:\\?fileName=abc.csv&noop=true").split().unmarshal().csv()
.to("direct:insertToDb").end();
from("direct:insertToDb")
.to("direct:getDataId")
.to("direct:getDataParameters")
.to("direct:insertDataInDb");
from("direct:getDataId")
.to("sql:SELECT id FROM data WHERE name = :#name)
.choice()
.when(header("id").isGreaterThan(0) )
.setProperty("id", header("id"))
.otherwise()
.log("Error for")
.endChoice().end();
I want that if direct:getDataId dont find any record , my execution of route for current record from CSV get skip and program process next request. it would be equal to continue keyword.
How i can achieve this in Apache Camel route?
You can modify your routes like this:
from("file:D:\\?fileName=abc.csv&noop=true").split().unmarshal().csv()
.to("sql:SELECT id FROM data WHERE name = :#name?outputHeader=id&outputType=SelectOne)
.choice().when(header("id").isGreaterThan(0))
.to("direct:getDataParameters")
.to("direct:insertDataInDb")
.end();
Have you got a test for this? I suggest you try using CamelTestSupport because what you want is how camel will execute by default.
From Camel Split Docs:
stopOnException
default:false
description: Whether or not to stop continue processing immediately when an exception occurred. If disable, then Camel continue splitting and process the sub-messages regardless if one of them failed. You can deal with exceptions in the AggregationStrategy class where you have full control how to handle that.

Apache Camel runs part of onCompletion and doesn't show stack trace

I'm trying to use a Camel poll-once route that will use a file if it's present and log an error if not.
By default the route does nothing if the file does not exist so I've started by adding consumer.sendEmptyMessageWhenIdle=true to the URI. I then check for null body to decide whether to log an exception or continue:
from(theFileUri)
.onCompletion()
.onCompleteOnly()
.log("SUCCESS")
.bean(theOtherAction, "start")
.end()
.onException(Exception.class)
.logStackTrace(true)
.log(ERROR, "Failed to load file")
.handled(true)
.end()
.choice()
.when(body().isNotNull())
.to(NEXT_ROUTE_URI)
.endChoice()
.otherwise()
.throwException(new FileNotFoundException(theFileUri))
.endChoice();
There are two problems with this:
if the file is missing, the success log still happens (but not the bean!)
the stack trace is not printed
If there is a better way to do this then I'd welcome suggestions but I'd also like to know what I'm doing wrong in this method.
It's still not completely clear to me what is going on. However, I believe that the onException call needs to be separated from the chain. It looks to me that logStackTrace applies only to redelivery attempts. The number of attempts defaults to 0 and this is what I want. The only way to access the exception from the Java DSL appears to be a custom Processor. The getException() method will return null if you are using handled(true) so you must use Exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class).
I also suspect that the log message from the onCompletion is due to it running in parallel before the exception aborts:
Camel 2.13 or older - On completion runs in separate thread Icon The
onCompletion runs in a separate thread in parallel with the original
route. It is therefore not intended to influence the outcome of the
original route. The idea for on completion is to spin off a new thread
to eg send logs to a central log database, send an email, send alterts
to a monitoring system, store a copy of the result message etc.
Therefore if you want to do some work that influence the original
route, then do not use onCompletion for that. Notice: if you use the
UnitOfWork API as mentioned in the top of this page, then you can
register a Synchronization callback on the Exchange which is executed
in the original route. That way allows you to do some custom code when
the route is completed; this is how custom components can enlist on
completion services which they need, eg the File component does that
for work that moves/deletes the original file etc.
Since I want to not run this code on exception, I think I can just abort the route with the exception.
I currently have this:
onException(Exception.class)
.handled(true)
.process(new Processor()
{
#Override
public void process(Exchange anExchange) throws Exception
{
Exception myException = anExchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
LOGGER.error("Failed to load", myException);
}
});
from(theFileUri)
.choice()
.when(body().isNotNull())
.to(NEXT_ROUTE_URI)
.log("SUCCESS")
.bean(theOtherAction, "start")
.endChoice()
.otherwise()
.throwException(new FileNotFoundException(theFileUri));

Categories