Showing posts with label XML. Show all posts
Showing posts with label XML. Show all posts

Sunday, January 16, 2011

Spring WS: Handling XML With XPath Using Jaxp13XPathTemplate

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.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring WS: Handling XML With XPath Using Jaxp13XPathTemplate ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring WS: Handling XML With XPath Using JaxenXPathTemplate

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 JaxenXPathTemplate. 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 JaxenXPathTemplate?
Implementation of XPathOperations that uses Jaxen.

Source: Spring API JaxenXPathTemplate

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 JaxenXPathTemplate, 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 JaxenXPathTemplate 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.

StringJaxenXPathTemplateController
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.transform.StringSource;
import org.springframework.xml.xpath.JaxenXPathTemplate;
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/jaxenxpathtemplate")
public class StringJaxenXPathTemplateController {

 protected static Logger logger = Logger.getLogger("controller");

    private XPathOperations template = new JaxenXPathTemplate();

    @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");
  ((JaxenXPathTemplate) 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", "JaxenXPathTemplate 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/jaxenxpathtemplate
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");
  ((JaxenXPathTemplate) 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/jaxenxpathtemplate

Here's the screenshot:

Here's the log file:
[DEBUG] [http-8080-Processor24 01:38:50] (StringJaxenXPathTemplateController.java:mapNode:60) code
[DEBUG] [http-8080-Processor24 01:38:50] (StringJaxenXPathTemplateController.java:mapNode:61) 200
[DEBUG] [http-8080-Processor24 01:38:50] (StringJaxenXPathTemplateController.java:mapNode:62) description
[DEBUG] [http-8080-Processor24 01:38:50] (StringJaxenXPathTemplateController.java:mapNode:63) 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.

FileJaxenXPathTemplateController
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.JaxenXPathTemplate;
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/jaxenxpathtemplate")
public class FileJaxenXPathTemplateController {

 protected static Logger logger = Logger.getLogger("controller");

    private XPathOperations template = new JaxenXPathTemplate();

    @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 namespaces = new HashMap();
  namespaces.put("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
  namespaces.put("base", "http://krams915.blogspot.com/ws/schema/oss");
  ((JaxenXPathTemplate) template).setNamespaces(namespaces);
  
  logger.debug("Evaluating expression");
        SubscriptionResponse response = template.evaluateAsObject("//SOAP-ENV:Envelope//base:subscriptionResponse", source, new NodeMapper() {
            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", "JaxenXPathTemplate 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/jaxenxpathtemplate


Here's the log file:
[DEBUG] [http-8080-Processor22 01:48:15] (FileJaxenXPathTemplateController.java:mapNode:60) code
[DEBUG] [http-8080-Processor22 01:48:15] (FileJaxenXPathTemplateController.java:mapNode:61) 200
[DEBUG] [http-8080-Processor22 01:48:15] (FileJaxenXPathTemplateController.java:mapNode:62) description
[DEBUG] [http-8080-Processor22 01:48:15] (FileJaxenXPathTemplateController.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 JaxenXPathTemplate. 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.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring WS: Handling XML With XPath Using JaxenXPathTemplate ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring WS: Handling XML With XPath Using XPathExpression

In this tutorial we will study how to retrieve nodes and attributes from an XML document using Spring's XPath support via XPathExpression. 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 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
namespaces
The 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/xpathexpression
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. 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) plain
We 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.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring WS: Handling XML With XPath Using XPathExpression ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Sunday, January 2, 2011

Spring 3 - Task Scheduling via "scheduled-tasks" Element

In this tutorial we will explore Spring 3's task scheduling support based on the scheduled-tasks element. Spring also provides scheduling support using the Quartz Scheduler. However, we will focus solely on Spring 3's built-in scheduler for this tutorial. We will build our application on top of a simple Spring MVC 3 application. Although MVC is not required, I would like to show how easy it is to integrate.

Why do we need scheduling?
Scheduling is needed if you want to automate the repetition of a task at specific intervals or particular date. You could of course manually watch the time and execute your task, albeit an inefficient task. Who wants to watch the computer every 3 seconds just to hit the Enter key? No one.

The work
We want to run the following sample class at specific intervals:

The task that we're interested is inside the work() method. This example is based on Mark Fisher's example at Task Scheduling Simplifications in Spring 3.0. This method retrieves the thread name, prints the starting and beginning of the method, simulates work by putting the thread in sleep for 10 seconds.

To schedule this using Spring's scheduling support via XML, we'll use the tag scheduled-tasks element.

The 'scheduled-tasks' element
The most powerful feature of Spring's task namespace is the support for configuring tasks to be scheduled within a Spring Application Context. This follows an approach similar to other "method-invokers" in Spring, such as that provided by the JMS namespace for configuring Message-driven POJOs. Basically a "ref" attribute can point to any Spring-managed object, and the "method" attribute provides the name of a method to be invoked on that object.

Source: Spring Framework 3 Reference, Chapter 25. Task Execution and Scheduling
We've prepared a separate XML file to contain all configurations relating to scheduling.

spring-scheduler.xml

This config is actually shorter if we remove the comments.

Notice we declared three references to our tasks inside the scheduled-tasks element:

Each has task has their own triggers (the schedule when they will be executed). These tasks are managed by the taskScheduler

The id is used as the default thread name prefix. You can also configure the pool size.

We've declared three beans as tasks:

These are simple POJOs with no dependency over Spring's scheduling. This mean we can easily add scheduling to existing beans.

Here are the classes for these beans:

FixedDelayWorker

FixedRateWorker

FixedDelayWorker

Worker

Running the application, the logs show the following output:

Notice how the FixedRateWorker is executed multiple times. This doesn't look right. We've scheduled it using the fixed-rate attribute (an interval-based trigger where the interval is measured from the start time of the previous task). This means it will run every 5 seconds from the start time of the previous task.

Starting from 06:00:31 up to 06:01:01, there are five intervals of 5 seconds each.
06:00:36
06:00:41
06:00:46
06:00:51
06:00:56
This means our FixedRateWorker has been queued 5 times. If we look at the logs from 06:01:01 to 06:01:41, FixedRateWorker has been executed 5 times. The point to take here is be careful when combining multiple schedules. We combined them here for the sake of completeness. Also, we've run the following example within a Spring MVC application just by adding an extra configuration spring-scheduler.xml

To see the output, please check the logs :) Feel free to modify the MVC app to fit your needs. You might wanna try integrating a web service with scheduling and show the results via MVC. The welcome page is accesible at
http://localhost:8080/spring-mvc-task-scheduling-xml/krams/main/welcome
The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-scheduling/

You can download the project as a Maven build. Look for the spring-mvc-task-scheduling-xml.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 need to know more about Task Scheduling in Spring 3.0, please visit the following links:

StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3 - Task Scheduling via "scheduled-tasks" Element ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share