prev table of contents next

4.3 Assembling Data with Links (ID, IDREF)

4.3.1 One Element per Identification

It is not unusual that the data that is to be represented as XML contains elements repeatedly that are either identical or equal. You may, of course, decide to emit each of these occurrences in full, but this has obvious disadvantages:

  1. It increases the volume of the XML text.
  2. There will be duplicated objects when the unmarshalled data is transferred into application objects.
The XML Schema language provides the schema data types xsd:ID and xsd:IDREF, which we've already discussed in the section
Referring to Another XML Element. Recalling briefly that a key element or attribute with type xsd:ID has to be added to the element that is to be referenced from elsewhere and that a simple element or attribute with type xsd:IDREF is used in place of occurrences of the full element, we'll proceed to discuss the techniques for assembling a document tree where elements are linked in this way.

Our example is a variation of the order data. Here, an order may either contain a full customer XML element, or a reference to a customer element that is in some other place.

<xsd:complexType name="CustomerType">
  <xsd:sequence>
    <xsd:element name="id"   type="xsd:ID"/>
    <xsd:element name="name" type="xsd:string"/>
  </xsd:sequence>
</xsd:complexType>

<xsd:complexType name="OrderType">
  <xsd:sequence>
    <xsd:choice>
      <xsd:element name="customer" type="CustomerType"/>
      <xsd:element name="custref"  type="xsd:IDREF"/>
    </xsd:choice>
    <xsd:element name="items" type="ItemType" maxOccurs="unbounded"/>
  </xsd:sequence>
</xsd:complexType>
The Java code for OrderType deserves a quick inspection. We now have getters and setters for a CustomerType, i.e., the full, inline customer element, or for a customer reference. The Java type JAXB uses for the reference is plain Object, even though we'll only use objects of type CustomerType here, too.
public class OrderType {

    protected CustomerType customer;
    protected Object custref;
    protected List items;

    public CustomerType getCustomer() {
        return customer;
    }

    public void setCustomer(CustomerType value) {
        this.customer = value;
    }

    public Object getCustref() {
        return custref;
    }

    public void setCustref(Object value) {
        this.custref = value;
    }

    public List getItems() {
        if (items == null) {
            items = new ArrayList();
        }
        return this.items;
    }
}

The XML element may contain full elements and references in two slightly different arrangements. The first possibility replaces all occurrences with references. This means that the full elements must be added separately, and in a way that is not to be confused with the actual document. The other option is to leave the first occurrence of a specific element in place and replace all duplicates. Notice that "first occurrence" is not necessarily the foremost element in the final XML text. Both ways you'll have to keep track of the association between keys and element references.

Continuing our example, where we'll use the second method, we'll look at a method that adds the customer to an order.
private ObjectFactory oFact = new ObjectFactory();
Map<String,CustomerType> id2cust = new HashMap<String,CustomerType>();

public void addCust( OrderType order, String custId, String custName ){
    CustomerType cust = id2cust.get( custId );
    if( cust == null ){      
        // Create and insert customer.
        cust = oFact.createCustomerType();
        order.setCustomer( cust );
        // complete customer
        cust.setId( custId );
        cust.setName( custName );
        // save in map
        id2cust.put( custId, cust );
    } else {
        order.setCustref( cust );
    }
}
If the lookup in the mapping of customer ids to customer elements returns null, then we meet a customer for the first time. We create a CustomerType object, and make sure that the assembly of this element includes a call to the setter for the key element, i.e., the "id" string. This key is also used as a key in the map, where we keep that element for future reference. If the customer lookup returns an object, we simply use the object reference value of the full element as an argument to the alternative setter for the custref element. JAXB takes care of generating the XML text representing the reference. Here is a look into a folder containing two orders to the same customer.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<folder>
    <orders>
        <customer>
            <id>c12789</id>
            <name>Smith</name>
        </customer>
        <items>
            <id>12</id>
            <quantity>1</quantity>
        </items>
        <items>
            <id>24</id>
            <quantity>100</quantity>
        </items>
    </orders>
    <orders>
        <custref>c12789</custref>
        <items>
            <id>35</id>
            <quantity>10</quantity>
        </items>
    </orders>
</folder>
Don't be confused when you look at the generated XML code and detect that the value in the "custref" element is nothing but a string. Memory addresses - the convenient material for references - aren't useful in an XML text file. JAXB, however, hides this as long as possible by letting you handle references implemented as addresses, changing them magically to the corresponding string values.


prev table of contents next