Агрегировать с xslt - PullRequest
       4

Агрегировать с xslt

0 голосов
/ 10 декабря 2018

Я пытаюсь агрегировать сообщение после получения данных из базы данных.Кажется, трудно объяснить, что я сначала начну рисовать поток.

[ORIGINAL_DOCUMENT] --> SPLIT[GET SOME IDs from ORIGINAL_DOCUMENT] --> [GET DATA FROM DATABASE USING MYBATIS] --> [ENRICH ORIGINAL_DOCUMENT BY GOT DATA FROM DATABASE]

первый маршрут:

 <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>

второй маршрут:

    <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" />

мой оригиналxml, который я получаю от 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>

И конечно, для каждого дополнения к id выглядит так:

<document>
    <additionals>
        <fieldToEnrich1>131</fieldToEnrich1>
        <fieldToEnrich2>3232</fieldToEnrich2>
        <fieldToEnrich3>3213</fieldToEnrich3>
    </additionals>
</document>

Моя цель - создать документ наподобие original_document, используя дополнительные данные из базы данных.,Я не знаю, как должен выглядеть второй маршрут.Надеюсь это понятно.

Ответы [ 3 ]

0 голосов
/ 11 декабря 2018

Вы можете указать стратегию агрегации при разделении.Я не мог заставить XsltAggregationStrategy работать с преобразователем xalan xslt по умолчанию, поэтому я добавил camel-saxon в мои зависимости и добавил свойство transformerFactoryClass, чтобы XsltAggregationStrategy использовал его.Я также должен был предоставить конструктор arg для указания имени файла 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>

Я не думаю, что XsltAggregationStrategy будет делать то, что вы хотите.Я не использовал его раньше, поэтому попробовал, и его поведение для агрегирования:

1-й вызов, извлеките тело из нового сообщения и просто сохраните его.2-й и последующие вызовы, сохраните старое тело (то есть то, что до сих пор агрегировано), поместите новое тело сообщения в свойство (по умолчанию new-exchange) и выполните перевод xslt с помощью предоставленного файла xslt.

Так для вашего примераесли вы разделите ваш оригинальный документ на // запись, вы получите 3 сообщения.

Сообщение 1 содержит

    <entry>
        <entryId>1</entryId>
        <fieldToEnrich1></fieldToEnrich1>
        <fieldToEnrich2></fieldToEnrich2>
        <fieldToEnrich3></fieldToEnrich3>
    </entry>

Возможно, вы обогатите его до

    <entry>
        <entryId>1</entryId>
        <fieldToEnrich1>value1</fieldToEnrich1>
        <fieldToEnrich2>value2</fieldToEnrich2>
        <fieldToEnrich3>value3</fieldToEnrich3>
    </entry>

Это помещается прямо в агрегированное тело сообщения.Xslt не запускается против него.

Сообщение 2 имеет

    <entry>
        <entryId>2</entryId>
        <fieldToEnrich1></fieldToEnrich1>
        <fieldToEnrich2></fieldToEnrich2>
        <fieldToEnrich3></fieldToEnrich3>
    </entry>

Снова вы обогащаете его до

    <entry>
        <entryId>2</entryId>
        <fieldToEnrich1>value1</fieldToEnrich1>
        <fieldToEnrich2>value2</fieldToEnrich2>
        <fieldToEnrich3>value3</fieldToEnrich3>
    </entry>

В объединенном теле сообщения все еще есть

    <entry>
        <entryId>1</entryId>
        <fieldToEnrich1>value1</fieldToEnrich1>
        <fieldToEnrich2>value2</fieldToEnrich2>
        <fieldToEnrich3>value3</fieldToEnrich3>
    </entry>

Он также получает свойство с именем new-exchange, установленное в

    <entry>
        <entryId>2</entryId>
        <fieldToEnrich1>value1</fieldToEnrich1>
        <fieldToEnrich2>value2</fieldToEnrich2>
        <fieldToEnrich3>value3</fieldToEnrich3>
    </entry>

. Вы можете использовать xslt, например, чтобы объединить их, в указанном выше маршруте имя должно быть 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>

Что приведет к

<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>

Третье сообщение также будет добавлено.Но в итоге вы получите только часть тела исходного документа.Я не вижу простого способа включить весь исходный документ, если вы используете встроенную версию XsltAggregationStrategy.Тебе, наверное, лучше написать свою.

0 голосов
/ 13 декабря 2018

@ pcoates имеет хорошую идею использовать XsltAggregationStrategy.Поэтому я делаю рабочий пример для вас с помощью шаблонов xslt и измененной стратегии.Маршрут:

        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класс:

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);
}

Вывод:

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>

И небольшой совет: если вам нужно сохранить временное тело или некоторые вычисленные данные, используйте свойства вместо заголовков, потому что заголовки могут быть обработаныпо конечным точкам.

0 голосов
/ 11 декабря 2018

Я не знаю, полностью ли я понимаю, как вы хотите объединить ваши документы, но я думаю, что вы хотите разделить исходный XML на entry и объединить каждую запись с additionals.

, так какэто просто одиночное слияние двух документов (выполняется для каждого entry), а не повторное объединение, вы можете использовать Enrich EIP вместо Aggregator .Также требуется стратегия агрегирования, чтобы решить, как объединить два документа.

Вы просто обогащаете entry XML с помощью additionals XML (который получен из другого маршрута), используя ваш XSL в качестве стратегии агрегации

<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>

Таким образом, у вас есть четкое разделение создания дополнительного документа и упрощенная логика в разделителе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...