
// Copyright (c) 1999, 2000 Oracle Corporation
package oracle.jbo.common.ampool;

import java.util.Vector;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Properties;

import javax.naming.*;

import oracle.jbo.*;

import oracle.jbo.common.Diagnostic;
import oracle.jbo.common.PropertyMetadata;
import oracle.jbo.common.JboEnvUtil;
import oracle.jbo.common.Configuration;

class ReferencedListElement
{
   ReferencedListElement nextElement = null;
   ApplicationModule appModule  = null;
}

class ApplicationModuleInfo
{
   boolean                     isCheckedOut = false;
   SessionCookie               mReferencingSessionCookie = null;
   long                        timeToCreateMillis = -1;
   long                        creationTimeMillis = -1;
}

class LoadBalanceInfo
{
   private AMHomeDesc[] mDescs;
   private int mCreationPolicy = ApplicationPoolImpl.CREATION_POLICY_SERIAL;
   private int mMaxHandlePolicy = ApplicationPoolImpl.MAX_HANDLE_POLICY_SPILL;
   private boolean mBalanced = false;
   private int mIndex = -1;
   private Vector[] mAMInstVecs;

   LoadBalanceInfo(AMHomeDesc[] descs)
   {
      mDescs = descs;
      mAMInstVecs = new Vector[descs.length];
   }


   int getCreationPolicy()
   {
      return mCreationPolicy;
   }


   void setCreationPolicy(int creationPolicy)
   {
      mCreationPolicy = creationPolicy;
   }


   int getMaxHandlePolicy()
   {
      return mMaxHandlePolicy;
   }


   void setMaxHandlePolicy(int maxHandlePolicy)
   {
      mMaxHandlePolicy = maxHandlePolicy;
   }


   boolean isBalanced()
   {
      return mBalanced;
   }


   void setBalanced(boolean balanced)
   {
      mBalanced = balanced;
   }


   AMHomeDesc getNextHomeDescForCreate()
   {
      if (mDescs == null || mDescs.length == 0)
      {
         return null;
      }

      AMHomeDesc hDesc = null;
      int tryIndex = mIndex;

      if (tryIndex < 0)
      {
         tryIndex = 0;
      }
      else  if (getCreationPolicy() == ApplicationPoolImpl.CREATION_POLICY_ROUND_ROBIN)
      {
         tryIndex++;
      }

      int j;

      for (j = 0; j < mDescs.length; j++)
      {
         if (tryIndex >= mDescs.length)
         {
            tryIndex = 0;
         }

         if (mAMInstVecs[tryIndex] == null)
         {
            mAMInstVecs[tryIndex] = new Vector();
         }

         int numOfInsts = mAMInstVecs[tryIndex].size();

         hDesc = mDescs[tryIndex];

         if (hDesc.getMaxNumOfInsts() < 0 || numOfInsts < hDesc.getMaxNumOfInsts())
         {
            break;
         }

         tryIndex++;
      }

      if (j >= mDescs.length)
      {
         // All home descs are full
         return null;
      }

      return hDesc;
   }
}


/**
 * This class provides the default implementation of the ApplicationPool interface.
 * <BR>
 * <a HREF="ApplicationPool.txt">View definition of ApplicationPool</a>
 * <BR>
 * <a HREF="ApplicationPoolImpl.txt">View Implementation of ApplicationPoolImpl</a>
 * <P>
 * @author Juan Oropeza
 */

public class ApplicationPoolImpl extends Object implements ApplicationPool
{
   // static final String   IS_CHECKED_OUT = "Yes";
   // static final String   IS_NOT_CHECKED_OUT = "No";

   public static int           CREATION_POLICY_SERIAL = 0;
   public static int           CREATION_POLICY_ROUND_ROBIN = 1;

   public static int           MAX_HANDLE_POLICY_SPILL = 0;
   public static int           MAX_HANDLE_POLICY_ERROR = 1;

   private long                signature;
   private Hashtable           env;
   private String              poolName;
   private Vector              instances = new Vector(10);
   private Hashtable           userData;
   private String              sConnectString;
   private String              sApplicationModule;
   private Hashtable           mInstanceInfo = new Hashtable(10);
   private Hashtable           mSessionCookies = new Hashtable(10);
   private ReferencedListElement mReferencedInstanceList;
   private int                 cookieNum = 0;
   private String              sUserName;
   private String              sPassword;

   private LoadBalanceInfo     mLBInfo = null;

   /**
    * Constructor
    */
   public ApplicationPoolImpl()
   {
      signature = System.currentTimeMillis();
   }


   long getSignature()
   {
      return signature;
   }


   int getNextCookieNum()
   {
      int nextCookieNum = -1;

      synchronized(mSessionCookies)
      {
         nextCookieNum = cookieNum++;
      }

      return nextCookieNum;
   }


   public void initialize(String sPoolName , String sApplicationModule, String sConnectString, Hashtable env)
   {
      this.poolName = sPoolName;
      this.env = env;
      this.sConnectString = sConnectString;
      // Bug 1387996 -- Not properly picking up username password from named
      // database connections (SPM)
      if (env != null) {
       String un = (String) env.get(Configuration.DB_USERNAME_PROPERTY);
       String pw = (String) env.get(Configuration.DB_PASSWORD_PROPERTY);
       if (un!=null) setUserName(un);
       if (pw!=null) setPassword(pw);
      }
      this.sApplicationModule = sApplicationModule;
   }


   public String getApplicationModuleClass()
   {
      return this.sApplicationModule;
   }


   public String getConnectString()
   {
      return this.sConnectString;
   }


   public Hashtable getEnvironment()
   {
      return this.env;
   }


   public synchronized void checkin(ApplicationModule appModule)
   {
      // Determine if the pool recognizes this application module
      // as having been checked out.  If not, throw an exception.
      if (isAvailable(appModule))
      {
         throw new ApplicationPoolException(
            AMPoolMessageBundle.class
            , AMPoolMessageBundle.EXC_AMPOOL_INVALID_CHECKIN
            , new Object[] {poolName});
      }

      SessionCookie cookie = getReferencingSessionCookie(appModule);

      // Clean up any old session state that may be association with this
      // application module before returning.  When a stateless check-in occurs
      // it is assumed that there is no longer any application module state
      // associated with the session.
      if (cookie.getPassivationId() >= 0)
      {
         try
         {
            appModule.removeState(cookie.getPassivationId());
         }
         catch(oracle.jbo.pcoll.PCollException pcex)
         {
            // Eat the persistence exception if it is related to an invalid root
            // node.  An exception is currently thrown if a non-existing
            // passivation id is specified.  This may occur if the database
            // administrator has performed some maintenance tasks since the
            // passivation id was generated.
            if ((pcex.getErrorCode() != null)
               && (pcex.getErrorCode().equals(CSMessageBundle.EXC_PCOLL_INVALID_ROOT_NODE)))
            {
               Diagnostic.println(new StringBuffer(32)
                  .append("The root passivation record for passivate id, ")
                  .append(cookie.getPassivationId())
                  .append(", was not located").toString());
            }
            else
            {
               throw pcex;
            }
         }
      }

      removeReferencedInstance(appModule);

      removeSessionCookie(appModule, cookie);

      // Rollback the application module first to clean up any potential
      // database state.  This is okay because we have chosen not to retain
      // application module state.  If we do not perform a rollback then
      // an exception may occur when application module state is activated.
      appModule.getTransaction().rollback();
      disconnect(appModule, false);

      setAvailable(appModule, true);
   }

