站内搜索: 请输入搜索关键词
当前页面: 图书首页 > XML and Java: Developing Web Applications, Second Edition

XML and Java: Developing Web Applications, Second Edition

[ directory ] Previous Section Next Section

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.

[1] The data binding tools that we describe in Chapter 15 are most useful in such cases.

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 PurchaseOrder class, chap08/isomorphic/PurchaseOrder.java
public class PurchaseOrder {

   Customer customer;
   String comment;
   Vector items = new Vector();
  ...
   }
Listing 8.3 Customer class, chap08/isomorphic/Customer.java
public class Customer {

   String name;
   int customerId;
   String address;
  ...
   }
Listing 8.4 Item class, 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 PurchaseOrder class, as shown in Figure 8.2.

Figure 8.2. Creating a Java object from a DOM element

graphics/08fig02.gif

To do this, we provide a static method called unmarshal() in the class 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 PurchaseOrder class, chap08/isomorphic/PurchaseOrder.java
    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 e of this method points to a <purchaseOrder> element in a DOM tree. This method first creates an instance of the PurchaseOrder class and then scans the child nodes of the element e to fill in the fields of the PurchaseOrder instance. For example, when the method encounters a <customer> element, it creates a Customer object by calling the static method unmarshal() of the class Customer and sets the created object to the customer field of the PurchaseOrder object (lines 48?0).

When seeing a <comment> element, the method converts the contents of the element into a String object and assigns it to the comment field (lines 52?4).

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 Item class and adds it to the item field of the PurchaseOrder (lines 56?8). 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 TypeConversion class, chap08/isomorphic/TypeConversion.java
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 Customer class? 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 PurchaseOrder class.

Listing 8.7 unmarshal() method for Customer class, chap08/isomorphic/ Customer.java
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 Item class, chap08/isomorphic/Item.java
     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 PurchaseOrder class, chap08/isomorphic/ PurchaseOrder.java
    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 79?3 parse the input and obtain a DOM tree. After checking that the root element is in fact a purchaseOrder element, 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.

    [ directory ] Previous Section Next Section