diff --git a/core-tiger/src/main/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapter.java b/core-tiger/src/main/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapter.java index 028ecce0..ed940bfb 100644 --- a/core-tiger/src/main/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapter.java +++ b/core-tiger/src/main/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapter.java @@ -62,6 +62,7 @@ public class XPathParamAnnotationEndpointAdapter extends AbstractMethodEndpointA private Properties namespaces; + /** Sets namespaces used in the XPath expression. Maps prefixes to namespaces. */ public void setNamespaces(Properties namespaces) { this.namespaces = namespaces; } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 4bcafb42..603b2bbf 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,6 +6,8 @@ + Added XPath template, for evaluating XPaths in a template-like fashion + MTOM support Added XPathExpressionFactoryBean, for injection of XPath expressions diff --git a/xml/src/main/java/org/springframework/xml/xpath/AbstractXPathTemplate.java b/xml/src/main/java/org/springframework/xml/xpath/AbstractXPathTemplate.java new file mode 100644 index 00000000..c9dbc33a --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/AbstractXPathTemplate.java @@ -0,0 +1,64 @@ +/* + * Copyright 2007 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.xml.xpath; + +import java.util.Properties; +import javax.xml.transform.Source; + +import org.springframework.xml.transform.TransformerObjectSupport; +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; + +/** + * Abstract base class for implementations of {@link XPathOperations}. Contains a namespaces property. + * + * @author Arjen Poutsma + */ +public abstract class AbstractXPathTemplate extends TransformerObjectSupport implements XPathOperations { + + private Properties namespaces; + + /** Returns namespaces used in the XPath expression. */ + public Properties getNamespaces() { + return namespaces; + } + + /** Sets namespaces used in the XPath expression. Maps prefixes to namespaces. */ + public void setNamespaces(Properties namespaces) { + this.namespaces = namespaces; + } + + public final void evaluate(String expression, Source context, NodeCallbackHandler callbackHandler) + throws XPathException { + evaluate(expression, context, new NodeCallbackHandlerNodeMapper(callbackHandler)); + } + + /** Static inner class that adapts a {@link NodeCallbackHandler} to the interface of {@link NodeMapper}. */ + private static class NodeCallbackHandlerNodeMapper implements NodeMapper { + + private final NodeCallbackHandler callbackHandler; + + public NodeCallbackHandlerNodeMapper(NodeCallbackHandler callbackHandler) { + this.callbackHandler = callbackHandler; + } + + public Object mapNode(Node node, int nodeNum) throws DOMException { + callbackHandler.processNode(node); + return null; + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/JaxenXPathTemplate.java b/xml/src/main/java/org/springframework/xml/xpath/JaxenXPathTemplate.java new file mode 100644 index 00000000..69ca538d --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/JaxenXPathTemplate.java @@ -0,0 +1,171 @@ +/* + * Copyright 2007 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.xml.xpath; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; + +import org.jaxen.JaxenException; +import org.jaxen.SimpleNamespaceContext; +import org.jaxen.XPath; +import org.jaxen.dom.DOMXPath; +import org.springframework.xml.dom.DomUtils; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Implementation of {@link XPathOperations} that uses Jaxen. + *

+ * Namespaces can be set using the namespaces property. + * + * @author Arjen Poutsma + * @see Jaxen + */ +public class JaxenXPathTemplate extends AbstractXPathTemplate { + + public boolean evaluateAsBoolean(String expression, Source context) throws XPathException { + try { + XPath xpath = createXPath(expression); + Element element = DomUtils.getRootElement(context, getTransformerFactory()); + return xpath.booleanValueOf(element); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex); + } + catch (TransformerException ex) { + throw new XPathException("Could not transform context to DOM Node", ex); + } + } + + public Node evaluateAsNode(String expression, Source context) throws XPathException { + try { + XPath xpath = createXPath(expression); + Element element = DomUtils.getRootElement(context, getTransformerFactory()); + return (Node) xpath.selectSingleNode(element); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex); + } + catch (TransformerException ex) { + throw new XPathException("Could not transform context to DOM Node", ex); + } + } + + public List evaluateAsNodeList(String expression, Source context) throws XPathException { + try { + XPath xpath = createXPath(expression); + Element element = DomUtils.getRootElement(context, getTransformerFactory()); + return xpath.selectNodes(element); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex); + } + catch (TransformerException ex) { + throw new XPathException("Could not transform context to DOM Node", ex); + } + } + + public double evaluateAsDouble(String expression, Source context) throws XPathException { + try { + XPath xpath = createXPath(expression); + Element element = DomUtils.getRootElement(context, getTransformerFactory()); + return xpath.numberValueOf(element).doubleValue(); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex); + } + catch (TransformerException ex) { + throw new XPathException("Could not transform context to DOM Node", ex); + } + } + + public String evaluateAsString(String expression, Source context) throws XPathException { + try { + XPath xpath = createXPath(expression); + Element element = DomUtils.getRootElement(context, getTransformerFactory()); + return xpath.stringValueOf(element); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex); + } + catch (TransformerException ex) { + throw new XPathException("Could not transform context to DOM Node", ex); + } + } + + public Object evaluateAsObject(String expression, Source context, NodeMapper nodeMapper) throws XPathException { + try { + XPath xpath = createXPath(expression); + Element element = DomUtils.getRootElement(context, getTransformerFactory()); + Node node = (Node) xpath.selectSingleNode(element); + if (node != null) { + try { + return nodeMapper.mapNode(node, 0); + } + catch (DOMException ex) { + throw new XPathException("Mapping resulted in DOMException", ex); + } + } + else { + return null; + } + + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex); + } + catch (TransformerException ex) { + throw new XPathException("Could not transform context to DOM Node", ex); + } + } + + public List evaluate(String expression, Source context, NodeMapper nodeMapper) throws XPathException { + try { + XPath xpath = createXPath(expression); + Element element = DomUtils.getRootElement(context, getTransformerFactory()); + List nodes = (List) xpath.selectNodes(element); + List results = new ArrayList(nodes.size()); + for (int i = 0; i < nodes.size(); i++) { + Node node = (Node) nodes.get(i); + try { + results.add(nodeMapper.mapNode(node, i)); + } + catch (DOMException ex) { + throw new XPathException("Mapping resulted in DOMException", ex); + } + } + return results; + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex); + } + catch (TransformerException ex) { + throw new XPathException("Could not transform context to DOM Node", ex); + } + } + + private XPath createXPath(String expression) throws JaxenException { + XPath xpath = new DOMXPath(expression); + if (getNamespaces() != null && !getNamespaces().isEmpty()) { + xpath.setNamespaceContext(new SimpleNamespaceContext(getNamespaces())); + } + return xpath; + } +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactory.java b/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactory.java index 34fbead2..f430628d 100644 --- a/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactory.java +++ b/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactory.java @@ -28,7 +28,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** - * JAXP 1.3-specific factory creating XPathExpressions. + * JAXP 1.3-specific factory creating {@link XPathExpression} objects. * * @author Arjen Poutsma * @see #createXPathExpression(String) diff --git a/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathTemplate.java b/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathTemplate.java new file mode 100644 index 00000000..42356336 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathTemplate.java @@ -0,0 +1,168 @@ +/* + * Copyright 2007 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.xml.xpath; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathFactoryConfigurationException; + +import org.springframework.xml.dom.DomUtils; +import org.springframework.xml.namespace.SimpleNamespaceContext; +import org.springframework.xml.transform.StaxSource; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +/** + * Implementation of {@link XPathOperations} that uses JAXP 1.3. JAXP 1.3 is part of Java SE since 1.5. + *

