Camel unmarshaller supporting multiple dataformats - apache-camel

Is there a Camel unmarshaler that I could use to unmarshal from multiple data formats (JSON, XML, etc) to, say XML?
This "universal" unmarshaller would then be used as, for example:
<route id="myRoute">
<from uri="file:test/input"/>
<!-- The input can be in JSON or in XML -->
<unmarshal ref="universalUnmarshallerToXML"/>
<!-- The input payload is always in XML -->
<choice >
<when>
<xpath>/order/customer/country = 'US'</xpath>
<to uri="file:test/output/us"/>
</when>
<when>
<xpath>/order/customer/country = 'UK'</xpath>
<to uri="file:test/output/uk"/>
</when>
<otherwise>
<to uri="file:test/output/others"/>
</otherwise>
</choice>
</route>
Does this universal unmarshaller exist (hopefully it does), or should I implement my own?
Thanks!

It's not exactly a universal unmarshaller, but similar to what you are asking for:
When you create a REST service, you can set a BindingMode so that Camel will unmarshal from JSON or XML automatically, depending on the incoming Content-Type:
restConfiguration()
.bindingMode(RestBindingMode.auto) // can also be .xml, .json,....
.component("servlet");
But, I haven't seen this being feature used or exposed outside of REST services, yet.
Implementation is in RestBindingAdvice if you're interested.

Related

Is it possible to read a file after receiving an event?

I'm using a ActiveMQ Broker with built-in Camel Routes. I want to read a file after an Event received.
<pseudo>
from Event A
read File XY
to Event B with Body from File XY
</pseuod>
I simple tried moving files from a temporary directory based on an event but only event B is written. In the Log file are no Exceptions or Error messages.
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<!-- You can use Spring XML syntax to define the routes here using the <route> element -->
<route>
<description>Example Camel Route</description>
<from uri="activemq:example.A"/>
<from uri="file://tmp/a?delete=true"/>
<to uri="file://tmp/b?overruleFile=copy-of-${file:name}"/>
<to uri="activemq:example.B"/>
</route>
</camelContext>
Update with working solution for single file:
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<!-- You can use Spring XML syntax to define the routes here using the <route> element -->
<route>
<description>Example Camel Route</description>
<from uri="activemq:example.A"/>
<pollEnrich>
<constant>file:///tmp/a?fileName=file1</constant>
</pollEnrich>
<log message="file content ${body}"/>
<to uri="activemq:example.B"/>
</route>
</camelContext>
You need to use Content Enrichers for this. This is exactly what you are looking for.
<route>
<from uri="activemq:example.A"/>
<pollEnrich>
<constant>file://tmp/a?delete=true</constant>
</pollEnrich>
<to uri="activemq:example.B"/>
</route>
Please be aware that for camel version 2.15 or older
pollEnrich does not access any data from the current Exchange which
means when polling it cannot use any of the existing headers you may
have set on the Exchange. For example you cannot set a filename in the
Exchange.FILE_NAME header and use pollEnrich to consume only that
file. For that you must set the filename in the endpoint URI.

Apache Camel unzip, process and aggregate not completing