   /**
    * Check in and retain the state of the specified application module.  This
    * method generates a cookie for the application module that can be used as
    * a unique identifier for the application module's session state.  Check-in
    * with session state will also disconnect the application module's JDBC
    * connection and return the JDBC connection to the JDBC connection pool for
    * reuse.  Consequently, when using this method there must not be any
    * database state that must be saved across sessions.  Examples of database
    * state include pessimistic locks, open cursors, or posted but uncommited
    * changes.  If any database state must be saved between requests
    * checkinWithSessionState should not be used.  Instead the application
    * module reference should be maintained by the client until the database
    * state has been committed/rolled back.
    *
    * @param doFailover If true, the application module will be passivated
    *    immediately and the application passivation id will be returned with
    *    the session id.  If false, the application module will not be
    *    passivated until it is reused by a session other than the session that
    *    has checked in the application module.
    *
    * @returns A unique id representing the session's application module state.
    *    The id may be used to retrieve the session state when checking out
    *    an application module from the pool later in the application life
    *    cycle.
    */
   public synchronized String checkinWithSessionState(ApplicationModule appModule)
   {
      // Determine if the pool recognizes this application module
      // as having been checked out.  If not, throw an exception.
      if (isAvailable(appModule))
      {
         throw new ApplicationPoolException(
            AMPoolMessageBundle.class
            , AMPoolMessageBundle.EXC_AMPOOL_INVALID_CHECKIN
            , new Object[] {poolName});
      }

      StringBuffer returnId = new StringBuffer();

      SessionCookie cookie = getReferencingSessionCookie(appModule);

      // The cookie may have been cleaned up since the application module
      // was checked out.  Create new cookie.
      if (cookie == null)
      {
         cookie = createSessionCookie(appModule);
      }

      returnId.append(cookie.getCookieNum());

      boolean doFailover = JboEnvUtil.getPropertyAsBoolean(
         PropertyMetadata.ENV_DO_FAILOVER.pName
         , Boolean.valueOf(PropertyMetadata.ENV_DO_FAILOVER.pDefault)
            .booleanValue());

      // If failover support has been requested then passivate the
      // application module before setting it as available.
      if (doFailover)
      {
         Diagnostic.println("Application Module failover is enabled");

         doFailover(appModule, cookie);

         // Append the passivation id to the session id so that it may be used
         // to activate the application module if the middle tier VM instance
         // dies.
         returnId.append(',');
         returnId.append(String.valueOf(cookie.mPassivationId));
      }

      // Release the application module's JDBC connection to the connection
      // pool.  Retain the application module state so that the application
      // does not have to be activated if it is requested by the same session.
      try
      {
         disconnect(appModule, true);
      }
      catch (JboException ex)
      {
         // Disconnect the application module without retaining AM state if
         // a database state exception is thrown.
         if ((ex.getErrorCode() != null)
            && (ex.getErrorCode().equals(CSMessageBundle.EXC_DATABASE_STATE_EXISTS)))
         {
            Diagnostic.println("Database state was detected while disconnecting the application module's connection");

            // If the application module was not already passivated then
            // passivate.
            if (!doFailover)
            {
               Diagnostic.println("Passivating the application module state.");
               doFailover(appModule, cookie);

               // Append the passivation id to the session id so that it may be used
               // to activate the application module if the middle tier VM instance
               // dies.
               returnId.append(',');
               returnId.append(String.valueOf(cookie.mPassivationId));
            }

            // Rollback the application module first to clean up any potential
            // database state.  This is okay because we have chosen not to retain
            // application module state.  If we do not perform a rollback then
            // an exception may occur when application module state is activated.
            appModule.getTransaction().rollback();
            disconnect(appModule, false);

            // Mark the cookie's doActivate flag to indicate that activation
            // must occur upon checkout.
            cookie.mDoActivate = true;
         }
         else
         {
            throw ex;
         }
      }

      setAvailable(appModule, true);

      return returnId.toString();
   }


   public long getTimeToCreateMillis(ApplicationModule instance)
   {
      ApplicationModuleInfo info =
         (ApplicationModuleInfo)mInstanceInfo.get(instance);

      return info.timeToCreateMillis;
   }


   public long getCreationTimeMillis(ApplicationModule instance)
   {
      ApplicationModuleInfo info =
         (ApplicationModuleInfo)mInstanceInfo.get(instance);

      return info.creationTimeMillis;
   }


   public boolean isAvailable(ApplicationModule instance)
   {
      synchronized(instance)
      {
         ApplicationModuleInfo info =
            (ApplicationModuleInfo)mInstanceInfo.get(instance);

         return (!info.isCheckedOut);
      }
   }


