body { margin:0; padding:0; font-family:times; font-size:9pt; } p { margin-top:5; margin-bottom:5; font-weight:plain; font-family:serif; font-size:9pt; } h1 { margin-top:5; font-size:16pt; font-weight:bold; text-align:center; font-family:sans-serif; } h2 { margin-top:20; font-size:10pt; font-weight:bold; text-align:left; font-family:sans-serif; } pre { color:#993333; font-size:9pt; } code { color:#993333; } .section { text-align:justify; padding:5; }
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.
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.
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.
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.
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.