We have seen that the Java library contains parsers that create tree-like data structures from files containing XML documents.
The nodes of this tree are Java objects, instances of sub-classes of Java's dom.Node class.
This tree constitutes the document object model of the XML document.
It is also possible to encode or serialize a DOM tree into an XML document. This is done using Java's XMLEncoder class.
See TestEncoder.java for a demonstration.
On the other hand, object-oriented programs typically process models not of XML documents, but of businesses. We often need to create business objects out of document objects:
Recall the business class model developed for ABC, our mythical benefits and compensation framework:
Here's an implementation:
A business object model would be an instance of this business class model. It might include an instance of the Organization class containing several instances of the Member class. These instances might contain several instances of the Dependant class as spouses or children.
Recall that the OrgML document org0.xml also described the members and dependants of some organization.
Using DOMUtils.getDocument() we can convert this file into a document object model:
Document doc = DOMUtils.getDocument("org1.xml");
This object (which is actually the root of a parse tree) is an object, but this object represents the structure of the document, not the business.
How do we convert DOMs to BOMs?
The OrgUnmarshal class contains methods for converting organization, member and dependant elements into organization, member and dependant objects:
public class OrgUnmarshaller {
public Member unmarshalMember(Element
node) {
if
(!node.getNodeName().equals("member")) return null;
Member result;
// create & init result
return result;
}
public Dependant
unmarshalDependant(Element node) {
if
(!node.getNodeName().equals("dependant")) return null;
Dependant result;
// create & init result
return result;
}
public Organization
unmarshalOrganization(Element node) {
if
(!node.getNodeName().equals("org")) return null;
Organization result;
// create & init result
return result;
}
}
Here's a simple test harness:
public class TestOrg {
public static void main(String[] args)
{
try {
Element root =
DOMUtils.getRoot("org1.xml");
OrgUnmarshaller oum = new
OrgUnmarshaller();
Organization org =
oum.unmarshalOrganization(root);
System.out.println(org);
}
catch (Exception e) {
System.out.println(e);
}
}
}
One might imagine that the "create & init result" sections of the unmarshallers would be straight forward: Create the needed object, then use the attributes and content of the corresponding DOM element to initialize the fields. This almost works. The problem is that the DOM elements contain IDREFs. These are references to other DOM elements in the same file by their ID attributes.
For example, the spouse and children of a member are IDREFs:
<member
id="p1" gender="male">
<lastName>Simpson</lastName>
<firstName>Homer</firstName>
<dob>1952-07-04</dob>
<spouse id="p2"/>
<child id="p3"/>
<child id="p4"/>
<child id="p5"/>
</member>
Also, the sponsor of a dependant is an IDREF:
<dependant
id="p2" gender="female">
<lastName>Simpson</lastName>
<firstName>Marge</firstName>
<dob>1955-10-20</dob>
<sponsor id="p1"/>
</dependant>
In an object model IDREFs should translate into pointers (address references) to the corresponding object. But how will we translate IDs to pointers?
The trick is to maintain a table that associates IDs to pointers. Recall that IDs are simply unique strings. Since pointers are either pointers to Member or Dependant objects, we can generalize and say that our table maintains associations between IDs and pointers to Person objects. Here's the declaration:
private Map<String, Person> idTable = new Hashtable<String, Person>();
The unmarshallers begin by using the node's id to search the id table. If no corresponding object exists, one is created and placed in the id table. Then the content and attributes of the node are used to initialize the object:
public Member
unmarshalMember(Element node) {
if
(!node.getNodeName().equals("member")) return null;
String id =
node.getAttribute("id");
Member result =
(Member)idTable.get(id);
if (result == null) {
result = new Member();
idTable.put(id, result);
result.setId(id);
}
setPersonFields(result, node);
// etc.
return result;
}
public Dependant
unmarshalDependant(Element node) {
if
(!node.getNodeName().equals("dependant")) return null;
String id =
node.getAttribute("id");
Dependant result =
(Dependant)idTable.get(id);
if (result == null) {
result = new Dependant();
idTable.put(id, result);
result.setId(id);
}
setPersonFields(result, node);
// etc.
return result;
}
All that remains is to use the IDREFs to initialize the sponsor, spouse, and children fields.
For example, to initialize the sponsor of a dependant, we use the IDREF to search the ID table. If the search fails, we create a sponsoring Member object, put him in the id table (where, presumably, he will be initialized later, and set the sponsor field to the address of this object:
Element sponsor =
(Element)(node.getElementsByTagName("sponsor").item(0));
String sponsorID =
sponsor.getAttribute("id");
Member member =
(Member)idTable.get(sponsorID);
if (member == null) {
member = new Member();
idTable.put(sponsorID, member);
member.setId(sponsorID);
}
result.setSponsor(member);
The complete implementation can be found in: