/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.ejb.plugins;

import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.util.Stack;
import java.util.Iterator;
import java.lang.reflect.Constructor;

import org.jboss.ejb.Container;
import org.jboss.ejb.InstancePool;
import org.jboss.ejb.EnterpriseContext;

import org.w3c.dom.Element;

import EDU.oswego.cs.dl.util.concurrent.FIFOSemaphore;

import org.jboss.deployment.DeploymentException;
import org.jboss.metadata.MetaData;
import org.jboss.metadata.XmlLoadable;
import org.jboss.logging.Logger;

/**
 *	Abstract Instance Pool class containing the basic logic to create
 *  an EJB Instance Pool.
 *
 *	@see <related>
 *	@author <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
 *	@author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
 * @author <a href="mailto:andreas.schaefer@madplanet.com">Andreas Schaefer</a>
 * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>
 * @author <a href="mailto:scott.stark@jboss.org">Scott Stark/a>
 *
 * @version $Revision: 1.9.6.9 $
 *
 *  <p><b>Revisions:</b>
 *  <p><b>20010704 marcf:</b>
 *  <ul>
 *  <li>- Pools if used, do not reuse but restock the pile with fresh instances
 *  </ul>
 *  <p><b>20010920 Sacha Labourey:</b>
 *  <ul>
 *  <li>- Pooling made optional and only activated in concrete subclasses for SLSB and MDB
 *  </ul>
 *  <p><b>20011208 Vincent Harcq:</b>
 *  <ul>
 *  <li>- Added InstancePoolFeeder concept for initializing the pool.
 *  </ul>
 */