   public void setAvailable(ApplicationModule instance, boolean bSet)
   {
      synchronized(instance)
      {
         ApplicationModuleInfo info =
            (ApplicationModuleInfo)mInstanceInfo.get(instance);

         info.isCheckedOut = (!bSet);
      }
   }

   public synchronized ApplicationModule createNewInstance() throws Exception
   {
      // This method does not check out the new application module instance
      // that is created.  This is because the method is being used by
      // checkout if an available or referenced instance is not located.
      // Perhaps this method should be made protected to prevent improper
      /// use by client applications.

      // create a new instance using the connect info
      Context initialContext;

      if(JboEnvUtil.inJServer())
      {
        initialContext = oracle.jbo.server.xml.aurora.AuroraJNDIContextHelper.createContext(env);
      }
      else
      {
        initialContext = new InitialContext(env);
      }

      ApplicationModuleHome home = (ApplicationModuleHome) initialContext.lookup(sApplicationModule);

      long start = System.currentTimeMillis();
      ApplicationModule am = home.create();
      long end = System.currentTimeMillis();

      connect(am);

      ApplicationModuleInfo info = new ApplicationModuleInfo();

      info.timeToCreateMillis = end - start;
      info.creationTimeMillis = end;

      mInstanceInfo.put(am, info);

      // add instance to collection
      instances.addElement(am);

      setAvailable(am, true);

      return am;
   }

   public synchronized ApplicationModule checkout() throws Exception
   {
      ApplicationModule rtn = null;
      ApplicationModule current = null;

      boolean isAvailable = true;

      for (int i = 0 ; i < instances.size(); i ++)
      {
         current = (ApplicationModule) instances.elementAt(i);

         if ((current != null)
            && (isAvailable(current))
            && (!isReferenced(current)))
         {
            Diagnostic.println("Recycling an unreferenced, available pool instance");

            rtn = current;

            reconnect(rtn);

            break;
         }
      }

      // If an available, unreferenced application module was not located above
      // then grab the first referenced application module off of the FIFO
      // stack.  Use a FIFO stack to ensure that the oldest references are
      // recycled first.
      if (rtn == null)
      {
         ReferencedListElement listElement = mReferencedInstanceList;

         int recycleThreshold = JboEnvUtil.getPropertyAsInt(
            PropertyMetadata.ENV_POOL_RECYCLE_THRESHOLD.pName
            , Integer.valueOf(PropertyMetadata.ENV_POOL_RECYCLE_THRESHOLD.pDefault)
               .intValue());

         // If the number of pool instances is less than the recycle threshold
         // then do not attempt to recycle a referenced application module.
         // A new application module will be created below.
         if (getInstanceCount() >= recycleThreshold)
         {
            while (listElement != null)
            {
               current = listElement.appModule;

               if ((current != null) && (isAvailable(current)))
               {
                  Diagnostic.println("Recycling a referenced, available pool instance");

                  rtn = current;

                  reconnect(rtn);

                  // Remove the application module's session references
                  SessionCookie oldCookie = getReferencingSessionCookie(rtn);

                  boolean doFailover = JboEnvUtil.getPropertyAsBoolean(
                     PropertyMetadata.ENV_DO_FAILOVER.pName
                     , Boolean.valueOf(PropertyMetadata.ENV_DO_FAILOVER.pDefault)
                        .booleanValue());

                  // If the deferred passivation has been specified then passivate
                  // the referenced application module's state before continuing.
                  // Otherwise, the application module's state has already been
                  // passivated.
                  if (!doFailover)
                  {
                     int passivationId = rtn.passivateState(null);

                     oldCookie.setPassivationId(passivationId);
                  }

                  // Remove the instance from the referenced list.
                  removeReferencedInstance(rtn);

                  oldCookie.setApplicationModule(null);

                  // Rollback.  This is necessary to remove any
                  // unposted transaction validation listeners which may still be
                  // registered on a stateful application module's transaction.
                  rtn.getTransaction().rollback();

                  rtn.clearVOCaches(null, true);

                  // Clear the entity caches as well.
                  rtn.getTransaction().clearEntityCache(null);

                  break;
               }

               listElement = listElement.nextElement;
            }
         }
      }

      // If an application module has still not been located then create a new
      // instance.
      if (rtn == null)
      {
         Diagnostic.println("Creating a new pool instance");

         rtn = createNewInstance();
      }

      // Create a new session cookie for this checkout.  Associate the new
      // session cookie with the checked out application module.
      createSessionCookie(rtn);

      setAvailable(rtn, false);

      // Immediately add the application module to the referenced instance list.
      addReferencedInstance(rtn);

      return rtn;
   }

