INT-1156 completed namespace support for XPath Transformer
This commit is contained in:
@@ -41,9 +41,19 @@ public class XPathTransformerParser extends AbstractTransformerParser {
|
||||
@Override
|
||||
protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||
String expression = element.getAttribute("expression");
|
||||
Assert.isTrue(StringUtils.hasText(expression), "The 'expression' attribute is required.");
|
||||
builder.addConstructorArgValue(expression);
|
||||
String expressionRef = element.getAttribute("expression-ref");
|
||||
boolean hasRef = StringUtils.hasText(expressionRef);
|
||||
Assert.isTrue(hasRef ^ StringUtils.hasText(expression),
|
||||
"Exactly one of the 'expression' or 'expression-ref' attributes is required.");
|
||||
if (hasRef) {
|
||||
builder.addConstructorArgReference(expressionRef);
|
||||
}
|
||||
else {
|
||||
builder.addConstructorArgValue(expression);
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "evaluation-type");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "node-mapper");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "converter");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package org.springframework.integration.xml.transformer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
@@ -34,6 +31,13 @@ import org.springframework.xml.xpath.XPathExpressionFactory;
|
||||
/**
|
||||
* Transformer implementation that evaluates an XPath expression against the inbound
|
||||
* Message payload and returns a Message whose payload is the result of that evaluation.
|
||||
* Prior to evaluation, the payload may be converted by the configured {@link XmlPayloadConverter}
|
||||
* instance. The default converter type is {@link DefaultXmlPayloadConverter}.
|
||||
* <p>
|
||||
* The evaluation result type will depend on either the enumeration value provided to
|
||||
* {@link #setEvaluationType(XPathEvaluationType)} or the presence of a {@link NodeMapper},
|
||||
* which takes precedence. If no {@link NodeMapper} or evaluation type is configured explicitly,
|
||||
* the default evaluation type is {@link XPathEvaluationType#STRING_RESULT}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @since 2.0
|
||||
@@ -49,18 +53,21 @@ public class XPathTransformer extends AbstractTransformer {
|
||||
private volatile NodeMapper nodeMapper;
|
||||
|
||||
|
||||
/**
|
||||
* Create an {@link XPathTransformer} that will create an XPath expression from the given String
|
||||
* to be evaluated against converted inbound Message payloads.
|
||||
*/
|
||||
public XPathTransformer(String expression) {
|
||||
this.xpathExpression = XPathExpressionFactory.createXPathExpression(expression);
|
||||
}
|
||||
|
||||
public XPathTransformer(String expression, Map<String, String> namespaces) {
|
||||
this.xpathExpression = XPathExpressionFactory.createXPathExpression(expression, namespaces);
|
||||
}
|
||||
|
||||
public XPathTransformer(String expression, String prefix, String namespace) {
|
||||
Map<String, String> namespaces = new HashMap<String, String>();
|
||||
namespaces.put(prefix, namespace);
|
||||
this.xpathExpression = XPathExpressionFactory.createXPathExpression(expression, namespaces);
|
||||
/**
|
||||
* Create an {@link XPathTransformer} that will evaluate the given {@link XPathExpression}
|
||||
* against converted inbound Message payloads.
|
||||
*/
|
||||
public XPathTransformer(XPathExpression expression) {
|
||||
Assert.notNull(expression, "expression must not be null");
|
||||
this.xpathExpression = expression;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,16 +75,16 @@ public class XPathTransformer extends AbstractTransformer {
|
||||
* Specify the expected {@link XPathEvaluationType}. The default is {@link XPathEvaluationType#STRING_RESULT}.
|
||||
*/
|
||||
public void setEvaluationType(XPathEvaluationType evaluationType) {
|
||||
Assert.notNull(evaluationType, "evaluationType must not be null.");
|
||||
this.evaluationType = evaluationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link NodeMapper} to use for generating the result object.
|
||||
* This will also set the evaluationType to <code>null</code> since
|
||||
* the actual type determination is a responsibility of the NodeMapper.
|
||||
* Set a {@link NodeMapper} to use for generating the result object. By default the NodeMapper is null,
|
||||
* but if explicitly set, type determination is the responsibility of the NodeMapper, taking precedence
|
||||
* over any configured evaluationType.
|
||||
*/
|
||||
public void setNodeMapper(NodeMapper nodeMapper) {
|
||||
this.evaluationType = null;
|
||||
this.nodeMapper = nodeMapper;
|
||||
}
|
||||
|
||||
@@ -93,8 +100,7 @@ public class XPathTransformer extends AbstractTransformer {
|
||||
protected Object doTransform(Message<?> message) throws Exception {
|
||||
Node node = this.converter.convertToNode(message.getPayload());
|
||||
Object result = null;
|
||||
if (evaluationType == null) {
|
||||
Assert.notNull(this.nodeMapper, "NodeMapper required if evaluationType is null.");
|
||||
if (this.nodeMapper != null) {
|
||||
result = this.xpathExpression.evaluateAsObject(node, this.nodeMapper);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -166,13 +166,27 @@
|
||||
</xsd:annotation>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="inputOutputEndpoint">
|
||||
<xsd:attribute name="expression" type="xsd:string" use="required">
|
||||
<xsd:attribute name="expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The XPath expression to be evaluated against the input Message's payload.
|
||||
The XPath expression string to be evaluated against the input Message's payload.
|
||||
Either this or 'expression-ref' must be provided, but not both.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="expression-ref" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Reference to the XPathExpression instance to be evaluated against the input Message's payload.
|
||||
Either this or 'expression' must be provided, but not both.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.xml.xpath.XPathExpression"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="evaluation-type" default="STRING_RESULT">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -182,13 +196,39 @@
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="BOOLEAN_RESULT"/>
|
||||
<xsd:enumeration value="STRING_RESULT"/>
|
||||
<xsd:enumeration value="NUMBER_RESULT"/>
|
||||
<xsd:enumeration value="NODE_RESULT"/>
|
||||
<xsd:enumeration value="NODE_LIST_RESULT"/>
|
||||
<xsd:enumeration value="NUMBER_RESULT"/>
|
||||
<xsd:enumeration value="STRING_RESULT"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="node-mapper">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Reference to a NodeMapper. If this is provided, the 'evaluation-type' will be ignored. Instead, the
|
||||
org.springframework.xml.xpath.XPathExpression's evaluateAsObject(Node node, NodeMapper nodeMapper)
|
||||
method will be invoked.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.xml.xpath.NodeMapper"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="converter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify the XmlPayloadConverter to use when converting a Message payload prior to XPath evaluation.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.xml.XmlPayloadConverter"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
@@ -24,4 +24,16 @@
|
||||
|
||||
<xpath-transformer input-channel="nodeListInput" expression="/person/@*" evaluation-type="NODE_LIST_RESULT" output-channel="output"/>
|
||||
|
||||
<xpath-transformer input-channel="nodeMapperInput" expression="/person/@age" node-mapper="testNodeMapper" output-channel="output"/>
|
||||
|
||||
<xpath-transformer input-channel="customConverterInput" expression="/test/@type" converter="testXmlPayloadConverter" output-channel="output"/>
|
||||
|
||||
<xpath-transformer input-channel="expressionRefInput" expression-ref="testExpression" evaluation-type="NUMBER_RESULT" output-channel="output"/>
|
||||
|
||||
<xpath-expression id="testExpression" expression="/person/@age * 2"/>
|
||||
|
||||
<beans:bean id="testNodeMapper" class="org.springframework.integration.xml.config.XPathTransformerParserTests$TestNodeMapper"/>
|
||||
|
||||
<beans:bean id="testXmlPayloadConverter" class="org.springframework.integration.xml.config.XPathTransformerParserTests$TestXmlPayloadConverter"/>
|
||||
|
||||
</beans:beans>
|
||||
|
||||
@@ -19,19 +19,28 @@ package org.springframework.integration.xml.config;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.Source;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.w3c.dom.DOMException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.channel.PollableChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.xml.XmlPayloadConverter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.xml.xpath.NodeMapper;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
@@ -56,6 +65,15 @@ public class XPathTransformerParserTests {
|
||||
@Autowired
|
||||
private MessageChannel nodeListInput;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel nodeMapperInput;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel customConverterInput;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel expressionRefInput;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel output;
|
||||
|
||||
@@ -99,4 +117,55 @@ public class XPathTransformerParserTests {
|
||||
assertEquals(3, nodeList.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nodeMapper() {
|
||||
this.nodeMapperInput.send(message);
|
||||
assertEquals("42-mapped", output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customConverter() {
|
||||
this.customConverterInput.send(message);
|
||||
assertEquals("custom", output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expressionRef() {
|
||||
this.expressionRefInput.send(message);
|
||||
assertEquals(new Double(84), output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class TestNodeMapper implements NodeMapper {
|
||||
|
||||
@Override
|
||||
public Object mapNode(Node node, int nodeNum) throws DOMException {
|
||||
return node.getTextContent() + "-mapped";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class TestXmlPayloadConverter implements XmlPayloadConverter {
|
||||
|
||||
public Source convertToSource(Object object) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Node convertToNode(Object object) {
|
||||
try {
|
||||
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
|
||||
new InputSource(new StringReader("<test type='custom'/>")));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Document convertToDocument(Object object) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,17 +19,26 @@ package org.springframework.integration.xml.transformer;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.Source;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.DOMException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.xml.XmlPayloadConverter;
|
||||
import org.springframework.integration.xml.xpath.XPathEvaluationType;
|
||||
import org.springframework.xml.xpath.NodeMapper;
|
||||
import org.springframework.xml.xpath.XPathExpression;
|
||||
import org.springframework.xml.xpath.XPathExpressionFactory;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
@@ -55,6 +64,14 @@ public class XPathTransformerTests {
|
||||
assertEquals("test", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xpathExpressionReferenceConstructorInsteadOfString() throws Exception {
|
||||
XPathExpression expression = XPathExpressionFactory.createXPathExpression("/parent/child/@name");
|
||||
XPathTransformer transformer = new XPathTransformer(expression);
|
||||
Object result = transformer.doTransform(message);
|
||||
assertEquals("test", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void numberResult() throws Exception {
|
||||
XPathTransformer transformer = new XPathTransformer("/parent/child/@age");
|
||||
@@ -111,6 +128,14 @@ public class XPathTransformerTests {
|
||||
assertEquals("test-mapped", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customConverter() throws Exception {
|
||||
XPathTransformer transformer = new XPathTransformer("/test/@type");
|
||||
transformer.setConverter(new TestXmlPayloadConverter());
|
||||
Object result = transformer.doTransform(message);
|
||||
assertEquals("custom", result);
|
||||
}
|
||||
|
||||
|
||||
private static class TestNodeMapper implements NodeMapper {
|
||||
|
||||
@@ -120,4 +145,26 @@ public class XPathTransformerTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestXmlPayloadConverter implements XmlPayloadConverter {
|
||||
|
||||
public Source convertToSource(Object object) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Node convertToNode(Object object) {
|
||||
try {
|
||||
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
|
||||
new InputSource(new StringReader("<test type='custom'/>")));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Document convertToDocument(Object object) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user