Camel : Iterate the elements in the header,process it parallely - java

I am storing the list of url's in the Apache camel header,below is the code,
List<String> supplierHotelRefs = new ArrayList();
supplierHotelRefs.add("a.com");
supplierHotelRefs.add("b.com");
supplierHotelRefs.add("c.com");
exchange.getIn().setBody(supplierHotelRefs);
Now i need to iterate this list present in the header and make a call to url's. This should be the parallel activity. I tried with split(..) which is working fine if we store the list in the body, But due to some constraints i can't store it in body. It will be helpful if i get code to iterate and parallely process the collection present in the Camel Header.
Regards,
Raghavan

You could set the list in the header and split on that header.
exchange.getIn().setHeader("supplierHotelRefs",supplierHotelRefs);
In your route definition you could split based on the header property and process them parallely.
from("").....
//split based on the header
split(header("supplierHotelRefs"))
//process every split exchange parallely
.parallelProcessing()
//end split block
.end()
//continue route definition after split
.log("completed split processing")
Note that the caller thread will still wait for all the split messages to be completed.

You can use Recipient List EIP, see http://camel.apache.org/recipient-list.html
You have to create a list with all recipients and store that list to a header. I observed in your code that you are not setting that list to a header, but as a body. You have to do something like
List<String> supplierHotelRefs = new ArrayList();
supplierHotelRefs.add("http4://a.com");
supplierHotelRefs.add("http4://b.com");
supplierHotelRefs.add("http4://c.com");
exchange.getIn().setHeader("yourHeaderName", supplierHotelRefs);
As you can notice each element of the list has a valid url of http4 Camel component.This component is used in order to make http calls. You can have anything you want into that list (other Camel's components).
Then you use the recipient list EIP, telling that all recipients are into the previous created header. parallelProcessing = true, call all items in the list in parallel. This is calling that in XML DSL:
<recipientList parallelProcessing="true">
<header>yourHeaderName</header>
</recipientList>
or in Java DSL:
from("...")
...
.recipientList(header("yourHeaderName"));

Related

Remove a document from Mongo DB with Apache Camel

My main issue might be not understanding some conventions in the Camel documents.
https://camel.apache.org/components/latest/mongodb-component.html#_delete_operations
They have a camel route commented out, and two Java objects being defined, which are not commented out. What are they trying to indicate? Where are these objects at in a project?
Anyway, I'm subscribed to a JMS queue that I have another camel route publishing to. The message is a JSON string, which I save to a Mongo DB. But what I'd like to do is remove any current documents (based on criteria) and replace it with the new message.
from("jms:topic:orderbook.raw.feed")
.log("JMS Message: ${body}")
.choice()
.when().jsonpath("$.[?(#.type=='partial')]")
// Figure out how to delete the old orderbook from Mongo with a type=T1
.to("mongodb:mongo?database=k2_dev&collection=orderbooks&operation=save");
Does your orderbook have an ID? If so, you can enrich the JSON with an _id field (MongoDB default representation for identifiers) whose value would be that ID. Thus you'll be "upserting" that orderbook.
Obs.: Sure the Camel docs could be better.
But if you really feel you'd have to perform a remove operation before saving an orderbook, another option would be to extract its type from the current JSON string and use it as a filter when removing. Something like:
from("jms:topic:orderbook.raw.feed")
.log("JMS Message: ${body}")
.filter("$.[?(#.type=='partial')]")
.multicast().stopOnException()
.to("direct://orderbook-removal")
.to("direct://orderbook-save")
.end()
;
from("direct://orderbook-removal")
// extract type and set it as the body message. e.g. {"type":"T1"}
.to("mongodb:mongo?database=k2_dev&collection=orderbooks&operation=remove")
;
from("direct://orderbook-save")
.to("mongodb:mongo?database=k2_dev&collection=orderbooks&operation=save")
;
The multicast sends a copy of the message to each destination. So the content won't be affected.

how to convert Flux<pojo> to ArrayList<String>

In my spring-boot springboot service class, I have created the following code which is not working as desired:
Service class:
Flux<Workspace> mWorkspace = webClient.get().uri(WORKSPACEID)
.retrieve().bodyToFlux(Workspace.class);
ArrayList<String> newmWorkspace = new ArrayList();
newmWorkspace = mWorkspace.blockLast();
return newmWorkspace;
Please someone help me on converting the list of json values to put it into arrayList
Json
[
{
"id:"123abc"
},
{
"id:"123abc"
}
]
Why is the code not working as desired
mWorkspace is a publisher of one or many items of type Workspace.
Calling newmWorkspace.blockLast() will get a Workspace from that Publisher:
which is an object of type: Workspace and not of type ArrayList<String>.
That's why : Type mismatch: cannot convert from Workspace to ArrayList<String>
Converting from Flux to an ArrayList
First of all, in reactive programming, a Flux is not meant to be blocked, the blockxxx methods are made for testing purposes. If you find yourself using them, then you may not need reactive logic.
In your service, you shall try this :
//initialize the list
ArrayList<String> newmWorkspace = new ArrayList<>();
Flux<Workspace> mWorkspace = webClient.get().uri(WORKSPACEID)
.retrieve().bodyToFlux(Workspace.class)
.map(workspace -> {
//feed the list
newmWorkspace.add(workspace.getId());
return workspace;
});
//this line will trigger the publication of items, hence feeding the list
mWorkspace.subscribe();
Just in case you want to convert a JSON String to a POJO:
String responseAsjsonString = "[{\"id\": \"123abc\"},{\"id\": \"123cba\"}] ";
Workspace[] workspaces = new ObjectMapper().readValue(responseAsjsonString, Workspace[].class);
You would usually want to avoid blocking in a non-blocking application. However, if you are just integrating from blocking to non-blocking and doing so step-by-step (unless you are not mixing blocking and non-blocking in your production code), or using a servlet stack app but want to only use the WebFlux client, it should be fine.
With that being said, a Flux is a Publisher that represents an asynchronous sequence of 1..n emitted items. When you do a blockLast you wait until the last signal completes, which resolves to a Workspace object.
You want to collect each resolved item to a list and return that. For this purpose, there is a useful method called collectList, which does this job without blocking the stream. You can then block the Mono<List<Workspace>> returned by this method to retrieve the list.
So this should give you the result you want:
List<Workspace> workspaceList = workspaceFlux.collectList().block();
If you must use a blocking call in the reactive stack, to avoid blocking the event loop, you should subscribe to it on a different scheduler. For the I/O purposes, you should use the boundedElastic Scheduler. You almost never want to call block on a reactive stack, instead subscribe to it. Or better let WebFlux to handle the subscription by returning the publisher from your controller (or Handler).

Iterating over user user defined collections in Apache came route

I got a requirement that i have on main route and child route. In the main route will get the list of objects, there i need to make a call to child route for each individual elements in the list.
Then in Child route will make a call to web-service by appending that element as one of the parameter.
Code:
from("direct:SupplierRoute")
.choice()
.when(header(IS_SUPPLIER_AVAILABLE).isEqualTo(true))
.split(body())
.parallelProcessing()
.streaming()
.to("direct:SUPGetHotelAggregatorRatesRQ")
.bean(parallelProcessingRequestProcessor)
.end()
.end()
.end();
from("direct:SUPGetHotelAggregatorRatesRQ")
.process(startOperation(DISTRIBUTION, GET_HOTEL_AGGREGATOR_RATES_API_GENERATE_VM_REQUEST))
.to("velocity:velocity/GetHotelAggregatorRatesRQToGetHotelSupplierRatesRQ.vm")
.process(endOperation(DISTRIBUTION, GET_HOTEL_AGGREGATOR_RATES_API_GENERATE_VM_REQUEST))
.end();
I'm setting the value to the exchange body as below,
public static final List<HotelRefs.HotelRef> supplierHotelRefs = new ArrayList();
exchange.getIn().setBody(supplierHotelRefs);
But the above code sample was not working,If anyone let us know if there is any approach in camel to iterate over user defined collections at route level.
Thanks,
Raghavan
Put a log statement after the Splitter and run the code.
.split(body())
.log("How many log lines do you receive?")
If you get just one log line, the Splitter does not work as expected. You could then try to use another Camel version.
If you get as many log lines as your ArrayList contains elements, the iteration works fine and you've got another problem. You should then find out the real problem and ask a new question.
If you get no log line at all, your condition in when is not satisfied.

Fetching string value from JMS header

I'm trying to figure out how to pull a value out of a JMS header so I can route it to a specific queue dynamically. I have spent hours trying various methods to extract the value but I haven't been successful. See the below snippet which explains what I'm after. Any help or even pointing me in the right direction would be much appreciated.
// fetch the queue name from the JMS header
String queue = "jms:queue:" + header("__DestinationQueue").toString();
choice = choice
.when(header("Match").isEqualTo("true"))
.id("JMSDaemonOutboundRouteBuilder")
.to(queue);
choice = choice
.otherwise().bean(UnroutableMessageLogAction.class)
.bean(EndConversationAction.class, "process")
.bean(ESBMessageCompleteAction.class, "process")
.to("jms:queue:UNROUTED").endChoice();
The data is definitely contained in the header (I've run .isEqualTo comparisons on the header value to confirm this) I am just having issues extracting the actual value into the string.
The solution was to use toD instead of to which natively allows routing to JMS header values. Link below:
http://camel.apache.org/message-endpoint.html

Apache camel Composed Message Processor

I am using Apache Camel in my Application. I am trying to use Composed Message Processor. I have exchange whose body contains some URLs to hit and by using split(body(), MyAggregationStrategy()), I am trying to get the data from urls and using Aggregation Strategy want to combine each data. But there is a problem where I am stuck. If there is some invalid url on the first line of the body then it happens that aggregation is working fine but it is not moving to the next processor and if invalid url is anywhere else except first line than it is working fine..
please help,
Here is the code for reference
onException(HttpOperationFailedException.class).handled(true)
.retryAttemptedLogLevel(LoggingLevel.DEBUG)
.maximumRedeliveries(5).redeliveryDelay(3000)
.process(new HttpExceptionProcessor(exceptions));
from("jms:queue:supplier")
.process(
new RequestParserProcessor(payloadDetailsMap,
metaDataDetailsPOJO, routesEndpointNamePOJO))
.choice().when(new AggregateStrategy(metaDataDetailsPOJO))
.to("direct:aggregate").otherwise().to("direct:single");
from("direct:aggregate").process(new SplitBodyProcessor())
.split(body(), new AggregatePayload(aggregatePayload))
.to("direct:aggregatepayloadData").end()
.to("direct:payloadDataAggregated").end();
from("direct:aggregatepayloadData").process(basicProcessor)
.recipientList(header(ApplicationConstants.URL));
from("direct:payloadDataAggregated")
.process(
new AggregateJsonGenerator(aggregatePayload,
payloadDetailsMap, metaDataDetailsPOJO)).
In this code AggregateJsonProcessor is never called if there some invalid url on the first hit..
You probably need to set continue(true) in your OnException code. See here:
http://camel.apache.org/exception-clause.html

Categories

Resources