prev table of contents next

4.3.2 Preserving Object Identity

In the previous section we have tacitly assumed that there is one and only one object with a certain identification which is readily available from the object. But what do you do if there is no such identification? Generating synthetic identifications isn't a problem, but how does this help to avoid generating full XML text for each occurrence? Should we use the equals(Object o) method for identifying objects that deserve the same synthetic identifier? The answer is that with a little extra effort you can still preserve object identity so that the same number of objects can be reconstructed when the XML data is read and unmarshalled back into memory.

Object identity can be tested by applying the operator == to any two objects. Keeping a list of marshalled elements and searching through it isn't attractive. Luckily there is a better way: we can use an instance of an IdentityHashMap (from java.util) to register marshalled elements. This map uses the object's default hash code, even if hashCode has been overridden. Also, we have to add an (artificial) identification to our objects as this is required as the xsd:ID value.

For an example we extend the schema describing orders with an additional AddressType and use this, once for a shipping address and once for a billing address.

<xsd:complexType name="AddressType">
  <xsd:sequence>
    <xsd:element name="street"  type="xsd:string"/>
    <xsd:element name="city"    type="xsd:string"/>
    <xsd:element name="country" type="xsd:string"/>
    <xsd:element name="zip"     type="xsd:int"/>
  </xsd:sequence>
  <xsd:attribute name="id" type="xsd:ID"/>
</xsd:complexType>

<xsd:complexType name="AddrOrRefType">
  <xsd:choice>
    <xsd:element name="addr"    type="AddressType"/>
    <xsd:element name="addrRef" type="xsd:IDREF"/>
  </xsd:choice>
</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="shipTo" type="AddrOrRefType"/>
    <xsd:element name="billTo" type="AddrOrRefType"/>
    <xsd:element name="items"  type="ItemType" maxOccurs="unbounded"/>
  </xsd:sequence>
</xsd:complexType>
The generated class AddrOrRefType enables us to choose between an address in full or a reference to such an XML element.of type AddressType. To see how this works, we assume that we have addresses in objects of type Address. The code below creates another XML element for AddressType from an Address object.
    Map<Address,AddressType> pojo2elem =
        new IdentityHashMap<Address,AddressType>();
    int refcount = 0;

    private String makeNextId(){
        return "a" + refcount++;
    }

    public AddrOrRefType
    makeAddrOrRefElement( ObjectFactory objFact, Address addrPojo ){
        AddrOrRefType arElem = objFact.createAddrOrRefType();
        AddressType addrElem = pojo2elem.get( addrPojo );
        if( addrElem == null ){
            // First time: generate the full XML element...
            addrElem = objFact.createAddressType();
            // ...and insert it into its parent.
            arElem.setAddr( addrElem );
            // Set the xsd:ID attribute.
            addrElem.setId( makeNextId() );
            // ...(Copy attributes from addrPojo into addrElem.)...
            // Register the object - id pair in the identity hash map.
            pojo2elem.put( addrPojo, addrElem );
        } else {
            // We've had this one before: insert its reference.
            arElem.setAddrRef( addrElem );
        }
        return arElem;
    }
Using Address objects as keys, we map these to assembled AddressType objects. Whenever we encounter a new object of class Address, we enter it into the map and generate an AddrOrRef element containing the full AddressType element. For an encore, the AddrOrRef receives the reference to the previously created AddressType element.


prev table of contents next