Friday, August 31, 2007

Comfortable XPath accessor

Three weeks ago I blogged about a "Groovy way" to create XML documents in Java. This article now is about a convenient way to access XML without requiring more than a single class (downloadable here) on top of the JRE's own XML library functions.

In fact I wrote this class before I even looked for an easy way to create XML myself, because at the time I just had to parse some XML and extract the values in an easy to use fashion.

I believe it is easiest to show an example of how to use the class. Consider the following very simple Java class:

import java.math.BigDecimal;

/**
 * Simple value object for a contact.
*/
public class Contact {
public String firstname;
public String lastname;
public boolean withAccount;
public Integer numberOfCalls;
public BigDecimal amountDue;
}

Usually the fields would not be public of course, but for the sake of the example, just imagine the getters and setters being there ;-)

Now consider getting an XML string back from an external system that you cannot change:

<representative>
   <address>
         <street>Somewhere</street>
         <city>Over The Rainbow</city>
         <details>null</details>
   </address>
   <personal>
       <name>Someone</name>
       <firstname>Special</firstname>
       <age>55</age>
       <acct>true</acct>
   </personal>
   <supportHistory>
       <phone>
          <total>14</total>
          <x11>5</x11>
          <x12>9</x12>
       </phone>
       <incident>
          <id>1</id>
          <cost>1.50</cost> 
       </incident>
       <incident>
          <id>2</id>
          <cost>2.50</cost>
       </incident>
       <incident>
          <id>3</id>
          <cost>3.50</cost>
       </incident>
       <incident>
          <id>4</id>
          <cost>4.50</cost>
       </incident>
   </supportHistory>
   <current> 
       <due>44.12</due>
       <total>100.88</total>
   </current> 
</representative>

What's the easiest way to get this into the “Contact” class above, without using persistence frameworks, mapping tools and the like? What about this:

XPathAccessor acc = new XPathAccessor(someXML);
Contact contact = new Contact();

contact.firstname = acc.xp("representative", "personal", "firstname");
contact.lastname = acc.xp("representative", "personal", "name");
contact.withAccount = acc.xpBool("representative", "personal", "acct");
contact.numberOfCalls = acc.xpInt("representative", "supportHistory", "phone", "total");
contact.amountDue = acc.xpBD("representative", "current", "due");

I find this rather straightforward. Of course the Strings should be declared as constants somewhere to prevent typos.

The example is really simple, because we just extract some plain values from the XML. However, you have the full power of XPath at your disposal. This means you could do something like this:

BigDecimal getTotalIncidentCost(String someXML) throws SAXException,      
IOException, ParserConfigurationException, XPathExpressionException {
XPathAccessor acc = new XPathAccessor(someXML);
BigDecimal tResult;
Node historyNode = acc.getNode("representative", "supportHistory");
tResult = acc.xpBD(historyNode, "sum(incident/cost)");
return tResult;
}

The XPathAccessor instance could (and should) be reused, of course, depending on how often you need to access the document. As it caches XPath expressions that have already been used, it saves some cycles to re-use it for all accesses to a particular document.

Getting the “historyNode” first is of course not really necessary in this simple case, however sometimes it can come in handy to keep the expressions readable when you need to access deeper parts of the XML.

As with the XElement class, feel free to use this one, too. I would be happy to get feedback and/or improvements.

No comments: