// Copyright (c) 2000 Dustin Sallings <dustin@spy.net> package net.spy.pool; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import net.spy.SpyObject; import net.spy.SpyThread; import net.spy.util.SpyConfig; import net.spy.util.TimeStampedHashMap; /** * ObjectPool is the entry point for all object pooling facilities in * net.spy.pool.*. ObjectPools have a shared reference to a pool, so there * is exactly one set of pools per ClassLoader. This can be very useful in * consolidating applications' pools into one. * <p> * Pools are referenced by name, so as long as two pools have two different * names, they will be used independently. * <p> * When creating a pool, you must have a PoolFiller that will populate the * pool with objects when it needs them. * <p> * The following is an example demonstrating how to instantiate a JDBC pool * using JDBCPoolFiller: * <pre> * SpyConfig conf=new SpyConfig("pool.conf"); * ObjectPool op=new ObjectPool(conf); * JDBCPoolFiller pf=new JDBCPoolFiller("db", conf); * op.createPool("db", pf); * </pre> */ public class ObjectPool extends SpyObject { // toString buffer length private static final int TOSTRING_LEN=256; // Number of cleans to run private static final int NUM_CLEANS=6; private static final int TIME_BETWEEN_CLEANS=300000; // This is static so we can check up on it. private static ObjectPoolCleaner cleaner=null; // This is static because we want everyone to see the same pools, of // course. private static TimeStampedHashMap<String, PoolContainer> pools=null; public ObjectPool(SpyConfig conf) { super(); initialize(); } /** * Create a new object pool. * * @param name The name of the object pool. * @param pf The PoolFiller object that will be used to create new * objects within the pool. * * @exception PoolException when bad things happen */ public void createPool(String name, PoolFiller pf) throws PoolException { synchronized(pools) { // Make sure we don't already have a this pool if(hasPool(name)) { throw new PoolException("There's already a pool called " + name); } // Grab a PoolContainer PoolContainer pc=new PoolContainer(name, pf); // add it to our pool list pools.put(name, pc); } } /** * Destory a pool. * * @param name The pool to destroy. * @exception PoolException if there's a problem removing the pool * @exception NoSuchPoolException if the pool we want to remove doesn't * exist */ public void destroyPool(String name) throws PoolException { synchronized (pools) { getPool(name); pools.remove(name); } } /** * Find out if the ObjectPool contains the named pool. * * @param name the name of the pool we're looking for */ public boolean hasPool(String name) { boolean ret=false; synchronized (pools) { ret=pools.containsKey(name); } return(ret); } /** * Get an object from a pool. * * @param name The pool from which we'll get our object. * * @return a PooledObject object. * * @exception PoolException if it can't get an object * @exception NoSuchPoolException if there isn't a pool by that name */ public PooledObject getObject(String name) throws PoolException { PooledObject ret=null; PoolContainer pc=null; checkCleaner(); synchronized (pools) { pc=getPool(name); } ret=pc.getObject(); return(ret); } /** * Get a count of the number of object pools. */ public int numPools() { int rv=0; synchronized (pools) { rv=pools.size(); } return(rv); } /** * Dump out the object pools. */ @Override public String toString() { StringBuilder out=new StringBuilder(TOSTRING_LEN); ArrayList<PoolContainer> a=new ArrayList<PoolContainer>(); synchronized (pools) { for(PoolContainer pc : pools.values()) { a.add(pc); } } // This is broken out to get out of the lock fast... for(PoolContainer pc : a) { out.append(pc); } return(out.toString()); } /** * Prune the object pools. This method requests that each individual * pool prune itself, removing unusable or unnecessary PoolAbles. * * @exception PoolException if something bad happens */ public void prune() throws PoolException { ArrayList<PoolContainer> a=new ArrayList<PoolContainer>(pools.size()); // Clean up any pools that are empty synchronized (pools) { for(Iterator<PoolContainer> i=pools.values().iterator(); i.hasNext();) { PoolContainer pc=i.next(); // If it's empty, remove it. if(pc.totalObjects()==0) { // Remove the pool from our collection of pools i.remove(); } else { a.add(pc); } } } // A second loop (out of the synchronized block) to ask each individual // pool to clean itself. for(PoolContainer pc : a) { pc.prune(); } } private static synchronized PoolContainer getPool(String name) throws PoolException { PoolContainer ret=null; synchronized (pools) { ret=pools.get(name); if(ret==null) { throw new NoSuchPoolException(name); } } return(ret); } private void initialize() { // Do we have a pool? synchronized(ObjectPool.class) { if(pools==null) { pools=new TimeStampedHashMap<String, PoolContainer>(); } } checkCleaner(); } // Make sure the cleaner is doing its job. private void checkCleaner() { synchronized(ObjectPool.class) { if(cleaner==null || (!cleaner.isAlive())) { cleaner=new ObjectPoolCleaner(this); } } } // This is a private class that keeps the pool clean. private static class ObjectPoolCleaner extends SpyThread { // The object pool reference we'll be cleaning. private ObjectPool op=null; // How many times we've cleaned so far. private int numCleans=0; // Last time we cleaned. private Date lastClean=null; // Create (and start) the ObjectPoolCleaner. public ObjectPoolCleaner(ObjectPool o) { super(); this.op=o; setDaemon(true); setName("ObjectPoolCleaner"); start(); } // Look like a normal thread, but report number of times the thing's // cleaned. @Override public String toString() { StringBuilder sb=new StringBuilder(TOSTRING_LEN); sb.append(super.toString()); sb.append(" - "); sb.append(numCleans); sb.append(" served"); if(lastClean!=null) { sb.append(". Most recent cleaning: "); sb.append(lastClean); } return(sb.toString()); } private void doPrune() throws Exception { if(getLogger().isDebugEnabled()) { getLogger().debug("Cleaning pool: " + op); } op.prune(); numCleans++; getLogger().debug("Finished cleaning, looks like this: %s", op); } @Override public void run() { // Only do six cleans (sleeping ten minutes, that's an hour!) while(numCleans<NUM_CLEANS) { try { // Prune every once in a while. sleep(TIME_BETWEEN_CLEANS); lastClean=new Date(); doPrune(); } catch(Exception e) { getLogger().error("Cleaner got an exception", e); } } } } // ObjectPoolCleaner } // ObjectPool