I am trying to process a zip file, which contains several files inside (all small, so working in memory is not a problem) that need to be transformed and zipped together again.
I managed to unzip, transform the files, but for some reason, the splitter is not completing, only using completionTimeout makes the aggregator to create the final zip archive.
Here's the route:
<route id="ZipFile">
<from uri="file:{{file.path.in}}?move=.done&moveFailed=.error&readLock=rename"/>
<setProperty propertyName="OriginalZipName">
<simple>${header.CamelFileName}</simple>
</setProperty>
<unmarshal>
<zipFile usingIterator="true"/>
</unmarshal>
<split streaming="true">
<simple>${body}</simple>
<log message="************ CamelSplitComplete = ${property.CamelSplitComplete}"/>
<to uri="direct:ProcessUnzippedFile"/>
<setHeader headerName="CamelFileName">
<simple>${property.OriginalZipName}</simple>
</setHeader>
<!-- Aggregate to zip -->
<aggregate strategyRef="zipAggregationStrategy" eagerCheckCompletion="true">
<correlationExpression>
<constant>true</constant>
</correlationExpression>
<completionPredicate>
<simple>${property.CamelSplitComplete}</simple>
</completionPredicate>
<setHeader headerName="CamelFileName">
<simple>${property.OriginalZipName}</simple>
</setHeader>
<to uri="file://{{file.path.out}}"/>
</aggregate>
</split>
</route>
Any idea what could be the issue?
The Camel Splitter provides a built- in aggregator, which makes it even easier to
aggregate split messages back into single outgoing message
You have to define the aggregation strategy in the split definition
<split strategyRef="zipAggregationStrategy">
Haven't tried it in XML but in Java DSL this is one of my examples that does exactly that.
.unmarshal(zipFile)
.split(bodyAs(Iterator.class),new ZipAggregationStrategy(true,true))
.streaming()
.stopOnException()
.to("direct:transform-ticket")
.end();

Apache Camel - Aggregate different object types

I have a route that, when lauched, requests messages from two datasources (routeA and routeB) and aggregates them into a single message. Each aggregated message MUST contain exactly one routeA message and one routeB message, if not, then drop it.
This process must be launched at specific intervals (i.e: every 5 min).
My question is, how can I let the aggregator know that all messages from routeA and routeB where processed and the messages that didn't find their pair, must be droped?
I'm currently using completionTimeout feature, but I don't like this solution for obvious reasons.
I know camel has a completionFromBatchConsumer feature, but I don't know how to use it with with multiple datasets.
Am greatefull for any advice.
Here's what I have right now:
<!-- main route -->
<route id="main">
<camel:from uri="timer://timer1?period=20000"/>
<multicast>
<to uri="direct:startA"/>
<to uri="direct:startB"/>
</multicast>
</route>
<!-- messages from route A -->
<route id="routeA" />
<from uri="direct:startA" />
<to uri="sql:select * from sampleDB?dataSource=ds"/>
<split>
<simple>${body}<simple>
<marshal ref="ObjectAJsonConverter"/>
<unmarshal ref="ObjectAJsonConverter"/>
<to uri="bean:myProcessor?method=addObjectACorrelationKey"/>
<to uri="seda:myAggregator"/>
</split>
<!-- messages from route B -->
<route id="routeB" />
<from uri="direct:startB"/>
<to uri="ldap:ldapcontext?base=DC=company,DC=net"/>
<split>
<simple>${body}<simple>
<marshal ref="ObjectBJsonConverter"/>
<unmarshal ref="ObjectBJsonConverter"/>
<to uri="bean:myProcessor?method=addObjectBCorrelationKey"/>
<to uri="seda:myAggregator"/>
</split>
<!-- aggregate the messages, create new ObjectC that contains ObjectA and ObjectB -->
<!-- wait 200000 ms for all messages from routeA and routeB to enter the aggregator -->
<route id="aggretatorRoute">
<from uri="seda:myAggregator"/>
<aggregate ref="myEntityAggregator" completionSize="2" completionTimeout="200000" discartOnCompletionTimeout="true" ignoreInvalidCorrelationKeys="true">
<correlationExpression><simple>${in.header.objectid}</simple></correlationExpression>
<to uri="bean:myProcessor?method=doSomethingWithObjectC"/>
</aggregate>
You can just in your AggregationStrategy only aggregate one of ObjectA and one of ObjectB. So if you see a 2nd of either of them, then just not aggregate it. And if you then want to drop what you have done so far, then you can mark the exchange to stop, by setting
exchange.setProperty("CamelRouteStop", true);
And if you then want to drop this immediately, then add a completionPredicate, that checks if that stop has been set.
<completionPredicate><simple>${property.CamelRouteStop} == true</simple></completionPredicate>
And for the correlationExpression, you can likely just use <constant>true</constant> as it seems you only work on one group.
Thanks Claus. Actually I took a different approach. Instead using an aggregator to join three different object, I now query a list of ids and use those to progressively build my complex object.
<route id="composeObject">
<from uri="sql:select id from people?oneSource">
<split><simple>${body}</simple>
<to uri="direct:getobjectOne"/>;
<to uri="bean:addToComplexObject"/>
<to uri="direct:getObjectTwo/>
<to uri="bean:addToComplexObject"/>
<to uri="direct:getobjectThree/>
<to uri="bean:addToComplexObject"/>
<to uri="seda:outChannel"/>
</split>
</route>

