/* ======================================================================
The Bodington System Software License, Version 1.0
  
Copyright (c) 2001 The University of Leeds.  All rights reserved.
  
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES 
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO 
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR 
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
=========================================================

This software was originally created by the University of Leeds and may contain voluntary 
contributions from others.  For more information on the Bodington Open Source Project, please 
see http://bodington.org/

====================================================================== */

package org.bodington.database;

import org.bodington.server.*;
import java.util.Enumeration;

/**
 * All objects that will be stored by the BuildingServer system must 
 * be subclasses of this abstract class.  Some work needs to go into
 * sub classes to make persistence work but this class does some
 * useful things.
 * How to create a new persistent data class;
 * <OL><LI>Declare the class as a subclass of this class or 
 * sub class of a subclass (etcx...) of this class.  </LI>
 * <LI>Declare the data members of the class as private.</LI>
 * <LI>Declare a set of public "get and "set" methods that put
 * data into the data members of the class.  There need not be
 * a one to one match  between data members and get/set methods.
 * Typically if you declare a member as "private int type" you
 * will declare methods "public int getType()" and 
 * "public void setType( int t )".  This will allow the database
 * to get an int from storage and pass it to setType or get an
 * int from getType and put it in storage.  You could deal with 
 * lists differently.  You might have a data member
 * "private Vector types" which will contain a set of Integers but
 * this will be stored in the database as a single comma separated 
 * string.  To cope with this declare methods
 * "public String getTypeList()" and 
 * "public void setTypeList( String l )".  Implementations of these
 * methods can pack and unpack the data.
 * </LI>
 * <LI>One of the data members must be of type PrimaryKey and have
 * get/set methods as described above named like "getThingyId()".</LI>
 * <LI>The class must implement getPrimaryKey and setPrimaryKey.
 * These will simply call the get/set methods dexribed in the previous
 * point.</LI>
 * <LI>Implementations of "set" methods should call setUnsaved()
 * to indicate that there are now unsaved changes to the object.</LI>
 * <LI>If the class refers to persistent objects of other types
 * this should be done by storing a PrimaryKey for each other object,
 * NOT an actual reference.  There should be methods like 
 * "getWhatsitId()" and "setWhatsidId()" to allow the database to
 * fetch the primarykeys of these foreign references.  You can
 * also provide methods like "getWhatsit()" and "setWhatsit()".
 * The set method will get the primary key from the object passed
 * and store that, NOT a reference to the object.  The get method
 * will call a "finder" method to load the object from storage 
 * and return a reference to that object.</LI>
 * <LI>Declare static "finder" methods.  These should be similar to
 * the "finder" methods in PersistentObject but will return
 * objects of this type and will omit the class name parameter.
 * E.g. "public static MyObject findMyObject( PrimaryKey key )".
 * The finder methods can be implemented just by calling
 * the PersistentObject.findPersistentObject methods.</LI>
 * <LI>If the object keeps references to lists of other objects
 * implement loadReferencedObjects().  This method should use
 * "finder" methods to get primary keys and/or references to
 * objects.</LI>
 * <LI>If you use loadReferencedObjects() you will probably
 * have to implement save() and delete().  Implmentations of
 * these two methods should call super.save() and super.delete()
 * at some point.</LI>
 * <LI>Depending on the actual implementation of Database that is in
 * use, configuration files/records will have to be set up so the
 * database knows how to map storage onto the get/set methods.
 * (With SQLDatabase you add a record to a classes table to describe
 * the class and a record to a fields table for each field in the
 * database for this class.  The records in fields map database
 * fields onto get/set method pairs.</LI>
 * 
 * </OL>
 * 
 * @author Jon Maber
 * @version 1
 */

