Package org.xmlBlaster.util.pool

Source Code of org.xmlBlaster.util.pool.PoolManager

/*------------------------------------------------------------------------------
Name:      PoolManager.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
------------------------------------------------------------------------------*/
package org.xmlBlaster.util.pool;

import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.Timeout;
import org.xmlBlaster.util.def.ErrorCode;

import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;


/**
* A little framework to handle a pool of limited resources.
* <p />
* You can use this pool as a base class handling of your limited resources
* like a 'JDBC connection pool' or a 'thread pool'.
* <p />
* The important attributes of a resource are gathered in the <code>ResourceWrapper</code> class.
* <p />
* You can easily handle bigger number of resources with good performance.
* <p />
* To find out how to use it see the TestPool example in the main() method of this class.
* <p />
* <pre>
*    State chart of resource handling:
*
*      +<------- reserve() if(numIdle==0) ---------------------+
*      |                                                       |
*      |     +<- reserve() --+        +<-preReserve() *-+      |
*      |     | if(numIdle>0) |        |                 |      |
*      |     |               |        |                 |      |
*    #########               ##########                 ##########
*   #         #             #          #               #          #
*   #  busy   #             #  idle    #               #  undef   #
*   #         #             #          #               #          #
*    #########               ##########                 ##########
*      |  |  |               | |  | | |                 | |  |  |
*      |  |  |   Explicit    | |  | | |   Explicit      | |  |  |
*      |  |  +-- release() ->+ |  | | +-- erase() ----->+ |  |  |
*      |  |                    |  | |                     |  |  |
*      |  +busyToIdleTimeout()>+  | +idleToEraseTimeout()>+  |  |
*      |                          |                          |  |
*      |                          +-- erase on max cycles *->+  |
*      |                                                        |
*      +--------- busyToEraseTimeout() since creation *-------->+
*
* </pre>
* There are three states:
* <ul>
*    <li>busy: The resource is in use
*    </li>
*    <li>idle: The resource is allocated and ready but not in use
*    </li>
*    <li>undef: The resource is not yet allocated or it is erased again
*    </li>
* </ul>
* <p>
* Note that state transitions marked with '*' are not yet implemented.
* If you need one of them, code it and contribute it please.
* <p>
* You can choose which states you wish for your resource and how timeouts
* are used to handle state transitions.<br />
* </p>
* <p>
* For example if you want to pool user login sessions and want to do
* an auto logout after 60 minutes, you would use the busyToIdleTimeout and set it to 60*60*1000.<br />
* If a user is active you can refresh the session with busyRefresh().<br />
* Often you want to use your own generated sessionId as the primary key
* for this resource, you can pass it as the instanceId argument to reserve(sessionId).<br />
* (See example [2] in this main() method)
* </p>
* <p>
* If you want to pool JDBC connections, reserve() a connection before you do your query
* and release() it immediately again. If you
* want to close connections after peak usage, you could set a idleToEraseTimeout,
* to erase your JDBC connection after some time not used (reducing the current pool size).<br />
* Note that in this example the connections are anonymous (all are logged in to the database
* with the same user name), it is not important which you receive.<br />
* (See example [1] in this main() method)
* </p>
* <p>
* For an implementation example see TestPoolManager.java
* </p>
*
* <p>This code is derived from the org.jutils.pool package.</p>
* @author "Marcel Ruff" <ruff@swand.lake.de>
* @see org.xmlBlaster.util.pool.ResourceWrapper
* @see org.xmlBlaster.test.classtest.TestPoolManager
*/
public final class PoolManager
{
   /** Nice, unique name for logging output */
   private String ME = "PoolManager";
   /** A nice name for the generated id */
   private String poolName = "Resource";
   /** The callback into the using application, which needs to implement the interface I_PoolManager */
   private I_PoolManager callback = null;
   /** Holds busy resources */
   private java.util.Hashtable busy = null;
   /** Holds free resources */
   private java.util.Vector idle = null;
   /** Default maximum pool size (number of resources) */
   private int maxInstances = 100;
   /** Default maximum busy time of a resource, on timeout it changes state from 'busy' to 'idle' */
   private long busyToIdleTimeout = 0;
   /* Default maximum life span of a resource, on timeout it changes state from 'busy' to 'undef' (it is deleted) */
   //private long busyToEraseTimeout = 0;
   /** Default maximum idle span of a resource, on timeout it changes state from 'idle' to 'undef' (it is deleted) */
   private long idleToEraseTimeout = 0;
   /** Unique counter to generate IDs */
   private long counter = 1;
   /** Use this constant with the reserve() method, the PoolManager uses the hashCode() of your object */
   public final static String USE_HASH_CODE = "h";
   /** Use this constant with the reserve() method, the PoolManager uses the toString() of your object, if no toString() exists the object reference is used, e.g. "oracle.jdbc.driver.OracleConnection@443226" */
   public final static String USE_OBJECT_REF = "o";
   /** Use this constant with the reserve() method, the PoolManager generates a random, unique in universe session ID */
   public final static String GENERATE_RANDOM = "r";
   /** Triggers transitions */
   private Timeout transitionTimer;


