Camel : Invoke one route from another and get back response synchronously - apache-camel

I have two route A and B as below
from("some endpoint") //route A
.to("direct:x")
from("direct:x") //route B
.doTry()
.bean("somebean") //set Custom object PQR as message body
.doCatch()
.bean("some other bean") //log exception
.end()
Now I want to invoke route B once route A is complete (already done). Now how to get the response PQR object (only if route B succeeds) back from route B in route A?

It is supposed to do that automatically. If you chain another to in route A after the to("direct:x") , it will take the response from route B. You can try logging the body after the to("direct:x") and check the result.

Related

Is it the designed and desired behavior that makes Camel routes to not execute onCompletion tasks in a pipeline with several SEDA queues?

I stumbled upon a problem with not working onCompletion between the routes that pass a message over SEDA queues.
The route configuration is similar to this simplified version:
from("direct:a")
.onCompletion().log("a - done").end()
.to("seda:b");
from("seda:b")
.onCompletion().log("b - done").end()
.to("seda:c");
from("seda:c")
.onCompletion().log("c - done").end()
.to("seda:d");
from("seda:d")
.onCompletion().log("d - done").end()
.to("mock:end");
With this configuration, I get only "d - done" logging.
I debugged the execution and noticed that the onCompletion handler from "a", "b", and "c" don't get executed because they are route-scoped and get attempted to be executed in the scope of the next route.
This happens because they get handed over from the initial exchange to an exchange prepared for the next route. It happens in the SedaProducer::addToQueue method with copy parameter defined as true, which makes the ::prepareCopy method being called, which in its turn calls ExchangeHelper.createCorrelatedCopy with handover defined as true.
It seems to me like a bug, because looking at the routes configuration I'd expect different behavior: all the onCompletion tasks get executed reporting on routes finalization. Though maybe I'm missing something here, and if this is the case then I would appreciate you guys helping me to find out the missing details.
The workaround I implemented is ugly but does the trick. Before sending to the next route (queue) we hand over completions to a holder exchange and after sending we hand over them back from the holder.
Here is a code example:
route
.process(exchange -> {
var holder = new DefaultExchange(exchange.getContext());
exchange.adapt(ExtendedExchange.class).handoverCompletions(holder);
exchange.setProperty(SYNC_HOLDER_PROPERTY, holder);
})
.to("seda://next")
.process(exchange -> exchange
.getProperty(SYNC_HOLDER_PROPERTY, ExtendedExchange.class)
.handoverCompletions(exchange));

How to dynamically return a from endpoint in apache camel DSL

