- Introduction
- Mapping to Almost Isomorphic Tree Structures
- Structure Adjustment by XSLT
- Mapping to Tables
- Mapping to Hash Tables
- Mapping to Graph Structures
- Summary
8.2 Mapping to Almost Isomorphic Tree Structures
The most typical mapping occurs when the structure of XML documents reflects the application data structure and thus the mapping is almost isomorphic.1 We use the hypothetical purchase order document, po.xml, in Listing 8.1 as our example.
Listing 8.1 Purchase order document, chap08/isomorphic/po.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE purchaseOrder SYSTEM "PurchaseOrder.dtd"> <purchaseOrder> <customer> <name>Robert Smith</name> <customerId>788335</customerId> <address>8 Oak Avenue, New York, US</address> </customer> <comment>Hurry, my lawn is going wild!</comment> <items> <item partNum="872-AA"> <productName>Lawnmower</productName> <quantity>1</quantity> <USPrice>148.95</USPrice> <shipDate>2002-09-03</shipDate> </item> <item partNum="926-AA"> <productName>Baby Monitor</productName> <quantity>1</quantity> <USPrice>39.98</USPrice> <shipDate>2002-08-21</shipDate> </item> </items> </purchaseOrder>
Our application reads in this purchase order document, calculates the price, and generates an invoice. One natural modeling of this program is to represent each of the concepts, such as purchase order, customer, and item, as a Java object. Hence, the three classes shown in Listings 8.2, 8.3, and 8.4 are to be prepared.
Listing 8.2 PurchaseOrderclass, chap08/isomorphic/PurchaseOrder.java
public class PurchaseOrder { Customer customer; String comment; Vector items = new Vector(); ... }
Listing 8.3 Customerclass, chap08/isomorphic/Customer.java
public class Customer { String name; int customerId; String address; }
Listing 8.4 Itemclass, chap08/isomorphic/Item.java
public class Item { String partNum; String productName; int quantity; float usPrice; String shipDate; }
How can we map an input XML document into instances of these classes? For simplicity, let us assume that we have already parsed an XML document and have a DOM tree. What we need to do is recursively scan this DOM tree, and for each element that represents a concept to be represented as a Java object, generate an instance of the corresponding class. For example, upon encountering a <purchaseOrder>element during the DOM tree scan, create a new instance of the PurchaseOrderclass, as shown in Figure 8.2.
To do this, we provide a static method called unmarshal()in the class
Figure 8.2 Creating a Java object from a DOM element
PurchaseOrder(see Listing 8.5). This method takes a DOM node representing a <purchaseOrder>element as an input parameter and returns a new PurchaseOrder instance.
Listing 8.5 unmarshal()method for PurchaseOrderclass, chap08/isomorphic/ PurchaseOrder.java(continued)
static PurchaseOrder unmarshal(Element e) { PurchaseOrder po = new PurchaseOrder(); for (Node c1=e.getFirstChild(); c1!=null; c1=c1.getNextSibling()) { if (c1.getNodeType()==Node.ELEMENT_NODE) { [48] if (c1.getNodeName().equals("customer")) { [49] // <customer> subelement [50] po.setCustomer(Customer.unmarshal((Element)c1)); [52] } else if (c1.getNodeName().equals("comment")) { [53] // <comment> subelement [54] po.setComment(TypeConversion.toString(c1)); [56] } else if (c1.getNodeName().equals("items")) { [57] // <items> subelement [58] for (Node c2 = c1.getFirstChild(); [59] c2 != null; [60] c2 = c2.getNextSibling()) { [61] if (c2.getNodeType()==Node.ELEMENT_NODE) { [62] Element childElement2 = (Element)c2; [63] if (c2.getNodeName().equals("item")) { [64] po.addItem(Item.unmarshal((Element) c2)); [65] } [66] } [67] } [68] } } } return po; }
Parameter eof this method points to a <purchaseOrder>element in a DOM tree. This method first creates an instance of the PurchaseOrderclass and then scans the child nodes of the element eto fill in the fields of the PurchaseOrder instance. For example, when the method encounters a <customer>element, it creates a Customerobject by calling the static method unmarshal()of the class Customerand sets the created object to the customerfield of the PurchaseOrderobject (lines 4850).
When seeing a <comment>element, the method converts the contents of the element into a Stringobject and assigns it to the commentfield (lines 5254).
Child nodes of an <items>element are a repetition of <item>elements, so the method goes further down to its subelements and for each <item>element, the method creates an instance of the Itemclass and adds it to the itemfield of the PurchaseOrder(lines 5668). As a helper class for handling a type conversion from a DOM node to a Java primitive type, we write a simple class called TypeConversion, shown in Listing 8.6.
Listing 8.6 TypeConversionclass, chap08/isomorphic/TypeConversion.java package chap08.isomorphic; import org.w3c.dom.Node; public class TypeConversion {
package chap08.isomorphic; import org.w3c.dom.Node; public class TypeConversion { static String toString(Node n) { String content = n.getFirstChild().getNodeValue(); return content; } static int toInteger(Node n) { String content = n.getFirstChild().getNodeValue(); return Integer.parseInt(content); } static float toFloat(Node n) { String content = n.getFirstChild().getNodeValue(); return Float.parseFloat(content); } }
What about unmarshaling of the Customerclass? As we did for our PurchaseOrder class, we write a static method called unmarshal()that is to be called whenever a <customer>element is found. The implementation of this method is shown in Listing 8.7. You may notice that it has exactly the same pattern as the unmarshal() method of our PurchaseOrderclass.
Listing 8.7 unmarshal()method for Customerclass, chap08/isomorphic/ Customer.java(continued)
static Customer unmarshal(Element e) { Customer co = new Customer(); for (Node c1=e.getFirstChild(); c1!=null; c1=c1.getNextSibling()) { if (c1.getNodeType()==Node.ELEMENT_NODE) { if (c1.getNodeName().equals("name")) { // <name> subelement co.setName(TypeConversion.toString(c1)); } else if (c1.getNodeName().equals("customerId")) { // <customreId> subelement co.setCustomerId(TypeConversion.toInteger(c1)); } else if (c1.getNodeName().equals("address")) { // <address> subelement co.setAddress(TypeConversion.toString(c1)); } } } return co; }
Similarly, we can build the unmarshal()method for the class Item. In this code, shown in Listing 8.8, we also extract the value of the attribute partNum (line 57).
Listing 8.8 unmarshal()method for Itemclass, chap08/isomorphic/Item.java
(continued)
static Item unmarshal(Element e) { Item item = new Item(); [57] item.setPartNum(e.getAttribute("partNum")); for (Node c1=e.getFirstChild(); c1!=null; c1=c1.getNextSibling()) { if (c1.getNodeType()==Node.ELEMENT_NODE) { if (c1.getNodeName().equals("productName")) { // <productName> subelement item.setProductName(TypeConversion.toString(c1)); } else if (c1.getNodeName().equals("quantity")) { // <quantity> subelement item.setQuantity(TypeConversion.toInteger(c1)); } else if (c1.getNodeName().equals("USPrice")) { // <USPrice> subelement item.setUSPrice(TypeConversion.toFloat(c1)); } else if (c1.getNodeName().equals("shipDate")) { // <shipDate> subelement item.setShipDate(TypeConversion.toString(c1)); } } } return item; }
Now we are ready to convert a whole DOM tree into our application data model. We need to parse an input XML document and give the resulting DOM tree to one of the unmarshal()methods that we programmed. The main()method, shown in Listing 8.9, does exactly that.
Listing 8.9 main()method for PurchaseOrderclass, chap08/isomorphic/ PurchaseOrder.java(continued)
public static void main(String[] argv) throws Exception { if (argv.length < 1) { System.err.println("Usage: java chap08.PurchaseOrder file"); System.exit(1); } [79] DocumentBuilderFactory factory = [80] DocumentBuilderFactory.newInstance(); [81] DocumentBuilder builder = factory.newDocumentBuilder(); [82] builder.setErrorHandler(new MyErrorHandler()); [83] Document doc = builder.parse(argv[0]); Element root = doc.getDocumentElement(); if (root.getNodeName().equals("purchaseOrder")) { [87] PurchaseOrder po = unmarshal(root); Customer co = po.getCustomer(); System.out.println("************ Invoice ************** "); System.out.println("Date:"+ new java.util.Date()); System.out.println(""); System.out.println("To: "+co.getName()); System.out.println(" "+co.getAddress()); System.out.println(" Customer#:"+co.getCustomerId()); System.out.println(""); System.out.println("----------------------"); int i=0; float total = (float)0.0; for (Iterator itr=po.getItems(); itr.hasNext(); i++) { Item item = (Item)itr.next(); System.out.println("Item #"+i+" : "+item.getPartNum()+ ", Quantity="+item.getQuantity()+ ", UnitPrice="+item.getUSPrice()+ ", ShipDate="+item.getShipDate()); total += item.getUSPrice(); } System.out.println("----------------------"); System.out.println("total = $"+total); } }
Lines 7983 parse the input and obtain a DOM tree. After checking that the root element is in fact a purchaseOrderelement, we create a PurchaseOrder instance by calling the static method unmarshal()in line 87. The rest of the code is pure application logic that is responsible for generating an invoice.
What can we learn from this manual mapping? We have seen that if the mapping preserves the structural similarity between the XML document and the Java data structure, writing unmarshaling codes can be a fairly automatic task. You prepare one class for one complex element type (for example, <purchaseOrder>, <customer>, or <item>) that has a static method, unmarshal(), for scanning a DOM tree and creating a corresponding Java instance.
The question is, then, is it possible to automatically generate such mapping codes from a schema of input XML documents? The answer is yes. In Chapter 15, we describe a few of these tools.