   private final Object meetingPoint = new Object();

   /**
   * Create a new pool instance with the desired behavior.
   * <p />
   * Implementation:<br />
   * There are two list in this class
   * <ul>
   *    <li>busy: Holds resources which are currently busy</li>
   *    <li>idle: Holds resources in the free pool, waiting to be reused</li>
   * </ul>
   * If you ask for a new resource, this will move into the busy list<br />
   * Is a resource released or a specified timeout occurred, it is moved into the idle list.
   * <p />
   * @param poolName       A nice name for this pool manager instance.
   * @param callback       The interface 'I_PoolManager' callback
   * @param maxInstances   Max. number of resources in this pool.
   * @param busyToIdleTimeout Max. busy time of this resource in milli seconds<br />
   *                       On timeout it changes state from 'busy' to 'idle'.<br />
   *                       You can overwrite this value for each resource instance<br />
   *                       0 switches it off<br />
   *                       You get called back through I_PoolManager.busyToIdle() on timeout
   *                       allowing you to code some specific handling.
   * @param idleToEraseTimeout Max. idle time span of this resource in milli seconds<br />
   *                     On timeout it changes state from 'idle' to 'undef' (it is deleted).<br />
   *                     You can overwrite this value for each resource instance<br />
   *                     0 switches it off<br />
   *                     You get called back through I_PoolManager.toErased() on timeout
   *                     allowing you to code some specific handling.
   */
   public PoolManager(String poolName, I_PoolManager callback, int maxInstances, long busyToIdleTimeout, long idleToEraseTimeout) {
      this.poolName = poolName; // e.g. "SessionId"
      this.callback = callback;
      this.ME = poolName + "-PoolManager"; // e.g. "SessionId-PoolManager"
      this.setMaxInstances(maxInstances);
      this.setBusyToIdleTimeout(busyToIdleTimeout);
      this.setIdleToEraseTimeout(idleToEraseTimeout);
      this.busy = new java.util.Hashtable(maxInstances);
      this.idle = new java.util.Vector(maxInstances);
      /*
        if (busyToIdleTimeout > 0 || idleToEraseTimeout > 0) {
       System.err.println(ME + ": Created PoolManager with a maximum of " + maxInstances + " instances" +
        " and a default busy recycling-timeout of " + TimeHelper.millisToNice(this.busyToIdleTimeout) +
        " and a default idle recycling-timeout of " + TimeHelper.millisToNice(this.idleToEraseTimeout));
        }
        else
       System.err.println(ME + ": Created PoolManager with a maximum of " + maxInstances + " instances without auto-recycling.");
        */
   }

