package jeffaschenk.commons.frameworks.cnxidx.utility.pool; import jeffaschenk.commons.exceptions.NoFreeResourcesException; import jeffaschenk.commons.exceptions.PoolNotAvailableException; import jeffaschenk.commons.exceptions.ResourceProviderException; import java.util.Enumeration; import java.util.Properties; import java.util.Vector; /** * Provides access to a pool of scarce resources. Typically, * resources will be "connection" type resources. However, this * pool can be used for any scarce or expensive to construct resources * that applications need reoccuring access to. Resources placed into this * pool must implement the ResourceProvider interface. * <p> The ResourcePool uses several different properties which are passed * in to the constructor as TypedProperties. These properties vary * depending on the scarce resource being managed. * <p> The resource pool is implemented using two vectors. One vector represents * the "free" or available resources and the other vector represents the "in use" * resources. Thus, when a client obtains a resource from this pooling mechanism, * the resource is moved from the free pool into the used pool. * <p> It is IMPORTANT that clients "free" the resources after they are done using * them. Otherwise, there will be no resources available to other clients. */ public class ResourcePool { /* * Kro - 2001/09/12 - added the ability to lock the pool. */ /** * Indicates not to wait if no resources are available. */ static public final int NO_WAIT = -1; /** * Indicates to wait until a resources becomes available if none are already available. */ static public final int WAIT_FOREVER = 0; /** * Identifies the pool's state. */ private boolean locked; /** * Why the pool has been locked - placed in exception messages * when locked is true. */ private String lockReason = null; /** * Collects resources that are created and available */ private Vector<Object> freeResources = new Vector<>(); /** * Collects resources that are created and currently in use */ private Vector<Object> usedResources = new Vector<>(); /** * The name of the owner of this pool instance */ private String owner; /** * The minimum number of resources this pool should ever have */ private int min; /** * The maximum number of resources this pool should ever have */ private int max; /** * Default wait period for obtaining a resource from the free pool */ private long timeout; /** * provides lifecycle services for resources in the pool */ private ResourceProvider rp; /** * provides lifecycle services for resources in the pool */ private Properties props; /** * determins if the resources are validated on each retrieval from the pool * for use or not */ protected boolean resourceValidation = false; /** * The constructor takes the name of the pool owner, a properties list * that will be used by the ResourcePool and possibly the ResourceProvider * as well to construct resources. * * @param owner The owner of the pool * @param min Minimum resources to initialize the pool with. * @param max Maximum resources to initialize the pool with. * @param timeout The time to wait for a resource to become available. * @param rp The resource provider object which provides lifecycle * services for the resource * @param validation set to true if we should validate the resource each time * we get one. False will never validate them. */ public ResourcePool(String owner, int min, int max, int timeout, ResourceProvider rp, boolean validation) { this.owner = owner; this.min = min; this.max = max; this.timeout = timeout; this.rp = rp; this.locked = false; this.resourceValidation = validation; // Initialize the pool initPool(); } /** * The constructor takes the name of the pool owner, a properties list * that will be used by the ResourcePool and possibly the ResourceProvider * as well. * * @param owner The owner of the pool. * @param props Properties which are needed to create the pool or the ResourceProvider. * @param rp The resource provider object which provides lifecycle services for the resource. */ public ResourcePool(String owner, Properties props, ResourceProvider rp) { this.owner = owner; this.min = Integer.parseInt((String)props.get("min")); // Default 1 this.max = Integer.parseInt((String)props.get("max")); // Default 1 this.timeout = Integer.parseInt((String)props.get("timeout")); // Default 0 this.rp = rp; this.props = props; this.locked = false; initPool(); } /** * Obtain a pooled resource, but only wait the default number * of seconds to obtain that resource. The default number of * seconds to wait for a resources is retrieve from the properties * object passed into the constructor. The property name is 'timeout'. * <p> Remember to release or free the resource after using it. * * @return Object the resource acquired from the pool. * @throws jeffaschenk.commons.exceptions.NoFreeResourcesException Thrown when there are no resources available. */ public synchronized Object acquireResource() throws NoFreeResourcesException { return acquireResource(this.timeout * 1000); } /** * Obtain a pooled resource, but only wait the specified * number of milliseconds to obtain that resource. If you * want to wait "forever" until a resource is availble, then * pass in 0. If you don't want to wait at all, then pass in -1. * * @param waitMillis The number of milli-seconds to wait for a resource. * @return Object the resource acquired from the pool. * @throws NoFreeResourcesException Thrown when no free resources are * available. */ public synchronized Object acquireResource(long waitMillis) throws NoFreeResourcesException { // Caller does not want to wait for an available resource. if (waitMillis < 0) { return getResource(); } // Caller is willing to wait a specified time for an available // resource. If 'waitMillis' is zero, we will wait indefinately, // until a resource is available. long timeLeft = waitMillis; long timeDecrement = 100; if (waitMillis > 0) { timeDecrement = timeLeft / 100; } while (true) { try { // see if a resource is ripe for the picking return getResource(); } catch (NoFreeResourcesException nfre) { // if we have waited all we can then get out if ((waitMillis > 0) && (timeLeft <= 0)) { throw nfre; } // wait some more try { wait(timeDecrement); } catch (InterruptedException ie) { } if (waitMillis > 0) { timeLeft -= timeDecrement; } } } } /** * Releases the resource back into the "free" list so it can be used again. * * @param resource The resource to release. */ public void releaseResource(Object resource) { releaseResource(resource, false); } /** * Releases the resource back into the "free" list so it can be used * again. * * @param resource The resource to release. * @param isBad Flag to determine if the resource is "bad" or not. */ public void releaseResource(Object resource, boolean isBad) { // <KBR CONTINUUM FIX> if (resource == null) { return; } // Otherwise - we add a null resource! // If the resource is bad, attempt to destroy it if (isBad) { try { destroy(freeResources); rp.destroy(resource); } catch (ResourceProviderException rpe) { } } // call the synchronized method to add it back to the list and // decrement the number of resources checked out usedToFreeResources(resource, isBad); } /** * Determines if the resource passed in belongs to the pool (e.g., was created by * this pool.) * * @param resource The resource to check. * @return True if the resource belongs to this pool. */ public boolean contains(Object resource) { return (usedResources.contains(resource) || freeResources.contains(resource)); } /** * Destroys all resources in both the free pool and the used pool. */ public synchronized void destroy() { // Destroy both used and free pools destroy(freeResources); destroy(usedResources); } /** * Iterates over apool of reosurces and delegates the * individual destroy to the ResourceProvider. * * @param resources either the free pool or the unused pool */ private void destroy(Vector resources) { Enumeration e = resources.elements(); while (e.hasMoreElements()) { try { // Defer to the resource provider for destruction of resource. rp.destroy(e.nextElement()); } catch (ResourceProviderException rpe) { } } resources.removeAllElements(); } /** * Method to validate all the free resources in the pool. Validation * determines if the resources are still active. */ public void validate() throws PoolNotAvailableException { if (isPoolLocked()) { throw new PoolNotAvailableException(lockReason); } /* * Go through each free resource in the pool and validate it */ Object resource = null; for (int ii = 0; ii < freeResources.size(); ii++) { resource = freeResources.elementAt(ii); if (!rp.isValid(resource)) { try { rp.destroy(resource); } catch (ResourceProviderException rpe) { // ignore } freeResources.removeElementAt(ii); } } } /** * Returns the current number of free resources in the pool * * @return Number of free resources in the pool. */ public int getFreeCount() { return freeResources.size(); } /** * Gets the name of the owner of the pool. * * @return The owner of the pool. */ public String getOwner() { return owner; } /** * Returns a resource from the pool for a client to use * * @return The resource * @throws NoFreeResourcesException When there are no free resources available to return. */ private Object getResource() throws NoFreeResourcesException { if (isPoolLocked()) { throw new PoolNotAvailableException(lockReason); } Object resource = null; /* * See if there are any free resources in the pool. * If so return one of the resources */ if (freeResources.size() > 0) { String err = ""; while ((resource == null) && (freeResources.size() > 0)) { resource = freeResources.firstElement(); freeResources.removeElementAt(0); if (resourceValidation) { if (!rp.isValid(resource)) { try { rp.destroy(resource); } catch (ResourceProviderException rpe) { err = "Resource is no longer valid. Reason: " + rpe.getMessage(); } resource = null; } } } if (null == resource) { throw new NoFreeResourcesException(err); } } else if ((usedResources.size() < max) || (max == 0)) { /* * If no free resources and we are under the limit of maximum * resources (or if there is no max), try to create a new resource */ try { resource = newResource(); } catch (ResourceProviderException rpe) { // Couldn't create the resource throw new NoFreeResourcesException( "Couldn't expand the free pool size: " + rpe.getMessage()); } } else { String message = "PROGRAMMING ERROR: Max resources reached!"; // Since this method is a utility method used by the // acquireResource() method, we should never get here!!! // twp: I disagree, it looks to me that this code // would be reached everytime this method was called // and all the resources in the pool were allocated // (freeResources.size() ==0, max > 0, usedResources.size() == max) throw new NoFreeResourcesException(message); } // Successfully obtained a resource, add it to the used pool // before returning it to the client. usedResources.addElement(resource); return resource; } /** * Returns the current number of used resources in the pool * * @return Number of used resources in the pool. */ public int getUsedCount() { return usedResources.size(); } /** * Initializes the pool */ private void initPool() { // Create "min" number of resources for (int ii = 0; ii < min; ii++) { try { // Create the resource and add it to the free list Object resource = newResource(); freeResources.addElement(resource); } catch (ResourceProviderException rpe) { } } } /** * Is the pool full? * * @return Whether the pool is full */ private boolean maxedOut() { boolean maxedOut = true; if (!freeResources.isEmpty() || (usedResources.size() < max) || (max == 0)) { maxedOut = false; } return maxedOut; } /** * Creates a new resource. This method will delegate to this * resource pool's ResourceProvider. * * @return A new resource * @throws ResourceProviderException Thrown when a new and valid * resource cannot be constructed. */ private Object newResource() throws ResourceProviderException { // Proxy to the resource provider for creation Object resource = rp.create(props); if (!rp.isValid(resource)) { rp.destroy(resource); throw new ResourceProviderException("Resource is invalid"); } return resource; } /** * Moves the resource from the "used" list to the "free" list * * @param resource The resource to release * @param isBad Flag to determine if the resource is "bad" or not */ private synchronized void usedToFreeResources(Object resource, boolean isBad) { // Only add the resource back to the list if it is good if (!isBad) { // Don't let them free the same resource twice if (!freeResources.contains(resource)) { freeResources.addElement(resource); } } // Good or bad, delete it from the used list usedResources.removeElement(resource); notifyAll(); } /** * Returns whether any connections * in this pool are currently in use. * * @return whether any connections * in this pool are currently in use. */ public synchronized boolean isPoolInUse() { // if the size of the used pool is greater than 0, yeah - the // pool is in use. return getUsedCount() > 0; } /** * Returns whether any connections * in this pool are currently in use. * * @param timeoutSeconds wait in seconds for making a * determination * @return whether any connections * in this pool are currently in use. */ public synchronized boolean isPoolInUse(int timeoutSeconds) { // if the size of the used pool is greater than 0, yeah - the // pool is in use. long waitMillis; if (timeoutSeconds > 0) { waitMillis = timeoutSeconds * 1000; } else { return isPoolInUse(); } // Caller is willing to wait a specified time for an available // resource. If 'waitMillis' is zero, we will wait indefinately, // until a resource is available. long timeLeft = waitMillis; long startTime = System.currentTimeMillis(); while (isPoolInUse() == true && timeLeft > 0) { // wait try { wait(timeLeft); } catch (InterruptedException ie) { // we're being awokened by a resource becoming free // or by timeLeft expiring } timeLeft = waitMillis - (System.currentTimeMillis() - startTime); } return isPoolInUse(); } /** * Locks the pool. If false is returned, follow-up with * isPoolInUse() calls until isPoolInUse() returns false. * * @param why Reason for locking the pool. Unused if pool is * already locked. Reason shows up in exception messages. * @param timeoutSeconds wait in seconds to lock the pool and * ensure that no DirContext connections in the pool are in use. * @return Whether there are any pool connections in use. */ public synchronized boolean lockPool(String why, int timeoutSeconds) { if (!isPoolLocked()) { // set lock flag and reason if not already locked locked = true; // set flag so noone can aquire a free resource lockReason = why; } return (isPoolInUse(timeoutSeconds)); } /** * Unlocks the pool - making the pool available for use. */ public synchronized void unlockPool() { if (isPoolLocked()) { locked = false; lockReason = null; } } /** * Returns whether the pool has been locked. * * @return whether the pool has been locked. */ public synchronized boolean isPoolLocked() { return locked; } /** * Convenience routine for checking if the pool is unlocked * * @param timeoutSeconds wait in seconds for making a determination */ public synchronized boolean isPoolLocked(int timeoutSeconds) { // no waiting requested if (timeoutSeconds <= 0) { return (isPoolLocked()); } // Caller is willing to wait a specified time for an available resource. long waitMillis = timeoutSeconds * 1000; long timeLeft = waitMillis; long startTime = System.currentTimeMillis(); while ((!isPoolLocked()) && (timeLeft > 0)) { try { wait(timeLeft); } catch (InterruptedException ie) { // we're being awokened by a resource becoming free // or by timeLeft expiring } timeLeft = waitMillis - (System.currentTimeMillis() - startTime); } return (isPoolLocked()); } /** * Convenience routine for checking if the pool is unlocked and * has no connections in use. */ public synchronized boolean isPoolLockedAndUnused() { return isPoolLockedAndUnused(-1); } /** * Convenience routine for checking if the pool is unlocked and * has no connections in use. * * @param timeoutSeconds wait in seconds for making a * determination */ public synchronized boolean isPoolLockedAndUnused(int timeoutSeconds) { return !isPoolInUse(timeoutSeconds) && isPoolLocked(); } }