Convert XML into POJO using HierarchicalStreamReader

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:

<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>

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:

XStream xstream = new XStream(new DomDriver());
xstream.alias("PURCHASE", Purchase.class);
xstream.registerConverter(new PurchaseConverter());
Purchase purchase = (Purchase) xstream.fromXML(xmlContent);

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:

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);
	}

}

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

Right Tool For the Right Job

I wanted to start a discussion what is the better choice for an application server when building an enterprise J2E application? Should one go for a Tomcat + Spring combination which provide light-weight simplified alternative to JEE container or should more heavy weight app server like JBoss be considered?

I have found some resources on Stackoverflow that provided me enough information to think about:

  1. What are benefits of JBoss AS-based application architecture?
  2. When to ditch a J2EE container (i.e. JBoss) for straight Tomcat
  3. Should I go with Tomcat or a full J2EE container?

Installing Ubuntu 10.04.1 on Dell laptop

I got a new Dell Latitude e6410 from work. After trying Windows it for a few days, I decided to get back to Ubuntu, since I really prefer developing on Linux.

Unfortunately, after booting from live CD my screen started showing me colorful snow and flickering. The moment I select the language and choose to “install Ubuntu”, the screen goes “blah”.

After doing some research and trying several solutions suggested on Ubuntu forum, one of them suggested playing with various booting options (nomodeset,vga=771, etc.),  still I had no luck. After some more reading, I saw that some people that complain about similar problem, have the same graphics card as me:  Nvidia NVS 3100M 512MB.

Martin Wildam did an interesting review of Ubuntu 10.04 in his blog post Ubuntu 10.04 Experiences. So it is known that Linux distros can have problems with various GXF cards …

At the moment, I still haven’t resolved the problem, still looking for solutions …