   public Timeout getTransistionTimer() {
      if (this.transitionTimer == null) {
         synchronized(this) {
            if (this.transitionTimer == null)
               this.transitionTimer = new Timeout("XmlBlaster.PoolManagerTimer");
         }
      }
      return this.transitionTimer;
   }

   /**
   * Set the maximum pool size.
   * @param maxInstances How many resources are allowed
   */
   private void setMaxInstances(int maxInstances) {
      this.maxInstances = maxInstances;
   }

   /**
   * On timeout, the resource changes state from 'busy' to 'idle'.<br />
   * @param busyToIdleTimeout Max. busy time of this resource in milli seconds<br />
   *                       On timeout it changes state from 'busy' to 'idle'.<br />
   *                       You can overwrite this value for each resource instance<br />
   *                       0 switches it off
   */
   private void setBusyToIdleTimeout(long busyToIdleTimeout) {
      if (busyToIdleTimeout > 0 && busyToIdleTimeout < 100) {
         // System.err.println(ME + ": Setting minimum release timeout from " + busyToIdleTimeout + " to 100 milli seconds.");
         this.busyToIdleTimeout = 100;
      }
      else
         this.busyToIdleTimeout = busyToIdleTimeout;
   }

   /**
   * Set the max. life span of the resources.
   * @param idleToEraseTimeout Max. idle time of this resource in milli seconds<br />
   *                       You can overwrite this value for each resource instance<br />
   *                       0 switches it off
   */
   private void setIdleToEraseTimeout(long idleToEraseTimeout) {
      if (idleToEraseTimeout > 0 && idleToEraseTimeout < 100) {
         // System.err.println(ME + ": Setting minimum idleErase timeout from " + idleToEraseTimeout + " to 100 milli seconds.");
         this.idleToEraseTimeout = 100;
      }
      else
         this.idleToEraseTimeout = idleToEraseTimeout;
   }

   /**
   * Get a new resource.
   * <p />
   * The life span is set to the default value of the pool.
   * <p />
   * The instance Id is random and unique generated.
   * @exception XmlBlasterException Error with random generator
   */
   public ResourceWrapper reserve() throws XmlBlasterException {
      return reserve(busyToIdleTimeout, idleToEraseTimeout, GENERATE_RANDOM);
   }

   /**
   * Get a new resource.
   * <p />
   * The life span is set to the default value of the pool.
   * <p />
   * @param instanceId See description in other reserve() method.
   * @exception XmlBlasterException Error with random generator
   */
   public ResourceWrapper reserve(String instanceId) throws XmlBlasterException {
      return reserve(busyToIdleTimeout, idleToEraseTimeout, instanceId);
   }

