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:
committed by
Gary Russell
parent
8059566b9b
commit
256ff4c46f
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user