I have a very simple Camel route definition which just includes some OnException predicates to handle respective exceptions and some log-statements.
from("hazelcast:seda:someQueue")
.id("someQueueID")
.onException(CustomException.class)
.handled(true)
.log(LoggingLevel.WARN, "custom exception noticed")
.end()
.onException(IOException.class, FileNotFoundException.class)
.asyncDelayedRedelivery()
.redeliveryDelay(3*1000*60) // 3 Minutes
.maximumRedeliveries(3)
.log(LoggingLevel.WARN, "io exception noticed")
.end()
.onException(Exception.class)
.log(LoggingLevel.WARN, "general exception noticed")
.end()
.log("Starting route")
.bean(TestBean.class)
.log("Finished route");
The bean itself is simple too, it just checks a header parameter and throws an appropriate exception
public class TestBean
{
@Handler
public void checkData(@Headers final Map<String, Object> headers)
throws CustomException, IOException, Exception
{
Integer testVal = (Integer)headers.get("TestValue");
if (0 == testVal)
throw new CustomException("CustomException");
else if (1 == testVal)
throw new IOException("IOException");
else
throw new Exception("Exception");
}
}
As this test-setup is just a small part of a larger project it may sound silly to do it like presented here, but the core intend is to modify the redeliveryDelay at test time as a "forced" IOException will not need to wait 3 minutes and therefore, to speed up unit tests a bit, the redelivery delay could be reduced to like 10 ms.
In order to achieve this my test-method does the following:
@ContextConfiguration(classes = OnExceptionRouteTest.ContextConfig.class, loader = AnnotationConfigContextLoader.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class OnExceptionRouteTest extends CamelSpringTestSupport
{
@Override
protected AbstractApplicationContext createApplicationContext()
{
return new AnnotationConfigApplicationContext(ContextConfig.class)
}
@Configuration
public static class ContextConfig extends CamelConfiguration
{
@Override
protected void setupCamelContext(CamelContext camelContext) throws Exception
{
super.setupCamelContext(camelContext);
camelContext.addComponent("hazelcast", new StubComponent());
// some other unnecessary stuff
}
@Override
public List<RouteBuilder> routes()
{
final List<RouteBuilder> list = new ArrayList<>();
list.add(new OnExceptionRoute());
return list;
}
}
@Override
public boolean isUseAdviceWith()
{
return true;
}
@Test
public void testIOException()
{
context.getRouteDefinition("someQueueID")
.adviceWith(context, new AdviceWithRouteBuilder()
{
@Override
public void configure() throws Exception
{
this.weaveByType(OnExceptionDefinition.class)
.selectIndex(1)
.replace()
.onException(IOException.class, FileNotFound.class)
.asyncDelayedRedelivery()
.redeliveryDelay(10)
.maximumRedeliveries(3)
.log("modified io exception noticed")
.to("mock:ioError")
.end();
...
mockEndpoints();
}
});
context.start();
MockEndpoint ioErrorEndpoint = getMockEndpoint("mock:ioError");
...
ioErrorEndpoint.setExpectedMessageCount(1);
...
Map<String, Object> headers = new HashMap<>();
headers.put("TestValue", new Integer(1));
template.sendBodyAndHeaders("hazelcast:seda:someQueue", new Object(), headers);
...
ioErrorEndpoint.assertIsSatisfied();
...
}
}
Here the test just replaces the onException segment of the IOException to first reduce the redelivery delay from 3 minutes to 10 ms and adds a mock endpoint at the end. However when I try to run the unit test I'll get the following exception:
java.lang.IllegalArgumentException: The output must be added as top-level on the route. Try moving OnException[[class java.io.IOException, class java.io.FileNotFoundException] -> []] to the top of route.
However, the examples in the official documentation, as far as I understood them correctly, are very similar. I also tried to lookup the exception definition via a defined ID predicate and its corresponding method weaveById()
or via the weaveByToString()
method but with no other result. I also tried to remove the exception definition via weaveByType(OnExceptionDefinition.class).selectIndex(1).remove();
and add the OnException part via weaveAddFirst().onException(...).async...;
but with the same result.
Appending a mocked error endpoint, however, is possible via f.e. weaveByToString("Log[io exception noticed]").after().to("mock:ioError");
So any tips for modifying onException blocks or the redeliveryDelay for unit tests are more than welcome.
@Edit: I also tried now to move the onException declarations above the route definition (from(...)
) as suggested by the exception message and this was also the preferred case in Camel's exception samples. On doing this, however, all tests (even the working ones) fail as of a NullPointerException
on context.getRouteDefinition("someQueueID").adviceWith(context, new AdviceWithRouteBuilder() {... });
as obviously the route itself can't be found anymore. I doubt that this is an IntelliJ issue as both classes are within the same project and therefore a modification of the route should be visible to the test-class.
Camel version in use: 2.13.0, IntelliJ IDEA 13.1.2
@Edit2: For some reason context.getRouteDefinitions("someQueueID")
returns null if the OnException elements are defined outside of the from
block, whereas the general route can be obtained via context.getRouteDefinitions().get(0)
- though, the exception stating that the OnException part needs to be added as top-level element remains.
You use this route if you need to create some custom response message back to the caller, or do any other processing because that exception was thrown. If continued is true, then Camel will catch the exception and in fact just ignore it and continue routing in the original route.
Contents. A Camel route is where the integration flow is defined. For example to integrate two systems then a Camel route can be coded to specify how these systems are integrated. An example could be to take files from a FTP server and send to a ActiveMQ messaging system.
routeId() are for identifying routes. By adding ids you can in your tests use adviceWith() to mock or inject or remove parts of your route to perform automated tests without having access to backend systems.
When using the Java DSL, the id of the route is set using the .routeId()
method, not .id()
as you have coded above. That may help with your adviceWith
concerns.
Instead of hard-coding the retry delay, a better approach would be to make the delay configurable using properties. Check out the documentation around the method useOverridePropertiesWithPropertiesComponent()
on your CamelSpringTestSupport
class.
EDIT
You don't have to weave the onException
clause, simply state a new one. Here's a complete example:
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.test.junit4.CamelTestSupport;
public class DummyTest extends CamelTestSupport{
@Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder(){
@Override
public void configure() throws Exception {
from("direct://start")
.routeId("myroute")
.onException(Exception.class)
.id("myException")
.continued(true)
.end()
.throwException(new Exception())
.to("mock:end");
}
};
}
@org.junit.Test
public void doTest() throws Exception{
context.getRouteDefinition("myroute").adviceWith(context, new AdviceWithRouteBuilder(){
@Override
public void configure() throws Exception {
context.getRouteDefinition("myroute")
.onException(Exception.class).setBody(constant("adviceWith")).continued(true);
}});
context.start();
template.sendBody("direct://start", "original");
String bodyAtEndOfExchange = getMockEndpoint("mock:end")
.getExchanges().get(0).getIn().getBody(String.class);
assertEquals("adviceWith", bodyAtEndOfExchange);
context.stop();
}
@Override
public boolean isUseAdviceWith() {
return true;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With