   public synchronized void releaseInstances()
   {
      int nSize = instances.size();

      for (int i = 0; i < nSize; i++)
      {
         ApplicationModule instance = (ApplicationModule) instances.elementAt(i);

         instance.remove();
      }

      for (int i = nSize - 1; i >= 0; i--)
      {
         instances.removeElementAt(i);
      }

      mInstanceInfo.clear();
      mSessionCookies.clear();
      mReferencedInstanceList = null;
   }


   public synchronized int getAvailableNumPools()
   {
      int count = 0;
      ApplicationModule current = null;

      for(int i = 0 ; i < instances.size(); i++)
      {
         current = (ApplicationModule) instances.elementAt(i);

         if (isAvailable(current))
         {
            count++;
         }
      }

      return count;
   }


   public synchronized int  getInstanceCount()
   {
      return instances.size();
   }


   public synchronized ApplicationModule getInstance(int nIndex)
   {
      return (ApplicationModule) instances.elementAt(nIndex);
   }

   public synchronized ApplicationModule checkout(String sessionId)
   {
      ApplicationModule rtn = null;
      Integer passivationId = null;

      // Parse the session id before beginning.  The session id may be a comma
      // delimited structure consisting of a session id and a passivation id.
      // This will occur if immediate passivation was requested by the client.
      int commaIndex = sessionId.indexOf(',');
      if (commaIndex >= 0)
      {
         passivationId = new Integer(sessionId.substring(commaIndex + 1));
         sessionId = sessionId.substring(0, commaIndex);
      }

      SessionCookie cookie =
         (SessionCookie)mSessionCookies.get(sessionId);

      rtn = (cookie != null
         ? cookie.getApplicationModule()
         : null);

      if ((rtn != null) && (isAvailable(rtn)))
      {
         Diagnostic.println("Reusing cached session instance");

         setAvailable(rtn, false);

         reconnect(rtn);

         // If the doActivate flag is marked true on the session cookie
         // then activate the application module.  The application module
         // must have been passivated because database state existed.
         if (cookie.mDoActivate)
         {
            try
            {
               rtn.activateState(passivationId.intValue(), true);
            }
            catch(oracle.jbo.pcoll.PCollException pcex)
            {
               // Eat the persistence exception if it is related to an invalid root
               // node.  An exception is currently thrown if a non-existing
               // passivation id is specified.  This may occur if the database
               // administrator has performed some maintenance tasks since the
               // passivation id was generated.
               if ((pcex.getErrorCode() != null)
                  && (pcex.getErrorCode().equals(CSMessageBundle.EXC_PCOLL_INVALID_ROOT_NODE)))
               {
                  Diagnostic.println(new StringBuffer(32)
                     .append("The root passivation record for passivate id, ")
                     .append(passivationId)
                     .append(", was not located").toString());
               }
               else
               {
                  throw pcex;
               }
            }

            cookie.mDoActivate = false;
         }

         // Strange behaviour could occur if an old cookie that was not
         // generated by this VM's cookie sequence matches that of a different
         // client session's cookie.
         //
         // If the second client session's application module is available then
         // the implementation below will reuse the application module as it were
         // owned by the first client's session (this means any state specified
         // in the passivation id will not be activated).  If failover support has
         // been specified the second client's state will have been passivated
         // correctly, but the session conflict may cause problems when the
         // application module is returned.  In order to prevent this create a
         // new session cookie.
         SessionCookie newCookie = createSessionCookie(rtn);

         // Transfer the old cookie's passivation id to the new session cookie.
         newCookie.setPassivationId(cookie.getPassivationId());

         // Set the old cookie as not referencing the checked out application
         // module instance.  The old cookie is maintained in case an error
         // occurs before the application module is checked in and the new
         // session cookie id is returned to pool client.
         cookie.setApplicationModule(null);

         // Assign the newCookie reference to the local cookie reference.
         cookie = newCookie;

         // Finally move the application module to the end of the referenced
         // list.
         removeReferencedInstance(rtn);
         addReferencedInstance(rtn);
      }
      // An application module was not located for the specified session.
      // Checkout any application module.
      else
      {
         try
         {
            rtn = checkout();
         }
         catch (Exception ex)
         {
            Diagnostic.printStackTrace(ex);
            ApplicationPoolException aex = new ApplicationPoolException(
               AMPoolMessageBundle.class
               , AMPoolMessageBundle.EXC_AMPOOL_CHECKOUT_FAILED
               , new Object[] {poolName});

            aex.addToDetails(ex);
            throw aex;
         }

         // If a cookie was located for the specified session and a
         // passivation id was not specified then failover support
         // must have been disabled.  Get the passivation id from the
         // session cookie.  Leave the old cookie around so that the session
         // state may still be activiated if an error occurs before the
         // application module is checked back into the pool and the new cookie
         // id is returned to the pool client.  The cookie should not reference
         // an application module or else the top portion of this condition
         // would have been executed.  Consequently, the cookie does need
         // its referencing application module reset.
         if ((cookie != null)
            && (passivationId == null)
            && (cookie.getPassivationId() >= 0))
         {
            passivationId = new Integer(cookie.getPassivationId());
         }

         // Get the new session cookie that was generated during checkout.
         cookie = (SessionCookie)getReferencingSessionCookie(rtn);

         // If the passivation id is not null then the application module must
         // have been passivated.  Activitate the applciation module if the
         // referencing session id of the application module is not the specified
         // session id.  This is an optimization to prevent excessive activation
         // of application modules.
         if (passivationId != null)
         {
            // Set the cookie passivation id with the specified passivation id.
            // This is necessary because a new cookie has been generated for
            // the requesting session.  Future logic that uses the session
            // cookie must be aware that the session's state has already been
            // passivated.
            cookie.setPassivationId(passivationId.intValue());

            try
            {
               rtn.activateState(passivationId.intValue(), true);
            }
            catch(oracle.jbo.pcoll.PCollException pcex)
            {
               // Eat the persistence exception if it is related to an invalid root
               // node.  An exception is currently thrown if a non-existing
               // passivation id is specified.  This may occur if the database
               // administrator has performed some maintenance tasks since the
               // passivation id was generated.
               if ((pcex.getErrorCode() != null)
                  && (pcex.getErrorCode().equals(CSMessageBundle.EXC_PCOLL_INVALID_ROOT_NODE)))
               {
                  Diagnostic.println(new StringBuffer(32)
                     .append("The root passivation record for passivate id, ")
                     .append(passivationId)
                     .append(", was not located").toString());
               }
               else
               {
                  throw pcex;
               }
            }

            cookie.mDoActivate = false;
         }
      }

      return rtn;

   }

