User: mulder  
  Date: 00/06/02 06:48:47

  Added:       src/main/org/jboss/minerva/pools ObjectPool.java
                        ObjectRecord.java PoolEvent.java
                        PoolEventListener.java PoolGCThread.java
                        PoolObjectFactory.java PooledObject.java
                        package.html
  Log:
  Initial entry of Minerva JDBC Pools into CVS.
  
  Pools, DataSources, and other non-jBoss-dependent code is under
    org.jboss.minerva.*
  
  JavaDoc source HTML files are included - the package comments are in
    the package.html files in the various packages, and the overview
    comments are in org/jboss/minerva/minerva.html
  
  MBeans to load a pool into jBoss are in org.jboss.jdbc
  
  A new logging Writer is on org.jboss.logging.
  
  Revision  Changes    Path
  1.1                  jboss/src/main/org/jboss/minerva/pools/ObjectPool.java
  
  Index: ObjectPool.java
  ===================================================================
  /*
   * jBoss, the OpenSource EJB server
   *
   * Distributable under GPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.minerva.pools;
  
  import java.io.*;
  import java.util.*;
  
  /**
   * A generic object pool.  You must provide a PoolObjectFactory (or the class
   * of a Java Bean) so the pool knows what kind of objects to create.  It has
   * many configurable parameters, such as the minimum and maximum size of the
   * pool, whether to allow the pool to shrink, etc.  If the pooled objects
   * implement PooledObject, they will automatically be returned to the pool at
   * the appropriate times.
   * <P>In general, the appropriate way to use a pool is:</P>
   * <OL>
   *   <LI>Create it</LI>
   *   <LI>Configure it (set factory, name, parameters, etc.)</LI>
   *   <LI>Initialize it (once done, further configuration is not allowed)</LI>
   *   <LI>Use it</LI>
   *   <LI>Shut it down</LI>
   * </OL>
   * @see org.jboss.minerva.pools.PooledObject
   * @version $Revision: 1.1 $
   * @author Aaron Mulder ([EMAIL PROTECTED])
   */
  public class ObjectPool implements PoolEventListener {
      private final static String INITIALIZED = "Pool already initialized!";
      private final static PoolGCThread collector = new PoolGCThread();
      static {
          collector.start();
      }
  
      private PoolObjectFactory factory;
      private String poolName;
  
      private HashMap objects = null;
      private int minSize = 0;
      private int maxSize = 0;
      private boolean shrinks = false;
      private boolean runGC = false;
      private float shrinkPercent = 0.33f;             // reclaim 1/3 of stale objects
      private long shrinkMinIdleMillis = 600000l; // must be unsued for 10 minutes
      private long gcMinIdleMillis = 1200000l;    // must be idle for 20 minutes
      private long gcIntervalMillis = 120000l;    // shrink & gc every 2 minutes
      private long lastGC = System.currentTimeMillis();
      private boolean blocking = false;
      private boolean trackLastUsed = false;
      private PrintWriter logWriter = null;
  
      /**
       * Creates a new pool.  It cannot be used until you specify a name and
       * object factory or bean class, and initialize it.
       * @see #setName
       * @see #setObjectFactory
       * @see #initialize
       */
      public ObjectPool() {}
  
      /**
       * Creates a new pool with the specified parameters.  It cannot be used
       * until you initialize it.
       * @param factory The object factory that will create the objects to go in
       *    the pool.
       * @param poolName The name of the pool.  This does not have to be unique
       *    across all pools, but it is strongly recommended (and it may be a
       *    requirement for certain uses of the pool).
       * @see #initialize
       */
      public ObjectPool(PoolObjectFactory factory, String poolName) {
          setObjectFactory(factory);
          setName(poolName);
      }
  
      /**
       * Creates a new pool with the specified parameters.  It cannot be used
       * until you initialize it.
       * @param javeBeanClass The Class of a Java Bean.  New instances for the
       *    pool will be created with the no-argument constructor, and no
       *    particular initialization or cleanup will be performed on the
       *    instances.  Use a PoolObjectFactory if you want more control over
       *    the instances.
       * @param poolName The name of the pool.  This does not have to be unique
       *    across all pools, but it is strongly recommended (and it may be a
       *    requirement for certain uses of the pool).
       * @see #initialize
       */
      public ObjectPool(Class javaBeanClass, String poolName) {
          setObjectFactory(javaBeanClass);
          setName(poolName);
      }
  
      /**
       * Sets the object factory for the pool.  The object factory controls the
       * instances created for the pool, and can initialize instances given out
       * by the pool and cleanup instances returned to the pool.
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the object factory after the pool has been
       *    initialized.
       */
      public void setObjectFactory(PoolObjectFactory factory) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          this.factory = factory;
      }
  
      /**
       * Sets the object factory as a new factory for Java Beans.  New instances
       *    for the pool will be created with the no-argument constructor, and no
       *    particular initialization or cleanup will be performed on the
       *    instances.
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the object factory after the pool has been
       *    initialized.
       */
      public void setObjectFactory(Class javaBeanClass) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          factory = new BeanFactory(javaBeanClass);
      }
  
      /**
       * Sets the name of the pool.  This is not required to be unique across all
       * pools, but is strongly recommended.  Certain uses of the pool (such as
       * a JNDI object factory) may require it.  This must be set exactly once
       * for each pool (it may be set in the constructor).
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the name of the pool more than once.
       */
      public void setName(String name) {
          if(poolName != null)
              throw new IllegalStateException("Cannot change pool name once set!");
          poolName = name;
      }
  
      /**
       * Gets the name of the pool.
       */
      public String getName() {return poolName;}
  
      /**
       * Gets a log writer used to record pool events.
       */
      public PrintWriter getLogWriter() throws java.sql.SQLException {
          return logWriter;
      }
  
      /**
       * Sets a log writer used to record pool events.
       */
      public void setLogWriter(PrintWriter writer) throws java.sql.SQLException {
          logWriter = writer;
      }
  
      /**
       * Sets the minimum size of the pool.  The pool always starts with zero
       * instances, but once running, it will never shrink below this size.  This
       * parameter has no effect if shrinking is not enabled.  The default is
       * zero.
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the minimum size after the pool has been
       *    initialized.
       */
      public void setMinSize(int size) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          minSize = size;
      }
  
      /**
       * Gets the minimum size of the pool.
       * @see #setMinSize
       */
      public int getMinSize() {return minSize;}
  
      /**
       * Sets the maximum size of the pool.  Once the pool has grown to hold this
       * number of instances, it will not add any more instances.  If one of the
       * pooled instances is available when a request comes in, it will be
       * returned.  If none of the pooled instances are available, the pool will
       * either block until an instance is available, or return null.  The default
       * is no maximum size.
       * @see #setBlocking
       * @param size The maximum size of the pool, or 0 if the pool should grow
       *    indefinitely (not recommended).
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the maximum size after the pool has been
       *    initialized.
       */
      public void setMaxSize(int size) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          maxSize = size;
      }
  
      /**
       * Gets the maximum size of the pool.
       * @see #setMaxSize
       */
      public int getMaxSize() {return maxSize;}
  
      /**
       * Sets whether the pool should release instances that have not been used
       * recently.  This is intended to reclaim resources (memory, database
       * connections, file handles, etc) during periods of inactivity.  This runs
       * as often as garbage collection (even if garbage collection is disabled,
       * this uses the same timing parameter), but the required period of
       * inactivity is different.  Also, you may choose to release only a fraction
       * of the eligible objects to slow the shrinking further.  So the algorithm
       * is:
       * <UL>
       *   <LI>Run every <I>n</I> milliseconds.</LI>
       *   <LI>Count the number of connections that are not in use, and whose last
       *     used time is greater than the period of inactivity.</LI>
       *   <LI>Multiply this by the fraction of connections to release, but if the
       *     last total was greater than one, this will always be at least one.</LI>
       *   <LI>Attempt to release this many connections, of the ones identified
       *     above.  Do not release any connection that has been marked as in use
       *     while this runs, and do not allow the pool to shrink below the
       *     specified minimum size.</LI>
       * </UL>
       * <P>The default is disabled.</P>
       * @see #setGCInterval
       * @see #setShrinkMinIdleTime
       * @see #setShrinkPercent
       * @see #setMinSize
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the shrinking state after the pool has been
       *    initialized.
       */
      public void setShrinkingEnabled(boolean allowShrinking) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          shrinks = allowShrinking;
      }
  
      /**
       * Gets whether shrinking of the pool is enabled.
       * @see #setShrinkingEnabled
       */
      public boolean isShrinkingEnabled() {return shrinks;}
  
      /**
       * Sets whether garbage collection is enabled.  This is the process of
       * returning objects to the pool if they have been checked out of the pool
       * but have not been used in a long periond of time.  This is meant to
       * reclaim resources, generally caused by unexpected failures on the part
       * of the pool client (which forestalled returning an object to the pool).
       * This runs on the same schedule as shrinking (if enabled), but objects
       * that were just garbage collected will not be eligible for shrinking
       * immediately (after all, they presumably represented "active" clients).
       * Connections that are garbage collected will be returned immediately if
       * a client is blocking waiting for an object.  The deafult value is
       * disabled.
       * @see #setGCMinIdleTime
       * @see #setGCInterval
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the garbage collection state after the pool
       *    has been initialized.
       */
      public void setGCEnabled(boolean enabled) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          runGC = enabled;
      }
  
      /**
       * Gets whether garbage collection is enabled.
       * @see #setGCEnabled
       */
      public boolean isGCEnabled() {return runGC;}
  
      /**
       * Sets the shrink percent as a fraction between 0 and 1.  This controls
       * how many of the available connection which have been idle for too long
       * will be released.  If set to 1, all eligible connections will be
       * released (subject to the minimum size), and if set to 0, only one will
       * be released each time (if any are eligible - see the algorithm in
       * setShrinkingEnabled).  The default value is 33%.
       * @see #setShrinkingEnabled
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the shrinking percent after the pool
       *    has been initialized.
       * @throws java.lang.IllegalArgumentException
       *    Occurs when the percent parameter is not between 0 and 1.
       */
      public void setShrinkPercent(float percent) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          if(percent < 0f || percent > 1f)
              throw new IllegalArgumentException("Percent must be between 0 and 1!");
          shrinkPercent = percent;
      }
  
      /**
       * Gets the shrink percent as a fraction between 0 and 1.
       * @see #setShrinkPercent
       */
      public float getShrinkPercent() {return shrinkPercent;}
  
      /**
       * Sets the minimum idle time to make an object eligible for shrinking.  If
       * the object is not in use and has not been used for this amount of time,
       * it may be released from the pool.  If timestamps are enabled, the client
       * may update the last used time.  Otherwise, the last used time is only
       * updated when an object is acquired or released.  The default value is
       * 10 minutes.
       * @see #setShrinkingEnabled
       * @param millis The idle time, in milliseconds.
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the shrinking idle time after the pool
       *    has been initialized.
       */
      public void setShrinkMinIdleTime(long millis) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          shrinkMinIdleMillis = millis;
      }
  
      /**
       * Gets the minimum idle time to make an object eligible for shrinking.
       * @see #setShrinkMinIdleTime
       */
      public long getShrinkMinIdleTime() {return shrinkMinIdleMillis;}
  
      /**
       * Sets the minimum idle time to make an object eligible for garbage
       * collection.  If the object is in use and has not been used for this
       * amount of time, it may be returned to the pool.  If timestamps are
       * enabled, the client may update the last used time (this is generally
       * recommended if garbage collection is enabled).  Otherwise, the last used
       * time is only updated when an object is acquired or released.  The default
       * value is 20 minutes.
       * @see #setGCEnabled
       * @param millis The idle time, in milliseconds.
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the garbage collection idle time after the
       *    pool has been initialized.
       */
      public void setGCMinIdleTime(long millis) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          gcMinIdleMillis = millis;
      }
  
      /**
       * Gets the minimum idle time to make an object eligible for garbage
       * collection.
       * @see #setGCMinIdleTime
       */
      public long getGCMinIdleTime() {return gcMinIdleMillis;}
  
      /**
       * Sets the length of time between garbage collection and shrinking runs.
       * This is inexact - if there are many pools with garbage collection and/or
       * shrinking enabled, there will not be a thread for each one, and several
       * nearby actions may be combined.  Likewise if the collection process is
       * lengthy for certain types of pooled objects (not recommended), other
       * actions may be delayed.  This is to prevend an unnecessary proliferation
       * of threads (the total number of which may be limited by your OS, e.g. in
       * a "native threads" VM implementation).  Note that this parameter controls
       * both garbage collection and shrinking - and they will be performed
       * together if both are enabled.  The deafult value is 2 minutes.
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the garbage collection interval after the
       *    pool has been initialized.
       */
      public void setGCInterval(long millis) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          gcIntervalMillis = millis;
      }
  
      /**
       * Gets the length of time between garbage collection and shrinking runs.
       * @see #setGCInterval
       */
      public long getGCInterval() {return gcIntervalMillis;}
  
      /**
       * Sets whether a request for an object will block if the pool size is
       * maxed out and no objects are available.  If set to block, the request
       * will not return until an object is available.  Otherwise, the request
       * will return null immediately (and may be retried).  If multiple
       * requests block, there is no guarantee which will return first.  The
       * default is not to block (to return null).
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the blocking parameter after the
       *    pool has been initialized.
       */
      public void setBlocking(boolean blocking) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          this.blocking = blocking;
      }
  
      /**
       * Gets whether a request for an object will block if the pool size is
       * maxed out and no objects are available.
       * @see #setBlocking
       */
      public boolean isBlocking() {return blocking;}
  
      /**
       * Sets whether object clients can update the last used time.  If not, the
       * last used time will only be updated when the object is given to a client
       * and returned to the pool.  This time is important if shrinking or
       * garbage collection are enabled (particularly the latter).  The default
       * is false.
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to set the timestamp parameter after the
       *    pool has been initialized.
       */
      public void setTimestampUsed(boolean timestamp) {
          if(objects != null)
              throw new IllegalStateException(INITIALIZED);
          trackLastUsed = timestamp;
      }
  
      /**
       * Gets whether object clients can update the last used time.
       */
      public boolean isTimestampUsed() {return trackLastUsed;}
  
      /**
       * Prepares the pool for use.  This must be called exactly once before
       * getObject is even called.  The pool name and object factory must be set
       * before this call will succeed.
       * @throws java.lang.IllegalStateException
       *    Occurs when you try to initialize the pool without setting the object
       *    factory or name, or you initialize the pool more than once.
       */
      public void initialize() {
          if(factory == null || poolName == null)
              throw new IllegalStateException("Factory and Name must be set before 
pool initialization!");
          if(objects != null)
              throw new IllegalStateException("Cannot initialize more than once!");
          objects = new HashMap();
          factory.poolStarted(this);
          lastGC = System.currentTimeMillis();
          collector.addPool(this);
      }
  
      /**
       * Shuts down the pool.  All outstanding objects are closed and all objects
       * are released from the pool.  No getObject or releaseObject calls will
       * succeed after this method is called - and they will probably fail during
       * this method call.
       */
      public void shutDown() {
          collector.removePool(this);
          factory.poolClosing(this);
          HashMap localObjects = objects;
          objects = null;
  
          // close all objects
          for(Iterator it = localObjects.values().iterator(); it.hasNext();) {
              ObjectRecord rec = (ObjectRecord)it.next();
              if(rec.isInUse())
                  factory.returnObject(rec.getClientObject());
              factory.deleteObject(rec.getObject());
              rec.close();
          }
  
          localObjects.clear();
          factory = null;
          poolName = null;
      }
  
      /**
       * Gets an object from the pool.  If all the objects in the pool are in use,
       * creates a new object, adds it to the pool, and returns it.  If all
       * objects are in use and the pool is at maximum size, will block or
       * return null.
       * @see #setBlocking
       */
      public Object getObject() {
          while(true) {
              Iterator it = new HashSet(objects.values()).iterator();
              while(it.hasNext()) {
                  ObjectRecord rec = (ObjectRecord)it.next();
                  if(!rec.isInUse()) {
                      try {
                          rec.setInUse(true);
                          Object ob = rec.getObject();
                          Object result = factory.prepareObject(ob);
                          if(result != ob) rec.setClientObject(result);
                          if(result instanceof PooledObject)
                              ((PooledObject)result).addPoolEventListener(this);
                          log("Pool "+this+" gave out pooled object: "+result);
                          return result;
                      } catch(ConcurrentModificationException e) {}
                  }
              }
  
              // Serialize creating new connections
              synchronized(objects) {  // Don't let 2 threads add at the same time
                  if(minSize == 0 || objects.size() < maxSize) {
                      Object ob = factory.createObject();
                      ObjectRecord rec = new ObjectRecord(ob);
                      objects.put(ob, rec);
                      Object result = factory.prepareObject(ob);
                      if(result != ob) rec.setClientObject(result);
                      if(result instanceof PooledObject)
                          ((PooledObject)result).addPoolEventListener(this);
                      log("Pool "+this+" gave out new object: "+result);
                      return result;
                  } else System.out.println("Object Pool "+poolName+" is full 
("+objects.size()+"/"+maxSize+")!");
              }
  
              if(blocking) {
                  log("Pool "+this+" waiting for a free object");
                  synchronized(this) {
                      try {
                          wait();
                      } catch(InterruptedException e) {}
                  }
              } else {
                  break;
              }
          }
  
          log("Pool "+this+" couldn't find an object to return!");
          return null;
      }
  
      /**
       * Sets the last used time for an object in the pool that is currently
       * in use.  If the timestamp parameter is not set, this call does nothing.
       * Otherwise, the object is marked as last used at the current time.
       * @see #setTimestampUsed
       */
      public void setLastUsed(Object object) {
          if(!trackLastUsed) return;
          Object ob = factory.translateObject(object);
          ObjectRecord rec = (ObjectRecord)objects.get(ob);
          rec.setLastUsed();
      }
  
      /**
       * Returns an object to the pool.  This must be the exact object that was
       * given out by getObject, and it must be returned to the same pool that
       * generated it.  If other clients are blocked waiting on an object, the
       * object may be re-released immediately.
       * @throws java.lang.IllegalArgumentException
       *    Occurs when the object is not in this pool.
       */
      public void releaseObject(Object object) {
          synchronized(object) {
              Object pooled = null;
              try {
                  pooled = factory.translateObject(object);
              } catch(Exception e) {
                  return;        // We can't release it if the factory can't recognize 
it
              }
              if(pooled == null) // We can't release it if the factory can't recognize 
it
                  return;
              ObjectRecord rec = (ObjectRecord)objects.get(pooled);
              if(rec == null) // Factory understands it, but we don't
                  throw new IllegalArgumentException("Object "+object+" is not in pool 
"+poolName+"!");
              if(!rec.isInUse()) return; // Must have been released by GC?
              if(object instanceof PooledObject)
                  ((PooledObject)object).removePoolEventListener(this);
              factory.returnObject(object);
              rec.setInUse(false);
          }
          log("Pool "+this+" returned object "+object+" to the pool.");
          if(blocking) {
              synchronized(this) {
                  notify();
              }
          }
      }
  
      private int getUsedCount() {
          if(objects == null) return 0;
          int total = 0;
          Iterator it = new HashSet(objects.values()).iterator();
          while(it.hasNext()) {
              ObjectRecord or = (ObjectRecord)it.next();
              if(or.isInUse()) ++total;
          }
          return total;
      }
  
      /**
       * Returns the pool name and status.
       */
      public String toString() {
          return poolName+" ["+getUsedCount()+"/"+(objects == null ? 0 : 
objects.size())+"/"+(maxSize == 0 ? "Unlimited" : Integer.toString(maxSize))+"]";
      }
  
  
      // ---- PoolEventListener Implementation ----
  
      /**
       * If the object has been closed, release it.
       */
      public void objectClosed(PoolEvent evt) {
          releaseObject(evt.getSource());
      }
  
      /**
       * If the object had an error, we assume this will propogate and preclude it
       * from being closed, so we will close it.
       */
      public void objectError(PoolEvent evt) {
          releaseObject(evt.getSource());
      }
  
      /**
       * If we're tracking the last used times, update the last used time for the
       * specified object.
       */
      public void objectUsed(PoolEvent evt) {
          if(!trackLastUsed) return;
          setLastUsed(evt.getSource());
      }
  
      long getNextGCMillis(long now) {
          if(!runGC) return Long.MAX_VALUE;
          return lastGC + gcIntervalMillis - now;
      }
  
      // Allow GC if we're within 10% of the desired interval
      boolean isTimeToGC() {
          return System.currentTimeMillis() >=
                 lastGC + Math.round((float)gcIntervalMillis * 0.9f);
      }
  
      void runGCandShrink() {
          if(runGC) { // Garbage collection - return any object that's been out too 
long with no use
              Iterator it = new HashSet(objects.values()).iterator();
              while(it.hasNext()) {
                  ObjectRecord rec = (ObjectRecord)it.next();
                  if(rec.isInUse() && rec.getMillisSinceLastUse() >= gcMinIdleMillis) {
                      releaseObject(rec.getClientObject());
                  }
              }
          }
          if(shrinks) { // Shrinking the pool - remove objects from the pool if they 
have not been used in a long time
               // Find object eligible for removal
              HashSet eligible = new HashSet();
              Iterator it = new HashSet(objects.values()).iterator();
              while(it.hasNext()) {
                  ObjectRecord rec = (ObjectRecord)it.next();
                  if(!rec.isInUse() && rec.getMillisSinceLastUse() > 
shrinkMinIdleMillis)
                      eligible.add(rec);
              }
              // Calculate number of objects to remove
              int count = Math.round(eligible.size() * shrinkPercent);
              if(count == 0 && eligible.size() > 0) count = 1;
              // Attempt to remove that many objects
              it = eligible.iterator();
              for(int i=0; i<count; i++) {
                  if(objects.size() <= minSize) break; // Don't fall below the minimum
                  if(!it.hasNext()) break; // If the objects have meanwhile been 
checked out, we're done
                  try {
                      ObjectRecord rec = (ObjectRecord)it.next();
                      rec.setInUse(true);  // Don't let someone use it while we 
destroy it
                      Object pooled = rec.getObject();
                      objects.remove(pooled);
                      try {
                          factory.deleteObject(pooled);
                      } catch(Exception e) {
                          log("Pool "+this+" factory ("+factory.getClass().getName()+" 
delete error: "+e);
                      }
                      rec.close();
                  } catch(ConcurrentModificationException e) {
                      --i;
                  }
              }
          }
          lastGC = System.currentTimeMillis();
      }
  
      private void log(String message) {
          if(logWriter != null)
              logWriter.println(message);
      }
  }
  
  class BeanFactory extends PoolObjectFactory {
      private Class beanClass;
  
      public BeanFactory(Class beanClass) {
          try {
              beanClass.getConstructor(new Class[0]);
          } catch(NoSuchMethodException e) {
              throw new IllegalArgumentException("Bean class doesn't have no-arg 
constructor!");
          }
          this.beanClass = beanClass;
      }
  
      public Object createObject() {
          try {
              return beanClass.newInstance();
          } catch(Exception e) {
              System.out.println("Unable to create instance of 
"+beanClass.getName()+": "+e);
          }
          return null;
      }
  }
  
  
  
  1.1                  jboss/src/main/org/jboss/minerva/pools/ObjectRecord.java
  
  Index: ObjectRecord.java
  ===================================================================
  /*
   * jBoss, the OpenSource EJB server
   *
   * Distributable under GPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.minerva.pools;
  
  import java.util.*;
  
  /**
   * Stores the properties of an object in a pool.
   * @version $Revision: 1.1 $
   * @author Aaron Mulder ([EMAIL PROTECTED])
   */
  class ObjectRecord {
      private long created;
      private long lastUsed;
      private Object object;
      private Object clientObject;
      private boolean inUse;
  
      /**
       * Created a new record for the specified pooled object.  Objects default to
       * being in use when created, so that they can't be stolen away from the
       * creator by another thread.
       */
      public ObjectRecord(Object ob) {
          created = lastUsed = System.currentTimeMillis();
          object = ob;
          inUse = true;
      }
  
      /**
       * Gets the date when this connection was originally opened.
       */
      public Date getCreationDate() {
          return new Date(created);
      }
  
      /**
       * Gets the date when this connection was last used.
       */
      public Date getLastUsedDate() {
          return new Date(lastUsed);
      }
  
      /**
       * Gets the time (in milliseconds) since this connection was last used.
       */
      public long getMillisSinceLastUse() {
          return System.currentTimeMillis() - lastUsed;
      }
  
      /**
       * Tells whether this connection is currently in use.  This is not
       * synchronized since you probably want to synchronize at a higher level
       * (if not in use, do something), etc.
       */
      public boolean isInUse() {
          return inUse;
      }
  
      /**
       * Sets whether this connection is currently in use.
       * @throws java.util.ConcurrentModificationException
       *          Occurs when the connection is already in use and it is set to be
       *          in use, or it is not in use and it is set to be not in use.
       */
      public synchronized void setInUse(boolean inUse) throws 
ConcurrentModificationException {
          if(this.inUse == inUse)
              throw new ConcurrentModificationException();
          this.inUse = inUse;
          lastUsed = System.currentTimeMillis();
          if(!inUse) clientObject = null;
      }
  
      /**
       * Sets the last used time to the current time.
       */
      public void setLastUsed() {
          lastUsed = System.currentTimeMillis();
      }
  
      /**
       * Gets the pooled object associated with this record.
       */
      public Object getObject() {
          return object;
      }
  
      /**
       * Sets the client object associated with this object.  Not always used.
       */
      public void setClientObject(Object o) {
          clientObject = o;
      }
      /**
       * Gets the client object associated with this object.  If there is none,
       * returns the normal object (which is the default).
       */
      public Object getClientObject() {
          return clientObject == null ? object : clientObject;
      }
  
      /**
       * Shuts down this object - it will be useless thereafter.
       */
      public void close() {
          object = null;
          clientObject = null;
          created = lastUsed = Long.MAX_VALUE;
          inUse = true;
      }
  }
  
  
  1.1                  jboss/src/main/org/jboss/minerva/pools/PoolEvent.java
  
  Index: PoolEvent.java
  ===================================================================
  /*
   * jBoss, the OpenSource EJB server
   *
   * Distributable under GPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.minerva.pools;
  
  import java.util.EventObject;
  
  /**
   * An event caused by an object in a pool.  The event indicates that the
   * object was used, closed, or had an error occur.  The typical response is
   * to update the last used time in the pool for used events, and return the
   * object to the pool for closed or error events.
   * @version $Revision: 1.1 $
   * @author Aaron Mulder ([EMAIL PROTECTED])
   */
  public class PoolEvent extends EventObject {
      /**
       * The object has been closed and should be returned to the pool.  Note this
       * is not a final sort of closing - the object must still be able to be
       * returned to the pool and reused.
       */
      public final static int OBJECT_CLOSED = -8986432;
      /**
       * Indicates that an error occured with the object.  The object will be
       * returned to the pool, since there will presumably be an exception
       * thrown that precludes the client from closing it or returning it
       * normally.  This should not be used for final or destructive errors - the
       * object must stil be able to be returned to the pool and reused.
       */
      public final static int OBJECT_ERROR  = -8986433;
      /**
       * Indicates that the object was used, and its timestamp should be updated
       * accordingly (if the pool tracks timestamps).
       */
      public final static int OBJECT_USED   = -8986434;
  
      private int type;
  
      /**
       * Create a new event.
       * @param source The source must be the object that was returned from the
       *        getObject method of the pool - the pool will use the source for
       *        some purpose depending on the type, so it cannot be an arbitrary
       *        object.
       * @param type The event type.
       */
      public PoolEvent(Object source, int type) {
          super(source);
          if(type != OBJECT_CLOSED && type != OBJECT_ERROR && type != OBJECT_USED)
              throw new IllegalArgumentException("Invalid event type!");
          this.type = type;
      }
  
      /**
       * Gets the event type.
       * @see #OBJECT_CLOSED
       * @see #OBJECT_USED
       * @see #OBJECT_ERROR
       */
      public int getType() {
          return type;
      }
  }
  
  
  1.1                  jboss/src/main/org/jboss/minerva/pools/PoolEventListener.java
  
  Index: PoolEventListener.java
  ===================================================================
  /*
   * jBoss, the OpenSource EJB server
   *
   * Distributable under GPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.minerva.pools;
  
  /**
   * A listener for object pool events.
   * @version $Revision: 1.1 $
   * @author Aaron Mulder ([EMAIL PROTECTED])
   */
  public interface PoolEventListener {
      /**
       * The pooled object was closed and should be returned to the pool.
       */
      public void objectClosed(PoolEvent evt);
      /**
       * The pooled object had an error and should be returned to the pool.
       */
      public void objectError(PoolEvent evt);
      /**
       * The pooled object was used and its timestamp should be updated.
       */
      public void objectUsed(PoolEvent evt);
  }
  
  
  1.1                  jboss/src/main/org/jboss/minerva/pools/PoolGCThread.java
  
  Index: PoolGCThread.java
  ===================================================================
  /*
   * jBoss, the OpenSource EJB server
   *
   * Distributable under GPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.minerva.pools;
  
  import java.util.*;
  
  /**
   * Runs garbage collection on all available pools.  Only one GC thread is
   * created, no matter how many pools there are - it just tries to calculate
   * the next time it should run based on the figues for all the pools.  It will
   * run on any pools which are "pretty close" to their requested time.
   * @version $Revision: 1.1 $
   * @author Aaron Mulder ([EMAIL PROTECTED])
   */
  class PoolGCThread extends Thread {
      private HashSet pools = new HashSet();
  
      PoolGCThread() {
          super("Minerve ObjectPool GC Thread");
          setDaemon(true);
      }
  
      public void run() {
          while(true) {
              // Don't do anything while there's nothing to do
              waitForPools();
              // Figure out how long to sleep
              long delay = getDelay();
              // Sleep
              if(delay > 0l) {
                  try {
                      sleep(delay);
                  } catch(InterruptedException e) {}
              }
              // Run garbage collection on eligible pools
              runGC();
          }
      }
  
      private synchronized void waitForPools() {
          while(pools.size() == 0) {
              try {
                  wait();
              } catch(InterruptedException e) {
              }
          }
      }
  
      private synchronized long getDelay() {
          long next = Long.MAX_VALUE;
          long now = System.currentTimeMillis();
          long current;
          for(Iterator it = pools.iterator(); it.hasNext();) {
              ObjectPool pool = (ObjectPool)it.next();
              current = pool.getNextGCMillis(now);
              if(current < next) next = current;
          }
          return next >= 0l ? next : 0l;
      }
  
      private synchronized void runGC() {
          for(Iterator it = pools.iterator(); it.hasNext();) {
              ObjectPool pool = (ObjectPool)it.next();
              if(pool.isTimeToGC())
                  pool.runGCandShrink();
          }
      }
  
      synchronized void addPool(ObjectPool pool) {
          if(pool.isGCEnabled())
              pools.add(pool);
          notify();
      }
  
      synchronized void removePool(ObjectPool pool) {
          pools.remove(pool);
      }
  }
  
  
  1.1                  jboss/src/main/org/jboss/minerva/pools/PoolObjectFactory.java
  
  Index: PoolObjectFactory.java
  ===================================================================
  /*
   * jBoss, the OpenSource EJB server
   *
   * Distributable under GPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.minerva.pools;
  
  /**
   * Creates objects to be used in an object pool.  This is a class instead of
   * an interface so you can ignore any of the methods you don't need.
   * @version $Revision: 1.1 $
   * @author Aaron Mulder ([EMAIL PROTECTED])
   */
  public abstract class PoolObjectFactory {
      /**
       * Creates a new object to be stored in an object pool.  This is the
       * instance that will actually be sotred in the pool and reused.  If you
       * want to wrap it somehow, or return instances of a different type that
       * refers to these, you can implement prepareObject.
       * @see #prepareObject
       */
      public abstract Object createObject();
  
      /**
       * Indicates to the factory that the pool has started up.  This will be
       * called before any other methods of the factory are called (on behalf of
       * this pool).
       * @param pool The pool that is starting.  You may decide to allow
       *    multiple pools you use your factory, or to restrict it to a one-to-one
       *    relationship.
       */
      public void poolStarted(ObjectPool pool) {
      }
  
      /**
       * Prepares an object to be returned to the client.  This may be used to
       * configure the object somehow, or actually return a completely different
       * object (so long as the original can be recovered in translateObject or
       * returnObject).  This will be called whenever an object is returned to
       * the client, whether a new object or a previously pooled object.
       * @param pooledObject The object in the pool, as created by createObject.
       * @return The object to return to the client.  If different, the pooled
       *    object must be recoverable by translateObject and returnObject.
       */
      public Object prepareObject(Object pooledObject) {
          return pooledObject;
      }
  
      /**
       * If the objects supplied to the client are different than the objects in
       * the pool, extracts a pool object from a client object.  This should only
       * be called between prepareObject and returnObject for any given pool
       * object (and associated client object).  However, it may be called once
       * after an object has been released if the garbage collector and a client
       * attempt to release an object at the same time.  In this case, this
       * method may work, return null, or throw an exception and the pool will
       * handle it gracefully.  The default implementation returns the parameter
       * object (assumes client and pooled objects are the same).
       * @param clientObject The client object, as returned by prepareObject
       * @return The pooled object, as originally returned by createObject
       */
      public Object translateObject(Object clientObject) {
          return clientObject;
      }
  
      /**
       * Prepares an object to be returned to the pool.  Any cleanup or reset
       * actions should be performed here.  This also has the same effect as
       * translateObject (only relevant if the pooled objects are different than
       * the objects supplied to the client).
       * @param clientObject The client object, as returned by prepareObject
       * @return The pooled object, as originally returned by createObject, ready
       *     to be put back in the pool and reused.
       */
      public Object returnObject(Object clientObject) {
          return clientObject;
      }
  
      /**
       * Indicates to the factory that the pool is closing down.  This will be
       * called before all the instances are destroyed.  There may be calls to
       * returnObject or translateObject after this, but no calls to
       * createObject or prepareObject (on behalf of this pool).
       * @param pool The pool that is closing.  You may decide to allow
       *    multiple pools you use your factory, or to restrict it to a one-to-one
       *    relationship.
       */
      public void poolClosing(ObjectPool pool) {
      }
  
      /**
       * Permanently closes an object, after it is removed from the pool.  The
       * object will not be returned to the pool - after this, it is gone.  This
       * is called when the pool shrinks, and when the pool is shut down.
       */
      public void deleteObject(Object pooledObject) {
      }
  }
  
  
  1.1                  jboss/src/main/org/jboss/minerva/pools/PooledObject.java
  
  Index: PooledObject.java
  ===================================================================
  /*
   * jBoss, the OpenSource EJB server
   *
   * Distributable under GPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.minerva.pools;
  
  /**
   * Optional interface for an object in an ObjectPool.  If the objects created
   * by the ObjcetFactory implement this, the pool will register as a listener
   * when an object is checked out, and deregister when the object is returned.
   * Then if the object sends a close or error event, the pool will return the
   * object to the pool without the client having to do so explicitly.
   * @version $Revision: 1.1 $
   * @author Aaron Mulder ([EMAIL PROTECTED])
   */
  public interface PooledObject {
      /**
       * Adds a new listener.
       */
      public void addPoolEventListener(PoolEventListener listener);
      /**
       * Removes a listener.
       */
      public void removePoolEventListener(PoolEventListener listener);
  }
  
  
  1.1                  jboss/src/main/org/jboss/minerva/pools/package.html
  
  Index: package.html
  ===================================================================
  <HTML>
    <HEAD>
      <TITLE>Minerva Pools: Package org.jboss.minerva.pools</TITLE>
    </HEAD>
    <BODY BGCOLOR="WHITE">
      <P>Contains classes required for generic object pools.  With these classes,
        you can create pools for any type of objects - all you do is supply the
        object factory.  There are specific object factory implementations for
        JDBC connections in the package <B>org.minerva.factories</B>.</P>
      <P>This documentation links to the J2SE v1.3 documentation on the JavaSoft
        web site, but the classes are compatible with v1.2 as well.</P>
    </BODY>
  </HTML>
  
  

Reply via email to