+ * Namespaces can be set using the namespaces property. + * + * @author Arjen Poutsma + * @see #setNamespaces(java.util.Properties) + */ +public class Jaxp13XPathTemplate extends AbstractXPathTemplate { + + private XPathFactory xpathFactory; + + public Jaxp13XPathTemplate() { + this(XPathFactory.DEFAULT_OBJECT_MODEL_URI); + } + + public Jaxp13XPathTemplate(String xpathFactoryUri) { + try { + xpathFactory = XPathFactory.newInstance(xpathFactoryUri); + } + catch (XPathFactoryConfigurationException ex) { + throw new XPathException("Could not create XPathFactory", ex); + } + } + + public boolean evaluateAsBoolean(String expression, Source context) throws XPathException { + Boolean result = (Boolean) evaluate(expression, context, XPathConstants.BOOLEAN); + return result != null && result.booleanValue(); + } + + public Node evaluateAsNode(String expression, Source context) throws XPathException { + return (Node) evaluate(expression, context, XPathConstants.NODE); + } + + public List evaluateAsNodeList(String expression, Source context) throws XPathException { + NodeList result = (NodeList) evaluate(expression, context, XPathConstants.NODESET); + List nodes = new ArrayList(result.getLength()); + for (int i = 0; i < result.getLength(); i++) { + nodes.add(result.item(i)); + } + return nodes; + } + + public double evaluateAsDouble(String expression, Source context) throws XPathException { + Double result = (Double) evaluate(expression, context, XPathConstants.NUMBER); + return result != null ? result.doubleValue() : Double.NaN; + } + + public String evaluateAsString(String expression, Source context) throws XPathException { + return (String) evaluate(expression, context, XPathConstants.STRING); + } + + public Object evaluateAsObject(String expression, Source context, NodeMapper nodeMapper) throws XPathException { + Node node = evaluateAsNode(expression, context); + if (node != null) { + try { + return nodeMapper.mapNode(node, 0); + } + catch (DOMException ex) { + throw new XPathException("Mapping resulted in DOMException", ex); + } + } + else { + return null; + } + } + + public List evaluate(String expression, Source context, NodeMapper nodeMapper) throws XPathException { + NodeList nodes = (NodeList) evaluate(expression, context, XPathConstants.NODESET); + List results = new ArrayList(nodes.getLength()); + for (int i = 0; i < nodes.getLength(); i++) { + try { + results.add(nodeMapper.mapNode(nodes.item(i), i)); + } + catch (DOMException ex) { + throw new XPathException("Mapping resulted in DOMException", ex); + } + } + return results; + } + + private Object evaluate(String expression, Source context, QName returnType) throws XPathException { + XPath xpath = xpathFactory.newXPath(); + if (getNamespaces() != null && !getNamespaces().isEmpty()) { + SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext(); + namespaceContext.setBindings(getNamespaces()); + xpath.setNamespaceContext(namespaceContext); + } + try { + if (context instanceof StaxSource) { + Element element = DomUtils.getRootElement(context, getTransformerFactory()); + return xpath.evaluate(expression, element, returnType); + } + else if (context instanceof SAXSource) { + SAXSource saxSource = (SAXSource) context; + return xpath.evaluate(expression, saxSource.getInputSource(), returnType); + } + else if (context instanceof DOMSource) { + DOMSource domSource = (DOMSource) context; + return xpath.evaluate(expression, domSource.getNode(), returnType); + } + else if (context instanceof StreamSource) { + StreamSource streamSource = (StreamSource) context; + InputSource inputSource = null; + if (streamSource.getInputStream() != null) { + inputSource = new InputSource(streamSource.getInputStream()); + } + else if (streamSource.getReader() != null) { + inputSource = new InputSource(streamSource.getReader()); + } + else { + throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader"); + } + return xpath.evaluate(expression, inputSource, returnType); + } + else { + throw new IllegalArgumentException("context type unknown"); + } + } + catch (javax.xml.xpath.XPathException ex) { + throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex); + } + catch (TransformerException ex) { + throw new XPathException("Could not transform context to DOM Node", ex); + } + } + +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/NodeCallbackHandler.java b/xml/src/main/java/org/springframework/xml/xpath/NodeCallbackHandler.java new file mode 100644 index 00000000..3462486c --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/NodeCallbackHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2007 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.xml.xpath; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; + +/** + * An interface used by {@link XPathOperations} implementations for processing {@link Node} objects on a per-node basis. + * Implementations of this interface perform the actual work of processing nodes, but don't need to worry about + * exception handling. + *