   /**
   * Get a new resource.
   *
   * @param localBusyToIdleTimeout Max. busy time of this resource in milli seconds,
   *                       only for this current resource<br />
   *                       Overwrite locally the default<br />
   *                       0 switches busy timeout off
   *
   * @param localIdleToEraseTimeout Max. idle time of this resource in milli seconds,
   *                       only for this current resource<br />
   *                       Overwrite locally the default<br />
   *                       0 switches idle timeout off
   *
   * @param instanceId If given and string length > 1, the delivered ID is used:
   *             If in busy list found, this is returned, else a new is created.<br />
   *             USE_HASH_CODE   - The PoolManager generates a simple one (hashCode())<br />
   *             USE_OBJECT_REF  - The object reference is used<br />
   *             GENERATE_RANDOM - The PoolManager generates a random, unique in universe session ID
   *
   * @return rw The resource handle (always != null)
   *
   * @exception XmlBlasterException Error with random generator
   */
   public ResourceWrapper reserve(long localBusyToIdleTimeout, long localIdleToEraseTimeout, String instanceId) throws XmlBlasterException {
      synchronized (meetingPoint) {
         ResourceWrapper rw = null;
         if (instanceId != null && instanceId.length() > 1) {
            rw = findBusySilent(instanceId);
            if (rw != null) {
               // System.out.println(ME + ": Reconnected to busy resource '" + instanceId + "' ...");
               return rw;
            }
         }
         if (idle.size() > 0) {
            // System.out.println(ME + ": Resource is recycled from idle pool ...");
            rw = (ResourceWrapper) idle.lastElement();
            rw.init(createId(instanceId), rw.getResource(), localBusyToIdleTimeout, localIdleToEraseTimeout);
            swap(rw, true);
            // System.out.println(ME + ": Access on cached resource '" + rw.getInstanceId() + "' granted");
            // dumpState(rw.getInstanceId());
         }
         else
            if (busy.size() >= maxInstances) {
               // CAUTION: ErrorCode.RESOURCE_EXHAUST is a well defined string which may not be changed, it is used from the caller to identify the situation
               XmlBlasterException e = new XmlBlasterException("ResourceExhaust", "Sorry, " + maxInstances + " resources consumed, no more resources available");
               e.changeErrorCode(ErrorCode.RESOURCE_EXHAUST);
               throw e;
            }
            else {
               instanceId = createId(instanceId);
               Object resource = callback.toCreate(instanceId);
               rw = new ResourceWrapper(this, instanceId, resource, localBusyToIdleTimeout, idleToEraseTimeout);
               rw.toBusy();
               busy.put(rw.getInstanceId(), rw);
               // System.out.println(ME + ": Granted access to new resource '" + rw.getInstanceId() + "'");
               // dumpState(rw.getInstanceId());
            }
         return rw;
      }
   }

   /**
   * Release a resource explicitly from 'busy' into the 'idle' pool.
   * @param instanceId The unique resource ID
   * @exception XmlBlasterException
   */
   public void release(String instanceId) throws XmlBlasterException {
      synchronized (meetingPoint) {
         swap(findLow(instanceId), false);
      }
      //System.out.println(ME + ": Resource <" + instanceId + "> released explicitly");
      // dumpState(instanceId);
   }

   /**
   *  Generate a unique resource ID <br>
   *
   *  @instanceId USE_HASH_CODE=null   ResourceWrapper generates a simple ID = resource.hashCode()<br />
   *              "..."                Your supplied ID is used<br />
   *              GENERATE_RANDOM=""   A random ID is generated, unique in universe and over time
   *  @return unique ID or null
   *  @exception XmlBlasterException random generator
   */
   private String createId(String instanceId) throws XmlBlasterException {
      if (instanceId == null || instanceId.equals(GENERATE_RANDOM) == false)
         return instanceId; // ResourceWrapper generates a simple id

      try {
         String ip;
         try {
            java.net.InetAddress addr = java.net.InetAddress.getLocalHost();
            ip = addr.getHostAddress();
         }
         catch (Exception e) {
            // System.err.println(ME + ": Can't determin your IP address");
            ip = "localhost";
         }

         // This is a real random, but probably not necessary here:
         // Random random = new java.security.SecureRandom();
         java.util.Random ran = new java.util.Random(); // is more or less currentTimeMillis

         // Note: We should include the process ID from this JVM on this host to be granted unique

         //   <IP-Address>-<InstanceName>-<TimestampMilliSec>-<RandomNumber>-<LocalCounter>
         return ip + "-" + poolName + "-" + System.currentTimeMillis() + "-" + ran.nextInt() + "-" + (counter++);
      }
      catch (Exception e) {
         String text = "Can't generate a unique instanceId: " + e.toString();
         throw new XmlBlasterException("ResourceNoId", text);
      }
   }