public abstract class AbstractInstancePool
   implements InstancePool, XmlLoadable
{
   // Constants -----------------------------------------------------

   // Attributes ----------------------------------------------------
   /** A semaphore that is set when the strict max size behavior is in effect.
    When set, only maxSize instance may be active and any attempt to get an
    instance will block until an instance is freed.
    */
   private FIFOSemaphore strictMaxSize;
   /** The time in milliseconds to wait for the strictMaxSize semaphore.
    */
   private long strictTimeout = Long.MAX_VALUE;
   /** The logging interface */
   protected Logger log = Logger.getLogger(this.getClass());
   /** The Container the instance pool is associated with */
   protected Container container;
   /** The instance pool stack */
   protected Stack pool = new Stack();
   /** The maximum number of instances allowed in the pool */
   protected int maxSize = 30;
   /** determine if we reuse EnterpriseContext objects i.e. if we actually do pooling */
   protected boolean reclaim = false;
   /** The pool seed task set from the feeder-policy config element */
   protected InstancePoolFeeder poolFeeder;

   // Static --------------------------------------------------------

	// Constructors --------------------------------------------------

	// Public --------------------------------------------------------

	/**
	*   Set the callback to the container. This is for initialization.
	*   The IM may extract the configuration from the container.
	*
	* @param   c
	*/
   public void setContainer(Container c)
   {
      this.container = c;
   }

   /**
   * @return Callback to the container which can be null if not set proviously
   **/
   public Container getContainer()
   {
      return container;
   }

   public void init()
      throws Exception
   {
   }

   public void start()
      throws Exception
   {
      if( poolFeeder != null && poolFeeder.isStarted() == false )
         poolFeeder.start();
   }

   public void stop()
   {
      if( poolFeeder != null )
         poolFeeder.stop();
      poolFeeder = null;
   }

   public void destroy()
   {
      freeAll();
      this.container = null;
   }

   /**
    * A pool is reclaim if it push back its dirty instances in its stack.
    */
   public boolean getReclaim()
   {
      return reclaim;
   }

   public void setReclaim(boolean reclaim)
   {
      this.reclaim = reclaim;
   }

   /** Add a instance in the pool by invoking create() with a new
    bean instance.
    @exception Exception, thrown on ctx creation failure
    */
   public void add()
      throws Exception
   {
      EnterpriseContext ctx = null;
      try
      {
         ctx = create(container.createBeanClassInstance());
      }
      catch (InstantiationException e)
      {
         throw new ServerException("Could not instantiate bean", e);
      }
      catch (IllegalAccessException e)
      {
         throw new ServerException("Could not instantiate bean", e);
      }
      if( log.isTraceEnabled() )
         log.trace("Add instance "+this+"#"+ctx);
      this.pool.push(ctx);
   }

   /** Get an instance without identity. Can be used by finders,create-methods,
    and activation. This method cannot be synchronized since it may block on
    the strictMaxSize semaphore.

    @return Context with instance
    @exception RemoteException
   */
   public EnterpriseContext get()
      throws Exception
   {
      boolean trace = log.isTraceEnabled();
      if( trace )
         log.trace("Get instance "+this+"#"+pool.empty()+"#"+getContainer().getBeanClass());

      if( strictMaxSize != null )
      {
         // Block until an instance is available
         boolean acquired = strictMaxSize.attempt(strictTimeout);
         if( trace )
            log.trace("Acquired("+acquired+") strictMaxSize semaphore, remaining="+strictMaxSize.permits());
         if( acquired == false )
            throw new ServerException("Failed to acquire the pool semaphore, strictTimeout="+strictTimeout);
      }
      EnterpriseContext ctx = internalGet();
      return ctx;
   }

   /** Called by the public get() method with the strictMaxSize has been acquired
    if strictMaxSize is being used. This method obtains a ctx from the
    pool if it is not empty, else a new ctx is created by calling add().
    */
   private synchronized EnterpriseContext internalGet()
      throws Exception
   {
      EnterpriseContext ctx = null;
      if( pool.empty() == false )
      {
         // Take the next available instance
         ctx = (EnterpriseContext) pool.pop();
      }
      else
      {
         // Create an instance
         if(poolFeeder != null && poolFeeder.isStarted() && log.isDebugEnabled())
         {
            log.debug("The Pool for " + container.getBeanClass().getName()
               + " has been overloaded.  You should change pool parameters.");
         }
         add();
         ctx = (EnterpriseContext) pool.pop();
      }

      return ctx;
   }

   /**
    *   Return an instance after invocation.
    *
    *   Called in 2 cases:
    *   a) Done with finder method
    *   b) Just removed
    *
    * @param   ctx
    */
   public void free(EnterpriseContext ctx)
   {
      if( log.isTraceEnabled() )
      {
         String msg = "Free instance:"+this+"#"+ctx
            +"#"+ctx.getTransaction()
            +"#"+reclaim
            +"#"+getContainer().getBeanClass();
         log.trace(msg);
      }
      // If we block when maxSize instances are in use, invoke release on strictMaxSize
      if( strictMaxSize != null )
         strictMaxSize.release();
      internalFree(ctx);
   }

   private synchronized void internalFree(EnterpriseContext ctx)
   {
      ctx.clear();
      try
      {
         if( this.reclaim && pool.size() < maxSize )
         {
            // If we pool instances return the ctx to the pool stack 
            pool.push(ctx);
         }
         else
         {
            // Discard the context
            ctx.discard();
         }
      }
      catch (Exception e)
      {
         log.debug("free failure", e);
      }
   }

   public void discard(EnterpriseContext ctx)
   {
      if( log.isTraceEnabled() )
      {
         String msg = "Discard instance:"+this+"#"+ctx
            +"#"+ctx.getTransaction()
            +"#"+reclaim
            +"#"+getContainer().getBeanClass();
         log.trace(msg);
      }

      // If we block when maxSize instances are in use, invoke release on strictMaxSize
      if( strictMaxSize != null )
         strictMaxSize.release();

      // Throw away, unsetContext()
      try
      {
         ctx.discard();
      }
      catch (RemoteException e)
      {
      }
   }

   public int getCurrentSize()
   {
      return this.pool.size();
   }
   public int getMaxSize()
   {
      return this.maxSize;
   }

   // Z implementation ----------------------------------------------

	/**
    * XmlLoadable implementation
    */
   public void importXml(Element element) throws DeploymentException
   {
      // Get the maximum capacity of the pool
      String maximumSize = MetaData.getElementContent(MetaData.getOptionalChild(element, "MaximumSize"));
      try
      {
         if( maximumSize != null )
            this.maxSize = Integer.parseInt(maximumSize);
      }
      catch (NumberFormatException e)
      {
         throw new DeploymentException("Invalid MaximumSize value for instance pool configuration");
      }

      // Get whether the pool will block when MaximumSize instances are active
      String strictValue = MetaData.getElementContent(MetaData.getOptionalChild(element, "strictMaximumSize"));
      Boolean strictFlag = Boolean.valueOf(strictValue);
      if( strictFlag == Boolean.TRUE )
         this.strictMaxSize = new FIFOSemaphore(this.maxSize);
      String delay = MetaData.getElementContent(MetaData.getOptionalChild(element, "strictTimeout"));
      try
      {
         if( delay != null )
            this.strictTimeout = Integer.parseInt(delay);
      }
      catch (NumberFormatException e)
      {
         throw new DeploymentException("Invalid strictTimeout value for instance pool configuration");
      }

      // Get the pool feeder task
      String feederPolicy = MetaData.getElementContent(MetaData.getOptionalChild(element, "feeder-policy"));
      poolFeeder = null;
      if (feederPolicy != null)
      {
         try
         {
            Class cls = Thread.currentThread().getContextClassLoader().loadClass(feederPolicy);
            Constructor ctor = cls.getConstructor(new Class[] {});
            this.poolFeeder = (InstancePoolFeeder)ctor.newInstance(new Class[] {});
            this.poolFeeder.setInstancePool(this);
            this.poolFeeder.importXml(element);
         }
         catch (Exception x)
         {
            throw new DeploymentException("Can't create instance pool feeder", x);
         }
      }
      log.debug("config - MaximumSize="+maxSize+", strictMaximumSize="+strictFlag+", feederPolicy="+feederPolicy);
   }

   // Package protected ---------------------------------------------

	// Protected -----------------------------------------------------
   protected abstract EnterpriseContext create(Object instance)
      throws Exception;

   // Private -------------------------------------------------------

   /**
    * At undeployment we want to free completely the pool.
    */
   private void freeAll()
   {
      Iterator i = this.pool.iterator();
      while (i.hasNext())
      {
         EnterpriseContext ctx = (EnterpriseContext)i.next();
         // Clear TX so that still TX entity pools get killed as well
         ctx.clear();
         discard(ctx);
      }
      pool.clear();
   }

   // Inner classes -------------------------------------------------

}
