From 256ff4c46f746863c75d994eb467acbf034c2722 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 14 Apr 2015 21:30:39 +0300 Subject: [PATCH] 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 --- .../xml/config/XPathExpressionParser.java | 50 +++-- .../config/XPathMessageSplitterParser.java | 12 +- .../xml/splitter/XPathMessageSplitter.java | 199 +++++++++++++++--- .../xml/config/spring-integration-xml-4.2.xsd | 34 ++- .../XPathSplitterParserTests-context.xml | 23 +- .../xml/config/XPathSplitterParserTests.java | 9 +- .../splitter/XPathMessageSplitterTests.java | 19 +- src/reference/asciidoc/whats-new.adoc | 9 + src/reference/asciidoc/xml.adoc | 24 +++ 9 files changed, 306 insertions(+), 73 deletions(-) diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathExpressionParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathExpressionParser.java index 85263f6f83..c24f11968b 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathExpressionParser.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathExpressionParser.java @@ -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 namespaceMap = new HashMap(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); - } - } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathMessageSplitterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathMessageSplitterParser.java index f5ca2653cc..cd49c37fa0 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathMessageSplitterParser.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathMessageSplitterParser.java @@ -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; } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/splitter/XPathMessageSplitter.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/splitter/XPathMessageSplitter.java index a809c9b73a..28870f392f 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/splitter/XPathMessageSplitter.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/splitter/XPathMessageSplitter.java @@ -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 true. - * + * * @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()); @@ -70,9 +92,22 @@ public class XPathMessageSplitter extends AbstractMessageSplitter { public XPathMessageSplitter(String expression, Map 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 nodes = splitNode(document); - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - List splitStrings = new ArrayList(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 items = (List) nodes; + List splitStrings = new ArrayList(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((Iterator) nodes, new Function() { + + @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 splitNode(Node node) throws ParserConfigurationException { - List 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 nodeList = this.xpathExpression.evaluateAsNodeList(node); + if (this.createDocuments) { + return convertNodesToDocuments(nodeList); + } + return nodeList; } - return nodeList; } private List convertNodesToDocuments(List nodes) throws ParserConfigurationException { - DocumentBuilder documentBuilder = this.getNewDocumentBuilder(); + DocumentBuilder documentBuilder = getNewDocumentBuilder(); List documents = new ArrayList(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 { + + 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"); + } + + } + } diff --git a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-4.2.xsd b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-4.2.xsd index a990640df7..5969a08af3 100644 --- a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-4.2.xsd +++ b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-4.2.xsd @@ -749,6 +749,32 @@ + + + + A set of output properties that will be used to override any of the same properties + in effect for the transformation. + + + + + + + + + + + + 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. + + + + + + @@ -862,14 +888,14 @@ - + - + @@ -879,12 +905,12 @@ - + - + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathSplitterParserTests-context.xml b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathSplitterParserTests-context.xml index 04b69aaa50..8ae7ac3d86 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathSplitterParserTests-context.xml +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathSplitterParserTests-context.xml @@ -1,20 +1,31 @@ + 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"> - + + yes + + + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathSplitterParserTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathSplitterParserTests.java index 49f6e15f01..140c87da56 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathSplitterParserTests.java +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathSplitterParserTests.java @@ -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", diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/splitter/XPathMessageSplitterTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/splitter/XPathMessageSplitterTests.java index 45228c3659..ded441906d 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/splitter/XPathMessageSplitterTests.java +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/splitter/XPathMessageSplitterTests.java @@ -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(""); splitter.handleMessage(new GenericMessage(doc)); @@ -95,7 +96,7 @@ public class XPathMessageSplitterTests { } } - @Test(expected = MessagingException.class) + @Test(expected = MessageHandlingException.class) public void invalidPayloadType() { splitter.handleMessage(new GenericMessage(123)); } diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index b83a7f2ef6..d90d335f88 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -107,3 +107,12 @@ The `` now supports `confirm-correlation-expression` attributes with similar purpose as for ``. See <> for more information. + +[[x4.2-xpath-splitter]] +==== XPath Splitter improvements + +The `XPathMessageSplitter` (``) 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 <> for more information. diff --git a/src/reference/asciidoc/xml.adoc b/src/reference/asciidoc/xml.adoc index b6f11cd4d3..11fa989342 100644 --- a/src/reference/asciidoc/xml.adoc +++ b/src/reference/asciidoc/xml.adoc @@ -687,6 +687,30 @@ XPath splitter namespace support allows the creation of a Message Endpoint with ---- +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] +---- + + yes + + + + + +---- + +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 <> for more information. + [[xml-xpath-routing]] === Routing XML Messages Using XPath