/*
DBPool - JDBC Connection Pool Manager
Copyright (c) Giles Winstanley
*/
package snaq.util;
import java.io.*;
import java.text.*;
import java.util.*;
/**
* Base class for a pool system implementation.
* This class provides all the base functionality required and can be easily
* extended to provide pooling support for many different types of object.
* <p>New objects are retrieved on demand according to specified limits,
* and the pool can also ensure an object's validity before returning it.
* The limits which can be set for a pool include the number of items to be
* held in the pool, and the maximum number to ever be created.
* <p>The pool will try to maintain <tt>poolSize</tt> items open and ready
* for use (unless that number has not yet been created), but if expiry is
* enabled this number will reduce if the items are not used frequently.
* If <tt>maxSize</tt> is specified then the pool will never create more
* than this number of items, and another can only be obtained from the pool
* if it is handed back by another client.
* <p><tt>ObjectPool</tt> should be sub-classed to override
* the following methods:</p>
* <pre>
* protected Reusable create() throws Exception
* protected boolean isValid(final Reusable o)
* protected void destroy(final Reusable o)
* </pre>
* <p>It is recommended that the sub-class implements methods for obtaining
* and returning items within the pool, casting the objects returned by this
* class as required to the appropriate class type.
* <p>ObjectPool also support asynchronous destruction of items, which can be
* useful in circumstances where destruction of items held can take a long
* time which would delay the <tt>checkIn</tt> method. This also applies
* to the release of the pool after it's final use, which should always be
* done using either <tt>release</tt> or <tt>releaseAsync</tt>.
* @author Giles Winstanley
*/
public abstract class ObjectPool extends LogUtil
{
// Pool access method
private static final int ACCESS_FIFO = 1;
private static final int ACCESS_LIFO = 2;
private static final int ACCESS_RANDOM = 3;
private int accessMethod = ACCESS_LIFO;
// Other variables
private String name;
private List free, used;
private int poolSize, maxSize;
private long expiryTime;
private long requests, hits;
private boolean released = false;
private boolean asyncDestroy = false;
private DateFormat dateFormat;
private Cleaner cleaner;
private InitThread initer;
private static int cleanerCount = 0;
private List listeners = new ArrayList();
/**
* Creates new object pool.
* @param name pool name
* @param poolSize maximum number of pooled objects, or 0 for no limit
* @param maxSize maximum number of possible objects, or 0 for no limit
* @param expiryTime expiry time (milliseconds) for pooled object, or 0 for no expiry
*/
protected ObjectPool(String name, int poolSize, int maxSize, long expiryTime)
{
Class type = getPoolClass();
if (!List.class.isAssignableFrom(type))
throw new RuntimeException("Invalid pool class type specified: " + type.getName() + " (must implement java.util.List)");
try
{
free = (List)type.newInstance();
used = (List)type.newInstance();
}
catch (Exception e)
{
throw new RuntimeException("Unable to instantiate pool class type: " + type.getName());
}
this.name = name;
this.setParameters(poolSize, maxSize, expiryTime);
}
/**
* Creates new object pool.
* @param name pool name
* @param poolSize maximum number of pooled objects, or 0 for no limit
* @param maxSize maximum number of possible objects, or 0 for no limit
* @param expiryTime expiry time (milliseconds) for pooled object, or 0 for no expiry
*/
protected ObjectPool(String name, int poolSize, int maxSize, int expiryTime)
{
this(name, poolSize, maxSize, (long)expiryTime);
}
/**
* Initializes the given number of items in the pool.
* This spawns a new thread to create them in the background.
*/
public final void init(int num)
{
if (num == 0)
return;
if (num > 0 && num <= getMaxSize())
{
if (initer != null)
{
initer.halt();
try { initer.join(); } catch (InterruptedException ie) {}
}
initer = new InitThread(num);
initer.start();
}
else
throw new IllegalArgumentException("Invalid number of items specified for initialization");
}
/**
* Checks out an item from the pool. If no free item
* is available, a new item is created unless the maximum
* number of items has been reached. If a free item
* is not valid it is removed from the pool and another
* is retrieved.
* @return item from the pool, or null if nothing available
* @exception Exception if there is an error creating a new object
*/
protected final synchronized Reusable checkOut() throws Exception
{
if (released)
throw new RuntimeException("Pool no longer valid for use");
int oldTotalConns = used.size() + free.size();
TimeWrapper tw = null;
Reusable o = null;
if (free.size() > 0)
{
// Get an object from the free list
switch(accessMethod)
{
case ACCESS_FIFO:
tw = (TimeWrapper)free.remove(0);
break;
case ACCESS_RANDOM:
tw = (TimeWrapper)free.remove((int)(free.size() * Math.random()));
break;
case ACCESS_LIFO:
default:
tw = (TimeWrapper)free.remove(free.size() - 1);
}
o = (Reusable)tw.getObject();
boolean valid = isValid(o);
while (!valid && free.size() > 0)
{
destroyObject(o);
log("Removed invalid item from pool");
tw = (TimeWrapper)free.remove(0);
o = (Reusable)tw.getObject();
valid = isValid(o);
}
if (free.size() == 0 && !valid)
o = null;
}
boolean hit = (o != null);
// If no free items and can create more...create new item
if (o == null)
{
if (maxSize > 0 && used.size() == maxSize)
fireMaxSizeLimitErrorEvent();
else if (used.size() < maxSize || maxSize == 0)
{
o = create();
if (!isValid(o))
throw new RuntimeException("Unable to create a valid connection");
}
}
// If a connection has been obtained/created, add it to used items collection
if (o != null)
{
used.add(o);
requests++;
if (hit)
hits++;
firePoolCheckOutEvent();
// Check for limit reaching so events can be fired.
// (Events only fired on increase of connection numbers).
int totalConns = used.size() + free.size();
if (totalConns == poolSize && totalConns > oldTotalConns)
fireMaxPoolLimitReachedEvent();
else if (totalConns == poolSize + 1 && totalConns > oldTotalConns)
fireMaxPoolLimitExceededEvent();
if (totalConns == maxSize && totalConns > oldTotalConns)
fireMaxSizeLimitReachedEvent();
}
if (debug)
{
String ratio = used.size() + "/" + (used.size() + free.size());
String hitRate = " (HitRate=" + getHitRate() + "%)";
log("Checkout - " + ratio + hitRate + (o == null ? " - null returned" : ""));
}
return o;
}
/**
* Checks out an item from the pool.
* If there is no pooled item available and the maximum number
* possible has not been reached, another is created.
* If a free item is detected as being invalid it is removed
* from the pool and the another is retrieved.
* If an item is not available and the maximum number possible
* has been reached, the method waits for the timeout period
* for one to become available by being checked in.
* @param timeout timeout value in milliseconds
* @return item from the pool, or null if nothing available within timeout period
* @exception Exception if there is an error creating a new object
*/
protected final synchronized Reusable checkOut(long timeout) throws Exception
{
long time = System.currentTimeMillis();
Reusable o = null;
o = ch