   public synchronized String getPoolName()
   {
      return poolName;
   }

   /**
    * returns the User Data hashtable. This is a generic container for
    * any settings the user would like to associate with this application instance.
    */
   public Hashtable getUserData()
   {
      return userData;
   }


   /**
    * Replaces the userData with the new Hashtable.
    */
   public void setUserData(Hashtable data)
   {
      this.userData = data;
   }

    /**
  *    returns the user name
  **/
  public String getUserName()
  {
      return this.sUserName;
  }

  public void setUserName(String sUser)
  {
   this.sUserName = sUser;
  }

  public void setPassword(String sPassword)
  {
   this.sPassword = sPassword;
  }

  /**
  *   returns the password
  **/
  public String getPassword()
  {
   return this.sPassword;
  }


   public void commitAndSyncCache(ApplicationModule instance)
   {
      int snapId = instance.getTransaction().commitAndSaveChangeSet();

      if (snapId >= 0)
      {
         for(int i = 0 ; i < instances.size(); i++)
         {
            ApplicationModule current = (ApplicationModule) instances.elementAt(i);

            if (current != instance)
            {
               current.getTransaction().applyChangeSet(snapId);
            }
         }

         instance.getTransaction().removeChangeSet(snapId);
      }
   }

   private SessionCookie getReferencingSessionCookie(ApplicationModule instance)
   {
      ApplicationModuleInfo info =
         (ApplicationModuleInfo)mInstanceInfo.get(instance);

      return info.mReferencingSessionCookie;
   }