public abstract class PersistentObject
	{
	private boolean unsaved;
	
	/**
	 * Some basic initialisation is done here.
	 */
	public PersistentObject()
		{
		clearUnsaved();
		}

	/**
	 * All implementations must be capable of returning their 
	 * primary key.  In subclasses this is likely to be
	 * implemented like this;
	 * <PRE>
	 * public PrimaryKey getPrimaryKey()
	 * 	{
	 * 	return getMyObjectId();
	 * 	}
	 * </PRE>
	 * 
	 * @return The primary key for this object or null if this object has
	 * never been stored.
	 */
	public abstract PrimaryKey getPrimaryKey();

	/**
	 * All implmentations must be capable of receiving a primary key.
	 * Likely to be implmented as:
	 * <PRE>
	 * public void setPrimaryKey( PrimaryKey key )
	 * 	{
	 * 	setMyObjectId( key );
	 * 	}
	 * </PRE>
	 * 
	 * @param key The primary key that will identify this object in storage.
	 */
	public abstract void setPrimaryKey( PrimaryKey key );
	
	/**
	 * Implmentations may choose to overide this method.  It will be 
	 * called after an instance has been loaded from the database to 
	 * give the object a chance to fetch other data objects from the 
	 * database.  (E.g. lists of records etc.)
	 * 
	 * @exception org.bodington.server.BuildingServerException Thrown if there are database problems.
	 */
	public void loadReferencedObjects()
		throws BuildingServerException
		{
		return;
		}
		
	/**
	 * Sets the flag to indicate that changes have been made that 
	 * need saving.
	 */
	public void setUnsaved()
		{
		unsaved=true;
		}

	/**
	 * Clears the flag to indicate that there are no unsaved changes
	 *
	 */
	public void clearUnsaved()
		{
		unsaved=false;
		}
		
	/**
	 * Are there unsaved changes?
	 * 
	 * @return If there are unsaved changes returns true.
	 */
	public boolean isUnsaved()
		{
		return unsaved;
		}

	/**
	 * This method can be overridden in a subclass but the method
	 * should call super.save() at some point.
	 * 
	 * @exception org.bodington.server.BuildingServerException Thrown if there are database problems.
	 */
		
	public void save()
		throws BuildingServerException
		{
		BuildingContext context = BuildingContext.getContext();
        Database db = context.getDatabase();
		if ( getPrimaryKey() == null )
			{
			db.insertObject( this );
			}
		else
			{
			db.updateObject( this );
			}
		}

    /**
     * This method can be overridden in a subclass but the method
     * should call super.save() at some point.
     * 
     * @exception org.bodington.server.BuildingServerException Thrown from the database code.
     */

    public void delete()
        throws BuildingServerException
        {
		if ( getPrimaryKey() == null )
            throw new BuildingServerException( "Can't delete object - it has no primary key." );        
		BuildingContext context = BuildingContext.getContext();
        Database db = context.getDatabase();
		db.deleteObject( this );
        }

	/**
	 * Used to load an Object from the database.  It can
	 * load any subclass of PersistentObject.
	 * 
	 * @param primary_key The unique identifier of the requested object.
	 * @param java_class_name The class name of the requested object.
	 * @return The object if found or null if not found.  Also
	 * null if the key given was null.
	 * @exception org.bodington.server.BuildingServerException  Thrown if
	 * there are database problems.
	 */

	public static PersistentObject findPersistentObject( PrimaryKey primary_key, String java_class_name )
		throws BuildingServerException
		{
		Database database = BuildingContext.getContext().getDatabase();
		return database.findPersistentObject( primary_key, java_class_name );
		}

	/**
	 * Used to load an Object from the database.  It can
	 * load any subclass of PersistentObject.
	 * 
	 * @param index_key The unique identifier of the requested object.
	 * @param java_class_name The class name of the requested object.
	 * @return The object if found or null if not found.  Also
	 * null if the key given was null.
	 * @exception org.bodington.server.BuildingServerException  Thrown if
	 * there are database problems.
	 */

	public static PersistentObject findPersistentObject( IndexKey index_key, String java_class_name )
		throws BuildingServerException
		{
		Database database = BuildingContext.getContext().getDatabase();
		return database.findPersistentObject( index_key, java_class_name );
		}

	/**
	 * Searches for the object using a query string.
	 * 
	 * @param where_clause An SQL WHERE clause.  The programmer must know the
	 * SQL field names for the stored object.
	 * @param java_class_name The class name requested.
	 * @return The object if found or null if not found.
	 * @exception org.bodington.server.BuildingServerException  Thrown if
	 * there are database problems.  Also if more than one object matched.
	 */
		
	public static PersistentObject findPersistentObject( String where_clause, String java_class_name )
		throws BuildingServerException
		{
		Database database = BuildingContext.getContext().getDatabase();
		return database.findPersistentObject( where_clause, java_class_name );
		}

	/**
	 * Finds a set of objects.
	 * 
	 * @param where_clause An SQL WHERE clause.  The programmer must know the
	 * SQL field names for the stored object.
	 * @param java_class_name The class name requested.
	 * @return An enumeration of the objects found.
	 * @exception org.bodington.server.BuildingServerException  Thrown if
	 * there are database problems. 
	 */
		
	public static Enumeration findPersistentObjects( String where_clause, String java_class_name )
		throws BuildingServerException
		{
		Database database = BuildingContext.getContext().getDatabase();
		return database.findPersistentObjects( where_clause, null, java_class_name );
		}

	/**
	 * Finds a set of objects in given sorted order.
	 * 
	 * @param where_clause An SQL WHERE clause.  The programmer must know the
	 * SQL field names for the stored object.
	 * @param order_by_clause An SQL ORDER BY clause.
	 * @param java_class_name The class name requested.
	 * @return An enumeration of the objects found.
	 * @exception org.bodington.server.BuildingServerException  Thrown if
	 * there are database problems. 
	 */
		
	public static Enumeration findPersistentObjects( String where_clause, String order_by_clause, String java_class_name )
		throws BuildingServerException
		{
		Database database = BuildingContext.getContext().getDatabase();
		return database.findPersistentObjects( where_clause, order_by_clause, java_class_name );
		}

	/**
	 * Searches for objects but just loads their
	 * primary keys.
	 * 
	 * @param where_clause An SQL WHERE clause.  The programmer must know the
	 * SQL field names for the stored object.
	 * @param java_class_name The class name requested.
	 * @return An enumeration of the primary keys found.
	 * @exception org.bodington.server.BuildingServerException  Thrown if
	 * there are database problems. 
	 */

	public static Enumeration findPrimaryKeys( String where_clause, String java_class_name )
		throws BuildingServerException
		{
		Database database = BuildingContext.getContext().getDatabase();
		return database.findPrimaryKeys( where_clause, null, java_class_name );
		}

	/**
	 * Searches for objects but just loads their
	 * primary keys.
	 * 
	 * @param where_clause An SQL WHERE clause.  The programmer must know the
	 * SQL field names for the stored object.
	 * @param java_class_name The class name requested.
	 * @return An enumeration of the primary keys found.
	 * @exception org.bodington.server.BuildingServerException  Thrown if
	 * there are database problems. 
	 */

	public static Enumeration findPrimaryKeys( String where_clause, String order_by_clause, String java_class_name )
		throws BuildingServerException
		{
		Database database = BuildingContext.getContext().getDatabase();
		return database.findPrimaryKeys( where_clause, order_by_clause, java_class_name );
		}
	
	/**
	 * Counts a set of objects.
	 * 
	 * @param where_clause An SQL WHERE clause.  The programmer must know the
	 * SQL field names for the stored object.
	 * @param java_class_name The class name requested.
	 * @return An enumeration of the objects found.
	 * @exception org.bodington.server.BuildingServerException  Thrown if
	 * there are database problems. 
	 */
		
	public static int countPersistentObjects( String where_clause, String java_class_name )
		throws BuildingServerException
		{
		Database database = BuildingContext.getContext().getDatabase();
		return database.countPersistentObjects( where_clause, java_class_name );
		}
		
	public IndexKey[] getIndexKeys()
	    {
	    return null;
	    }
		
    /**
     * Check if the indexes key matches this object. If you use indexkeys you 
     * must implement this.
     * @param ikey The IndexKey to check against.
     * @return <code>true</code> if the supplied key matches.
     */
	public boolean matchesKey( IndexKey ikey )
	    {
	    return false;
	    }
		
	public boolean equals( Object other )
		{
		if ( !(other instanceof PersistentObject) )
			return false;
		
		PersistentObject other_object = (PersistentObject)other;
		if ( other_object.getPrimaryKey()==null )
			return false;
		if ( this.getPrimaryKey()==null )
			return false;
		return this.getPrimaryKey().equals( other_object.getPrimaryKey() );
		}
	} 
 
