In this tutorial we will study how to retrieve nodes and attributes from an XML document using Spring's XPath support via
XPathTemplate that uses a
Jaxp13XPathTemplate. We will be handling two XML sources: from a File source and from a String source. To display the results, we will base our application on Spring MVC, though you could easily port the code in a standard desktop application.
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 XPathTemplate?
Spring Web Services has two ways to use XPath within your application: the faster XPathExpression or the more flexible XPathTemplate....
The XPathExpression only allows you to evaluate a single, pre-compiled expression. A more flexible, though slower, alternative is the XpathTemplate.
Source: http://static.springsource.org/spring-ws/sites/2.0/reference/html/common.html#xpath
What is Jaxp13XPathTemplate?
mplementation of XPathOperations that uses JAXP 1.3. JAXP 1.3 is part of Java SE since 1.5.
Source: Spring API Jaxp13XPathTemplate
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
XPathTemplate with
Jaxp13XPathTemplate, we just use it directly on our classes. There's no need to declare an external configuration unlike the XPathExpression (See
Spring WS: Handling XML With XPath Using XPathExpression)
The Controller
Since we don't need any external configuration, we can begin with our controller that will utilize the
Jaxp13XPathTemplate 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.
StringJaxp13XPathTemplateController
package org.krams.tutorial.controller;
import java.io.InputStream;
import java.util.HashMap;
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.transform.StringSource;
import org.springframework.xml.xpath.Jaxp13XPathTemplate;
import org.springframework.xml.xpath.NodeMapper;
import org.springframework.xml.xpath.XPathOperations;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Controller for handling XPathTemplate requests
*/
@Controller
@RequestMapping("/string/jaxp13xpathtemplate")
public class StringJaxp13XPathTemplateController {
protected static Logger logger = Logger.getLogger("controller");
private XPathOperations template = new Jaxp13XPathTemplate();
@RequestMapping(method = RequestMethod.GET)
public String getResults(final Model model) {
logger.debug("Received request to show demo page");
// Load the XML document
StringSource source = new StringSource(getStringXML());
// Set the namespace; otherwise we won't find our items
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
namespaces.put("base", "http://krams915.blogspot.com/ws/schema/oss");
((Jaxp13XPathTemplate) template).setNamespaces(namespaces);
logger.debug("Evaluating expression");
SubscriptionResponse response = template.evaluateAsObject("//SOAP-ENV:Envelope//base:subscriptionResponse", source, 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", "Jaxp13XPathTemplate 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/jaxp13xpathtemplate
Notice 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. Load an XML document from a String source.
StringSource source = new StringSource(getStringXML())
2. Set the
setNamespaces property. This forces the parser to honor the namespaces; otherwise, it won't find our elements. This is very important!
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
namespaces.put("base", "http://krams915.blogspot.com/ws/schema/oss");
((Jaxp13XPathTemplate) template).setNamespaces(namespaces);
Notice our XPath expression
/SOAP-ENV:Envelope//base:subscriptionResponse 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
3. Start mapping the elements and retrieved the corresponding values that we're interested at.
SubscriptionResponse response = template.evaluateAsObject("//SOAP-ENV:Envelope//base:subscriptionResponse", source, new NodeMapper() {
public SubscriptionResponse mapNode(Node node, int nodeNum) throws DOMException {
...
return response;
}
});
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
4. 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/jaxp13xpathtemplate
Here's the screenshot:
Here's the log file:
[DEBUG] [http-8080-Processor24 02:00:28] (StringJaxp13XPathTemplateController.java:mapNode:59) code
[DEBUG] [http-8080-Processor24 02:00:28] (StringJaxp13XPathTemplateController.java:mapNode:60) 200
[DEBUG] [http-8080-Processor24 02:00:28] (StringJaxp13XPathTemplateController.java:mapNode:61) description
[DEBUG] [http-8080-Processor24 02:00:28] (StringJaxp13XPathTemplateController.java:mapNode:62) 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.
FileJaxp13XPathTemplateController
package org.krams.tutorial.controller;
import java.io.InputStream;
import java.util.HashMap;
import javax.xml.transform.stream.StreamSource;
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.Jaxp13XPathTemplate;
import org.springframework.xml.xpath.NodeMapper;
import org.springframework.xml.xpath.XPathOperations;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Controller for handling XPathTemplate requests
*/
@Controller
@RequestMapping("/file/jaxp13xpathtemplate")
public class FileJaxp13XPathTemplateController {
protected static Logger logger = Logger.getLogger("controller");
private XPathOperations template = new Jaxp13XPathTemplate();
@RequestMapping(method = RequestMethod.GET)
public String getResults(final Model model) {
logger.debug("Received request to show demo page");
// Load the XML document
InputStream reportStream = this.getClass().getResourceAsStream("/sample.xml");
StreamSource source = new StreamSource( reportStream );
// Set the namespace; otherwise we won't find our items
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
namespaces.put("base", "http://krams915.blogspot.com/ws/schema/oss");
((Jaxp13XPathTemplate) template).setNamespaces(namespaces);
logger.debug("Evaluating expression");
SubscriptionResponse response = template.evaluateAsObject("//SOAP-ENV:Envelope//base:subscriptionResponse", source, 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", "Jaxp13XPathTemplate 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 a
StringSource
StringSource source = new StringSource(getStringXML());
to an
InputStream and
StreamSource instead:
InputStream reportStream = this.getClass().getResourceAsStream("/sample.xml");
StreamSource source = new StreamSource( reportStream );
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/jaxp13xpathtemplate
Here's the log file:
[DEBUG] [http-8080-Processor22 02:03:04] (FileJaxp13XPathTemplateController.java:mapNode:60) code
[DEBUG] [http-8080-Processor22 02:03:04] (FileJaxp13XPathTemplateController.java:mapNode:61) 200
[DEBUG] [http-8080-Processor22 02:03:04] (FileJaxp13XPathTemplateController.java:mapNode:62) description
[DEBUG] [http-8080-Processor22 02:03:04] (FileJaxp13XPathTemplateController.java:mapNode:63) plain
We have the same results.
Conclusion
That's it. We're done with our study of Spring's XPath support via
XPathTemplate that uses a
Jaxp13XPathTemplate. 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.
Subscribe by reader
Subscribe by email
Share