   private void setReferencingSessionCookie(
      ApplicationModule instance
      , SessionCookie cookie)
   {
      ApplicationModuleInfo info =
         (ApplicationModuleInfo)mInstanceInfo.get(instance);

      info.mReferencingSessionCookie = cookie;
   }

   private boolean isReferenced(ApplicationModule instance)
   {
      ApplicationModuleInfo info =
         (ApplicationModuleInfo)mInstanceInfo.get(instance);

      return (info.mReferencingSessionCookie != null);
   }


   private ApplicationModule getReferencedInstance()
   {
      // Return the next referenced application module in the reference
      // application module linked list.  Referenced application modules are
      // those application modules that may still have a weak session reference.
      // These application modules are not recycled by the pool until there
      // are not unreferenced, checked out application modules available.
      // Referenced application modules are returned in FIFO fashion to ensure
      // that the oldest reference is recycled first.
      ApplicationModule rtn = null;

      if (mReferencedInstanceList != null)
      {
         rtn = mReferencedInstanceList.appModule;
      }

      return rtn;
   }

   private ApplicationModule removeReferencedInstance(
      ApplicationModule appModule)
   {
      ApplicationModule rtn = null;

      ReferencedListElement lastElement = null;
      ReferencedListElement element = mReferencedInstanceList;

      while (element != null)
      {
         if (appModule == element.appModule)
         {
            rtn = element.appModule;

            // Remove the element from the linked list
            if (lastElement != null)
            {
               lastElement.nextElement = element.nextElement;
            }
            else
            {
               mReferencedInstanceList = null;
            }
            break;
         }

         lastElement = element;
         element = element.nextElement;
      }

      return rtn;
   }

   private void addReferencedInstance(ApplicationModule appModule)
   {
      ApplicationModule rtn = null;

      ReferencedListElement newElement = new ReferencedListElement();
      newElement.appModule = appModule;

      // Get the last element
      ReferencedListElement element = mReferencedInstanceList;
      ReferencedListElement lastElement = null;
      while (element != null)
      {
         // Keep an eye out for an existing entry.  If one is found
         // simply return.
         if (element.appModule == appModule)
         {
            return;
         }

         lastElement = element;
         element = element.nextElement;
      }

      if (lastElement != null)
      {
         lastElement.nextElement = newElement;
      }
      else
      {
         mReferencedInstanceList = newElement;
      }
   }

   private SessionCookie createSessionCookie(ApplicationModule appModule)
   {
      SessionCookie cookie = new SessionCookie(this);

      mSessionCookies.put(cookie.toString(), cookie);

      setReferencingSessionCookie(appModule, cookie);
      cookie.setApplicationModule(appModule);

      return cookie;
   }

   private void removeSessionCookie(
      ApplicationModule appModule, SessionCookie cookie)
   {
      // Clean up the cookie.
      mSessionCookies.remove(cookie.toString());

      cookie.setApplicationModule(null);
      setReferencingSessionCookie(appModule, null);
   }