Here is my code
from("google-pubsub:123:subscription1?maxMessagesPerPoll=3 & concurrentConsumers=5" ).routeId("myroute")
.process(new ProducerProcessor())
to("google-pubsub:123:topic1")
;
In my code above ,the from channel I want to make it generic.Basically it should be able to consume data from good-pubsub or may be from a file or from a JMS queue.Hence depending upon a parameter I want to return
a different from channel.Something like below
private RouteDefinition fromChannel(String parameter) {
if (parameter is "google" then
return from("google-pubsub:123:subscription1?maxMessagesPerPoll=3 & concurrentConsumers=5" )
if (parameter is "file" then
return from(/my/fileFolder/)).split(body().tokenize("\n")).streaming().parallelProcessing();
}
I tried this but I am getting null pointer exception in the fromChannel method.Please let me know if you have better ideas.
Rewrite based on comment
You can for example create a (static) template route for every input type and generate the routes based on a configured endpoint list.
I described such an endpoint configuration and route generation scenario in this answer.
Like this you can generate the split part for every file route and any other specialty for other route types.
All these input routes are routing at their end to a common processing route
.from(pubsubEndpoint)
.to("direct:genericProcessingRoute")
.from(fileEndpoint)
.split(body()
.tokenize("\n"))
.streaming()
.parallelProcessing()
.to("direct:genericProcessingRoute")
.from("direct:genericProcessingRoute")
... [generic processing]
.to("google-pubsub:123:topic1")
The multiple input (and output) routes around a common core route is called hexagonal architecture and Camel fits very well into this.

Multiple GET routes with different parameters

does anyone know if u can and if its good practice to define multiple GET routes such as:
GET: '/channels' returns all channels in the app
GET: '/channels/:username' returns all channels created by a user
GET: '/channels/:channelname' return details about a channel
POST: '/channels' creates a new channel
PUT: '/channels/:channelname' edits a channel with the name ':channelname'
What im confused is the third GET route. Don't know if it's possible or if theres a better way to do it..
You cannot have multiple routes with the same parameter.
I think that the most logical way is to do this :
GET: '/channels/user/:username' returns all channels created by a user
You couldn't use same verb (get, post, put ..) for same route. You can use same verb for different route or same route for different verb.
in your problem used
GET: '/channels/:username' and
GET: '/channels/:channelname'
both act as a same thing because when you request by /channels/somename fro client site then valid for both routes because username can be somename or channelname can be somename.
so to differentiate two routes need to change your any one route. like
GET: '/channels/user/:username' // that different from /channels/:channelname
for node.js and express you can use like:
var router = express.Router();
router.route('/place')
.get(placeController.getPlaces)
.post(placeController.createPlace);
router.route('/place/:placeId') // same route for different verb
.get(placeController.getPlaceById)
.put(placeController.updatePlaceById)
.delete(placeController.deletePlaceById);
router.route('/hotel/:placeId') // here :placeId params name same for both but /hotel differentiate from /place
.get(hotelController.getHotelById)
.put(hotelController.updateHotelById)
.delete(hotelController.deletHotelById);

Possible to initialize a bindy (Apache Camel DataFormat - FixedLength and use it in the same route

My input file consists of several type of FixedLengthRecord, so I have lots of FixedLengthDataFormat to unmarshal each post.
I split the body per row
for the first I should realize which DataFormat I should use, and create an object
Then unmarshal
Something like this one:
from(myURI)
.split().tokenize("\n")
.process(initializeMyBindyDataFormat)
.unmarshal(bindy)
.end();
But my problem is, I get NPE for that bindy object when I initilize it via a process.
But if I create a bindy object before my route definition (before from) it will be work fine. My bindy object is depended on body and I cannot initialize it before route definition.
Actually Apache Camel process initialization of bindy object before starting the route
The answer is using .inout
Since I want to have unmarshalling in another route, a simple example should be as below:
from(myURI)
.split().tokenize("\n")
.inout("direct:unmarshalSpecificRow")
.end();
from(direct:unmarshalSpecificRow")
.choice()
.when(firstPredicate)
unmarshal(new BindyFixedLengthDataFormat(package1)
.when(secondPredicate)
unmarshal(new BindyFixedLengthDataFormat(package1)
.when(thirdPredicate)
unmarshal(new BindyFixedLengthDataFormat(package1)
.otherwise()
.throwException(new IllegalArgumentException("Unrecognised post")
.end();
Thanks jakub-korab for his help.
In this case I think it is better to divide your processing in two seps.
A main route which receives the different data. Here you define the predicate rules that determine what kind of body it is. Check the start of the body, or something that determines it is of this type or that. Add a choice() when() and based on which predicate gets set to true set it to separate route.
In the secondary routes add the specific bindy format and do your marshal/unmarshal work.
An example from the the documentation:
Predicate isWidget = header("type").isEqualTo("widget");
from("jms:queue:order")
.choice()
.when(isWidget).to("bean:widgetOrder")
.when(isWombat).to("bean:wombatOrder")
.otherwise()
.to("bean:miscOrder")
.end();
http://camel.apache.org/predicate.html

How is URL delivered to the router in Cake?

www.foo.com/blog/posts/view/12/12/2013
There is no possible file corresponds to this request. So this URL needs to be parsed by some logic, or else you get a 404.
Since there is no corresponding file, the response cannot be automated by the server. I was just wondering which part of Cake FIRST responds to a request like this. I understand simple page requests are first parsed and resolved by the router. But the URL cannot just magically get to the front door of the router, right? I really want to know what is going on behind the scene that brings the URL to the router.
Check your app/webroot/index.php, the bottom:
$Dispatcher = new Dispatcher();
$Dispatcher->dispatch(
new CakeRequest(),
new CakeResponse()
);
The key method of the Dispatcher is parseParams. This method gets triggered through the event system at the start of Dispatcher::dispatch(), checkt that method as well in the class.
What happens is basically that the dispatcher uses the router to parse the plain url and turns it into params and adds the parsed result to the request object and then dispatches a controller based on the parsed result.
/**
* Applies Routing and additionalParameters to the request to be dispatched.
* If Routes have not been loaded they will be loaded, and app/Config/routes.php will be run.
*
* #param CakeEvent $event containing the request, response and additional params
* #return void
*/
public function parseParams($event) {
$request = $event->data['request'];
Router::setRequestInfo($request);
$params = Router::parse($request->url);
$request->addParams($params);
if (!empty($event->data['additionalParams'])) {
$request->addParams($event->data['additionalParams']);
}
}

Resources