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 */