INT-3599/INT-2042: Improve XPathMessageSplitter

JIRA: https://jira.spring.io/browse/INT-2042,
https://jira.spring.io/browse/INT-3599

* Expose `outputProperties` for the `XPathMessageSplitter`
* Add `iterator` mode for the `XPathMessageSplitter`

Address PR comments

Doc Polishing
This commit is contained in:
Artem Bilan
2015-04-14 21:30:39 +03:00
committed by Gary Russell
parent 8059566b9b
commit 256ff4c46f
9 changed files with 306 additions and 73 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
@@ -29,13 +30,13 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.xml.xpath.XPathExpressionFactory;
import org.w3c.dom.Element;
/**
* Parser for the <xpath-expression> element.
*
* @author Jonas Partner
* @author Soby Chacko
* @author Artem Bilan
*/
public class XPathExpressionParser extends AbstractSingleBeanDefinitionParser {
@@ -58,6 +59,15 @@ public class XPathExpressionParser extends AbstractSingleBeanDefinitionParser {
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String expression = element.getAttribute("expression");
Assert.hasText(expression, "The 'expression' attribute is required.");
builder.setFactoryMethod("createXPathExpression");
builder.addConstructorArgValue(expression);
parseAndPopulateNamespaceMap(element, parserContext, builder);
}
static void parseAndPopulateNamespaceMap(Element element, ParserContext parserContext,
BeanDefinitionBuilder builder) {
String nsPrefix = element.getAttribute("ns-prefix");
String nsUri = element.getAttribute("ns-uri");
String namespaceMapRef = element.getAttribute("namespace-map");
@@ -73,36 +83,32 @@ public class XPathExpressionParser extends AbstractSingleBeanDefinitionParser {
if (prefixProvided || namespaceProvided) {
Assert.isTrue(prefixProvided && namespaceProvided,
"Both 'ns-prefix' and 'ns-uri' must be specified if one is specified.");
Assert.isTrue(!namespaceMapProvided, "It is not valid to specify both, the namespace attributes ('ns-prefix' and 'ns-uri') and the 'namespace-map' attribute.");
Assert.isTrue(!mapSubElementProvided, "It is not valid to specify both, the namespace attributes ('ns-prefix' and 'ns-uri') and the 'map' sub-element.");
} else if (mapSubElementProvided) {
Assert.isTrue(!namespaceMapProvided, "It is not valid to specify both, the 'namespace-map' attribute and the 'map' sub-element.");
Assert.isTrue(!namespaceMapProvided, "It is not valid to specify both, " +
"the namespace attributes ('ns-prefix' and 'ns-uri') and the 'namespace-map' attribute.");
Assert.isTrue(!mapSubElementProvided, "It is not valid to specify both, " +
"the namespace attributes ('ns-prefix' and 'ns-uri') and the 'map' sub-element.");
}
else if (mapSubElementProvided) {
Assert.isTrue(!namespaceMapProvided, "It is not valid to specify both, " +
"the 'namespace-map' attribute and the 'map' sub-element.");
}
builder.setFactoryMethod("createXPathExpression");
builder.addConstructorArgValue(expression);
if (prefixProvided) {
Map<String, String> namespaceMap = new HashMap<String, String>(1);
namespaceMap.put(nsPrefix, nsUri);
builder.addConstructorArgValue(namespaceMap);
} else if (namespaceMapProvided) {
}
else if (namespaceMapProvided) {
builder.addConstructorArgReference(namespaceMapRef);
} else if (mapSubElementProvided) {
}
else if (mapSubElementProvided) {
Element mapElement = mapElements.get(0);
if (mapElement != null) {
builder.addConstructorArgValue(this.parseNamespaceMapElement(
mapElement, parserContext, builder.getBeanDefinition()));
BeanDefinitionParserDelegate beanParser = parserContext.getDelegate();
beanParser.initDefaults(mapElement.getOwnerDocument().getDocumentElement(), beanParser);
builder.addConstructorArgValue(beanParser.parseMapElement(mapElement, builder.getRawBeanDefinition()));
}
}
}
protected Map<?,?> parseNamespaceMapElement(Element element, ParserContext parserContext, BeanDefinition parentDefinition) {
BeanDefinitionParserDelegate beanParser = parserContext.getDelegate();
beanParser.initDefaults(element.getOwnerDocument().getDocumentElement(), beanParser);
return beanParser.parseMapElement(element, parentDefinition);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ package org.springframework.integration.xml.config;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
@@ -34,8 +33,6 @@ import org.springframework.util.StringUtils;
*/
public class XPathMessageSplitterParser extends AbstractConsumerEndpointParser {
private final XPathExpressionParser xpathParser = new XPathExpressionParser();
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XPathMessageSplitter.class);
@@ -46,8 +43,9 @@ public class XPathMessageSplitterParser extends AbstractConsumerEndpointParser {
boolean hasReference = StringUtils.hasText(xPathExpressionRef);
Assert.isTrue(hasChild ^ hasReference, "Exactly one of 'xpath-expression' or 'xpath-expression-ref' is required.");
if (hasChild) {
BeanDefinition beanDefinition = this.xpathParser.parse((Element) xPathExpressionNodes.item(0), parserContext);
builder.addConstructorArgValue(beanDefinition);
Element xpathExpressionElement = (Element) xPathExpressionNodes.item(0);
builder.addConstructorArgValue(xpathExpressionElement.getAttribute("expression"));
XPathExpressionParser.parseAndPopulateNamespaceMap(xpathExpressionElement, parserContext, builder);
}
else {
builder.addConstructorArgReference(xPathExpressionRef);
@@ -55,6 +53,8 @@ public class XPathMessageSplitterParser extends AbstractConsumerEndpointParser {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "doc-builder-factory", "documentBuilder");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "create-documents");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "apply-sequence");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "iterator");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "output-properties");
return builder;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,27 +19,39 @@ package org.springframework.integration.xml.splitter;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.integration.splitter.AbstractMessageSplitter;
import org.springframework.integration.util.Function;
import org.springframework.integration.util.FunctionIterator;
import org.springframework.integration.xml.DefaultXmlPayloadConverter;
import org.springframework.integration.xml.XmlPayloadConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.util.Assert;
import org.springframework.xml.namespace.SimpleNamespaceContext;
import org.springframework.xml.transform.StringResult;
import org.springframework.xml.xpath.XPathException;
import org.springframework.xml.xpath.XPathExpression;
import org.springframework.xml.xpath.XPathExpressionFactory;
@@ -49,20 +61,30 @@ import org.springframework.xml.xpath.XPathExpressionFactory;
* The return value will be either Strings or {@link Node}s depending on the
* received payload type. Additionally, node types will be converted to
* Documents if the 'createDocuments' property is set to <code>true</code>.
*
*
* @author Jonas Partner
* @author Mark Fisher
* @author Artem Bilan
*/
public class XPathMessageSplitter extends AbstractMessageSplitter {
private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
private final Object documentBuilderFactoryMonitor = new Object();
private final XPathExpression xpathExpression;
private javax.xml.xpath.XPathExpression jaxpExpression;
private volatile boolean createDocuments;
private volatile DocumentBuilderFactory documentBuilderFactory;
private volatile XmlPayloadConverter xmlPayloadConverter = new DefaultXmlPayloadConverter();
private volatile Properties outputProperties;
private volatile boolean iterator = true;
public XPathMessageSplitter(String expression) {
this(expression, new HashMap<String, String>());
@@ -70,9 +92,22 @@ public class XPathMessageSplitter extends AbstractMessageSplitter {
public XPathMessageSplitter(String expression, Map<String, String> namespaces) {
this(XPathExpressionFactory.createXPathExpression(expression, namespaces));
XPath xpath = XPathFactory.newInstance().newXPath();
SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext();
namespaceContext.setBindings(namespaces);
xpath.setNamespaceContext(namespaceContext);
try {
this.jaxpExpression = xpath.compile(expression);
}
catch (XPathExpressionException e) {
throw new org.springframework.xml.xpath.XPathParseException(
"Could not compile [" + expression + "] to a XPathExpression: " + e.getMessage(), e);
}
}
public XPathMessageSplitter(XPathExpression xpathExpression) {
Assert.notNull(xpathExpression, "'xpathExpression' must not be null.");
this.xpathExpression = xpathExpression;
this.documentBuilderFactory = DocumentBuilderFactory.newInstance();
this.documentBuilderFactory.setNamespaceAware(true);
@@ -97,6 +132,37 @@ public class XPathMessageSplitter extends AbstractMessageSplitter {
this.xmlPayloadConverter = xmlPayloadConverter;
}
/**
* The {@code iterator} mode: {@code true} (default) to return an {@link Iterator}
* for splitting {@code payload}, {@code false} to return a {@link List}.
* @param iterator {@code boolean} flag for iterator mode. Default to {@code true}.
* @since 4.2
*/
public void setIterator(boolean iterator) {
this.iterator = iterator;
}
/**
* A set of output properties that will be
* used to override any of the same properties in affect
* for the transformation.
* @param outputProperties the {@link Transformer} output properties.
* @see Transformer#setOutputProperties(Properties)
* @since 4.2
*/
public void setOutputProperties(Properties outputProperties) {
this.outputProperties = outputProperties;
}
@Override
protected void doInit() {
super.doInit();
if (this.iterator && this.jaxpExpression == null) {
logger.info("The 'iterator' option isn't available for an external XPathExpression. Will be ignored");
this.iterator = false;
}
}
@Override
protected Object splitMessage(Message<?> message) {
try {
@@ -113,51 +179,134 @@ public class XPathMessageSplitter extends AbstractMessageSplitter {
return result;
}
catch (ParserConfigurationException e) {
throw new MessagingException(message, "failed to create DocumentBuilder", e);
throw new MessageConversionException(message, "failed to create DocumentBuilder", e);
}
catch (Exception e) {
throw new MessagingException(message, "failed to split Message payload", e);
throw new MessageHandlingException(message, "failed to split Message payload", e);
}
}
@SuppressWarnings("unchecked")
private Object splitDocument(Document document) throws Exception {
List<Node> nodes = splitNode(document);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
List<String> splitStrings = new ArrayList<String>(nodes.size());
for (Node nodeFromList : nodes) {
StringResult result = new StringResult();
transformer.transform(new DOMSource(nodeFromList), result);
splitStrings.add(result.toString());
Object nodes = splitNode(document);
final Transformer transformer;
synchronized (this.transformerFactory) {
transformer = this.transformerFactory.newTransformer();
}
if (this.outputProperties != null) {
transformer.setOutputProperties(this.outputProperties);
}
if (nodes instanceof List) {
List<Node> items = (List<Node>) nodes;
List<String> splitStrings = new ArrayList<String>(items.size());
for (Node nodeFromList : items) {
StringResult result = new StringResult();
transformer.transform(new DOMSource(nodeFromList), result);
splitStrings.add(result.toString());
}
return splitStrings;
}
else {
return new FunctionIterator<Node, String>((Iterator<Node>) nodes, new Function<Node, String>() {
@Override
public String apply(Node node) {
StringResult result = new StringResult();
try {
transformer.transform(new DOMSource(node), result);
}
catch (TransformerException e) {
throw new IllegalStateException("failed to create DocumentBuilder", e);
}
return result.toString();
}
});
}
return splitStrings;
}
private List<Node> splitNode(Node node) throws ParserConfigurationException {
List<Node> nodeList = this.xpathExpression.evaluateAsNodeList(node);
if (nodeList.size() == 0) {
throw new IllegalArgumentException("failed to split message with XPath expression: " + this.xpathExpression);
private Object splitNode(Node node) throws ParserConfigurationException {
if (this.iterator) {
try {
NodeList nodeList = (NodeList) this.jaxpExpression.evaluate(node, XPathConstants.NODESET);
return new NodeListIterator(nodeList);
}
catch (XPathExpressionException e) {
throw new XPathException("Could not evaluate XPath expression:" + e.getMessage(), e);
}
}
if (this.createDocuments) {
return convertNodesToDocuments(nodeList);
else {
List<Node> nodeList = this.xpathExpression.evaluateAsNodeList(node);
if (this.createDocuments) {
return convertNodesToDocuments(nodeList);
}
return nodeList;
}
return nodeList;
}
private List<Node> convertNodesToDocuments(List<Node> nodes) throws ParserConfigurationException {
DocumentBuilder documentBuilder = this.getNewDocumentBuilder();
DocumentBuilder documentBuilder = getNewDocumentBuilder();
List<Node> documents = new ArrayList<Node>(nodes.size());
for (Node node : nodes) {
Document document = documentBuilder.newDocument();
document.appendChild(document.importNode(node, true));
Document document = convertNodeToDocument(documentBuilder, node);
documents.add(document);
}
return documents;
}
private Document convertNodeToDocument(DocumentBuilder documentBuilder, Node node) {
Document document = documentBuilder.newDocument();
document.appendChild(document.importNode(node, true));
return document;
}
private DocumentBuilder getNewDocumentBuilder() throws ParserConfigurationException {
synchronized (this.documentBuilderFactory) {
synchronized (this.documentBuilderFactoryMonitor) {
return this.documentBuilderFactory.newDocumentBuilder();
}
}
private class NodeListIterator implements Iterator<Node> {
private final DocumentBuilder documentBuilder;
private final NodeList nodeList;
private int index;
public NodeListIterator(NodeList nodeList) throws ParserConfigurationException {
this.nodeList = nodeList;
if (XPathMessageSplitter.this.createDocuments) {
this.documentBuilder = getNewDocumentBuilder();
}
else {
this.documentBuilder = null;
}
}
@Override
public boolean hasNext() {
return index < nodeList.getLength();
}
@Override
public Node next() {
if (!hasNext())
return null;
Node node = nodeList.item(index++);
if (this.documentBuilder != null) {
node = convertNodeToDocument(this.documentBuilder, node);
}
return node;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Operation not supported");
}
}
}

View File

@@ -749,6 +749,32 @@
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="output-properties">
<xsd:annotation>
<xsd:documentation>
A set of output properties that will be used to override any of the same properties
in effect for the transformation.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java.util.Properties"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="iterator" default="true">
<xsd:annotation>
<xsd:documentation>
The iterator mode: 'true' (default) to return an 'java.util.Iterator'
for splitting 'payload', 'false to return a 'java.util.List'.
Note: the 'list' contains transformed nodes whereas with the
'iterator' each node is transformed while iterating.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -862,14 +888,14 @@
<xsd:attribute name="expression" type="xsd:string" use="optional"/>
<xsd:attribute name="value" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="resultType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="DOMResult"/>
<xsd:enumeration value="StringResult"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="evaluationType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="BOOLEAN_RESULT"/>
@@ -879,12 +905,12 @@
<xsd:enumeration value="NODE_LIST_RESULT"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="schemaType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="xml-schema"/>
<xsd:enumeration value="relax-ng"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -1,20 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration/xml"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/xml
http://www.springframework.org/schema/integration/xml/spring-integration-xml.xsd">
http://www.springframework.org/schema/integration/xml/spring-integration-xml.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<si:channel id="output">
<si:queue/>
</si:channel>
<xpath-splitter id="xpathSplitter" input-channel="input" apply-sequence="false" create-documents="true">
<util:properties id="outputProperties">
<beans:prop key="#{T (javax.xml.transform.OutputKeys).OMIT_XML_DECLARATION}">yes</beans:prop>
</util:properties>
<xpath-splitter id="xpathSplitter"
input-channel="input"
apply-sequence="false"
create-documents="true"
output-properties="outputProperties"
iterator="false">
<xpath-expression expression="/orders/order"/>
</xpath-splitter>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@ package org.springframework.integration.xml.config;
import static org.junit.Assert.*;
import java.util.Properties;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,10 +41,15 @@ public class XPathSplitterParserTests {
@Autowired @Qualifier("xpathSplitter.handler")
private MessageHandler xpathSplitter;
@Autowired @Qualifier("outputProperties")
private Properties outputProperties;
@Test
public void testXpathSplitterConfig() {
assertTrue(TestUtils.getPropertyValue(this.xpathSplitter, "createDocuments", Boolean.class));
assertFalse(TestUtils.getPropertyValue(this.xpathSplitter, "applySequence", Boolean.class));
assertFalse(TestUtils.getPropertyValue(this.xpathSplitter, "iterator", Boolean.class));
assertSame(this.outputProperties, TestUtils.getPropertyValue(this.xpathSplitter, "outputProperties"));
assertEquals("/orders/order",
TestUtils.getPropertyValue(this.xpathSplitter,
"xpathExpression.xpathExpression.xpath.m_patternString",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,19 +27,18 @@ import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.handler.ReplyRequiredException;
import org.springframework.integration.xml.util.XmlTestUtil;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.support.GenericMessage;
/**
* @author Jonas Partner
*/
public class XPathMessageSplitterTests {
private String splittingXPath = "/orders/order";
private XPathMessageSplitter splitter;
private QueueChannel replyChannel = new QueueChannel();
@@ -47,8 +46,10 @@ public class XPathMessageSplitterTests {
@Before
public void setUp(){
String splittingXPath = "/orders/order";
splitter = new XPathMessageSplitter(splittingXPath);
splitter.setOutputChannel(replyChannel);
splitter.setRequiresReply(true);
}
@@ -64,7 +65,7 @@ public class XPathMessageSplitterTests {
}
}
@Test(expected = MessagingException.class)
@Test(expected = ReplyRequiredException.class)
public void splitDocumentThatDoesNotMatch() throws Exception {
Document doc = XmlTestUtil.getDocumentForString("<wrongDocument/>");
splitter.handleMessage(new GenericMessage<Document>(doc));
@@ -95,7 +96,7 @@ public class XPathMessageSplitterTests {
}
}
@Test(expected = MessagingException.class)
@Test(expected = MessageHandlingException.class)
public void invalidPayloadType() {
splitter.handleMessage(new GenericMessage<Integer>(123));
}

View File

@@ -107,3 +107,12 @@ The `<int-amqp:outbound-gateway>` now supports `confirm-correlation-expression`
attributes with similar purpose as for `<int-amqp:outbound-channel-adapter>`.
See <<amqp>> for more information.
[[x4.2-xpath-splitter]]
==== XPath Splitter improvements
The `XPathMessageSplitter` (`<int-xml:xpath-splitter>`) now allows the configuration of `output-properties`
for the internal `javax.xml.transform.Transformer` and supports an `Iterator` mode (defaults to `true`) for the xpath
evaluation `org.w3c.dom.NodeList` result.
See <<xml-xpath-splitting>> for more information.

View File

@@ -687,6 +687,30 @@ XPath splitter namespace support allows the creation of a Message Endpoint with
</int-xml:xpath-splitter>
----
Starting with `version 4.2`, the `XPathMessageSplitter` exposes `outputProperties`
(such as `OutputKeys.OMIT_XML_DECLARATION`) property for the `javax.xml.transform.Transformer` instances when a
request `payload` isn't of `org.w3c.dom.Node` type:
[source,xml]
----
<util:properties id="outputProperties">
<beans:prop key="#{T (javax.xml.transform.OutputKeys).OMIT_XML_DECLARATION}">yes</beans:prop>
</util:properties>
<xpath-splitter input-channel="input"
output-properties="outputProperties">
<xpath-expression expression="/orders/order"/>
</xpath-splitter>
----
Starting with `version 4.2`, the `XPathMessageSplitter` exposes an `iterator` option as a `boolean` flag (defaults
to `true`).
This allows the "streaming" of split nodes in the downstream flow.
With the `iterator` mode, each node is transformed while iterating. When false, all entries are transformed first,
before the split nodes start being sent to the output channel (transform, send, transform, send Vs. transform,
transform, send, send).
See <<splitter>> for more information.
[[xml-xpath-routing]]
=== Routing XML Messages Using XPath