Java tutorial
/* $Id$ Copyright (C) 2003-2010 Virginia Tech. All rights reserved. SEE LICENSE FOR MORE INFORMATION Author: Middleware Services Email: middleware@vt.edu Version: $Revision$ Updated: $Date$ */ package edu.vt.middleware.ldap.pool; import java.util.LinkedList; import java.util.Queue; import java.util.Timer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import edu.vt.middleware.ldap.BaseLdap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * <code>AbstractLdapPool</code> contains the basic implementation for pooling * ldap objects. The main design objective for the supplied pooling * implementations is to provide a pool that does not block on object creation * or destruction. This is what accounts for the multiple locks available on * this class. The pool is backed by two queues, one for available objects and * one for active objects. Objects that are available for {@link #checkOut()} * exist in the available queue. Objects that are actively in use exist in the * active queue. Note that depending on the implementation an object can exist * in both queues at the same time. * * @param <T> type of ldap object * * @author Middleware Services * @version $Revision$ $Date$ */ public abstract class AbstractLdapPool<T extends BaseLdap> implements LdapPool<T> { /** Lock for the entire pool. */ protected final ReentrantLock poolLock = new ReentrantLock(); /** Condition for notifying threads that an object was returned. */ protected final Condition poolNotEmpty = poolLock.newCondition(); /** Lock for check ins. */ protected final ReentrantLock checkInLock = new ReentrantLock(); /** Lock for check outs. */ protected final ReentrantLock checkOutLock = new ReentrantLock(); /** Log for this class. */ protected final Log logger = LogFactory.getLog(this.getClass()); /** List of available ldap objects in the pool. */ protected Queue<PooledLdap<T>> available = new LinkedList<PooledLdap<T>>(); /** List of ldap objects in use. */ protected Queue<PooledLdap<T>> active = new LinkedList<PooledLdap<T>>(); /** Ldap pool config. */ protected LdapPoolConfig poolConfig; /** Factory to create ldap objects. */ protected LdapFactory<T> ldapFactory; /** Timer for scheduling pool tasks. */ private Timer poolTimer = new Timer(true); /** * Creates a new pool with the supplied pool configuration and ldap factory. * The pool configuration will be marked as immutable by this pool. * * @param lpc <code>LdapPoolConfig</code> * @param lf <code>LdapFactory</code> */ public AbstractLdapPool(final LdapPoolConfig lpc, final LdapFactory<T> lf) { this.poolConfig = lpc; this.poolConfig.makeImmutable(); this.ldapFactory = lf; } /** {@inheritDoc} */ public LdapPoolConfig getLdapPoolConfig() { return this.poolConfig; } /** {@inheritDoc} */ public void setPoolTimer(final Timer t) { this.poolTimer = t; } /** {@inheritDoc} */ public void initialize() { if (this.logger.isDebugEnabled()) { this.logger.debug("beginning pool initialization"); } this.poolTimer.scheduleAtFixedRate(new PrunePoolTask<T>(this), this.poolConfig.getPruneTimerPeriod(), this.poolConfig.getPruneTimerPeriod()); if (this.logger.isDebugEnabled()) { this.logger.debug("prune pool task scheduled"); } this.poolTimer.scheduleAtFixedRate(new ValidatePoolTask<T>(this), this.poolConfig.getValidateTimerPeriod(), this.poolConfig.getValidateTimerPeriod()); if (this.logger.isDebugEnabled()) { this.logger.debug("validate pool task scheduled"); } this.initializePool(); if (this.logger.isDebugEnabled()) { this.logger.debug("pool initialized to size " + this.available.size()); } } /** * Attempts to fill the pool to its minimum size. * * @throws IllegalStateException if the pool does not contain at least one * connection and it's minimum size is greater than zero */ private void initializePool() { if (this.logger.isDebugEnabled()) { this.logger.debug("checking ldap pool size >= " + this.poolConfig.getMinPoolSize()); } int count = 0; this.poolLock.lock(); try { while (this.available.size() < this.poolConfig.getMinPoolSize() && count < this.poolConfig.getMinPoolSize() * 2) { final T t = this.createAvailable(); if (this.poolConfig.isValidateOnCheckIn()) { if (this.ldapFactory.validate(t)) { if (this.logger.isTraceEnabled()) { this.logger.trace("ldap object passed initialize validation: " + t); } } else { if (this.logger.isWarnEnabled()) { this.logger.warn("ldap object failed initialize validation: " + t); } this.removeAvailable(t); } } count++; } if (this.available.size() == 0 && this.poolConfig.getMinPoolSize() > 0) { throw new IllegalStateException("Could not initialize pool"); } } finally { this.poolLock.unlock(); } } /** {@inheritDoc} */ public void close() { this.poolLock.lock(); try { while (this.available.size() > 0) { final PooledLdap<T> pl = this.available.remove(); this.ldapFactory.destroy(pl.getLdap()); } while (this.active.size() > 0) { final PooledLdap<T> pl = this.active.remove(); this.ldapFactory.destroy(pl.getLdap()); } if (this.logger.isDebugEnabled()) { this.logger.debug("pool closed"); } } finally { this.poolLock.unlock(); } this.poolTimer.cancel(); } /** * Create a new ldap object and place it in the available pool. * * @return ldap object that was placed in the available pool */ protected T createAvailable() { final T t = this.ldapFactory.create(); if (t != null) { final PooledLdap<T> pl = new PooledLdap<T>(t); this.poolLock.lock(); try { this.available.add(pl); } finally { this.poolLock.unlock(); } } else { if (this.logger.isWarnEnabled()) { this.logger.warn("unable to create available ldap object"); } } return t; } /** * Create a new ldap object and place it in the active pool. * * @return ldap object that was placed in the active pool */ protected T createActive() { final T t = this.ldapFactory.create(); if (t != null) { final PooledLdap<T> pl = new PooledLdap<T>(t); this.poolLock.lock(); try { this.active.add(pl); } finally { this.poolLock.unlock(); } } else { if (this.logger.isWarnEnabled()) { this.logger.warn("unable to create active ldap object"); } } return t; } /** * Create a new ldap object and place it in both the available and active * pools. * * @return ldap object that was placed in the available and active pools */ protected T createAvailableAndActive() { final T t = this.ldapFactory.create(); if (t != null) { final PooledLdap<T> pl = new PooledLdap<T>(t); this.poolLock.lock(); try { this.available.add(pl); this.active.add(pl); } finally { this.poolLock.unlock(); } } else { if (this.logger.isWarnEnabled()) { this.logger.warn("unable to create available and active ldap object"); } } return t; } /** * Remove an ldap object from the available pool. * * @param t ldap object that exists in the available pool */ protected void removeAvailable(final T t) { boolean destroy = false; final PooledLdap<T> pl = new PooledLdap<T>(t); this.poolLock.lock(); try { if (this.available.remove(pl)) { destroy = true; } else { if (this.logger.isWarnEnabled()) { this.logger.warn("attempt to remove unknown available ldap object: " + t); } } } finally { this.poolLock.unlock(); } if (destroy) { if (this.logger.isTraceEnabled()) { this.logger.trace("removing available ldap object: " + t); } this.ldapFactory.destroy(t); } } /** * Remove an ldap object from the active pool. * * @param t ldap object that exists in the active pool */ protected void removeActive(final T t) { boolean destroy = false; final PooledLdap<T> pl = new PooledLdap<T>(t); this.poolLock.lock(); try { if (this.active.remove(pl)) { destroy = true; } else { if (this.logger.isWarnEnabled()) { this.logger.warn("attempt to remove unknown active ldap object: " + t); } } } finally { this.poolLock.unlock(); } if (destroy) { if (this.logger.isTraceEnabled()) { this.logger.trace("removing active ldap object: " + t); } this.ldapFactory.destroy(t); } } /** * Remove an ldap object from both the available and active pools. * * @param t ldap object that exists in the both the available and active * pools */ protected void removeAvailableAndActive(final T t) { boolean destroy = false; final PooledLdap<T> pl = new PooledLdap<T>(t); this.poolLock.lock(); try { if (this.available.remove(pl)) { destroy = true; } else { if (this.logger.isDebugEnabled()) { this.logger.debug("attempt to remove unknown available ldap object: " + t); } } if (this.active.remove(pl)) { destroy = true; } else { if (this.logger.isDebugEnabled()) { this.logger.debug("attempt to remove unknown active ldap object: " + t); } } } finally { this.poolLock.unlock(); } if (destroy) { if (this.logger.isTraceEnabled()) { this.logger.trace("removing active ldap object: " + t); } this.ldapFactory.destroy(t); } } /** * Attempts to activate and validate an ldap object. Performed before an * object is returned from {@link LdapPool#checkOut()}. * * @param t ldap object * * @throws LdapPoolException if this method fais * @throws LdapActivationException if the ldap object cannot be activated * @throws LdapValidateException if the ldap object cannot be validated */ protected void activateAndValidate(final T t) throws LdapPoolException { if (!this.ldapFactory.activate(t)) { if (this.logger.isWarnEnabled()) { this.logger.warn("ldap object failed activation: " + t); } this.removeAvailableAndActive(t); throw new LdapActivationException("Activation of ldap object failed"); } if (this.poolConfig.isValidateOnCheckOut() && !this.ldapFactory.validate(t)) { if (this.logger.isWarnEnabled()) { this.logger.warn("ldap object failed check out validation: " + t); } this.removeAvailableAndActive(t); throw new LdapValidationException("Validation of ldap object failed"); } } /** * Attempts to validate and passivate an ldap object. Performed when an object * is given to {@link LdapPool#checkIn}. * * @param t ldap object * * @return whether both validate and passivation succeeded */ protected boolean validateAndPassivate(final T t) { boolean valid = false; if (this.poolConfig.isValidateOnCheckIn()) { if (!this.ldapFactory.validate(t)) { if (this.logger.isWarnEnabled()) { this.logger.warn("ldap object failed check in validation: " + t); } } else { valid = true; } } else { valid = true; } if (valid && !this.ldapFactory.passivate(t)) { valid = false; if (this.logger.isWarnEnabled()) { this.logger.warn("ldap object failed activation: " + t); } } return valid; } /** {@inheritDoc} */ public void prune() { if (this.logger.isTraceEnabled()) { this.logger.trace("waiting for pool lock to prune " + this.poolLock.getQueueLength()); } this.poolLock.lock(); try { if (this.active.size() == 0) { if (this.logger.isDebugEnabled()) { this.logger.debug("pruning pool of size " + this.available.size()); } while (this.available.size() > this.poolConfig.getMinPoolSize()) { PooledLdap<T> pl = this.available.peek(); final long time = System.currentTimeMillis() - pl.getCreatedTime(); if (time > this.poolConfig.getExpirationTime()) { pl = this.available.remove(); if (this.logger.isTraceEnabled()) { this.logger.trace("removing " + pl.getLdap() + " in the pool for " + time + "ms"); } this.ldapFactory.destroy(pl.getLdap()); } else { break; } } if (this.logger.isDebugEnabled()) { this.logger.debug("pool size pruned to " + this.available.size()); } } else { if (this.logger.isDebugEnabled()) { this.logger.debug("pool is currently active, no objects pruned"); } } } finally { this.poolLock.unlock(); } } /** {@inheritDoc} */ public void validate() { this.poolLock.lock(); try { if (this.active.size() == 0) { if (this.poolConfig.isValidatePeriodically()) { if (this.logger.isDebugEnabled()) { this.logger.debug("validate for pool of size " + this.available.size()); } final Queue<PooledLdap<T>> remove = new LinkedList<PooledLdap<T>>(); for (PooledLdap<T> pl : this.available) { if (this.logger.isTraceEnabled()) { this.logger.trace("validating " + pl.getLdap()); } if (this.ldapFactory.validate(pl.getLdap())) { if (this.logger.isTraceEnabled()) { this.logger.trace("ldap object passed validation: " + pl.getLdap()); } } else { if (this.logger.isWarnEnabled()) { this.logger.warn("ldap object failed validation: " + pl.getLdap()); } remove.add(pl); } } for (PooledLdap<T> pl : remove) { if (this.logger.isTraceEnabled()) { this.logger.trace("removing " + pl.getLdap() + " from the pool"); } this.available.remove(pl); this.ldapFactory.destroy(pl.getLdap()); } } this.initializePool(); if (this.logger.isDebugEnabled()) { this.logger.debug("pool size after validation is " + this.available.size()); } } else { if (this.logger.isDebugEnabled()) { this.logger.debug("pool is currently active, " + "no validation performed"); } } } finally { this.poolLock.unlock(); } } /** {@inheritDoc} */ public int availableCount() { return this.available.size(); } /** {@inheritDoc} */ public int activeCount() { return this.active.size(); } /** * Called by the garbage collector on an object when garbage collection * determines that there are no more references to the object. * * @throws Throwable if an exception is thrown by this method */ protected void finalize() throws Throwable { try { this.close(); } finally { super.finalize(); } } /** * <code>PooledLdap</code> contains an ldap object that is participating in a * pool. Used to track how long an ldap object has been in either the * available or active queues. * * @param <T> type of ldap object */ static protected class PooledLdap<T extends BaseLdap> { /** hash code seed. */ protected static final int HASH_CODE_SEED = 89; /** Underlying ldap object. */ private T ldap; /** Time this object was created. */ private long createdTime; /** * Creates a new <code>PooledLdap</code> with the supplied ldap object. * * @param t ldap object */ public PooledLdap(final T t) { this.ldap = t; this.createdTime = System.currentTimeMillis(); } /** * Returns the ldap object. * * @return underlying ldap object */ public T getLdap() { return this.ldap; } /** * Returns the time this object was created. * * @return creation time */ public long getCreatedTime() { return this.createdTime; } /** * Returns whether the supplied <code>Object</code> contains the same data * as this bean. * * @param o <code>Object</code> * * @return <code>boolean</code> */ public boolean equals(final Object o) { if (o == null) { return false; } return o == this || (this.getClass() == o.getClass() && o.hashCode() == this.hashCode()); } /** * This returns the hash code for this object. * * @return <code>int</code> */ public int hashCode() { int hc = HASH_CODE_SEED; if (this.ldap != null) { hc += this.ldap.hashCode(); } return hc; } } }