errorHandle in multiple, hierarhical RouteBuilders - apache-camel

I am migration to Camel 3.20.1 from 2.20.1 and am facing some difficulties with my current workflow.
I have three RouteBuilders with routes which are called in hierarchical order like this.
RouteBuilderA
#Override
public void configure() throws Exception {
errorHandler(deadLetterChannel("direct:dead")
.maximumRedeliveries(2)
.redeliveryDelay(1000));
from("direct:A")
.to("direct:B");
}
RouteBuilderB
#Override
public void configure() throws Exception {
from("direct:B")
.to("direct:C");
}
RouteBuilderC
#Override
public void configure() throws Exception {
errorHandler(noErrorHandler())
from("direct:C")
.throwException(new RuntimeException("Something went wrong"))
.to("direct:end");
}
Current flow:
As you can see in the code, route in RouteBuilder3 will trigger exception which will be passed to RouteBuilder2. From there it will not be handled, as no error handler is defined for that route, and it will fail with stacktrace...
Desired flow:
What I need is to pass that exception to RouteBuilder1 and handle it there where (my global deadLetterChannel) error handler is defined.
One could say, just add errorHandler(noErrorHandler()) into RouteBuilderB, right. That is clear and it works, BUT I don't want that because...
Because, in that case redelivery will kick in and route message through routes in RouteBuilderB and RouteBuilderC. But, as I got exception in RouteBuilderC, I only want to route message through route where it failed, that is RouteBuilderC.
Background:
In old Camel version I somehow managed to make it work by using includeRoutes(injectedRouteBuilder), which is not available anymore. RouteBuilderC bean is injected into RouteBuilderB, which is injected in RouteBuilderA.
Question:
Does anybody knows how to solve that? I cannot find a way to make this work. I tried a lot of things, including getCamelContext().addRoutes(injectedRouteBuilder);.
*p.s. I am aware of recommendation to use inheritance for RouteBuilders, but in my case this is not an option. I need to pass it to RouteBuilder1 as only that one knows to which error queue, message needs to be send. *

Related

Is there a way to do content based routing on a message's exchange pattern in Camel 2.15?

I currently have a process that needs to do some routing based on a message's exchange pattern. If a message is InOut, then the route ends and whatever the message contains gets sent back to the callback location. If the message is InOnly then the message gets routed somewhere else. The code is below:
...
.process(new Processor(){
public void process(Exchange e) throws Exception {
e.getIn().setHeader("ExchangePattern", e.getPattern().name());
}
})
.choice()
.when(header("ExchangePattern").isEqualTo("InOnly"))
.to(DESTINATION);
I know that in Camel 2.16 you can get the exchange pattern via the simple expression (exchange.getPattern.getName) but that's not available in 2.15. Is there a more elegant want do doing the routing or am I stuck with the ugly routing logic above?

Apache camel how to test a rollback scenario

I have a generic message router that has its routes created at run time by various route builders based on some configuration.
The configuration is stored as XML and when loaded in memory it gets converted into a RouteConfig domain object exposing via its getters how the route should be build. Such a RouteConfig will have a getFromUri(), getDestinations(), getDeadLetterUri(), isTransacted(), etc methods defined.
An example such a route builder would look like below:
public class NonTransactedRouteBuilder extends AbstractRouteBuilder {
#Override
protected void buildRoute(String endPoint, RouteConfig routeConfig) {
RouteBean routeBean = getRouteBean(routeConfig.getBean());
final String[] destinations = routeConfig.getDestinations();
from(endPoint).routeId(createRouteId())
.autoStartup(false)
.threads(routeConfig.getThreads())
.filter(body().isNotNull())
.process((Processor) routeBean)
.filter(body().isNotNull())
.choice()
.when(header("dead.letter").isNotNull())
.to(getDeadLetterUri())
.otherwise()
.loadBalance().random()
.to(destinations)
.endChoice();
}
}
The above is just an example to give you an idea about what kind of routes we build. The only one relevant thing is that the route is not transacted. Now to unit test it works as expected we extend the TestNG flavor of CamelTestSupport and pass a mocked RouteConfig instance that will result in a route being configured to move messages between a direct:test and two mock:test1 and mock:test2 end points. The route bean has a bit of logic in it and depending of the message content help us with all testing scenarios:
#Test
public void shouldDiscardNullMessages() throws Exception {
...
}
#Test
public void shouldDiscardScamMessages() throws Exception {
...
}
#Test
public void shouldRouteMessageToDeadLetterQueue() throws Exception {
...
}
#Test(timeOut = 1000)
public void shouldRouteMessagesInALoadBalancedWay()
...
}
Everything works fine and we are very happy we can make code changes and test them immediately. However most of our routes builders will build transacted routes. From our integration and end to end tests we know the transacted functionality works fine but would be so much better to be able to test this at your code changing point with an unit test.
So my question is:
Having the same route as above with just an autoStartup(false).transacted() change on it would it be a way to getting the message back in the sender direct:test end point so we can cover this part of the functionality as well. We would be happy with any work around suggestion just to prove this aspect works.
Thank you in advance for your inputs.
UPDATE 1:
One of the things I tried was to configure my test camel context with a TransactionErrorHandler that had a mock jta transaction manager injected. Something like bleow:
#Test
public void shouldBeAbleToRollback() throws Exception {
TransactionErrorHandlerBuilder errorHandlerBuilder = new TransactionErrorHandlerBuilder();
errorHandlerBuilder.setTransactionManager(jtaTransactionManagerMock);
context().setErrorHandlerBuilder(errorHandlerBuilder);
template.sendBody(FROM_1, "rollback message");
...
}
Then I hoped that I will be able to capture a jtaTransactionManagerMock.rollback() but this did not happen. Wat would be the reason not to work.
UPDATE 2:
Unable to achieve the above I stepped back and started integrating ActiveMQ as a transactional resource in my unit tests and everything worked fine. In reality our routes include also file and database end points but for the purpose of unit testing a generic queue builder using just a JMS resource is enough. I did not realized how easy would be when you deal all day long with Webshere MQ manager where you need to have MQ manager created and configured and you have to have the queues already there and all the heavy infrastructure that you have to build for this. ActiveMQ just did a very good job.
However I will still be interested in whether there would be an way to mock this transnational behavior.

