Building A GPO Based Data Model

Author: Martyn Cutcher

email martyn@cutthecrap.biz

website www.cutthecrap.biz

The Generic Persistent Object Model (from now referred to as GPO) is the basis for most of the Cut The Crap software.

Whilst it is straightforward to use the primitive GPO objects such as GPOMap to store your data, it is usually a good idea to define java interfaces and classes to encapsulate the data model.

Its Easy!

Yes, its pretty straightforward, and a few examples should demonstrate this.

Elements from the Microsoft "Northwind" example system will be used to explain the patterns that can be used.

The Contact

Suppose we have a "Contact" object we would like to represent. The "Contact" may have properties such as "Name", "Address", "EMail" etc. Here is an interface that makes sense:

public interface IContact {
  public void setName(String name);
  public String getName();
  public void setAddress(String address);
  public String getAddress();
  public void setEMail(String email);
  public String getEMail();
}

The following class definition provides a complete GPO implementation:

public class Contact extends GPOMap implements IContact {
  static final String m_name = "contact/name";
  static final String m_address = "contact/address";
  static final String m_email = "contact/email";

Note that these strings will have "package" visibility

  public Contact() {}
	
  public Contact(IObjectManager om) {
    super(om);
  }

  public void setName(String name) {
  	set(m_name, name);
  }
  public String getName() {
  	return getString(m_name);
  }
  public void setAddress(String address) {
  	set(m_address, address);
  }
  public String getAddress() {
  	return getString(m_address);
  }
  public void setEMail(String email) {
  	set(m_email, email);
  }
  public String getEMail() {
  	return getString(m_email);
  }
}

The class could be used as follows:

IContact contact = new Contact(om);
contact.setName("A Contact");

Such an object, once created will be stored persistently by the Object Manager used to create it. But retrieving it is more of a problem, since there are no "global" object lists that can be searched.

The System Object

When deriving GPO-based systems it is worth considering how you would access the objects. A "root" object definition is the way to go here, consider the following "root" object interface:

public interface Northwind {
  public IContact createContact(String name);
  public Iterator getContacts();
  public IContact getContactByName(String name);
}

We have slipped in a couple of extras here. Rather than simply creating a Contact object, we have declared that a "name" must be provided. We have also added a method that will allow the retrieval of a Contact using a "name" value.

Here is the implementation:

public class Northwind extends GPOMap implements INorthwind {
  public Northwind() {}
  public Northwind(IObjectManager om) {
    super(om);
  }
  
  public IContact createContact(String name) {
    IContact contact = new Contact(getObjectManager());
    
    contact.setName(name);

We will define a new property on Contact to refer to the "root" object

    contact.setNorthwind(this);
    
    return contact;
  }
  

We use the new Contact.m_northwind property to define the set of Contacts that refer to it.

  public Iterator getContacts() {
    return getLinkSet(Contact.m_northwind).iterator();
  }

We register a classifier (on first creation of a "Northwind" object) to "classify" the set of "Contacts" using their "Name" property.

  protected void buildNew() {
    getObjectManager().registerClassifier(Contact.m_northwind, Contact.m_name);
  }

The classifier is used to retrieve the referenced Contact.

  public IContact getContactByName(String name) {
    ILinkSet ls = getLinkSet(Contact.m_northwind);
    return (IContact) ls.getClassifier(Contact.m_name).getValue(name);
  }
}

With this implementation we now have a "system" for creating and retrieving Contacts given a "root" Northwind object.

But how do we get hold of the Northwind object in the first place.

The GPO ObjectManager provides methods to remember and recall specific values. These methods can be used by a specialized OMClient implementation.

OMClient is an "initialization" object that unsurprisingly helps in the initialization of the system, for example:

OMClient client = new OMClient();
IObjectManager om = client.getObjectManager();

One of the methods the OMClient provides is a getDefaultRoot method. The default implementation of this returns the "Name Manager" object that "remembers" and "recalls" objects for the ObjectManager

OMClient derived classes should override getDefaultRoot to return the true "root" object, but should also define another type specific method, for example:

public class NorthwindClient extends OMClient {
  static final String m_northwind = "northwind/root";
	
  public NorthwindClient() {}
  public NorthwindClient(String xml) {
    super(xml);
  }
  
  INorthwind getNorthwind() {
    INorthwind root = (INorthwind) getObjectManager().recall(m_northwind);
  	
    if (root == null) {
      root = new Northwind(getObjectManager());
  	  
      getObjectManager().remember(m_northwind, root);
    }
  	
    return root;
  }
  
  IMetaSpecSrc getDefaultRoot() {
    return (IMetaSpecSrc) getNorthwind();
  }
}

With the definition of NorthwindClient we have closed all the loops to help us define a consistent system. We can now write:

NorthwindClient client = new NorthwindClient();

INorthwind nw = client.getNorthwind();

IContact contact = nw.createContact("Some Contact");
contact.setEmail("someone@someplace.com");

Everything else should just be more of the same.

..and then

After some thought you will realise how "automatic" these patterns are. This was the inspiration behind "The Alchemist" which will generate all the java code required to implement a data model described in some xml description file. Checkout the paper on The Alchemist for more information.