   /**
   * Idle - busy swapper.
   */
   private void swap(ResourceWrapper rw, boolean toBusy) {
      if (rw == null) {
         System.err.println("##########INTERNAL PROBLEM IN PollManager");
         return;
      }
      if (toBusy) {
         idle.removeElementAt(idle.size() - 1);
         busy.put(rw.getInstanceId(), rw);
         rw.toBusy();
         callback.idleToBusy(rw.getResource());
         //System.err.println("Swapped to busy:" + rw.getInstanceId());
      }
      else { // recycling ...
         busy.remove(rw.getInstanceId());
         idle.addElement(rw);
         rw.toIdle();
         callback.busyToIdle(rw.getResource());
         //System.err.println("Swapped to idle:" + rw.getInstanceId());
      }
   }

   /**
   * Find a resource in busy list.
   * @param instanceId The unique resource ID
   * @return The handle containing the resource.
   */
   private ResourceWrapper findBusySilent(String instanceId) {
      if (instanceId == null)
         return null;
      return (ResourceWrapper) busy.get(instanceId);
   }

   /**
   * Find a resource in idle list.
   * <p />
   * Note that this is currently a linear search (not high performing).
   * @param instanceId The unique resource ID
   * @return The handle containing the resource.
   */
   private ResourceWrapper findIdleSilent(String instanceId) {
      if (instanceId == null)
         return null;
      for (int ii = 0; ii < idle.size(); ii++) {
         ResourceWrapper rw = (ResourceWrapper) idle.elementAt(ii);
         if (rw.getInstanceId().equals(instanceId))
            return rw;
      }
      return null;
   }

   /**
   * Find a resource in busy list.
   * @param instanceId The unique resource ID
   * @return Handle containing resource
   * @exception XmlBlasterException "ResourceNotFound"
   */
   private ResourceWrapper findLow(String instanceId) throws XmlBlasterException {
      if (instanceId == null) {
         String text = "Your resource ID is null";
         throw new XmlBlasterException("ResourceNotFound", text);
      }
      ResourceWrapper rw = findBusySilent(instanceId);
      if (rw == null) {
         String text = "Resource '" + instanceId + "' is invalid, timed out?";
         throw new XmlBlasterException("ResourceNotFound", text);
      }
      return rw;
   }

   /**
   * Test if the resource is busy.
   *
   * @param instanceId The unique resource ID
   * @return true - is in 'busy' state<br />
   *         false - is in 'idle' state or unknown
   */
   public boolean isBusy(String instanceId) {
      ResourceWrapper rw = findBusySilent(instanceId);
      return (rw != null);
   }

   /**
   * Restart countdown for resource life cycle.
   * <p />
   * Rewind the timeout for 'busy' to 'idle' transition.
   * @param instanceId The unique resource ID
   * @exception XmlBlasterException ResourceNotFound
   */
   public void busyRefresh(String instanceId) throws XmlBlasterException {
      synchronized (meetingPoint) {
         ResourceWrapper rw = findLow(instanceId);
         rw.touchBusy();
      }
   }

   /**
   * Number of resources in the 'busy' list.
   * @return Number of 'busy' resources
   */
   public int getNumBusy() {
      return busy.size();
   }

   /**
   * Number of resources in the 'idle' list.
   * @return Number of 'idle' resources
   */
   public int getNumIdle() {
      return idle.size();
   }

   /**
   * Cleanup.
   */
   protected void finalize() throws Throwable {
      destroy();
   }

   /**
   * Dump the current state of this pool.
   */
   public String getState() {
      return "Overview for '" + ME + "':" + " BUSY=" + busy.size() + " IDLE=" + idle.size() + " MAX_AVAILABLE=" + (maxInstances - busy.size());
   }

   /**
   * Dump state of this object into a XML ASCII string.
   * <p />
   * @return internal state of this PoolManager as a XML ASCII string
   */
   public final String toXml() {
      return toXml((String) null);
   }