+ * Consider using a {@link NodeMapper} instead if you need to map exactly result object per node, assembling them in a + * List. + * + * @author Arjen Poutsma + * @see XPathOperations#evaluate(String,javax.xml.transform.Source,NodeCallbackHandler) + */ +public interface NodeCallbackHandler { + + /** + * Processed a single node. + * + * @param node the node to map + * @throws DOMException in case of DOM errors + */ + void processNode(Node node) throws DOMException; + +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/NodeMapper.java b/xml/src/main/java/org/springframework/xml/xpath/NodeMapper.java new file mode 100644 index 00000000..7cf35b19 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/NodeMapper.java @@ -0,0 +1,43 @@ +/* + * Copyright 2007 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.xml.xpath; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; + +/** + * An interface used by {@link XPathOperations} implementations for mapping {@link Node} objects on a per-node basis. + * Implementations of this interface perform the actual work of mapping each node to a result object, but don't need to + * worry about exception handling. + * + * @author Arjen Poutsma + * @see XPathOperations#evaluate(String,javax.xml.transform.Source,NodeMapper) + * @see XPathOperations#evaluateAsObject(String,javax.xml.transform.Source,NodeMapper) + */ +public interface NodeMapper { + + /** + * Maps a single node to an arbitrary object. + * + * @param node the node to map + * @param nodeNum the number of the current node + * @return object for the current node + * @throws DOMException in case of DOM errors + */ + Object mapNode(Node node, int nodeNum) throws DOMException; + +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/XPathExpression.java b/xml/src/main/java/org/springframework/xml/xpath/XPathExpression.java index 4ad7cb73..637b3e39 100644 --- a/xml/src/main/java/org/springframework/xml/xpath/XPathExpression.java +++ b/xml/src/main/java/org/springframework/xml/xpath/XPathExpression.java @@ -19,32 +19,46 @@ package org.springframework.xml.xpath; import org.w3c.dom.Node; /** - * Defines the contract for a precompiled XPath expression. Concrete instances can be obtained through the - * XPathExpressionFactory. + * Defines the contract for a precompiled XPath expression. Concrete instances can be obtained through the {@link + * XPathExpressionFactory}. + *