Camel and Spring: stop context when route is completed

I would like to run a route once and stop the context when the route is completed. Currently I do the usual Thread.sleep(3000) in the main Java class to leave some time for the route to finish but it's obviously not accurate, my route may take 1 second or 20 seconds I can not know in advance.
The Java class:
public static void main(String[] args) throws Exception {
try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("camel-context.xml")) {
CamelContext camelContext = SpringCamelContext.springCamelContext(context);
// context.start(); // apparently not necessary
camelContext.startRoute("route1");
try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
// context.stop(); // apparently not necessary
}
}
The Spring xml:
<route id="route1" autoStartup="false">
<from uri="timer://runOnce?repeatCount=1&delay=3000" />
...
</route>
After reading http://camel.465427.n5.nabble.com/Need-control-back-in-the-Main-routine-so-that-we-can-terminate-JVM-td4483312.html#a4484845 especially the 4th post, from Claus Ibsen, I was thinking of using camelContext.getRouteStatus() in a loop with a Thread.sleep() but wherever I try to get the route status in the code (even after the Thread.sleep(3000)), the status is always "started". I don't know any other way to detect when the route is done.
What is the recommended way to stop the Camel context when a/all route(s) is/are completed, using Spring?
The route will never stop because routes do not have complete state. They can just be started, stopped or paused. A route will always be running if it's in the started state unless you do something to change that.
To accomplish what you are looking for, you can do a couple of things:
You can use the controlbus component and stop the route in the last step of your route. That way you can check (for example the way you mentioned checking for camelContext.getRouteStatus()) when you should stop the context as well.
You can write a small Processor that whenever it receives an Exchange it will stop the camelContext. Once ready, you will add it to the last step of your route.
Camel supports onCompletion callbacks, which can be equivalent to the option above. See the camel page.
Probably, the first option is the easiest for your use case, however I would go for the second option. It seems cleaner to me.
A more elegant way would be to use a synchronisation mechanism provided by Java like CountDownLatch. Main thread will wait for the latch to be opened by the Route thread. Something like :
CountDownLatch latch = new CountDownLatch(1);
camelContext.addRoutes(createRoute(latch));
and somewhere in the createRoute Method add a processor at the end of the route to open the latch. This worked perfectly for me.
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
latch.countDown();
}
});

Correlating messages on two Camel Routes

In my application I have a generic Camel Route such as the following
from("direct:something").to("direct:outgoing")
and then dynamically in my code I deploy another route:
from("direct:outgoing").process(processor)
When flowing from route 1 to route 2 a new Exchange will be created. Is there an idiomatic way to correlate both? Should I set EXCHANGE.Correlation_ID header on the first route before sending it out?
This should definitely all be processed on the one exchange. Run this test and you'll see the same camel Exchange, with the same properties, etc.
public class CamelExchangeTest {
public static void main(String[] args) throws Exception {
final Processor showExchangeIdProcessor = new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println(exchange.getExchangeId());
}
};
Main camelMain = new Main();
camelMain.addRouteBuilder(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("timer:foo?period=1s&repeatCount=1")
.log("excgabge created!")
.process(showExchangeIdProcessor)
.to("direct:outgoing")
;
from("direct:outgoing")
.log("outgoing!")
.process(showExchangeIdProcessor)
;
}
});
camelMain.run();
}
}
Output:
ID-MYPC-55760-1411129552791-0-2
ID-MYPC-55760-1411129552791-0-2
So something else is going on. When you say "direct:outgoing", do you mean exactly that or is it something different - a different component perhaps?
When you say the route is created dynamically, how exactly is that done, and when (and why?)
From the Camel doc:
Some EIP patterns will spin off a sub message, and in those cases, Camel will add a correlation id on the Exchange as a property with they key Exchange.CORRELATION_ID, which links back to the source Exchange. For example the Splitter, Multicast, Recipient List, and Wire Tap EIP does this.
Thus, Exchange.CORRELATION_ID is set by Camel and should not be set by your application. But feel free to set a custom header or property if you need to such as:
exchange.getIn().setProperty("myProperty", myIdentifier);

Apache Camel routing to an interface (or rather adding listeners dynamically)

I am working on a simple use case that would allow clients to dynamically register for events from a JMS endpoint. My current implementation looks like this:
...
public void addListener(Event event, Listener listener){
try {
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from(event.from()).bean(listener);
}
});
} catch (Exception exception) {
exception.printStackTrace();
}
}
...
event.from() above would identify the endpoint from which the message would be consumed ("activemq:topic:market.stocks.update.ibm") and listener would be an implementation of the Listener interface.
I had envisaged a typical invocation as:
notifications.addListener(updateEvent, new Listener(){
void listen(){
System.out.println("Hey! Something got updated");
}
});
Except, of course, none of the above works since the camel route expects to have a concrete bean as the recipient and hence camel context fails to start-up.
What is the recommended way of adding bean end points dynamically?
answered on camel-users forum...
http://camel.465427.n5.nabble.com/Observer-Pattern-using-Camel-td4491726.html

Resources