Reputation: 1367
I have a use case where I need to take an existing XML document and augment it from a database, for an integration process.
I'm starting with something like:
<parent>
<child>
<data>A</data>
</child>
<child>
<data>B</data>
</child>
<parentData>
<data/>
</parentData>
</parent>
What I am trying to do is add a <moreData .../>
tree to each of the child
elements.
I could just write a custom bean that does everything, but that doesn't feel the right approach. I've considered using a splitter based on the xpath for child, followed by a content-enricher, which will allow me to fetch the additional data, but I can't see how to reassemble everything afterwards.
At the moment, I'm thinking I need to use a loop, but that feels clunky too, and will require a custom aggregation strategy for the content-enricher.
from("direct:a")
.loop().xpath("count( parent/child )", Integer.class )
.setHeader("Key")
.xpath( "parent/child[function:properties('CamelLoopIndex')]/data", String.class )
.enrich("sql:SELECT xmldata FROM dataTable WHERE key = :#Key?dataSource=myDS",
new MyCustomAggregationStrategy() )
This must be an everyday occurrence in the world of Camel but I can't find any examples of how to do it.
If I were doing this in a custom bean, I'd get an xpath for the child
element, then iterate through the nodeset performing the query and attaching the result as a new child to node. I just can't see how to do this "nicely" in Camel.
Any ideas or hints would be great! Thanks!
Upvotes: 0
Views: 677
Reputation: 855
You can try prepare map of new nodes , and then transform parent xml with xslt and get prepared new nodes using java inside xsl. Here some example. Route:
@Override
public void configure() throws Exception {
from("timer://foo?period=30s")
.setBody(constant("<parent>\n" +
" <child>\n" +
" <data>A</data>\n" +
" </child>\n" +
" <child>\n" +
" <data>B</data>\n" +
" </child>\n" +
" <parentData>\n" +
" <data/>\n" +
" </parentData>\n" +
"</parent>"))
.convertBodyTo(org.w3c.dom.Document.class)
.setProperty("oldBody", simple("body"))
.split(xpath("//child"), (oldExchange, newExchange) -> {
Map<String, String> map = oldExchange != null ? oldExchange.getProperty("map", Map.class) : new HashMap<>();
map.put(newExchange.getIn().getHeader("Key", String.class), newExchange.getIn().getBody(String.class));
newExchange.setProperty("map", map);
return newExchange;
})
.setHeader("Key", xpath("//data/text()"))
// .to("sql:SELECT xmldata FROM dataTable WHERE key = :#Key?dataSource=#myDS")
//emulate result of your sql
.process(exchange -> {
exchange.getIn().setBody("<someNewData>".concat(exchange.getIn().getHeader("Key", String.class).concat("Result")).concat("</someNewData>"));
})
.end()
.setBody(exchangeProperty("oldBody"))
.to("xslt:xslt/result.xsl?transformerFactory=#nsTF")
.log(LoggingLevel.INFO, "Body:${body}");}
public static String getElement(Object map, String key) {
return (String) ((Map) map).get(key);
}
nsTF is bean of class:
public class NonSecureTransfomerFactory extends TransformerFactoryImpl {
@Override
//for using java inside xsl
public boolean isSecureProcessing()
{
return false;
}
}
xslt stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:getter="my.package.RouteHelper">
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
<xsl:strip-space elements='*'/>
<xsl:param name="map"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="child">
<xsl:copy>
<xsl:variable name="key" select="data/text()"/>
<xsl:value-of disable-output-escaping="yes" select="getter:getElement($map,$key)"/>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
Output xml:
<parent>
<child>
<someNewData>AResult</someNewData>
<data>A</data>
</child>
<child>
<someNewData>BResult</someNewData>
<data>B</data>
</child>
<parentData>
<data/>
</parentData>
</parent>
Upvotes: 1