   private void doFailover(ApplicationModule appModule, SessionCookie cookie)
   {
      // After passivating the application module attempt to clean up any
      // records associated with previously passivated state.  State may not be
      // removed if the VM instance has died (pass a previous state to this
      // method?  Add logic to the application registry to handle this?).
      // JRS TODO:  This logic should occur AFTER passivation in order to
      // ensure failover support if an exception occurs during passivation.
      // However, a bug in the passivation framework that prevents an
      // application module instance from being passivated multiple times
      // has required that the logic appear before passivation for now.
      if (cookie.getPassivationId() >= 0)
      {
         try
         {
            appModule.removeState(cookie.getPassivationId());
         }
         catch(oracle.jbo.pcoll.PCollException pcex)
         {
            // Eat the persistence exception if it is related to an invalid root
            // node.  An exception is currently thrown if a non-existing
            // passivation id is specified.  This may occur if the database
            // administrator has performed some maintenance tasks since the
            // passivation id was generated.
            if ((pcex.getErrorCode() != null)
               && (pcex.getErrorCode().equals(CSMessageBundle.EXC_PCOLL_INVALID_ROOT_NODE)))
            {
               Diagnostic.println(new StringBuffer(32)
                  .append("The root passivation record for passivate id, ")
                  .append(cookie.getPassivationId())
                  .append(", was not located").toString());
            }
            else
            {
               throw pcex;
            }
         }
      }

      int passivationId = appModule.passivateState(null);

      // Store the passivation id on the application module cookie so that
      // we know the last passivation id.
      cookie.setPassivationId(passivationId);
   }

   /**
    * Establish the inital application module JDBC connection.  This method is
    * invoked by the application module pool when new application module
    * instances are instantiated.  Application developers who would like to
    * plug a custom connection framework into the application module pool may
    * override this method.
    */
   protected void connect(ApplicationModule appModule)
   {
      if (!appModule.getTransaction().isConnected())
      {
         Diagnostic.println("AM pool is establishing an application module connection");

         Properties jdbcProp = new Properties();

         if (sUserName != null && sPassword != null)
         {
            jdbcProp.put(Configuration.DB_USERNAME_PROPERTY, sUserName);
            jdbcProp.put(Configuration.DB_PASSWORD_PROPERTY, sPassword);
         }

         appModule.getTransaction().connect(sConnectString, jdbcProp);
      }
   }

   /**
    * Re-establish an application module's JDBC connection.  This method is
    * invoked by the application module pool when an application module instance
    * is recycled by the pool.  Application developers who would like to
    * plug a custom connection framework into the application module pool may
    * override this method.
    */
   protected void reconnect(ApplicationModule appModule)
   {
      if (!appModule.getTransaction().isConnected())
      {
         Diagnostic.println("AM pool is re-establishing an application module connection");

         try
         {
            appModule.getTransaction().reconnect();
         }
         catch (NotConnectedException ex)
         {
            // If a NotConnectedException is thrown then reconnect using
            // the reconnect method on NullDBTransactionImpl.  The application
            // module must have been disconnected without retainState specified.
            appModule.getTransaction().reconnect(true);
         }
      }
   }

   /**
    * Disconnect an application module from its JDBC connection.  This method is
    * invoked by the application module pool when an application module instance
    * is checked into the pool.  The method checks the application property
    * jbo.doconnectionpooling before disconnecting the application
    * module.  Application developers who would like to plug a custom connection
    * framework into the application module pool may override this method.
    *
    * @param retainState Indicates whether the state of the application
    *    module's caches should be retained while disconnecting.  If true, the
    *    application module's Transaction should have not database state.
    */
   protected void disconnect(ApplicationModule appModule, boolean retainState)
   {
      if (retainState)
      {
         // If retainState has been specified then check the
         // jbo.doconnectionpooling property before attempting to disconnect
         // the application module.  The retain state flag will only be
         // true when the pool is managing an application module's state.
         String doDisconnectStr =
            (String)env.get(PropertyMetadata.ENV_DO_CONNECTION_POOLING.pName);

         boolean doDisconnect =
            (doDisconnectStr == null)
            ? Boolean.valueOf(PropertyMetadata.ENV_DO_CONNECTION_POOLING.pDefault)
               .booleanValue()
            : Boolean.valueOf(doDisconnectStr).booleanValue();

         if (doDisconnect)
         {
            Diagnostic.println("AM pool is disconnecting an application module connection");
            appModule.getTransaction().disconnect(true);
         }
      }
      else
      {
         // If the retainState has not been specified then disconnect the
         // application module regardless of the value of the
         // jbo.doconnectionpooling flag.  The retainState flag is only
         // specified when the pool is not managing the application
         // module state.
         Diagnostic.println("AM pool is disconnecting an application module connection");
         appModule.getTransaction().disconnect();
      }
   }
}
