From 2a89e80dfbbf9ae3de32eca06050dd45e82a23b4 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Tue, 8 Jun 2010 16:40:29 +0000 Subject: [PATCH] INT-1155 initial commit for XPathTransformer --- .../xml/transformer/XPathTransformer.java | 123 ++++++++++++++++++ .../transformer/XPathTransformerTests.java | 123 ++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathTransformer.java create mode 100644 spring-integration-xml/src/test/java/org/springframework/integration/xml/transformer/XPathTransformerTests.java 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 new file mode 100644 index 0000000000..c9d2a90f88 --- /dev/null +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathTransformer.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2010 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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; +import org.springframework.integration.transformer.AbstractTransformer; +import org.springframework.integration.xml.DefaultXmlPayloadConverter; +import org.springframework.integration.xml.XmlPayloadConverter; +import org.springframework.util.Assert; +import org.springframework.xml.xpath.NodeMapper; +import org.springframework.xml.xpath.XPathExpression; +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. + * + * @author Mark Fisher + * @since 2.0 + */ +public class XPathTransformer extends AbstractTransformer { + + public static enum ResultType { + BOOLEAN, NODE, NODELIST, NUMBER, OBJECT, STRING + } + + private final XPathExpression xpathExpression; + + private volatile XmlPayloadConverter converter = new DefaultXmlPayloadConverter(); + + private volatile ResultType expectedResultType = ResultType.STRING; + + private volatile NodeMapper nodeMapper; + + + 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); + } + + + /** + * Specify the expected {@link ResultType}. The default is {@link ResultType#STRING}. + */ + public void setExpectedResultType(ResultType expectedResultType) { + this.expectedResultType = expectedResultType; + } + + /** + * Set a {@link NodeMapper} to use for generating the result object. + * This will also set the expectedResultType to OBJECT since + * the actual type determination is a responsibility of the NodeMapper. + */ + public void setNodeMapper(NodeMapper nodeMapper) { + this.expectedResultType = ResultType.OBJECT; + this.nodeMapper = nodeMapper; + } + + /** + * Specify the {@link XmlPayloadConverter} to use when converting a Message payload prior to XPath evaluation. + */ + public void setConverter(XmlPayloadConverter converter) { + Assert.notNull(converter, "converter must not be null"); + this.converter = converter; + } + + @Override + protected Object doTransform(Message message) throws Exception { + Node node = this.converter.convertToNode(message.getPayload()); + Object result = null; + switch (this.expectedResultType) { + case BOOLEAN: + result = this.xpathExpression.evaluateAsBoolean(node); + break; + case NODE: + result = this.xpathExpression.evaluateAsNode(node); + break; + case NODELIST: + result = this.xpathExpression.evaluateAsNodeList(node); + break; + case NUMBER: + result = this.xpathExpression.evaluateAsNumber(node); + break; + case STRING: + result = this.xpathExpression.evaluateAsString(node); + break; + default: // works for OBJECT + Assert.notNull(this.nodeMapper, "NodeMapper required if expectedResultType is OBJECT"); + result = this.xpathExpression.evaluateAsObject(node, this.nodeMapper); + break; + } + return result; + } + +} 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 new file mode 100644 index 0000000000..8f66390884 --- /dev/null +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/transformer/XPathTransformerTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2010 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.xml.transformer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; + +import org.springframework.integration.core.Message; +import org.springframework.integration.message.MessageBuilder; +import org.springframework.integration.xml.transformer.XPathTransformer.ResultType; +import org.springframework.xml.xpath.NodeMapper; + +/** + * @author Mark Fisher + * @since 2.0 + */ +public class XPathTransformerTests { + + private static final String XML = ""; + + private volatile Message message; + + + @Before + public void createMessage() { + this.message = MessageBuilder.withPayload(XML).build(); + } + + + @Test + public void stringResultTypeByDefault() throws Exception { + XPathTransformer transformer = new XPathTransformer("/parent/child/@name"); + Object result = transformer.doTransform(message); + assertEquals("test", result); + } + + @Test + public void numberResult() throws Exception { + XPathTransformer transformer = new XPathTransformer("/parent/child/@age"); + transformer.setExpectedResultType(ResultType.NUMBER); + Object result = transformer.doTransform(message); + assertEquals(new Double(42), result); + } + + @Test + public void booleanResult() throws Exception { + XPathTransformer transformer = new XPathTransformer("/parent/child/@married"); + transformer.setExpectedResultType(ResultType.BOOLEAN); + Object result = transformer.doTransform(message); + assertEquals(Boolean.TRUE, result); + } + + @Test + public void nodeResult() throws Exception { + XPathTransformer transformer = new XPathTransformer("/parent/child"); + transformer.setExpectedResultType(ResultType.NODE); + Object result = transformer.doTransform(message); + assertTrue(result instanceof Node); + Node node = (Node) result; + assertEquals("child", node.getLocalName()); + assertEquals("test", node.getAttributes().getNamedItem("name").getTextContent()); + assertEquals("42", node.getAttributes().getNamedItem("age").getTextContent()); + assertEquals("true", node.getAttributes().getNamedItem("married").getTextContent()); + } + + @Test + @SuppressWarnings("unchecked") + public void nodeListResult() throws Exception { + XPathTransformer transformer = new XPathTransformer("/parent/child"); + transformer.setExpectedResultType(ResultType.NODELIST); + Message message = MessageBuilder.withPayload( + "").build(); + Object result = transformer.doTransform(message); + assertTrue(List.class.isAssignableFrom(result.getClass())); + List nodeList = (List) result; + assertEquals(2, nodeList.size()); + Node node1 = nodeList.get(0); + Node node2 = nodeList.get(1); + assertEquals("child", node1.getLocalName()); + assertEquals("foo", node1.getAttributes().getNamedItem("name").getTextContent()); + assertEquals("child", node2.getLocalName()); + assertEquals("bar", node2.getAttributes().getNamedItem("name").getTextContent()); + } + + @Test + public void nodeMapper() throws Exception { + XPathTransformer transformer = new XPathTransformer("/parent/child/@name"); + transformer.setNodeMapper(new TestNodeMapper()); + Object result = transformer.doTransform(message); + assertEquals("test-mapped", result); + } + + + private static class TestNodeMapper implements NodeMapper { + + @Override + public Object mapNode(Node node, int nodeNum) throws DOMException { + return node.getTextContent() + "-mapped"; + } + } + +}