One of many ways of dealing with an exception is handling it by marking it handled, effectively ignoring it and possibly taking same action based on the exception, and then continuing the execution of the Camel route. This is achieved by using continued(true) on the onException definition. However, continued(true) directive works in an unexpected manner, as will be demonstrated below.
Given this simple route that has an error handler, which forwards the message to the dead-letter-channel, and a route that triggers this by throwing an exception:
public class ErrorHandlingRoutes extends RouteBuilder {
@Override
public void configure() throws Exception {
errorHandler(deadLetterChannel("direct:error").maximumRedeliveries(0));
from("direct:in").routeId("errorRoute")
.log("start")
.throwException(new RuntimeException("Error"))
.log("Continuing");
}
In addition to the error handler, we also need an onException clause. I have created two JUnit tests that have slightly different onException clauses, showing the unexpected behaviour.
@Test()
public void testContinueWorks() throws Exception {
context.getRouteDefinition("errorRoute").adviceWith(context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
onException(RuntimeException.class).continued(true).log("fixit");
interceptSendToEndpoint("direct:error")
.skipSendToOriginalEndpoint().to("mock:error");
}
});
context.start();
MockEndpoint mockError = getMockEndpoint("mock:error");
mockError.setExpectedMessageCount(0);
template.sendBody("direct:in", "Go");
assertMockEndpointsSatisfied();
}
The test above adds an onException clause and continues the execution of the route, swallowing the exception. The interceptor redirects from direct:error to mock:error, and later we check that the mock:error endpoint has not received any messages, as that is what we would expect. Note the log statement in the onException statement, this will become crucial later.
The next test shows the unexpected behaviour. Instead of continuing the execution of the route, as we would expect, the mock:error endpoint receives a message, showing that the exception handler of the route has been called. Have a look at the code below and try to figure out why that is. It is not at all obvious from the code, but requires in-depth knowledge of Camel's innards. The clue is the missing log statement.
@Test()
public void testContinueNotWorking() throws Exception {
context.getRouteDefinition("errorRoute").adviceWith(context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
onException(RuntimeException.class).continued(true);
interceptSendToEndpoint("direct:error")
.skipSendToOriginalEndpoint().to("mock:error");
}
});
context.start();
MockEndpoint mockError = getMockEndpoint("mock:error");
mockError.setExpectedMessageCount(1);
template.sendBody("direct:in", "Go");
assertMockEndpointsSatisfied();
}
The explanation requires us to have a look at how Camel routes are implemented.
Most elements in Camel are implemented as processors, and the route itself is a pipeline of processors. The reason for continued(true) not working in the latter example is that it is not implemented as a processor. Adding any kind of processor, for example logging in our case, to the onException clause will make it work as expected.
No comments:
Post a Comment