   /**
   * Dump state of this object into a XML ASCII string.
   * <p />
   * @param extraOffset indenting of tags for nice output
   * @return internal state of this PoolManager as a XML ASCII string
   */
   public final String toXml(String extraOffset) {
      StringBuffer buf = new StringBuffer();
      String offset = "\n";
      if (extraOffset == null)
         extraOffset = "";
      offset += extraOffset;
      synchronized (meetingPoint) {
         buf.append(offset).append("<").append(ME).append(" maxInstances='").append(maxInstances);
         buf.append("' busyToIdleTimeout='").append(busyToIdleTimeout);
         buf.append("' idleToEraseTimeout='").append(idleToEraseTimeout).append("'>");
         buf.append(offset).append("   <busy num='").append(busy.size()).append("'>");
         for (Enumeration e = busy.elements(); e.hasMoreElements();) {
            ResourceWrapper rw = (ResourceWrapper) e.nextElement();
            buf.append(rw.toXml(extraOffset + "   "));
         }
         buf.append(offset).append("   </busy>");
         buf.append(offset).append("   <idle num='").append(idle.size()).append("'>");
         for (int ii = 0; ii < idle.size(); ii++) {
            ResourceWrapper rw = (ResourceWrapper) idle.elementAt(ii);
            buf.append(rw.toXml(extraOffset + "   "));
         }
         buf.append(offset).append("   </idle>");
         buf.append(offset).append("</" + ME + ">");
      }
      return buf.toString();
   }

   /**
   * Recycle busy resource after timeout.
   * <p />
   * This method is a callback from ResourceWrapper
   * @parameter userData The ResourceWrapper object
   */
   void timeoutBusyToIdle(ResourceWrapper rw) {
      // System.out.println(ME + ": Resource '" + rw.getInstanceId() + "' is recycled from 'busy' state, was not in use since " + TimeHelper.millisToNice(rw.busyElapsed()));
      synchronized (meetingPoint) {
         swap(rw, false);
      }
      // dumpState(rw.getInstanceId());
   }

   /**
   * Erase an idle resource after timeout.
   * <p />
   * This method is a callback from ResourceWrapper
   * @parameter userData The ResourceWrapper object
   */
   void timeoutIdleToErase(ResourceWrapper rw) {
      // System.out.println(ME + ": Resource '" + rw.getInstanceId() + "' is erased from 'idle' state after timeout");
      erase(rw);
   }

   /**
   * Explicitly remove a resource.
   * <p />
   * It is erased if it is found in the idle list or even when it is busy.
   * @param instanceId The unique resource ID
   */
   public void erase(String instanceId) {
      if (instanceId == null || instanceId.length() < 1)
         return;
      synchronized (meetingPoint) {
         ResourceWrapper rw = findBusySilent(instanceId);
         if (rw == null) {
            rw = findIdleSilent(instanceId);
         }
         erase(rw);
      }
   }

   /**
   * Remove a resource.
   * <p />
   * Remove it from the 'idle' or the 'busy' list.
   * @param rw The resource wrapper object
   */
   private void erase(ResourceWrapper rw) {
      if (rw == null)
         return;
      synchronized (meetingPoint) {
         if (busy.get(rw.getInstanceId()) != null) {
            swap(rw, false); // move to idle ... calls busyToIdle() callback
            //busy.remove(rw.getInstanceId());
         }
         idle.remove(rw);
         callback.toErased(rw.getResource());
         rw.destroy();
      }
      // dumpState(rw.getInstanceId());
   }

   /**
   * Cleanup everything.
   */
   public void destroy() {
      // System.out.println(ME + ": Freeing all resources, " + busy.size() + " are busy and " + idle.size() + " idle.");
      synchronized (meetingPoint) {
         java.util.Enumeration e = busy.elements();
         for (; e.hasMoreElements();) {
            erase((ResourceWrapper) e.nextElement());
         }
         busy.clear();
         for (int ii = 0; ii < idle.size(); ii++) {
            ResourceWrapper rw = (ResourceWrapper) idle.elementAt(ii);
            callback.toErased(rw.getResource());
            rw.destroy();
         }
         idle.removeAllElements();
      }
   }
}
TOP

Related Classes of org.xmlBlaster.util.pool.PoolManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.