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

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

Related

errorHandle in multiple, hierarhical RouteBuilders

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. *

Camel ProducerTemplate and ActiveMQ message and persistence issues

Issue:
I have multiple route sending messages to an ActiveMQ queue which later gets processed by a processor to save status information
The same message sending from ProducerTemplate to ActiveMQ queue somehow breakes the same code by not triggering logs on console, and saving status information to a randomly generated file name.
Desired Behavior:
Both sending method gets the messages processed the same way
Code Explanation:
On below codes the Save processor is the one producing the weird behavior where logs dont show up on console and writes to some random file, file name is basically the clientID from ActiveMQ
StartRoute calling the activemq:save works correctly
Code:
public class Save implements Processor {
public void process(Exchange exchange) throws Exception {
try {
Map<String, Object> map = new HashMap<String, Object>() {};
map.put(".....", ".....");
map.put(".....", ".....");
map.put(".....", ".....");
map.put(".....", ".....");
ProducerTemplate template = exchange.getContext().createProducerTemplate();
String response = template.requestBodyAndHeaders("activemq:save", "Batch Job Started", map, String.class);
FluentProducerTemplate FluentTemplate = exchange.getContext().createFluentProducerTemplate();
String result = FluentTemplate
.withHeader(".....", ".....")
.withHeader(".....", ".....")
.withHeader(".....", ".....")
.withHeader(".....", ".....")
.withHeader(".....", ".....")
.to("activemq:save")
.request(String.class);
} catch (Exception e) {
.....
}
}
}
public class StartRoute extends RouteBuilder {
restConfiguration()
.component("servlet")
.enableCORS(true);
rest("/start").id("start")
.post("/{filename}")
.route()
.....
.process(new Save())
.wireTap(startBackgroundProcessRoute)
.to("activemq:save")
.endRest();
}
public class SaveRoute extends RouteBuilder {
from("activemq:save")
.log("started saving")
.process(new FormatText())
.to("file:///status");
}
This question came from my original problem described here:
Camel Multicast Route call order
Solution can be found also there:
Camel Multicast Route call order
However to satisfy this question:
It seems Camel have few bugs regarding producer templates and maybe ActiveMQ, this is my initial conclusion.
The Only way i was able to use ProducerTemplate without issue is to use the send function with Exchanges, send() sends the messages correctly to the ActiveMQ same way as the to() however for whatever reason the content was still not written to the file.
After I dropped the ActiveMQ between the routes, everything started to work consistently. So possible miss configuration on ActiveMQ component or possible another camel framework bug.
If anyone knows the exact answer i would be happy to hear / see the reason for this behavior.
Code example:
public class SaveProcessor implements Processor {
public void process(Exchange exchange) throws Exception {
ProducerTemplate template = exchange.getContext().createProducerTemplate();
template.send(Utilities.trackBatchJobStatus, exchange);
/** NOTE
* DO NOT USE any other functions which are not working with EXCHANGES.
* Functions that uses body, header values and such are bugged
*/
}
}

How to use Apache Camel exchange messages?

I am newcomer in Apache Camel. Please have a look to my code bellow:
I have a service which exposed as cxf webservice:
interface CxfService{
public OutputType hello(InputType input);
}
This is my route:
from("cxf:/test?serviceClass=" + CxfService.class.getName())
.to("log:cxfLog1")
.recipientList(simple("direct:${header.operationName}"));
from("direct:hello")
.process(new Processor(){
public void process(Exchange exchange) throws Exception {
InputType file = exchange.getIn().getBody(InputType.class);
exchange.getOut().setBody(new OutputType());
}
});
The code works as expected, it consume InputType and produce OutputType.
I want to borrow my body to do another stuffs, so i rewrite that like this:
from("cxf:/test?serviceClass=" + CxfService.class.getName())
.to("log:cxfLog1")
.recipientList(simple("direct:${header.operationName}"));
from("direct:hello")
.process(new Processor(){
public void process(Exchange exchange) throws Exception {
InputType file = exchange.getIn().getBody(InputType.class);
exchange.getOut().setHeader("header.temporary", new OutputType());
}
})
.to("some endpoint")
.setBody(simple("${header.temporary}"));
This webservice consume InputType and produce nothing. What wrong with that?
In your second piece of code, when setting the header.temporary, you should change two things:
setHeader("temporary", new OutputType()) - the 'header' prefix isn't
needed - you're addressing headers directly via the method call.
Use getIn() instead of getOut(). The input will get copied to the
output. You may want to do some research into the procedure for
Camel building the out message for details - I'm not 100% sure of
this one.
Change
exchange.getOut().setHeader("header.temporary", new OutputType());
To
exchange.getIn().setHeader("temporary"), new OutputType());
.setHeader() is when you use the simple language. In 99% of the cases getIn() is sufficient.

