/**
* The Persistent class is an abstract base class for all
* persistent objects. A persistent object is any object which
* needs to be saved to a data store. The class does not care
* what type of data store is used. All code specific to a
* particular kind of persistence is pawned off onto the
* PersistentPeer.
* @see imaginary.persist.PersistentPeer
*/
package imaginary.persist;
import imaginary.util.Observable;
import java.rmi.RemoteException;
import java.util.Hashtable;
public abstract class Persistent extends Observable
implements RemotePersistent {
/************* Static persistence state values ***********/
/**
* The object is in the same form that is stored in the data store.
*/
static public final int UNMODIFIED = 1;
/**
* The object is brand new and has not yet been saved to the data store.
*/
static public final int NEW = 2;
/**
* The object has been modified either since creation or restoring
* from the data store.
*/
static public final int MODIFIED = 4;
/**
* The object has been deleted but is still in the data store.
*/
static public final int DELETED = 8;
// The master list of objects loaded on the application server
// This list is a hashtable of hashtables. For example,
// objects.get("bank.server.Customer") returns a Hashtable of
// all customer objects.
static private Hashtable objects = new Hashtable();
/************************* Class methods ***********************/
/**
* Gives a hashtable of values taken from a data store, this class
* method will find an existing object for that data or instantiate
* a new one. Once it has created that object, it will tell the
* object to restore itself using that data.
* @param trans the Transaction object to use for any further data store
* access
* @param data the data taken from the data store for this object
* @param class_name the name of the class to instantiate for this
* set of data
* @exception imaginary.persist.PersistenceException An error occurred
* retrieving the object.
*/
static public Persistent getPersistent(Transaction trans,
Hashtable data,
String class_name)
throws PersistenceException {
Persistent p;
Hashtable h;
Integer i;
// create an instance of the specified class name
// so that we know what the ID is
try {
p = (Persistent)Class.forName(class_name).newInstance();
p.setId(data);
}
catch( ClassNotFoundException e ) {
e.printStackTrace();
return null;
}
catch( InstantiationException e ) {
e.printStackTrace();
return null;
}
catch( IllegalAccessException e ) {
e.printStackTrace();
return null;
}
// find the hashtable for this class in the objects hashtable
if( objects.containsKey(class_name) ) {
h = (Hashtable)objects.get(class_name);
}
else {
h = new Hashtable();
objects.put(class_name, h);
}
// see if this object is already in that list
i = new Integer(p.getId());
if( h.containsKey(i) ) {
return (Persistent)h.get(i);
}
else {
h.put(i, p);
// restore the new object
p.restore(trans, data);
return p;
}
}
/**
* Find or restore an object of the specified class based on an ID.
* @param trans the Transaction to use for data store access
* @param id the ID of the object to be retrieved
* @param class_name the name of the class to instantiate
* @exception imaginary.persist.PersistenceException An error occurred
* restoring the requested object.
*/
static public Persistent getPersistent(Transaction trans, int id,
String class_name)
throws PersistenceException {
Integer i = new Integer(id);
Hashtable h;
// get the hashtable of objects for that class
if( objects.containsKey(class_name) ) {
h = (Hashtable)objects.get(class_name);
}
else {
h = new Hashtable();
objects.put(class_name, h);
}
// check if the object already exists
if( h.containsKey(i) ) {
return (Persistent)h.get(i);
}
else {
Persistent p;
try {
// no instance found, load a new one
p = (Persistent)Class.forName(class_name).newInstance();
// set its ID
p.setId(id);
h.put(i, p);
// restore it from the data store
p.restore();
}
catch( ClassNotFoundException e ) {
e.printStackTrace();
return null;
}
catch( InstantiationException e ) {
e.printStackTrace();
return null;
}
catch( IllegalAccessException e ) {
e.printStackTrace();
return null;
}
return p;
}
}
/******************* Instance attributes *****************/
// the object ID
private int id = -1;
// a Lock object is just a Thread that repeatedly calls mmonitorLock()
private Lock lock = null;
// this is a bitmask of persistence states
private int modifications = Persistent.UNMODIFIED;
// indicates a save is in progress; i.e. waiting on commit or rollback
private boolean saving = false;
/*********************** Constructors ********************/
/**
* Constructor for the Persistent class. Exports the object for
* remote viewing. Persistent is an abstract class, so it cannot
* be instantiated directly.
* @exception java.rmi.RemoteException Failed to export object
*/
public Persistent() throws RemoteException {
super();
}
/******************* State check methods ****************/
/**
* Checks to see if the object will be deleted from the data store
* at the next save.
* @return true if the object has been deleted
*/
public synchronized boolean isDeleted() {
return ((modifications & Persistent.DELETED) != 0);
}
/**
* Checks to see if a lock is being held on this object.
* @return true if some object is locking this one
*/
public synchronized boolean isLocked() {
return (lock != null);
}
/**
* Checks to see if this object differs from the data store.
* @return true if the object is new, modified, or deleted
*/
public synchronized boolean isModified() {
// if any modifiers are present, it has been modified
return !(modifications == Persistent.UNMODIFIED);
}
/**
* Checks to see if this object is new and not yet in the data store.
* This will also return false if the object has been deleted.
* @return true if the object is new
*/
public synchronized boolean isNew() {
if( isDeleted() ) {
return false;
}
else {
return ((modifications & Persistent.NEW) != 0);
}
}
/**
* Checks to see if a save operation is in progress.
* @return true if a save operation is in progress
*/
public synchronized boolean isSaving() {
return saving;
}
/***************** Attribute accessors ****************/
/**
* The ID is a unique identifier for this type of object. In real
* situations, you probably cannot always use an int for an ID field.
* To keep this library simple, however, we have assumed that all ID's
* are integers.
* @return the unique identifier for this object
*/
public synchronized int getId() {
return id;
}
/**
* Classes extending Persistent must know how to pull an ID field
* from a Hashtable describing restoration values. Any such class
* should implement this method by grabbing the ID field and
* calling setId(int). Example:
* <PRE>
* public void setId(Hashtable h) {
* setId(((Integer)h.get("t_customer.cust_id")).intValue());
* }
* </PRE>
* @param h the Hashtable of values gotten from the data store on restore
* @see imaginary.persist.Persistent#restore
*/
public abstract void setId(Hashtable h);
/**
* Sets the id field for the object. Once an id is set, it cannot
* be changed.
* @param i the id to assign to the object
*/
public synchronized void setId(int i) {
if( id != -1 ) {
return;
}
id = i;
}
/**
* Modifications is a bitmask of changes which have occurred to
* an object since it was either restored or created. Unmodified
* objects have a Modifications value equal to Persistent.UNMODIFIED.
* @return the modification state of this object
* @see imaginary.persist.Persistent#UNMODIFIED
* @see imaginary.persist.Persistent#NEW
* @see imaginary.persist.Persistent#MODIFIED
* @see imaginary.persist.Persistent#DELETED
*/
public synchronized int getModifications() {
return modifications;
}
/**
* Sets the modifications bitmask to NEW
*/
public synchronized void setNew() {
modifications = Persistent.NEW;
}
/**
* Persistent prescribes that a subclass implement this method to
* provide it with an instance of the peer which will perform
* all data store access for it.
* @return the PersistentPeer for this class
* @see imaginary.persist.PersistentPeer
*/
protected abstract PersistentPeer getPersistentPeer();
/************* Transaction management methods **************/
/**
* When a save to the data store is aborted for any reason, this
* method gets called in order to give the object a chance to
* clean up. The object should be restored to its state
* <I>immediately</I> before a save was attempted. Within the
* Persistent class, that simply means changing the flag indicating
* a save is in progress. Of course, objects extending this class
* may or may not have their own clean up to do.
*/
protected synchronized void abort() throws PersistenceException {
if( !isSaving() ) { // If this object is not saving, it needs no abort
return;
}
saving = false; // stop it from saving
// keep the lock in place
}
/**
* When one or more saves have been sent to the data store successfully,
* they are committed. This method allows an object to know the
* pending save was successful and release any locks.
*/
protected synchronized void commit() throws PersistenceException {
Transaction t;
// If the object is not being saved, commit not needed
if( !isSaving() ) {
return;
}
if( lock == null ) {
throw new PersistenceException("Attempt to commit an unlocked " +
"object.");
}
// end saving
saving = false;
// release lock
lock.releaseLock(this);
lock = null;
// reset modification state
if( isDeleted() ) {
modifications = Persistent.DELETED;
}
else {
modifications = Persistent.UNMODIFIED;
}
}
/******************* Persistence operations *****************/
/**
* This method flags the object for deletion.
* @param h the client make the modification to this object
* @exception imaginary.persist.LockException Attempt to delete locked
* object by a client not holding the lock
*/
public synchronized void remove(RemoteLockHolder h) throws LockException {
// Check for a lock
if( lock != null ) {
if( h.hashCode() != lock.getHolder().hashCode() ) {
throw new LockException("Illegal attempt to delete object " +
"without a lock.");
}
}
// Create the lock if it does not exist
if( lock == null ) {
lock = Lock.createLock(h, this);
}
// Mark the object deleted
modifications |= Persistent.DELETED;
setChanged();
}
/**
* This version of restore() is called for a one-off restore. In
* other words, you know the object ID and you want to restore just
* this object.
* @exception imaginary.persist.PersistenceException An error occured
* accessing the data store.
*/
protected synchronized void restore() throws PersistenceException {
Transaction t = Transaction.getTransaction();
t.restore(this);
}
/**
* Given a set of query data, restore this object.
* @param data the query data to use to restore this object
* @exception imaginary.persist.PersistenceException An error occurred
* accessing the data store.
*/
protected final synchronized void restore(Hashtable data)
throws PersistenceException {
Transaction t = Transaction.getTransaction();
t.restore(this, data);
}
/**
* Restore this object using only its ID for the query.
* @param t the Transaction to use to access the data store
* @exception imaginary.persist.PersistenceException An error occurred
* accessing the data store.
*/
public void restore(Transaction t) throws PersistenceException {
getPersistentPeer().restore(this, t);
}
/**
* This object's peer will go to the data store and grab all of the
* attributes for this object. Once it has those values, it will shove
* them into a Hashtable and pass them to this method. Objects extending
* the Persistent class should therefore implement this method so
* that it takes the values out of the Hashtable and assigns them to the
* appropriate object attributes.
* @param t the Transaction used for the restore
* @param data the Hashtable of object data
* @exception imaginary.persist.PersistenceException An error occured
* access the data store.
*/
public abstract void restore(Transaction t, Hashtable data)
throws PersistenceException;
/**
* This method flags the object as saving and then tells its
* peer to perform the actual save.
* @exception imaginary.persist.PersistenceException An error occured
* access the data store
*/
protected synchronized void save() throws PersistenceException {
PersistentPeer peer;
if( lock == null ) {
throw new PersistenceException("Attempt to save an unlocked: " +
"object.");
}
peer = getPersistentPeer();
// flag the object as saving
saving = true;
// determine what sort of operation is required and tell the
// peer to do it
if( isDeleted() ) {
peer.remove(this, lock.getTransaction());
}
else if( isNew() ) {
peer.insert(this, lock.getTransaction());
}
else {
peer.update(this, lock.getTransaction());
}
}
/******************* Locking operations *****************/
/**
* For any number of reasons, a client holding a lock might
* unexpectedly lose that lock. The most common reason is simply
* a network failure between client and server. In that event,
* we want to release the lock and restore the object to its
* unmodified state.
*/
protected synchronized void loseLock() {
lock = null;
modifications = Persistent.UNMODIFIED;
}
/**
* The lock has a thread that simply triggers this method
* every now and then. If the criteria for maintaining a lock are
* still in force, then everything is ok. Otherwise, a LockException
* is thrown. The only criteria for this lock is that the
* client holding the lock is still accessible. You could add
* timeouts or whatever you like.
* @exception imaginary.persist.LockException The lock should be lost
*/
protected synchronized void monitorLock() throws LockException {
// If we cared, we might add a last touched check in here
// to have the lock timeout
// If so, we would throw a LockException
notifyObservers();
}
/**
* Whenever an attribute in this object changes from its initial state,
* this method should be called in order to change the object's state.
* If an attempt is made to modify a locked by object a RemoteLockHolder
* that does not hold the lock, then a LockException gets thrown.
* @param h the client making the modifications
* @exception imaginary.persist.LockException Attempt to modify locked
* object by a Transaction not holding the lock.
*/
protected synchronized void modify(RemoteLockHolder h)
throws LockException {
// Someone is doing something they should not!
if( lock != null ) {
if( h.hashCode() != lock.getHolder().hashCode() ) {
throw new LockException("Illegal attempt to modify " +
"object without a lock.");
}
}
// First modification!
if( lock == null ) {
lock = Lock.createLock(h, this);
}
// Add Persistent.MODIFIED to the bitmask
modifications |= Persistent.MODIFIED;
setChanged();
}
}
|