Camel: How to go all "when" in "choice when" - apache-camel

I need to ask a problem on the operator "choice when" in Apache Camel route. In the following example, if I have two soap-env:Order elements which have 1, 2 value, then I want to create two xml file named output_1.xml and output_2.xml. However, the code can only create one file output_1.xml.
Can anyone give me any ideas or hints? Thanks for any help.
public void configure() {
...
from("direct:a")
.choice()
.when(ns.xpath("//soap-env:Envelope//soap-env:Order='1'"))
.to("file://data?fileName=output_1.xml")
.when(ns.xpath("//soap-env:Envelope//soap-env:Order='2'"))
.to("file://data?fileName=output_2.xml")
.when(ns.xpath("//soap-env:Envelope//soap-env:Order='3'"))
.to("file://data?fileName=output_3.xml")
}

My understanding is that the content based router implements "if - else if - else" semantics, meaning that as soon as one test evaluates to true, then the remaining tests are skipped. If you want to create files for every case that returns true then you'd have to change the route to something like this:
from("direct:a")
.choice()
.when(ns.xpath("//soap-env:Envelope//soap-env:Order='1'"))
.to("file://data?fileName=output_1.xml")
.end()
.choice()
.when(ns.xpath("//soap-env:Envelope//soap-env:Order='2'"))
.to("file://data?fileName=output_2.xml")
.end()
.choice()
.when(ns.xpath("//soap-env:Envelope//soap-env:Order='3'"))
.to("file://data?fileName=output_3.xml")
.end()

There is nothing wrong with the DSL and you dontt need end blocks here. I would look at your data and trace through why all calls are ending up in the same when block. Put a couple of log lines in or enable the tracer and look at the exchanges going through.

In Camel root choice() if you have multiple when() cases you have to write otherwise(). Please refer below.
from("direct:a")
.choice()
.when(header("foo").isEqualTo("bar"))
.to("direct:b")
.when(header("foo").isEqualTo("cheese"))
.to("direct:c")
.otherwise()
.to("direct:d")
.end;
The above mentioned solution will check all three conditions even if first one pass.

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 fix several input per route in Camel 3.X.X?

I have a route that looks like this:
from(URL_A)
.from(URL_B)
.to(URL_C)
.process(...)
// logging
.to(URL_D)
This route works perfectly in Camel 2.X.X but not in 3.7.X
The error message I get:
Only one input is allowed per route. Cannot accept input: From[direct:ABCD]
I checked the migration guide, but I cannot get how to migrate this sort of route.
Do you have any idea how to tackle it further?
I think you can use direct component: https://camel.apache.org/components/3.4.x/direct-component.html
For example:
from(URL_A)
.to(direct:collector)
from(URL_B)
.to(direct:collector)
from(direct:collector)
.to(URL_C)
.process(...)
// logging
.to(URL_D)
#Stepan Shcherbakov suggested a solution, below will be an enhancement of it:
String [] sources = {URL_A, URL_B}
for (String source : sources) {
from(source)
.to(direct:collector)
}
from(direct:collector)
.to(URL_C)
.process(...)
// logging
.to(URL_D)

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.

Camel ZipFileDataFormat splitting doesn't set the headers when streaming

I have a simple route which polls zip files from FTP server. The zip file consists of one file that needs processing and zero or more attachments.
I am trying to use ZipFileDataFormat for splitting and I'm able to split and route the items as desired i.e. send the processing file to the processor and other files to the aggregator endpoint.
The route looks like below:
from(sftp://username#server/folder/path?password=password&delay=600000)
.unmarshal(getZipFileDataFormat()).split(body(Iterator.class)).streaming()
.log("CamelSplitComplete :: ${header.CamelSplitComplete}")
.log("Split Size :: ${header.CamelSplitSize}")
.choice()
.when(header(MyConstants.CAMEL_FILE_NAME_HEADER).contains(".json"))
.to(JSON_ENDPOINT).endChoice()
.otherwise()
.to(AGGREGATOR_ENDPOINT)
.endChoice()
.end();
getZipFileDataFormat
private ZipFileDataFormat getZipFileDataFormat() {
ZipFileDataFormat zipFile = new ZipFileDataFormat();
zipFile.setUsingIterator(true);
return zipFile;
}
The splitting works fine. However, I can see in the logs that the two headers CamelSplitComplete and CamelSplitSize are not set correctly. Where CamelSplitComplete is always false, CamelSplitSize is not having any value.
Because of this, I am not able to aggregate based on the size. I am using eagerCheckCompletion() for getting the input exchange in the aggregator route. My aggregator route looks like below.
from(AGGREGATOR_ENDPOINT).aggregate(new ZipAggregationStrategy()).constant(true)
.eagerCheckCompletion().completionSize(header("CamelSplitSize"))to("file:///tmp/").end();
I read Apache Documentation that these headers are always set. Am I missing anything here? Any pointer in the right direction would be very helpful.
I was able to get the whole route to work. I had to add a sort of pre-processor which would set some essential headers (Outgoing file name and file count of the zip) I'd require for aggregation.
from(sftp://username#server/folder/path?password=password&delay=600000).to("file:///tmp/")
.beanRef("headerProcessor").unmarshal(getZipFileDataFormat())
.split(body(Iterator.class)).streaming()
.choice()
.when(header(Exchange.FILE_NAME).contains(".json"))
.to(JSON_ENDPOINT).endChoice()
.otherwise()
.to(AGGREGATOR_ENDPOINT)
.endChoice()
.end();
After that, the zip aggregation strategy worked as expected. Putting here the aggregation route just for completion of answer.
from(AGGREGATOR_ENDPOINT)
.aggregate(header(MyConstants.HEADER_OUTGOING_FILE_NAME), new ZipAggregationStrategy())
.eagerCheckCompletion().completionSize(header(MyConstants.HEADER_TOTAL_FILE_COUNT))
.setHeader(Exchange.FILE_NAME, simple("${header.outgoingFileName}"))
.to("file:///tmp/").end();

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

Resources