Apache Camel: How do we parse URL path for Servlet input and then use it as parameters in SQL output

I'd wish to create a general restful service to perform crud operations using Camel, but without using Restlet, just plain Servlet and SQL components.
I am familiar with Camel for 2 days only, and I am not able to get URL parts in order to use them in SQL query.
The idea is to use only XML configuring. I tried many doferent ways, came to using javascript. Here's my code so far (below).
I can't get how to check the url path, parse out it's parts, put into "message body" which should be a source for parameters for SQL query.
In this particular scenario it says, "ReferenceError: "response" is not defined".
<route>
<from uri="servlet:///crud?matchOnUriPrefix=true"/>
<setBody>
<javaScript><![CDATA[
request.headers.get("CamelHttpPath").match(/\/(\w+)\/(\w*)/)
]]></javaScript>
</setBody>
<choice>
<when>
<javaScript><![CDATA[
request.headers.get("CamelHttpMethod") == "GET" &&
response.body != null
]]></javaScript>
<to uri="sql:select * from person where id=2?dataSource=dataSource"></to>
</when>
<when>
<xpath>$CamelHttpMethod = 'POST'</xpath>
<transform>
<simple>Update!!! path - ${header.CamelHttpPath}, url - ${header.CamelHttpUrl}, uri = ${header.CamelHttpUri}, base uri = ${header.CamelHttpBaeUri}. And request is ${header.CamelHttpServletRequest}</simple>
</transform>
</when>
<when>
<xpath>$CamelHttpMethod = 'PUT'</xpath>
<transform>
<simple>Insert</simple>
</transform>
</when>
<when>
<xpath>$CamelHttpMethod = 'DELETE'</xpath>
<transform>
<simple>Delete...</simple>
</transform>
</when>
<otherwise>
<transform>
<simple>Unsupported method ${header.CamelHttpMethod} (${header.CamelHttpPath})</simple>
</transform>
</otherwise>
</choice>
</route>

Camel File Endpoint - Getting the file name

I have a camel route:
from("file:///u01/www/images/nonprofits-test?move=.done&preMove=.processing&filter=#nonpFileFilter&minDepth=2&recursive=true")
Later on in the route I need to access the origin file name. How do I get that information? All of the headers contain information in like ${file:name}, but not the actual file name.
Thanks in advance!
The base problem is that simple language is not being evaluated correctly in while running Camel with grails. This is being discussed further on the Camel user list.
there is a header called "CamelFileName" that stores this
see camel-file2 headers section for more details...
If your simple language is not working it would be because you are not using <simple> tag try something like below.
<route id="movedFailedFileForRetry">
<from uri="file:///opt/failed?delete=true" />
<log loggingLevel="INFO" message="Moving failed file ${header.CamelFileName} for retry" />
<choice>
<when>
<simple>${headers.CamelFileName} == 'file1.txt'</simple>
<to uri="file:///opt/input1" />
</when>
<otherwise>
<to uri="file:///opt/input2" />
</otherwise>
</choice>
</route>
Hope it helps!!
${headers.CamelFileName} will provide you with the CamelFileName that is read for processing. We have many other header properties that you can find from the Camel Documentation.

Resources