In this example I would like to show how unmarshall XML into a POJO using HierarchicalStreamReader. I will introduce how to use XStream and a custom XML-to-POJO converter, describe a case study and develop an application to show the solution.
Purchase History Example
Consider the following XML. It represents represents a purchase history of a customer, that has purchased different items during a week:
[xml]
<PURCHASE>
<ITEM>
<SUBITEM>
<CODE>PDCT-001</CODE>
<NAME>Product-1</NAME>
<DAY5>1.75</DAY5>
<TOTAL>1.75</TOTAL>
</SUBITEM>
<SUBITEM>
<CODE>PDCT-002</CODE>
<NAME>Product-2</NAME>
<DAY7>1.00</DAY7>
<DAY6>1.75</DAY6>
<TOTAL>2.75</TOTAL>
</SUBITEM>
<SUBITEM>
<CODE>PDCT-003</CODE>
<NAME>Product-3</NAME>
<DAY7>4.50</DAY7>
<DAY6>2.00</DAY6>
<DAY5>3.00</DAY5>
<DAY4>2.25</DAY4>
<TOTAL>11.75</TOTAL>
</SUBITEM>
<SUBITEM>
<CODE>PDCT-004</CODE>
<NAME>Product-4</NAME>
<DAY7>3.50</DAY7>
<DAY6>3.75</DAY6>
<DAY5>2.75</DAY5>
<DAY1>3.75</DAY1>
<TOTAL>13.75</TOTAL>
</SUBITEM>
<REFERENCE>9571-EDGDFG-DGSNJE-1837</REFERENCE>
<GRANDTOTAL>30</GRANDTOTAL>
</ITEM>
</PURCHASE>
[/xml]
To unmarshall the XML, I created three POJOs to represent the hierarchy of XML elements. The domain objects Purchase, Item and SubItem are involved. The Purchase has an Item, that has a collection of SubItem objects. My customer XML converter will transform customer’s purchase history from XML into a domain model using the POJOs.
I am not going to describe the full structure of the POJOs here, as I want to concentrate on talking about the XML converter . The source code as Eclipse project is attached to the current article, so you can download it.
I use XStream, which is a simple open-source Java library for serialize objects to and from XML. The following is the unit test case code snippet that show how I register my custom converter that is used for XML unmarshalling:
[java]
XStream xstream = new XStream(new DomDriver());
xstream.alias("PURCHASE", Purchase.class);
xstream.registerConverter(new PurchaseConverter());
Purchase purchase = (Purchase) xstream.fromXML(xmlContent);
[/java]
I created an alias for Purchase that matches XML root element and registered my custom converter. The converter is a specific converter that knows how to handle this particular XML example, so it is not a generic solution. The converter code as follows:
[java]
package asia.javabeans.streamreader.util;
import asia.javabeans.streamreader.domain.Purchase;
import asia.javabeans.streamreader.domain.SubItem;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
/**
* Current class represents converter for a Purchase XML.
*
* @author alexander.zagniotov
*
*/
final public class PurchaseConverter implements Converter {
private static final int NUM_OF_DAYS = 7;
public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Purchase purchase = new Purchase();
//while there are unread XML element children and the reader has not reached the closing PURCHASE element
while (!(reader.getNodeName().equals(XmlElements.PURCHASE) && !reader
.hasMoreChildren())) {
if (reader.hasMoreChildren()) {
reader.moveDown(); // Move down to ITEM element
// Process child SUBITEM elements of the parent ITEM
if (reader.getNodeName().equals(XmlElements.ITEM)) {
iterateThroughItemSubitems(reader, XmlElements.ITEM,
XmlElements.SUBITEM, purchase);
}
}
//store value of REFERENCE element in a POJO
if (reader.getNodeName().equals(XmlElements.REFERENCE)) {
purchase.setReference(reader.getValue());
}
reader.moveUp(); // Move back up to PURCHASE element
}
return purchase;
}
private Purchase iterateThroughItemSubitems(
HierarchicalStreamReader reader, String parentNameName,
String childNodeName, Purchase purchase) {
boolean hasSubitemNodes = true;
while (reader.getNodeName().equals(parentNameName) && hasSubitemNodes) {
reader.moveDown(); // Move down to SUBITEM element
// We have SUBITEM element that we can read
if (reader.getNodeName().equals(childNodeName)) {
purchase = iterateThroughSubitemChildren(reader, childNodeName,
purchase);
} else { // No more children SUBITEM elements to read
break;
}
reader.moveUp(); // Move back up to parent ITEM element
continue;
}
return purchase;
}
private Purchase iterateThroughSubitemChildren(
HierarchicalStreamReader reader, String nodeName, Purchase purchase) {
boolean hasChildrenNodes = true;
SubItem subItem = new SubItem();
while (reader.getNodeName().equals(nodeName) && hasChildrenNodes) {
// Move down to the children of current SUBITEM element
reader.moveDown();
if (reader.getNodeName().equals(XmlElements.CODE)) {
String subItemCode = reader.getValue();
subItem.setCode(subItemCode);
reader.moveUp(); // Move back up to parent SUBITEM
continue;
}
if (reader.getNodeName().equals(XmlElements.NAME)) {
String subItemName = reader.getValue();
subItem.setName(subItemName);
reader.moveUp(); // Move back up to parent SUBITEM
continue;
}
if (reader.getNodeName().startsWith(XmlElements.DAY)) {
for (int i = 1; i <= NUM_OF_DAYS; i++) {
String xmlDay = XmlElements.DAY + i;
if (reader.getNodeName().equals(xmlDay)) {
double hours = Double.parseDouble(reader.getValue());
subItem.addToDailyHours(xmlDay, hours);
purchase.addToGrandTotal(hours);
break;
}
}
reader.moveUp(); // Move back up to parent SUBITEM
continue;
}
if (reader.getNodeName().equals(XmlElements.TOTAL)) {
hasChildrenNodes = false; // This is the last child of parent SUBITEM
String itemTotalStr = reader.getValue();
subItem.setItemTotal(Double.parseDouble(itemTotalStr));
purchase.getItem().addSubItem(subItem);
reader.moveUp(); // Move back up to parent SUBITEM
continue;
}
}
return purchase;
}
@SuppressWarnings("unchecked")
public boolean canConvert(Class clazz) {
return clazz.equals(Purchase.class);
}
}
[/java]
As you can see, the key to operate a stream reader is to move it down to a child element for reading and back up to a parent element. Each time I move the reader down, it moves to an element that has not being read yet.
Conclusion
I presented a simple example where I show how to unmarshall XML into Java POJOs using HierarchicalStreamReader and XStream. The source code is attached.
I hope this tutorial was helpful to some of you,
Regards