What is XPath?
XPath, the XML Path Language, is a query language for selecting nodes from an XML document. In addition, XPath may be used to compute values (e.g., strings, numbers, or Boolean values) from the content of an XML document. XPath was defined by the World Wide Web Consortium (W3C).
Source: http://en.wikipedia.org/wiki/XPath
What is XPathExpression?
Spring Web Services has two ways to use XPath within your application: the faster XPathExpression or the more flexible XPathTemplate....
The XPathExpression is an abstraction over a compiled XPath expression, such as the Java 5 javax.xml.xpath.XPathExpression, or the Jaxen XPath class.
Source: http://static.springsource.org/spring-ws/sites/2.0/reference/html/common.html#xpath
The XML Document
Our XML document is a SOAP response from one of our Spring WS providers (See Spring WS 2: Client-side WS-Security Using XWSS tutorial).
sample.xml
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" SOAP-ENV:mustUnderstand="1"> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-1294933019426-1489568038"> <wsu:Created>2011-01-13T15:42:00.516Z</wsu:Created> <wsu:Expires>2011-01-13T15:47:00.516Z</wsu:Expires> </wsu:Timestamp> <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-12949330194261896507786" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:Username>mojo</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">ZalI6+DTAFvlYM2h4DBg56rpyhY=</wsse:Password> <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">smqvjzTKmKJkQlrSCubs/ZSm</wsse:Nonce> <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2011-01-13T15:42:00.521Z</wsu:Created> </wsse:UsernameToken> </wsse:Security> </SOAP-ENV:Header> <SOAP-ENV:Body> <subscriptionResponse xmlns="http://krams915.blogspot.com/ws/schema/oss"> <code id="200">SUCCESS</code> <description type="plain">User has been subscribed</description> </subscriptionResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>This is the same SOAP response document from the aforementioned tutorial, except that I added an extra id and type attributes in order to demonstrate how to retrieve their values.
The Configuration
To use an XPathExpression we need to declare an XPathExpressionFactoryBean.
What is XPathExpressionFactoryBean?
Factory for compiled XPathExpressions, being aware of JAXP 1.3+ XPath functionality, and Jaxen. Mainly for internal use of the framework.
The goal of this class is to avoid runtime dependencies a specific XPath engine, simply using the best XPath implementation that is available. Prefers JAXP 1.3+ XPath implementations to Jaxen
Source: Spring API: XPathExpressionFactory
Let's declare a separate context file that will contain our XPathExpressionFactoryBean
xpath-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- 4.3.1. XPathExpression See http://static.springsource.org/spring-ws/sites/2.0/reference/html/common.html#xpath--> <bean id="xpathExpression" class="org.springframework.xml.xpath.XPathExpressionFactoryBean"> <property name="expression" value="/SOAP-ENV:Envelope//base:subscriptionResponse"/> <property name="namespaces" > <map> <entry key="SOAP-ENV"> <value>http://schemas.xmlsoap.org/soap/envelope/</value> </entry> <entry key="base"> <value>http://krams915.blogspot.com/ws/schema/oss</value> </entry> </map> </property> </bean> </beans>Here we declared an XPathExpressionFactoryBean with two properties:
expression namespacesThe expression property contains the XPath expression that we're interested in the original XML document (See below)
<subscriptionResponse xmlns="http://krams915.blogspot.com/ws/schema/oss"> <code id="200">SUCCESS</code> <description type="plain">User has been subscribed</description> </subscriptionResponse>The expression /SOAP-ENV:Envelope//base:subscriptionResponse means follow all nodes from Envelope element and stop at the subscriptionResponse element. If you need to review the basics of XPath, I suggest you read the following tutorials: XPath Tutorial and The Java XPath API
Notice the expression also contains prefixes SOAP-ENV and base. This means the XPath expression must use namespaces as well, in addition to the elements name.
Why do we need XML namespaces?
XML namespaces are used for providing uniquely named elements and attributes in an XML document. They are defined in a W3C recommendation. An XML instance may contain element or attribute names from more than one XML vocabulary. If each vocabulary is given a namespace then the ambiguity between identically named elements or attributes can be resolved.
Source: http://en.wikipedia.org/wiki/XML_namespace
After the expression property, we declared a namespaces property as well
<property name="namespaces" > <map> <entry key="SOAP-ENV"> <value>http://schemas.xmlsoap.org/soap/envelope/</value> </entry> <entry key="base"> <value>http://krams915.blogspot.com/ws/schema/oss</value> </entry> </map> </property>Our expression isn't smart enough to know what SOAP-ENV and base are pointing at. We have to help it by providing the exact URI that matches these namespaces.
The Controller
We've setup the required configuration. Now we need to declare a controller that will utilize our XPathExpressionFactoryBean and display the results in a JSP page. We mentioned earlier we'll be reading from two XML sources. We'll start with the String source first.
StringXPathExpressionController
package org.krams.tutorial.controller; import java.io.InputStream; import java.io.StringReader; import javax.annotation.Resource; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.log4j.Logger; import org.krams.tutorial.oxm.SubscriptionResponse; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.xml.xpath.NodeMapper; import org.springframework.xml.xpath.XPathExpression; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.InputSource; /** * Controller for handling XPathExpression requests */ @Controller @RequestMapping("/string/xpathexpression") public class StringXPathExpressionController { protected static Logger logger = Logger.getLogger("controller"); // Loads an XPathExpression from the xpath-context.xml @Resource(name="xpathExpression") private XPathExpression xpathExpression; @RequestMapping(method = RequestMethod.GET) public String getResults(final Model model) { logger.debug("Received request to show demo page"); // Defines a factory API that enables applications to obtain a parser that // produces DOM object trees from XML documents. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // Enable namespaces because our XML uses namespaces factory.setNamespaceAware(true); // The Document interface represents the entire HTML or XML document. // Conceptually, it is the root of the document tree, and provides the primary // access to the document's data. Document doc = null; try { // Load a String XML InputSource source = new InputSource( new StringReader(getStringXML()) ); // Parse the XML file as an input source doc = factory.newDocumentBuilder().parse(source); } catch (Exception e) { logger.error(e); } logger.debug("Retrieving primary node"); Node nodeSource = doc.getDocumentElement(); logger.debug("Evaluating XPathExpression"); SubscriptionResponse response = xpathExpression.evaluateAsObject(nodeSource, new NodeMapper<SubscriptionResponse>() { public SubscriptionResponse mapNode(Node node, int nodeNum) throws DOMException { Element element = (Element) node; // Retrieve code element Element code = (Element) element.getChildNodes().item(1); // Retrieve description element Element description = (Element) element.getChildNodes().item(3); //Map XML values to our custom Object SubscriptionResponse response = new SubscriptionResponse(); response.setCode(code.getTextContent()); response.setDescription(description.getTextContent()); // Retrieve local name and attribute values for demonstration purposes logger.debug(code.getLocalName()); logger.debug(code.getAttribute("id")); logger.debug(description.getLocalName()); logger.debug(description.getAttribute("type")); // Add to model model.addAttribute("namespaceURI", element.getNamespaceURI()); model.addAttribute("nodeType", element.getNodeType()); model.addAttribute("nodeName", element.getNodeName()); model.addAttribute("parentNode", element.getParentNode()); model.addAttribute("prefix", element.getPrefix()); model.addAttribute("nextSibling", element.getNextSibling()); model.addAttribute("textContent", element.getTextContent()); return response; } }); // Add mapped object to model model.addAttribute("response", response); // Add type description to model model.addAttribute("type", "XPathExpression from a String source"); // This will resolve to /WEB-INF/jsp/xpathresultpage.jsp return "xpathresultpage"; } public String getStringXML() { String xml = "" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" + " <SOAP-ENV:Header>" + " <wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" SOAP-ENV:mustUnderstand=\"1\">" + " <wsu:Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"XWSSGID-1294933019426-1489568038\">" + " <wsu:Created>2011-01-13T15:42:00.516Z</wsu:Created>" + " <wsu:Expires>2011-01-13T15:47:00.516Z</wsu:Expires>" + " </wsu:Timestamp>" + " <wsse:UsernameToken xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"XWSSGID-12949330194261896507786\" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">" + " <wsse:Username>mojo</wsse:Username>" + " <wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">ZalI6+DTAFvlYM2h4DBg56rpyhY=</wsse:Password>" + " <wsse:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">smqvjzTKmKJkQlrSCubs/ZSm</wsse:Nonce>" + " <wsu:Created xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">2011-01-13T15:42:00.521Z</wsu:Created>" + " </wsse:UsernameToken>" + " </wsse:Security>" + " </SOAP-ENV:Header>" + " <SOAP-ENV:Body>" + " <subscriptionResponse xmlns=\"http://krams915.blogspot.com/ws/schema/oss\">" + " <code id=\"200\">SUCCESS</code>" + " <description type=\"plain\">User has been subscribed</description>" + " </subscriptionResponse>" + " </SOAP-ENV:Body>" + "</SOAP-ENV:Envelope>"; return xml; } }This controller declares a single mapping:
/string/xpathexpressionNotice the getStringXML() method contains our XML as a String. The bulk of the processing is inside the getResults() method.
Here's what's happening:
1. Declare an instance of DocumentBuilderFactory to retrieve a parser that produces a DOM object
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
2. Set the setNamespaceAware to true. This forces the parser to honor the namespaces; otherwise, it won't find our elements. This is very important!
factory.setNamespaceAware(true);
3. Load the XML document from a String source and start parsing it.
Document doc = null; try { InputSource source = new InputSource( new StringReader(getStringXML()) ); doc = factory.newDocumentBuilder().parse(source); } catch (Exception e) { logger.error(e); }
4. Retrieve a Node from the parsed Document
Node nodeSource = doc.getDocumentElement();
5. Start mapping the elements and retrieved the corresponding values that we're interested at.
SubscriptionResponse response = xpathExpression.evaluateAsObject(nodeSource, new NodeMapper() { public SubscriptionResponse mapNode(Node node, int nodeNum) throws DOMException { ... return response; } });
6. Finally, we return the a JSP page that contains the results:
// This will resolve to /WEB-INF/jsp/xpathresultpage.jsp return "xpathresultpage";
Here's the JSP page
xpathresultpage.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <style type="text/css"> .label {font-weight: bold;} </style> <title>Insert title here</title> </head> <body> <h4>Handling XML With Spring's XPath Support</h4> <h3>${type}</h3> <hr/> <p><span class="label">NamespaceURI:</span> ${namespaceURI}</p> <p><span class="label">NodeName:</span> ${nodeName}</p> <p><span class="label">NodeType:</span> ${nodeType}</p> <p><span class="label">ParentNode:</span> ${parentNode}</p> <p><span class="label">Prefix:</span> ${prefix}</p> <p><span class="label">NextSibling:</span> ${nextSibling}</p> <p><span class="label">TextContent:</span> ${textContent}</p> <p><span class="label">SubscriptionResponse:</span> <br/> code: ${response.code}<br/> description: ${response.description}<br/></p> </body> </html>
Let's run the application to see the result. To run the application, use the following URL:
http://localhost:8080/spring-ws-xpath/krams/string/xpathexpression
Here's the screenshot:
Here's the log file:
[DEBUG] [http-8080-Processor22 01:06:22] (StringXPathExpressionController.java:mapNode:81) code [DEBUG] [http-8080-Processor22 01:06:22] (StringXPathExpressionController.java:mapNode:82) 200 [DEBUG] [http-8080-Processor22 01:06:22] (StringXPathExpressionController.java:mapNode:83) description [DEBUG] [http-8080-Processor22 01:06:22] (StringXPathExpressionController.java:mapNode:84) plain
The File Source
We've handled an XML from a String source. Now let's handle an XML from a File source.
We need to create a new XML document that will contain our sample XML document. Here's what we need to do:
1. Create a new XML document, and name it sample.xml
2. Copy and paste our sample XML to this new document (See above)
3. Save the document under the classpath location
Create a new controller that will handle an XML document from a File source.
FileXPathExpressionController
package org.krams.tutorial.controller; import java.io.InputStream; import javax.annotation.Resource; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.log4j.Logger; import org.krams.tutorial.oxm.SubscriptionResponse; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.xml.xpath.NodeMapper; import org.springframework.xml.xpath.XPathExpression; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * Controller for handling XPathExpression requests */ @Controller @RequestMapping("/file/xpathexpression") public class FileXPathExpressionController { protected static Logger logger = Logger.getLogger("controller"); // Loads an XPathExpression from the xpath-context.xml @Resource(name="xpathExpression") private XPathExpression xpathExpression; @RequestMapping(method = RequestMethod.GET) public String getResults(final Model model) { logger.debug("Received request to show demo page"); // Defines a factory API that enables applications to obtain a parser that // produces DOM object trees from XML documents. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // Enable namespaces because our XML uses namespaces factory.setNamespaceAware(true); // The Document interface represents the entire HTML or XML document. // Conceptually, it is the root of the document tree, and provides the primary // access to the document's data. Document doc = null; try { // Load an external XML file InputStream source = this.getClass().getResourceAsStream("/sample.xml"); // Parse the XML file as an input stream doc = factory.newDocumentBuilder().parse(source); } catch (Exception e) { logger.error(e); } logger.debug("Retrieving primary node"); Node nodeSource = doc.getDocumentElement(); logger.debug("Evaluating XPathExpression"); SubscriptionResponse response = xpathExpression.evaluateAsObject(nodeSource, new NodeMapper<SubscriptionResponse>() { public SubscriptionResponse mapNode(Node node, int nodeNum) throws DOMException { Element element = (Element) node; // Retrieve code element Element code = (Element) element.getChildNodes().item(1); // Retrieve description element Element description = (Element) element.getChildNodes().item(3); //Map XML values to our custom Object SubscriptionResponse response = new SubscriptionResponse(); response.setCode(code.getTextContent()); response.setDescription(description.getTextContent()); // Retrieve local name and attribute values for demonstration purposes logger.debug(code.getLocalName()); logger.debug(code.getAttribute("id")); logger.debug(description.getLocalName()); logger.debug(description.getAttribute("type")); // Add to model model.addAttribute("namespaceURI", element.getNamespaceURI()); model.addAttribute("nodeType", element.getNodeType()); model.addAttribute("nodeName", element.getNodeName()); model.addAttribute("parentNode", element.getParentNode()); model.addAttribute("prefix", element.getPrefix()); model.addAttribute("nextSibling", element.getNextSibling()); model.addAttribute("textContent", element.getTextContent()); return response; } }); // Add mapped object to model model.addAttribute("response", response); // Add type description to model model.addAttribute("type", "XPathExpression from a File source"); // This will resolve to /WEB-INF/jsp/xpathresultpage.jsp return "xpathresultpage"; } }Our new controller is exactly the same as our first controller, except that now we don't have a
getStringXML() method because we're now using an external XML file.
Then we changed the source from an InputSource
InputSource source = new InputSource( new StringReader(getStringXML()) );to an InputStream instead:
InputStream source = this.getClass().getResourceAsStream("/sample.xml");Everything else is still exactly the same.
Let's run the application to see the results. To run the application, use the following URL:
http://localhost:8080/spring-ws-xpath/krams/file/xpathexpression
Here's the log file:
[DEBUG] [http-8080-Processor24 01:02:53] (FileXPathExpressionController.java:mapNode:78) code [DEBUG] [http-8080-Processor24 01:02:53] (FileXPathExpressionController.java:mapNode:79) 200 [DEBUG] [http-8080-Processor24 01:02:53] (FileXPathExpressionController.java:mapNode:80) description [DEBUG] [http-8080-Processor24 01:02:53] (FileXPathExpressionController.java:mapNode:81) plainWe have the same results.
Conclusion
That's it. We're done with our study of Spring's XPath support via XPathExpression. We've explored how to retrieved results from a String source and File source. We've also leveraged the study using Spring MVC.
To see the remaining MVC configuration, please see the source code below.
Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-xpath/
You can download the project as a Maven build. Look for the spring-ws-xpath.zip in the Download sections.
You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
Share the joy:
|
Subscribe by reader Subscribe by email Share
Today I was running into confusion using XPathExpression, and this article solved all my problems. Great job and thanks!
ReplyDeleteHi, I downloaded this project and imported it in eclipse, it's complaining :
ReplyDeleteMarkers :
Description Resource Path Location Type
cvc-complex-type.2.3: Element 'SOAP-ENV:Body' cannot have character [children], because the type's content type is element-only. sample.xml /spring-ws-xpath/src/main/resources line 16 XML Problem
Please how to solve this problem ?
thanks, your help is appreciated.
I had the same issue till i found that there are some characters in the document that is not good for xml, try to remove empty lines in the xml & spaces that is not needed, This fixed my problem
DeleteThis comment has been removed by the author.
ReplyDeleteI have read your blog its very attractive and impressive. I like it your blog.
ReplyDeleteSpring online training Spring online training Spring Hibernate online training Spring Hibernate online training Java online training
spring training in chennai spring hibernate training in chennai
This comment has been removed by the author.
ReplyDeleteInteresting post!!! Thanks for posting such a useful information. I wish to read your upcoming post to enhance my skill set and keep blogging.
ReplyDeleteNice blog,I understood the topic very clearly,And want to study more like this.
evs full form
raw agent full form
full form of tbh in instagram
dbs bank full form
https full form
tft full form
pco full form
kra full form in hr
tbh full form in instagram story
epc full form
bape outlet
ReplyDeletekevin durant shoes
hermes outlet online
supreme
fear of god essentials
off white outlet
jordan outlet
golden goose outlet
yeezy
palm angels outlet