问题
I am trying to aggregate message after getting data from database. It seems to be hard to explain to I will draw a flow firstly.
[ORIGINAL_DOCUMENT] --> SPLIT[GET SOME IDs from ORIGINAL_DOCUMENT] --> [GET DATA FROM DATABASE USING MYBATIS] --> [ENRICH ORIGINAL_DOCUMENT BY GOT DATA FROM DATABASE]
first route:
<route id="enrich-zamowienie">
<from uri="activemq:queue:original-document"/>
<setHeader headerName="pure-xml">
<simple>${body}</simple>
</setHeader>
<split>
<xpath>original-document/entry</xpath>
<unmarshal>
<jaxb contextPath="com.original-document"/>
</unmarshal>
<setBody>
<simple>${body.getEntryId()}</simple>
</setBody>
<to uri="activemq:queue:getAdditionalsByID" />
<marshal>
<jaxb contextPath="com.additionals"
encoding="utf-8" prettyPrint="true"/>
</marshal>
<setHeader headerName="entry">
<simple>${body}</simple>
</setHeader>
<setBody>
<simple>${header.pure-xml}</simple>
</setBody>
<to uri="direct:aggregate" />
</split>
</route>
second route:
<route>
<from uri="direct:aggregate" />
<aggregate strategyRef="aggregator">
<correlationExpression>
<xpath>?</xpath>
</correlationExpression>
</aggregate>
<log message="${body}" />
</route>
(...)
<bean id="aggregator" class="org.apache.camel.util.toolbox.XsltAggregationStrategy">
<constructor-arg value="com/transformXSLT.xsl" />
my original xml that I get fromy actimvemq:
<document>
<header>
<header_id>1</header_id>
</header>
<body>
<entry>
<entryId>1</entryId>
<fieldToEnrich1></fieldToEnrich1>
<fieldToEnrich2></fieldToEnrich2>
<fieldToEnrich3></fieldToEnrich3>
</entry>
<entry>
<entryId>2</entryId>
<fieldToEnrich1></fieldToEnrich1>
<fieldToEnrich2></fieldToEnrich2>
<fieldToEnrich3></fieldToEnrich3>
</entry>
<entry>
<entryId>3</entryId>
<fieldToEnrich1></fieldToEnrich1>
<fieldToEnrich2></fieldToEnrich2>
<fieldToEnrich3></fieldToEnrich3>
</entry>
</body>
</document>
And of coure for every id addtional looks like it:
<document>
<additionals>
<fieldToEnrich1>131</fieldToEnrich1>
<fieldToEnrich2>3232</fieldToEnrich2>
<fieldToEnrich3>3213</fieldToEnrich3>
</additionals>
</document>
My aim is to create document like original_document by with extra data from database. I don't know how to second route should looks. I hope it is understandable.
回答1:
I don't know if I fully understand how you want to merge your documents, but I think you want to split the original XML by entry
and merge every entry with additionals
.
Since this is just a single merge of two documents (done for every entry
) and not a repeated merge, you can use the Enrich EIP instead of the Aggregator. It also takes an aggregation strategy to decide how to merge two documents.
You simply enrich the entry
XML with the additionals
XML (that is retrieved from another route) using your XSL as aggregation strategy
<route id="enrich-zamowienie">
<from uri="activemq:queue:original-document"/>
...
<split>
<xpath>original-document/entry</xpath>
... [body is a single entry]
<enrich uri="direct:routeThatLoadsAdditionals" strategyRef="yourAggregatorStrategyBean"/>
... [body is a merge of entry and additionals]
</split>
</route>
<route id="load-additionals-for-entry">
<from uri="direct:routeThatLoadsAdditionals"/>
... [load additionals and set additionals XML as body]
... [the body of this message is merged with the body of caller message]
</route>
That way you have a clean separation of the creation of the additionals document and a simplified logic in the splitter.
回答2:
You can supply the aggregation strategy on the split. I couldn't get XsltAggregationStrategy to work with the default xalan xslt transformer, so I added camel-saxon to my dependencies and added a transformerFactoryClass property to get XsltAggregationStrategy to use it. I also had to supply a constructor arg to supply the file name of the xslt.
<bean id="xsltAggregationStrategy" class="org.apache.camel.util.toolbox.XsltAggregationStrategy">
<constructor-arg value="aggregation.xslt" />
<property name="transformerFactoryClass" value="net.sf.saxon.TransformerFactoryImpl"/>
</bean>
<route>
<from uri="direct:start"/>
<!-- refer to the aggregation strategy to be used -->
<split strategyRef="xsltAggregationStrategy">
<!-- split the body -->
<xpath>//entry</xpath>
... rest of the route to get the additional stuff
</split>
<log message="Aggregated message ${body}"/>
</route>
I don't think the XsltAggregationStrategy will do what you want. I hadn't used it before, so gave it a try and it's behaviour for aggregating is.
1st call, take the body from the new message and just keep that. 2nd and subsequent calls, keep the old body (i.e. what's aggregated so far), put the new message body in a property (default new-exchange) and do xslt translation with supplied xslt file.
So for your example if you split your original-document up on //entry you get 3 messages.
Message 1 has
<entry>
<entryId>1</entryId>
<fieldToEnrich1></fieldToEnrich1>
<fieldToEnrich2></fieldToEnrich2>
<fieldToEnrich3></fieldToEnrich3>
</entry>
You'll probably enrich it to be
<entry>
<entryId>1</entryId>
<fieldToEnrich1>value1</fieldToEnrich1>
<fieldToEnrich2>value2</fieldToEnrich2>
<fieldToEnrich3>value3</fieldToEnrich3>
</entry>
This gets put straight into the aggregated message body. No xslt is run against it.
Message 2 has
<entry>
<entryId>2</entryId>
<fieldToEnrich1></fieldToEnrich1>
<fieldToEnrich2></fieldToEnrich2>
<fieldToEnrich3></fieldToEnrich3>
</entry>
Again you enrich it to be
<entry>
<entryId>2</entryId>
<fieldToEnrich1>value1</fieldToEnrich1>
<fieldToEnrich2>value2</fieldToEnrich2>
<fieldToEnrich3>value3</fieldToEnrich3>
</entry>
The aggregated message body still has
<entry>
<entryId>1</entryId>
<fieldToEnrich1>value1</fieldToEnrich1>
<fieldToEnrich2>value2</fieldToEnrich2>
<fieldToEnrich3>value3</fieldToEnrich3>
</entry>
It also gets a property called new-exchange set to
<entry>
<entryId>2</entryId>
<fieldToEnrich1>value1</fieldToEnrich1>
<fieldToEnrich2>value2</fieldToEnrich2>
<fieldToEnrich3>value3</fieldToEnrich3>
</entry>
You could use an xslt like so to combine them, in the route shown above the name would need to be aggregation.xslt
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="new-exchange"/>
<xsl:template match="/">
<body>
<xsl:copy-of select="//entry" />
<xsl:copy-of select="$new-exchange/entry" />
</body>
</xsl:template>
</xsl:stylesheet>
Which would result in
<body>
<entry>
<entryId>1</entryId>
<fieldToEnrich1>field 1 for entry id 1</fieldToEnrich1>
<fieldToEnrich2>field 2 for entry id 1</fieldToEnrich2>
<fieldToEnrich3>field 3 for entry id 1</fieldToEnrich3>
</entry>
<entry>
<entryId>2</entryId>
<fieldToEnrich1>field 1 for entry id 2</fieldToEnrich1>
<fieldToEnrich2>field 2 for entry id 2</fieldToEnrich2>
<fieldToEnrich3>field 3 for entry id 2</fieldToEnrich3>
</entry>
</body>
The 3rd message would also get added on. But you end up with just the body section of the original document. I can't see an easy way to get the whole original document included if you use the in build XsltAggregationStrategy. Your probably better off writing you own one.
回答3:
@pcoates have good idea to use XsltAggregationStrategy. So i make working example for you with xslt templates and modified strategy. Route:
XsltAggregationStrategy aggregationStrategy = new CustomXsltAggregationStrategy("xslt/aggregate.xsl");
from("timer://foo?period=30s")
.setBody(constant("<document>\n" +
" <header>\n" +
" <header_id>1</header_id>\n" +
" </header>\n" +
" <body>\n" +
" <entry>\n" +
" <entryId>1</entryId>\n" +
" <fieldToEnrich1></fieldToEnrich1>\n" +
" <fieldToEnrich2></fieldToEnrich2>\n" +
" <fieldToEnrich3></fieldToEnrich3>\n" +
" </entry>\n" +
" <entry>\n" +
" <entryId>2</entryId>\n" +
" <fieldToEnrich1></fieldToEnrich1>\n" +
" <fieldToEnrich2></fieldToEnrich2>\n" +
" <fieldToEnrich3></fieldToEnrich3>\n" +
" </entry>\n" +
" <entry>\n" +
" <entryId>3</entryId>\n" +
" <fieldToEnrich1></fieldToEnrich1>\n" +
" <fieldToEnrich2></fieldToEnrich2>\n" +
" <fieldToEnrich3></fieldToEnrich3>\n" +
" </entry>\n" +
" </body>\n" +
"</document>"))
.convertBodyTo(Document.class)
.setProperty("updated-xml", simple("body"))
.split().xpath("//entry").aggregationStrategy(aggregationStrategy)
.setHeader("key", xpath("//entryId/text()", String.class))
.setProperty("update-node", simple("body"))
// <to uri="activemq:queue:getAdditionalsByID" />
// like you receive your data
.process(exchange -> {
String data = "enrich data for key:"+exchange.getIn().getHeader("key");
exchange.getIn().setBody(String.format("<document><additionals><fieldToEnrich1>%s</fieldToEnrich1><fieldToEnrich2>%s</fieldToEnrich2><fieldToEnrich3>%s</fieldToEnrich3></additionals></document>",
data,data,data));
})
.convertBodyTo(Document.class)
.setProperty("additional", simple("body"))
.setBody(exchangeProperty("update-node"))
.to("xslt:xslt/updateNode.xsl")//create modified node
.end()
.convertBodyTo(String.class)
.log(LoggingLevel.INFO, "Body:${body}");
updateNode.xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
<xsl:strip-space elements='*'/>
<xsl:param name="additional"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="entry">
<xsl:copy>
<xsl:apply-templates select="@*|node()[name()='entryId']"/>
<xsl:apply-templates select="$additional/document/additionals/*"/>
</xsl:copy>
</xsl:template>
aggregate.xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
exclude-result-prefixes="exslt">
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
<xsl:strip-space elements='*'/>
<xsl:param name="new-exchange"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//entry">
<xsl:choose>
<xsl:when test="./entryId/text()=$new-exchange/entry/entryId/text()">
<xsl:copy-of select="$new-exchange"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
CustomXsltAggregationStrategy class :
public class CustomXsltAggregationStrategy extends XsltAggregationStrategy {
/**
* Constructor.
*
* @param xslFileLocation location of the XSL transformation
*/
public CustomXsltAggregationStrategy(String xslFileLocation) {
super(xslFileLocation);
}
@Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
//by default result body of first split iteration become document that will be transformed by another iterations
//but we need original document to be transformable, so we make pseudo first body and setting there our original document
//others iteration will change only our document
if (oldExchange == null) {
oldExchange = newExchange.copy();
oldExchange.getIn().setBody(oldExchange.getProperty("updated-xml"));
}
return super.aggregate(oldExchange, newExchange);
}
Output:
2018-12-13 14:32:24,643 | INFO | 62 - timer://foo | route82 | 98 - org.apache.camel.camel-core - 2.16.3 | Body:<?xml version="1.0" encoding="UTF-8"?><document><header><header_id>1</header_id></header><body><entry><entryId>1</entryId><fieldToEnrich1>enrich data for key:1</fieldToEnrich1><fieldToEnrich2>enrich data for key:1</fieldToEnrich2><fieldToEnrich3>enrich data for key:1</fieldToEnrich3></entry><entry><entryId>2</entryId><fieldToEnrich1>enrich data for key:2</fieldToEnrich1><fieldToEnrich2>enrich data for key:2</fieldToEnrich2><fieldToEnrich3>enrich data for key:2</fieldToEnrich3></entry><entry><entryId>3</entryId><fieldToEnrich1>enrich data for key:3</fieldToEnrich1><fieldToEnrich2>enrich data for key:3</fieldToEnrich2><fieldToEnrich3>enrich data for key:3</fieldToEnrich3></entry></body></document>
And a little advice, if you need to save temporary body or some computed data, use properties instead of headers, cause headers can be processed by endpoints.
来源:https://stackoverflow.com/questions/53704380/aggregate-with-xslt