FileDocCategorySizeDatePackage
Persistent.javaAPI DocExample18684Sat Feb 01 07:06:48 GMT 1997imaginary.persist

Persistent.java

/**
 * 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();
    }
}