001/* ----------------------------------------------------------------------------
002   The Kiwi Toolkit - A Java Class Library
003   Copyright (C) 1998-2004 Mark A. Lindner
004
005   This library is free software; you can redistribute it and/or
006   modify it under the terms of the GNU General Public License as
007   published by the Free Software Foundation; either version 2 of the
008   License, or (at your option) any later version.
009
010   This library is distributed in the hope that it will be useful,
011   but WITHOUT ANY WARRANTY; without even the implied warranty of
012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013   General Public License for more details.
014
015   You should have received a copy of the GNU General Public License
016   along with this library; if not, write to the Free Software
017   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018   02111-1307, USA.
019 
020   The author may be contacted at: mark_a_lindner@yahoo.com
021   ----------------------------------------------------------------------------
022   $Log: PersistentObjectPool.java,v $
023   Revision 1.6  2004/05/05 21:37:04  markl
024   comment block updates
025
026   Revision 1.5  2003/01/19 09:35:41  markl
027   Javadoc & comment header updates.
028
029   Revision 1.4  2001/03/12 05:58:46  markl
030   Javadoc cleanup.
031
032   Revision 1.3  2001/03/12 01:17:30  markl
033   Source code cleanup.
034
035   Revision 1.2  1999/08/03 04:49:57  markl
036   Removed debug statements.
037
038   Revision 1.1  1999/02/28 01:21:43  markl
039   Initial revision
040   ----------------------------------------------------------------------------
041*/
042
043package kiwi.db;
044
045import java.sql.*;
046import java.lang.reflect.*;
047import java.util.*;
048
049import kiwi.util.*;
050
051/** This class is a fairly simple implementation of a caching mechanism for
052  * <code>PersistentObject</code>s. An application generally uses some sort of
053  * persistent store to save its data; this caching mechanism is meant to be
054  * used with those applications that use a relational database (RDMBS) as
055  * the perssitent store.
056  * <p>
057  * A problem domain object as represented by a Java class may have its state
058  * persisted in one or more rows in one or more tables in the database. The
059  * operations of writing an object's state to the RDBMS or reading it back
060  * from the RDBMS may be costly, and therefore it should only be done when
061  * absolutely necessary.
062  * <p>
063  * If a Java class needs access to a persistent object and that object is
064  * already in memory, perhaps as the result of a restore operation by an
065  * unrelated (and hence inaccessible) class, the class would be forced to load
066  * the object again, creating a second instance of it in memory. This can be
067  * very wasteful if it happens frequently. To alleviate this problem, a cache
068  * such as <code>PersistentObjectPool</code> maintains references to all
069  * <code>PersistentObject</code>s that are currently in memory. Whenever an
070  * object is retrieved from the pool, it is <i>timestamped</i>. A
071  * <code>PersistentObject</code> has a <i>ttl</i> (time to live) that
072  * specifies how long it can remain in memory before it is discarded. If the
073  * object ages past its <i>ttl</i> without being retrieved from the pool, it
074  * is removed from the pool. A subsequent retrieval of the object from the
075  * pool will cause it to be reloaded from the persistent store.
076  * <p>
077  * Even though an object has expired from the pool, other classes may still
078  * have references to it. Those classes may continue to access (and modify)
079  * the object, even though the object is not in memory as far as the pool is
080  * concerned. In general this should not be a problem, but it will be possible
081  * to alleviate it in Java 1.2 through the use of soft references.
082  * <p>
083  * If a <code>PersistentObject</code> is written to the persistent store while
084  * it is still in the pool, retrievals of that object from the pool will
085  * return the old copy of the object unless the old copy expires. To prevent
086  * this problem, the object should be explicitly expired when it is stored.
087  * See the documentation for <code>PersistentObject</code> for more
088  * information.
089  *
090  * @see kiwi.db.PersistentObject
091  *
092  * @author Mark Lindner
093  */
094
095public class PersistentObjectPool
096  {
097  private Hashtable pool;
098  private Object argList[] = new Object[1];
099  private Class emptyTypeList[] = new Class[0];
100  private Object emptyArgList[] = new Object[0];
101  private Class idTypeList[] = { int.class };
102  private static PersistentObjectPool instance = new PersistentObjectPool();
103  private Thread gc;
104
105  /* Private constructor. */
106  
107  private PersistentObjectPool()
108    {
109    pool = new Hashtable();
110
111    gc = new GarbageCollector();
112    gc.start();
113    }
114
115  /** Get a reference to the <code>PersistentObjectPool</code> singleton.
116    */
117  
118  public static PersistentObjectPool getInstance()
119    {
120    return(instance);
121    }
122
123  /** Retrieve an object from the pool. If the object is not already in the
124    * pool, it is retrieved from the persistent store.
125    *
126    * @param id The ID of the object.
127    * @param type The type of the object.
128    * @exception SQLException if the operation failed.
129    * @return The object on success, or <code>null</code> if the object could
130    * not be found.
131    */
132  
133  public PersistentObject getObject(int id, Class type) throws SQLException
134    {
135    PersistentObject o = null;
136    Integer key = new Integer(id);
137
138    synchronized(pool)
139      {
140      o = (PersistentObject)pool.get(key);
141      if(o == null)
142        {
143        o = loadObject(id, type);
144        if(o != null)
145          pool.put(key, o);
146        }
147      }
148
149    if(o != null)
150      o.timestamp();
151    
152    return(o);
153    }
154
155  /** Load a persistent object from the persistent store. Reflection is used to
156   * locate the <code>restore()</code> method of the specified class. An
157   * instance of the class is then created with the specified ID as an
158   * argument, and then the object is restored via a call to the
159   * <code>restore()</code> method.
160   *
161   * @param id The ID of the object to load.
162   * @param type The type of the object that is being loaded.
163   * @exception java.sql.SQLException If the operation failed.
164   * @return The loaded object.
165   */
166  
167  protected PersistentObject loadObject(int id, Class type) throws SQLException
168    {
169    PersistentObject p;
170    Constructor idConstructor = null;
171    Method loadMethod = null;
172
173    try
174      {
175      idConstructor = type.getConstructor(idTypeList);
176      loadMethod = type.getMethod("restore", emptyTypeList);
177      }
178    catch(Exception ex)
179      {
180      ex.printStackTrace();
181      }
182    
183    try
184      {
185      argList[0] = new Integer(id);
186      p = (PersistentObject)idConstructor.newInstance(argList);
187      loadMethod.invoke(p, emptyArgList);
188      }
189    catch(InvocationTargetException ex)
190      {
191      Throwable te = ex.getTargetException();
192      if(te instanceof SQLException)
193        throw((SQLException)te);
194      else
195        {
196        te.printStackTrace();
197        p = null;
198        }
199      }
200    catch(Exception ex)
201      {
202      ex.printStackTrace();
203      p = null;
204      }
205
206    return(p);
207    }
208
209  /** Remove all objects from the pool that have expired by the specified time.
210    * This method can be called directly, but it is called periodically by the
211    * internal pool garbage collection thread.
212    *
213    * @param now The current time.
214    */
215
216  public void expireObjects(long now)
217    {
218    synchronized(pool)
219      {
220      Enumeration e = pool.keys();
221      while(e.hasMoreElements())
222        {
223        Integer key = (Integer)e.nextElement();
224        
225        PersistentObject o = (PersistentObject)pool.get(key);
226        if(o.isExpired(now))
227          {
228          pool.remove(key);
229          // System.out.println("Dumping " + o + " from pool.");
230          }
231        }
232      }
233    }
234
235  /* Garbage collector for expiring objects. */
236
237  private class GarbageCollector extends Thread
238    {
239    public void run()
240      {
241      for(;;)
242        {
243        // sleep
244        KiwiUtils.sleep(120);
245        
246        // work
247        // System.out.println("Expiring objects...");
248        expireObjects(System.currentTimeMillis());
249        }
250      }
251    }
252  
253  }
254
255/* end of source file */