+ * Implementations of this interface are precompiled, and thus faster, but less flexible, than the XPath expressions + * used by {@link XPathOperations} implementations. * * @author Arjen Poutsma - * @see XPathExpressionFactory */ public interface XPathExpression { - /** - * Evaluates the given expression as a Node. Returns the evaluation of the expression, or - * null if it is invalid. - * - * @param node the starting point - * @return the result of the evaluation - */ - Node evaluateAsNode(Node node); - /** * Evaluates the given expression as a boolean. Returns the boolean evaluation of the expression, or * false if it is invalid. * * @param node the starting point * @return the result of the evaluation + * @see XPath specification */ boolean evaluateAsBoolean(Node node); + /** + * Evaluates the given expression as a Node. Returns the evaluation of the expression, or + * null if it is invalid. + * + * @param node the starting point + * @return the result of the evaluation + * @see XPath specification + */ + Node evaluateAsNode(Node node); + + /** + * Evaluates the given expression, and returns all Nodes that conform to it. Returns and empty array if + * no result could be found. + * + * @param node the starting point + * @return the Nodes that are selected by the expression + * @see XPath specification + */ + Node[] evaluateAsNodes(Node node); + /** * Evaluates the given expression as a number (double). Returns the numeric evaluation of the * expression, or Double.NaN if it is invalid. @@ -52,6 +66,7 @@ public interface XPathExpression { * @param node the starting point * @return the result of the evaluation * @see Double#NaN + * @see XPath specification */ double evaluateAsNumber(Node node); @@ -61,15 +76,7 @@ public interface XPathExpression { * * @param node the starting point * @return the first Node that is selected by the expression + * @see XPath specification */ String evaluateAsString(Node node); - - /** - * Evaluates the given expression, and returns all Nodes that conform to it. Returns and empty array if - * no result could be found. - * - * @param node the starting point - * @return the Nodes that are selected by the expression - */ - Node[] evaluateAsNodes(Node node); } diff --git a/xml/src/main/java/org/springframework/xml/xpath/XPathOperations.java b/xml/src/main/java/org/springframework/xml/xpath/XPathOperations.java new file mode 100644 index 00000000..2dabf5a9 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/XPathOperations.java @@ -0,0 +1,133 @@ +/* + * Copyright 2007 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.xml.xpath; + +import java.util.List; +import javax.xml.transform.Source; + +import org.w3c.dom.Node; + +/** + * Interface that specifies a basic set of XPath operations, implemented by various XPathTemplates. Contains numerous + * evaluation methods, + *

+ * The templates that implement this interface do not use precompiled XPath expressions. Consider using the {@link + * XPathExpressionFactory} or the {@link XPathExpressionFactoryBean} for optimal performance, but less flexibility. + * + * @author Arjen Poutsma + * @see Jaxp13XPathTemplate + * @see JaxenXPathTemplate + */ +public interface XPathOperations { + + /** + * Evaluates the given expression as a boolean. Returns the boolean evaluation of the expression, or + * false if it is invalid. + * + * @param expression the XPath expression + * @param context the context starting point + * @return the result of the evaluation + * @throws XPathException in case of XPath errors + * @see XPath specification + */ + boolean evaluateAsBoolean(String expression, Source context) throws XPathException; + + /** + * Evaluates the given expression as a {@link Node}. Returns the evaluation of the expression, or null + * if it is invalid. + * + * @param expression the XPath expression + * @param context the context starting point + * @return the result of the evaluation + * @throws XPathException in case of XPath errors + * @see XPath specification + */ + Node evaluateAsNode(String expression, Source context) throws XPathException; + + /** + * Evaluates the given expression as a list of {@link Node} objects. Returns the evaluation of the expression, or an + * empty list if no results are found. + * + * @param expression the XPath expression + * @param context the context starting point + * @return the result of the evaluation + * @throws XPathException in case of XPath errors + * @see XPath specification + */ + List evaluateAsNodeList(String expression, Source context) throws XPathException; + + /** + * Evaluates the given expression as a double. Returns the evaluation of the expression, or {@link + * Double#NaN} if it is invalid. + * + * @param expression the XPath expression + * @param context the context starting point + * @return the result of the evaluation + * @throws XPathException in case of XPath errors + * @see XPath specification + */ + double evaluateAsDouble(String expression, Source context) throws XPathException; + + /** + * Evaluates the given expression as a {@link String}. Returns the evaluation of the expression, or + * null if it is invalid. + * + * @param expression the XPath expression + * @param context the context starting point + * @return the result of the evaluation + * @throws XPathException in case of XPath errors + * @see XPath specification + */ + String evaluateAsString(String expression, Source context) throws XPathException; + + /** + * Evaluates the given expression, mapping a single {@link Node} result to a Java object via a {@link NodeMapper}. + * + * @param expression the XPath expression + * @param context the context starting point + * @param nodeMapper object that will map one object per node + * @return the single mapped object + * @throws XPathException in case of XPath errors + * @see XPath specification + */ + Object evaluateAsObject(String expression, Source context, NodeMapper nodeMapper) throws XPathException; + + /** + * Evaluates the given expression, mapping each result {@link Node} objects to a Java object via a {@link + * NodeMapper}. + * + * @param expression the XPath expression + * @param context the context starting point + * @param nodeMapper object that will map one object per node + * @return the result list, containing mapped objects + * @throws XPathException in case of XPath errors + * @see XPath specification + */ + List evaluate(String expression, Source context, NodeMapper nodeMapper) throws XPathException; + + /** + * Evaluates the given expression, handling the result {@link Node} objects on a per-node basis with a {@link + * NodeCallbackHandler}. + * + * @param expression the XPath expression + * @param context the context starting point + * @param callbackHandler object that will extract results, one row at a time + * @throws XPathException in case of XPath errors + * @see XPath specification + */ + void evaluate(String expression, Source context, NodeCallbackHandler callbackHandler) throws XPathException; +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/package.html b/xml/src/main/java/org/springframework/xml/xpath/package.html index 3dfd1381..36e7692e 100644 --- a/xml/src/main/java/org/springframework/xml/xpath/package.html +++ b/xml/src/main/java/org/springframework/xml/xpath/package.html @@ -1,5 +1,5 @@ -Provides classes for XPath evaluation using JAXP 1.3, Jaxen, and Xalan. Mostly for internal use by the framework. +Provides XPathTemplate implementations, and various classes for XPath evaluation using JAXP 1.3, Jaxen, and Xalan. diff --git a/xml/src/test/java/org/springframework/xml/xpath/AbstractXPathTemplateTestCase.java b/xml/src/test/java/org/springframework/xml/xpath/AbstractXPathTemplateTestCase.java new file mode 100644 index 00000000..dea21574 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/xpath/AbstractXPathTemplateTestCase.java @@ -0,0 +1,158 @@ +/* + * Copyright 2007 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.xml.xpath; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; + +import junit.framework.TestCase; +import org.springframework.core.io.ClassPathResource; +import org.springframework.xml.sax.SaxUtils; +import org.springframework.xml.transform.ResourceSource; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +public abstract class AbstractXPathTemplateTestCase extends TestCase { + + XPathOperations template; + + private Source namespaces; + + private Source nonamespaces; + + protected final void setUp() throws Exception { + template = createTemplate(); + namespaces = new ResourceSource(new ClassPathResource("namespaces.xml", AbstractXPathTemplateTestCase.class)); + nonamespaces = + new ResourceSource(new ClassPathResource("nonamespaces.xml", AbstractXPathTemplateTestCase.class)); + } + + protected abstract XPathOperations createTemplate() throws Exception; + + public void testEvaluateAsBoolean() { + boolean result = template.evaluateAsBoolean("/root/child/boolean", nonamespaces); + assertTrue("Invalid result", result); + } + + public void testEvaluateAsBooleanNamespaces() { + boolean result = template.evaluateAsBoolean("/prefix1:root/prefix2:child/prefix2:boolean", namespaces); + assertTrue("Invalid result", result); + } + + public void testEvaluateAsDouble() { + double result = template.evaluateAsDouble("/root/child/number", nonamespaces); + assertEquals("Invalid result", 42D, result, 0D); + } + + public void testEvaluateAsDoubleNamespaces() { + double result = template.evaluateAsDouble("/prefix1:root/prefix2:child/prefix2:number", namespaces); + assertEquals("Invalid result", 42D, result, 0D); + } + + public void testEvaluateAsNode() { + Node result = template.evaluateAsNode("/root/child", nonamespaces); + assertNotNull("Invalid result", result); + assertEquals("Invalid localname", "child", result.getLocalName()); + } + + public void testEvaluateAsNodeNamespaces() { + Node result = template.evaluateAsNode("/prefix1:root/prefix2:child", namespaces); + assertNotNull("Invalid result", result); + assertEquals("Invalid localname", "child", result.getLocalName()); + } + + public void testEvaluateAsNodes() { + List results = template.evaluateAsNodeList("/root/child/*", nonamespaces); + assertNotNull("Invalid result", results); + assertEquals("Invalid amount of results", 3, results.size()); + } + + public void testEvaluateAsNodesNamespaces() { + List results = template.evaluateAsNodeList("/prefix1:root/prefix2:child/*", namespaces); + assertNotNull("Invalid result", results); + assertEquals("Invalid amount of results", 3, results.size()); + } + + public void testEvaluateAsStringNamespaces() throws IOException, SAXException { + String result = template.evaluateAsString("/prefix1:root/prefix2:child/prefix2:text", namespaces); + assertEquals("Invalid result", "text", result); + } + + public void testEvaluateAsString() throws IOException, SAXException { + String result = template.evaluateAsString("/root/child/text", nonamespaces); + assertEquals("Invalid result", "text", result); + } + + public void testEvaluateDomSource() throws IOException, SAXException, ParserConfigurationException { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.parse(SaxUtils.createInputSource( + new ClassPathResource("nonamespaces.xml", AbstractXPathTemplateTestCase.class))); + + String result = template.evaluateAsString("/root/child/text", new DOMSource(document)); + assertEquals("Invalid result", "text", result); + } + + public void testEvaluateStreamSource() throws IOException, SAXException, ParserConfigurationException { + InputStream in = AbstractXPathTemplateTestCase.class.getResourceAsStream("nonamespaces.xml"); + String result = template.evaluateAsString("/root/child/text", new StreamSource(in)); + assertEquals("Invalid result", "text", result); + } + + public void testInvalidExpression() { + try { + template.evaluateAsBoolean("\\", namespaces); + fail("No XPathException thrown"); + } + catch (XPathException ex) { + // Expected behaviour + } + } + + public void testEvaluateAsObject() throws Exception { + String result = (String) template.evaluateAsObject("/root/child", nonamespaces, new NodeMapper() { + public Object mapNode(Node node, int nodeNum) throws DOMException { + return node.getLocalName(); + } + }); + assertNotNull("Invalid result", result); + assertEquals("Invalid localname", "child", result); + } + + public void testEvaluate() throws Exception { + List results = template.evaluate("/root/child/*", nonamespaces, new NodeMapper() { + public Object mapNode(Node node, int nodeNum) throws DOMException { + return node.getLocalName(); + } + }); + assertNotNull("Invalid result", results); + assertEquals("Invalid amount of results", 3, results.size()); + assertEquals("Invalid first result", "text", results.get(0)); + assertEquals("Invalid first result", "number", results.get(1)); + assertEquals("Invalid first result", "boolean", results.get(2)); + } +} diff --git a/xml/src/test/java/org/springframework/xml/xpath/JaxenXPathTemplateTest.java b/xml/src/test/java/org/springframework/xml/xpath/JaxenXPathTemplateTest.java new file mode 100644 index 00000000..9b13636a --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/xpath/JaxenXPathTemplateTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2007 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.xml.xpath; + +import java.util.Properties; + +public class JaxenXPathTemplateTest extends AbstractXPathTemplateTestCase { + + protected XPathOperations createTemplate() throws Exception { + JaxenXPathTemplate template = new JaxenXPathTemplate(); + Properties namespaces = new Properties(); + namespaces.setProperty("prefix1", "namespace1"); + namespaces.setProperty("prefix2", "namespace2"); + template.setNamespaces(namespaces); + return template; + } + +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/xpath/Jaxp13XPathTemplateTest.java b/xml/src/test/java/org/springframework/xml/xpath/Jaxp13XPathTemplateTest.java new file mode 100644 index 00000000..cea2f1b3 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/xpath/Jaxp13XPathTemplateTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2007 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.xml.xpath; + +import java.util.Properties; + +public class Jaxp13XPathTemplateTest extends AbstractXPathTemplateTestCase { + + protected XPathOperations createTemplate() throws Exception { + Jaxp13XPathTemplate template = new Jaxp13XPathTemplate(); + Properties namespaces = new Properties(); + namespaces.setProperty("prefix1", "namespace1"); + namespaces.setProperty("prefix2", "namespace2"); + template.setNamespaces(namespaces); + return template; + } + +} \ No newline at end of file