Apache Camel: Several Routes to the Same Route

I would like to route messages from more routes to the same route but it does not work in the manner as I assumed. I set up the following (I am just puting down the essence):
from("direct:a") [...]
.to("direct:c");
from("direct:b") [...]
.to("direct:c");
from(direct:c) <my aggregator functionality comes here>
.to("direct:someOtherRoute");
However, this works only when exactly one route either "a" or "b" goes to "c" but not both. How should I achieve to route both "a" and "b" to "c"? Thanks.
EDIT1:
I tried the solution of Alexey but using "seda" or "vm" did not solve the problem. Actually, regardless of calling route "c" with seda or vm, the aggregator is invoked only once either from route "a" or from route "b".
However, if I create another route "c2" with the same content and route e.g. "b" to "c2", then it works. Nevertheless, it is not really nice way to solve it.
Do you have any further ideas? I am using the routes within the same CamelContext, so within the same JVM.
I have also found an interesting remark on the link http://camel.apache.org/seda.html
It states as Alexey and Sunar also told that seda and vm are asynchronous and direct synchronous but you can also implement asynchronous functionality with direct as follows:
from("direct:stageName").thread(5).process(...)
"[...] Instead, you might wish to configure a Direct endpoint with a thread pool, which can process messages both synchronously and asynchronously. [...]
I also tested it but in my case it did not yield any fruits.
EDIT2:
I add here how I am using the aggregator, i.e. route "c" in this example:
from("vm:AGGREGATOR").routeId("AGGREGATOR")
.aggregate( constant("AGG"), new RecordAggregator())
.completionTimeout(AGGREGATOR_TIMEOUT)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
LOGGER.info("### Process AGGREGATOR");
[...]
}
})
.marshal().csv()//.tracing()
.to("file:extract?fileName=${in.header.AGG}.csv")
.end();
In the log the String "### Process Aggregator" appears only once. I am just wondering whether it cannot depend on the .completionTimeout(AGGREGATOR_TIMEOUT) I am using. In my undestanding, a file should be created for each different AGG value in the header within this time. Is this understanding correct?
I think the using of asynchronous components, such as seda, vm, activemq might solve your problem.
Such behavior direct component because direct is synchronous component, this is also likely related to using the aggregator in the third route.
Example:
from("direct:a") [...]
.to("seda:c");
from("direct:b") [...]
.to("seda:c");
from(seda:c) <your aggregator functionality comes here>
.to("direct:someOtherRoute");
EDIT1:
Now, when I see an aggregator, I think that's the problem, in the completion criteria.
In your case, you have to use expression for correlationExpression:
from("vm:AGGREGATOR").routeId("AGGREGATOR")
.aggregate().simple("${header.AGG}",String.class) // ${property.AGG}
.aggregationStrategy(new RecordAggregator())
.completionInterval(AGGREGATOR_TIMEOUT) //.completionTimeout(AGGREGATOR_TIMEOUT)
.forceCompletionOnStop()
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
LOGGER.info("### Process AGGREGATOR");
[...]
}
})
.marshal().csv()//.tracing()
.to("file:extract?fileName=${in.header.AGG}.csv&fileExist=Override")
.end();
and, maybe completionTimeout is too low...
Try the below , this is only a sample
from("timer:foo?repeatCount=1&delay=1000").routeId("firstroute")
.setBody(simple("sundar")).to("direct:a");
from("timer:foo1?repeatCount=1&delay=1000").routeId("secondRoute")
.setBody(simple("sundar1")).to("direct:a");
from("direct:a")
.aggregate(new AggregationStrategy() {
#Override
public Exchange aggregate(Exchange arg0, Exchange arg1) {
Exchange argReturn = null;
if (arg0 == null) {
argReturn= arg1;
}
if (arg1 == null) {
argReturn= arg0;
}
if (arg1 != null && arg0 != null) {
try {
String arg1Str = arg1.getIn()
.getMandatoryBody().toString();
String arg2Str = arg0.getIn()
.getMandatoryBody().toString();
arg1.getIn().setBody(arg1Str + arg2Str);
} catch (Exception e) {
e.printStackTrace();
}
argReturn= arg1;
}
return argReturn;
}
}).constant(true).completionSize(2)
.to("direct:b").end();
from("direct:b").to("log:sundarLog?showAll=true&multiline=true");
You can use seda or other asynchronous routes as pointed out by Yakunin.Using an aggregator here the major contention point would be the completionSize , where i have used 2 , since two routes are sending in the messages.

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);

Resources