diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathTransformerParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathTransformerParser.java index 629c265c60..d00aa3fc95 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathTransformerParser.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathTransformerParser.java @@ -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"); } } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathTransformer.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathTransformer.java index e1824cfbf9..4199f67b9b 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathTransformer.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathTransformer.java @@ -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}. + *

+ * 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 namespaces) { - this.xpathExpression = XPathExpressionFactory.createXPathExpression(expression, namespaces); - } - - public XPathTransformer(String expression, String prefix, String namespace) { - Map namespaces = new HashMap(); - 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 null 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 { diff --git a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd index 10afde02a7..2a8dfd8d35 100644 --- a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd +++ b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd @@ -166,13 +166,27 @@ - + - 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. + + + + Reference to the XPathExpression instance to be evaluated against the input Message's payload. + Either this or 'expression' must be provided, but not both. + + + + + + + + @@ -182,13 +196,39 @@ + + - - + + + + 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. + + + + + + + + + + + + Specify the XmlPayloadConverter to use when converting a Message payload prior to XPath evaluation. + + + + + + + + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathTransformerParserTests-context.xml b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathTransformerParserTests-context.xml index 0b7b7caa91..48e78380e2 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathTransformerParserTests-context.xml +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathTransformerParserTests-context.xml @@ -24,4 +24,16 @@ + + + + + + + + + + + + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathTransformerParserTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathTransformerParserTests.java index f2c95fc047..a5fabaff78 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathTransformerParserTests.java +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathTransformerParserTests.java @@ -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(""))); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + public Document convertToDocument(Object object) { + throw new UnsupportedOperationException(); + } + } + } diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/transformer/XPathTransformerTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/transformer/XPathTransformerTests.java index 8f1f24b5d8..1ca2ddaf26 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/transformer/XPathTransformerTests.java +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/transformer/XPathTransformerTests.java @@ -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(""))); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + public Document convertToDocument(Object object) { + throw new UnsupportedOperationException(); + } + } + }