Parsing XML from Source object using Spring’s XPathTemplate, demo with yahoo finance API client

Spring’s Source (javax.xml.transform.Source) object can handle any of these Source formats: DOMSource, SAXSource and StreamSource.

The following example client interacts with yahoo finance API using YQL(Yahoo Query Language), gets finance quotes data from the web server for a given symbol. The result is retrieved as a Source object and then applies the Spring’s, XPathTemplate provided method to map XML to objects (representing the XML data retrieved). The RestTemplate class provided by Spring is used here which takes care of the request creation and connection establishment details.

Configuration required to use Source and XPathTemplate:

Spring client needs the appropriate message converter to be configured in the applications config file to be able to handle Source objects. In my example i have provided the message converter in the applications root context xml file.


<bean id="rTemplate">
  <property name="messageConverters">
    <list>
      <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
    </list>
  <property/>
<bean/>

<bean id="xpathTemplate" class="org.springframework.xml.xpath.Jaxp13XPathTemplate"/>

Add the following to the Maven dependency in the project

<!-- SourceHttpMessageConverter to convert the HTTP XML response into a javax.xml.transform.Source -->
<dependency>
  <groupId>org.springframework.ws</groupId>
  <artifactId>spring-xml</artifactId>
  <version>2.0.5.RELEASE</version>
</dependency>

The client application is now ready with the dependencies all in place. Here is how the web client would interact with Yahoo’s finance API to get the quotes for a given symbol and retrieve it as a Source object.

private final RestTemplate rTemplate;
private XPathOperations xpathTemplate;
@Autowired
StockQuoteRetrieve(RestTemplate restTpl) {
  rTemplate = restTpl;
}
@Autowired
public void setXPathTemplate(XPathOperations xpTemplate) {
  xpathTemplate = xpTemplate;
}
@RequestMapping(value="/getquotesource", method=RequestMethod.GET)
@ResponseBody
public Source retrieveSource(String requestedSymbol) {
  String env = "store://datatables.org/alltableswithkeys";
  String symbolString = "(" + requestedSymbol + ")";
  String queryStr = "SELECT * from yahoo.finance.quotes where symbol in ";
  String restUrl = "http://query.yahooapis.com/v1/public/yql?q={qid}{symbol}&env={senv}";
  Source sresponse = rTemplate.getForObject(restUrl, Source.class, queryStr,symbolString,env);
  return sresponse;
}

If using google chrome, the data retrieved as a Spring Source object in the above code will be rendered as XML upon return of the response, as the response body contains the XML data in the Source object. However if using IE, please refer to the POST Rendering XML data using ViewResolver and Model.
The XML returned has the following structure.

<query>
  <results>
    <quote symbol="FB">
      <LastTradeDate>7/10/2012</LastTradeDate>
      <PreviousClose>32.17</PreviousClose>
      <Ask>31.74</Ask>
      ....
    </quote>
  </results>
</query>

We will now see how the client application can proceed to parse the Source object in order to map the received XML into objects. The below lines of code explains  how to accomplish that.

import org.springframework.xml.xpath.NodeMapper;
import org.springframework.xml.xpath.XPathOperations;
Query queryObject;
void parseAndProcessSource(Source response) {
  Result rObject = new Result();
  queryObject = new Query();
  List<Quote> qList = xpathTemplate.evaluate("//quote", response, new QUOTENODE_MAPPER());
  rObject.setQuote(qList);
  queryObject.setResult(rObject);
}

Here we are using the xpath expression(//quote) to retrieve the “quote” element from the XML response. QUOTENODE_MAPPER is an implementation of NodeMapper interface. The xpathTemplate uses “NodeMapper” a callback interface, whose mapnode() method is invoked as the xpath expression is encountered in the document. We provide implementation of this callback interface so the callback knows to evaluate our xpath expression in the document. We are extracting the “symbol” attribute of the quote element and all the subelements of the quote element and their contents by providing those implementation for the mapNode(…)  method of the NodeMapper interface.

class QUOTENODE_MAPPER implements NodeMapper<Quote> {
  QUOTENODE_MAPPER() {
  }
  public Quote mapNode(Node node, int nodeNum) throws DOMException {
    Quote quoteObj = new Quote();
    Element quoteElem = (Element) node;
    long longVal = 0;
    quoteObj.setSymbol(quoteElem.getAttribute("symbol"));
    Element askElement = (Element)quoteElem.getElementsByTagName("Ask").item(0);
    if(askElement != null)
      quoteObj.setAsk(askElement.getTextContent());
    Element avgDailyVol = (Element)quoteElem.getElementsByTagName("AverageDailyVolume").item(0);
    if(avgDailyVol != null) {
      longVal = Long.valueOf(avgDailyVol.getTextContent()).longValue();
      quoteObj.setAverageDailyVolume(longVal);
    }
    Element lastTrDate = (Element)quoteElem.getElementsByTagName("LastTradeDate").item(0);
    if(lastTrDate != null)
      quoteObj.setLastTradeDate(lastTrDate.getTextContent());
    Element Open = (Element)quoteElem.getElementsByTagName("Open").item(0);
    if(Open != null)
      quoteObj.setOpen(Open.getTextContent());
    Element PreviousClose = (Element)quoteElem.getElementsByTagName("PreviousClose").item(0);
    quoteObj.setPreviousClose(PreviousClose.getTextContent());
    Element Bid = (Element)quoteElem.getElementsByTagName("Bid").item(0);
    quoteObj.setBid(Bid.getTextContent());

    //Other sub lements are retrieved and mapped to the object
    return quoteObj;

}

}

Leave a comment