/*
 * @(#)ApplicationPoolImpl.java
 *
 * Copyright 2000-2002 by Oracle Corporation,
 * 500 Oracle Parkway, Redwood Shores, California, 94065, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Oracle Corporation.
 */

package oracle.jbo.common.ampool;

import com.sun.java.util.collections.ArrayList;
import com.sun.java.util.collections.HashMap;
import com.sun.java.util.collections.Iterator;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Date;
import java.util.Hashtable;
import java.util.Properties;
import oracle.jbo.SessionData;
import oracle.jbo.ApplicationModule;
import oracle.jbo.CSMessageBundle;
import oracle.jbo.ConnectionMetadata;
import oracle.jbo.JboException;
import oracle.jbo.Transaction;
import oracle.jbo.client.Configuration;
import oracle.jbo.common.Diagnostic;
import oracle.jbo.common.JBOClass;
import oracle.jbo.common.JboEnvUtil;
import oracle.jbo.common.PropertyMetadata;
import oracle.jbo.pool.RecentlyUsedLinkedList;
import oracle.jbo.pool.RecentlyUsedLinkedListElement;
import oracle.jbo.pool.ResourcePool;
import oracle.jbo.pool.ResourcePoolLogger;

/**
 * This class provides the default implementation of the ApplicationPool interface.
 *
 * A few notes about the locking strategy that has been employed to synchronize
 * access to the shared application module resources in this pool.  These
 * notes are provided for the benefit of the application pool developer.  They
 * are not required to use the application pool.
 * <p>
 * The pool utilizes a singleton internal monitor object instance to synchronize
 * read/write access to shared data.
 * <p>
 * In order to provide high pool availability, only those critical blocks that
 * read/write from the shared data structures have been synchronized.  Expensive
 * application module operations like connect/disconnect, activiate/passivate,
 * and rollback are performed outside of the pool lock.  In order for this to
 * work the application module is made not available inside of the pool lock
 * before its state is modified.  Using this strategy will prevent other
 * sessions from attempting to recycle the application module instance while
 * its state is not consistent.
 * <p>
 * This strategy of course assumes that application module access from the same
 * session is properly synchronized.  Consider the following scenario:
 * <p>
 * 1. Request thread one from session one acquires a pool lock.  While holding
 *   the lock the request sets the session application module as not available.
 *   Request thread one from session one then releases the pool lock and begins
 *   modifying the application module state.
 * <p>
 * 2. Request thread two from session one comes along.  Before the application
 *   module state has been modified by thread one above, request thread two
 *   invokes useApplicationModule.  Request thread two acquires a pool lock
 *   and finds the application module instance associated with the request
 *   session (even though the application module is not available).  Request
 *   thread two happily continues using the application module reference and
 *   a possible race condition between request threads one and two has occured.
 * <p>
 * In order to prevent this scenario, the pool assumes that session cookie
 * access is also properly synchronized.  This is indeed the case for the
 * provided implementations of oracle.jbo.common.ampool.SessionCookie.
 * <p>
 * The discussion above introduces two levels of locking, session locking and pool locking.
 * The pool's locking strategy utilizes the following rules to prevent deadlock:
 * <p>
 * 1.  Session locks are never acquired while pool locks are held.
 *    Please note that this rule does allow a pool lock to be acquired while
 *    a session lock is held.  In fact, the pool depends upon this to prevent
 *    the race conditions mentioned above.
 * <p>
 * From an availability standpoint this rule makes sense since the rule implies
 * that pool locks will be held for shorter duration then a session lock.
 * However, because of the two levels of locking the application pool developer
 * must be constantly aware of the potential for deadlock when implementing pool
 * logic.  To further understand the above rule consider the following scenario:
 * <p>
 * 1.  Request 1 for session 1 has acquired a pool lock without first acquiring
 *   a session lock.  In the critical section the request updates
 *   some shared session state that requires a session lock to be acquired.
 * <p>
 * 2.  Concurrently, request 2 for session 1 acquires a session lock.  In the
 *   critical section the request requires access to some shared application pool
 *   state that requires an application pool lock to be acquired.
 * <p>
 * Obviously, in the scenario above request one may be blocked waiting for
 * the session lock that is held by request two and request two may be blocked
 * waiting for the pool lock that is held by request one; the definition of
 * deadlock.
 * <p>
 * Most pool methods should be accessed using a session cookie, so that the rule
 * defined above holds true.  However, there are some instances where a cookie
 * lock could be acquired after a pool lock has been acquired:
 * <p>
 * 1.  When a request from session one recycles an application module that is
 *   referenced by session two.  If the session two state must be passivated
 *   then the request thread from session one must update the passivation id
 *   of the session two cookie.  This requires the request thread from session
 *   one to acquire a session lock for session two.  It is safe to assume that a
 *   session lock for the session one has already been acquired because request
 *   one is an active request that must have accessed the pool using the session
 *   cookie API.  It is not safe to assume that a session lock for session two
 *   has been acquired because the current pool request thread did not originate
 *   from session two.  Attempting to acquire a session lock for session two
 *   while holding a pool lock may cause the deadlock mentioned above.  This
 *   scenario is avoided by executing the section that requires the session
 *   lock for session two after the pool lock has been released.
 * <p>
 * 2.  When a referenced application module is removed by the high water mark
 *   functionality.  This scenario is very similar to the one described in item
 *   one above.  However, instead of the request thread from session one recycling
 *   an application module that is referenced by session two the request thread
 *   from session one is discarding the application module that is referenced by
 *   session two.
 * <p>
 * <br>
 * <a HREF="ApplicationPool.txt">View definition of ApplicationPool</a>
 * <br>
 * <a HREF="ApplicationPoolImpl.txt">View Implementation of ApplicationPoolImpl</a>
 * <p>
 */
public class ApplicationPoolImpl extends ResourcePool
   implements ApplicationPool
{
   static final long serialVersionUID = -6745281847170690848L;   

   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;

   // Immutable state
   private final long                      mSignature;

   // Mutable state
   private String                           mName;
   private Hashtable                        mUserData;
   private int                              mSessionId            = 0;
   private Hashtable                        mEnvironment;
   private String                           mUserName;
   private String                           mPassword;
   private String                           mConnectString;

   // Transient state
   private transient RecentlyUsedLinkedList mReferencedList       = new RecentlyUsedLinkedList();
   private transient RecentlyUsedLinkedList mUnrefAvailList       = new RecentlyUsedLinkedList();
   private transient RecentlyUsedLinkedList mRefAvailList         = new RecentlyUsedLinkedList();
   private transient ConnectionStrategy     mStrategy;
   private transient SessionCookieFactory   mSessionCookieFactory;

   private transient HashMap                mInstanceInfo         = new HashMap(10);
   private transient HashMap                mSessionCookieInfo    = new HashMap(10);

   // Checkout operations
   private static final int REUSE_REFERENCED_INSTANCE             = 1;
   private static final int RECYCLE_UNREFERENCED_INSTANCE         = 2;
   private static final int RECYCLE_REFERENCED_INSTANCE           = 3;
   private static final int CREATE_NEW_INSTANCE                   = 4;

   private static final byte PASSIVATE_STATE                      = 0;
   private static final byte ACTIVATE_STATE                       = 1;
   private static final byte REMOVE_STATE                         = 2;

   // Private property which is used to pass the session cookie between
   // internal pool methods.
   private static final String SESSION_COOKIE                     = "jbo.envinfoprovider";

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

   public long getSignature()
   {
      return mSignature;
   }


   private String getNextSessionId()
   {
      // JRS This method should not be used.  Memory generated session
      // identifers guarantee neither uniqueness nor consistency.
      String nextSessionId = null;

      nextSessionId = String.valueOf(mSessionId++);

      return nextSessionId;
   }

   public void initialize()
   {
      synchronized(mLock)
      {
         Properties props = new Properties();
         props.putAll(mEnvironment);
         super.initialize(props);
         
         if (mInstanceInfo == null)
         {
            mInstanceInfo = new HashMap(10);
         }

         if (mReferencedList == null)
         {
            mReferencedList = new RecentlyUsedLinkedList();
         }

         if (mUnrefAvailList == null)
         {
            mUnrefAvailList = new RecentlyUsedLinkedList();
         }

         if (mRefAvailList == null)
         {
            mRefAvailList = new RecentlyUsedLinkedList();
         }

         if (mSessionCookieInfo == null)
         {
            mSessionCookieInfo    = new HashMap(10);
         }
      }
   }

   public void initialize(
      String name
      , String applicationModuleClassName
      , String connectString
      , Hashtable env)
   {
      synchronized(mLock)
      {
         mName = name;
         mEnvironment = (Hashtable)env.clone();
         mConnectString = connectString;

         initialize();

         // 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);
         }

         if (applicationModuleClassName != null)
         {
            mEnvironment.put(ConnectionStrategy.APPLICATION_MODULE_CLASS_NAME_PROPERTY, applicationModuleClassName);
         }
      }
   }

   public ConnectionStrategy getConnectionStrategy()
   {
      synchronized(mLock)
      {
         if (mStrategy == null)
         {
            String className = getConnectionStrategyClassName();
            try
            {
               Class connectionStrategyClass = JBOClass.forName(className);
               mStrategy = (ConnectionStrategy)connectionStrategyClass.newInstance();
            }
            catch(ClassNotFoundException cnfex)
            {
               throw new JboException(cnfex);
            }
            catch(IllegalAccessException iaex)
            {
               throw new JboException(iaex);
            }
            catch (InstantiationException iex)
            {
               throw new JboException(iex);
            }
         }

         return mStrategy;
      }
   }

   public void setConnectionStrategy(ConnectionStrategy strategy)
   {
      synchronized(mLock)
      {
         if (strategy != null)
         {
            mStrategy = strategy;
            mEnvironment.put(
               PropertyMetadata.ENV_AMPOOL_CONNECTION_STRATEGY_CLASS_NAME.pName
               , strategy.getClass().getName());
         }
      }
   }

   public SessionCookieFactory getSessionCookieFactory()
   {
      synchronized(mLock)
      {
         if (mSessionCookieFactory == null)
         {
            String className = getSessionCookieFactoryClassName();
            try
            {
               Class sessionCookieFactoryClass = JBOClass.forName(className);
               mSessionCookieFactory = (SessionCookieFactory)sessionCookieFactoryClass.newInstance();
            }
            catch(ClassNotFoundException cnfex)
            {
               throw new JboException(cnfex);
            }
            catch(IllegalAccessException iaex)
            {
               throw new JboException(iaex);
            }
            catch (InstantiationException iex)
            {
               throw new JboException(iex);
            }
         }

         return mSessionCookieFactory;
      }
   }

   public void setSessionCookieFactory(SessionCookieFactory sessionCookieFactory)
   {
      synchronized(mLock)
      {
         // A null session cookie factory is not allowed
         if (sessionCookieFactory != null)
         {
            mSessionCookieFactory = sessionCookieFactory;
            mEnvironment.put(
               PropertyMetadata.ENV_AMPOOL_COOKIE_FACTORY_CLASS_NAME.pName
               , sessionCookieFactory.getClass().getName());
         }
      }
   }

   public int getMaxPoolSize()
   {
      return getProperty(
         PropertyMetadata.ENV_AMPOOL_MAX_POOL_SIZE.pName
         , getEnvironment()
         , Integer.valueOf(PropertyMetadata.ENV_AMPOOL_MAX_POOL_SIZE.pDefault)
            .intValue());
   }

   public int getInitPoolSize()
   {
      return getProperty(
         PropertyMetadata.ENV_AMPOOL_INIT_POOL_SIZE.pName
         , getEnvironment()
         , Integer.valueOf(PropertyMetadata.ENV_AMPOOL_INIT_POOL_SIZE.pDefault)
            .intValue());
   }

   public SessionCookie createSessionCookie(String applicationId, String cookieValue, Properties properties)
   {
      SessionCookie cookie =
         getSessionCookieFactory()
         .createSessionCookie(applicationId, cookieValue, this, properties);

      SessionCookieInfo info = null;
      boolean cookieExists = false;
      
      synchronized(mLock)
      {
         // If a session cookie info object already exists for this cookie
         // then do not replace it with the new cookie.  This is necessary if
         // someone is using the old pool APIs and a new session cookie for
         // the same session is created for each checkout.
         info = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
         if (info == null)
         {
            mSessionCookieInfo.put(cookie, new SessionCookieInfo(cookie));
         }
         else
         {
            cookieExists = true;
         }
      }

      // If the two cookie instances share the same address then this may
      // be a shared cookie.  Go ahead and return the instance without waiting
      // for the other session to remove the cookie.
      if ((cookieExists) && (cookie != info.mCookie))
      {
         // Another thread might be updating the cookie (see bug 1814844).
         // Wait for the cookie lock before preceding.
         synchronized(info.mCookie.getSyncLock())
         {
            synchronized(mLock)
            {
               // If a session cookie info object already exists for this cookie
               // then do not replace it with the new cookie.  This is necessary if
               // someone is using the old pool APIs and a new session cookie for
               // the same session is created for each checkout.
               info = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
               if (info == null)
               {
                  mSessionCookieInfo.put(cookie, new SessionCookieInfo(cookie));
               }
               else
               {
                  // At this point an exception should be thrown.  Remove should
                  // be invoked before create.
                  ApplicationPoolException apex = new ApplicationPoolException(
                     AMPoolMessageBundle.class
                     , AMPoolMessageBundle.EXC_AMPOOL_COOKIE_ALREADY_EXISTS
                     , new Object[] {cookie.getSessionId(), cookie.getApplicationId(), getName()});

                  apex.setSessionCookie(info.mCookie);
                  throw apex;
               }
            }
         }
      }

      return cookie;
   }

   public void addSessionCookie(SessionCookie cookie)
   {
      boolean doCookieSynchronization = false;
      SessionCookieInfo info = null;

      validateSessionCookie(cookie);
      
      synchronized(mLock)
      {
         // If the session cookie states are out of synch then copy
         // the state of the newer cookie to the older cookie.
         info = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
         if (info == null)
         {
            mSessionCookieInfo.put(cookie, new SessionCookieInfo(cookie));
         }
         else
         {
            doCookieSynchronization = true;
         }
      }

      if (doCookieSynchronization)
      {
         // Synchronize on the shared cookie instance.  This is to prevent
         // other threads from accessing the shared cookie while its state is
         // being updated.  Ideally, when addSessionCookie is invoked during
         // cookie de-serialization the cookie in the target pool should be
         // unused so contention should not occur.
         synchronized(info.mCookie.getSyncLock())
         {
            // If the cookie is referencing an application module and that
            // application module is not available then the cookie must
            // currently be using that application module.  Throw an exception.
            // Cookie replication should not target a pool in which that cookie
            // is active.
            if ((info.mApplicationModule != null)
               && (!isAvailable(info.mApplicationModule)))
            {
               throw new ApplicationPoolException(
                  AMPoolMessageBundle.class
                  , AMPoolMessageBundle.EXC_AMPOOL_INVALID_COOKIE_REPL
                  , new Object[] {cookie.getSessionId(), cookie.getApplicationId()});
            }

            if (cookie.getLastUpdate().after(info.mCookie.getLastUpdate()))
            {
               if (info.mApplicationModule != null)
               {
                  // The cookie that is being added was updated more recently than
                  // the cookie referenced by the pool.  Release the pool
                  // cookie's application module statelessly so that activation may
                  // occur properly the next time the session cookie is used.  This
                  // may seem expensive, but it should occur very infrequently.
                  resetApplicationModule(info.mCookie, true);
               }

               // Just in case an old cookie reference is held (would be bad),
               // but we can't be certain what clustering implementations look
               // like.  This won't hurt.
               cookie.copyInto(info.mCookie);

               if (cookie.getPassivationId() != SessionCookie.NULL_PASSIVATION_ID)
               {
                  info.mIsReferencingState = true;
               }
            }
            else if (info.mCookie.getLastUpdate().after(cookie.getLastUpdate()))
            {
               info.mCookie.copyInto(cookie);
            }

            // Replace the existing cookie reference with the new cookie
            // instance.
            info.mCookie = cookie;
         }
      }
   }

   public void removeSessionCookie(SessionCookie cookie)
   {
      // Check if the cookie is currently reserving an application module
      // Perform this validation inside the cookie lock.
      synchronized(cookie.getSyncLock())
      {
         boolean resetAppModuleState = false;
         ApplicationModule appModule = null;
         synchronized(mLock)
         {
            validateSessionCookieInPool(cookie);

            if (useApplicationModule(cookie, false) != null)
            {
               throw new ApplicationPoolException(
                  AMPoolMessageBundle.class
                  , AMPoolMessageBundle.EXC_AMPOOL_CANNOT_REMOVE_COOKIE
                  , new Object[] {cookie.getSessionId(), cookie.getApplicationId(), getName()});
            }

            // Check if this session cookie is still referencing an application
            // module instance.  If so, reset the application module state.
            appModule = getApplicationModuleForSession(cookie);
            if (appModule != null)
            {
               setAvailable(appModule, false);
               resetAppModuleState = true;
            }
            else
            {
               mSessionCookieInfo.remove(cookie);
            }
         } 

         // Reset the application module outside of the application pool
         // lock.  Okay, the application module is made unavailable above.
         if (resetAppModuleState)
         {
            try
            {
               resetApplicationModule(cookie, false);
            }
            finally
            {
               synchronized(mLock)
               {
                  // bug 2452345 - test if the appModule is still in the pool.
                  // if the application module was stale then
                  // it is possible that it was disassociated and removed
                  // by the resetApplicationModule invocation above.
                  if (mInstanceInfo.get(appModule) != null)
                  {
                     setAvailable(appModule, true);
                  }

                  cookie.resetStateInternal();

                  mSessionCookieInfo.remove(cookie);
               }
            }               
         }
      }
   }

   public String getApplicationModuleClass()
   {
      return (String)getEnvironment().get(ConnectionStrategy.APPLICATION_MODULE_CLASS_NAME_PROPERTY);
   }


   /**
    * @deprecated This value should be passed to the pool connection strategy as
    * SessionCookie environment or by implementing an EnvInfoProvider.  The
    * value may be acquired from the ApplicationPool or SessionCookie
    * environment by using the <tt>ConnectionStrategy.DB_CONNECT_STRING_PROPERTY</tt> key.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.ConnectionStrategy
    * @see oracle.jbo.common.ampool.EnvInfoProvider
    * @see oracle.jbo.common.ampool.SessionCookie
    */
   public String getConnectString()
   {
      // Do not synchronize.  This should only be written to asynchronously.
      return mConnectString;
   }


   public Hashtable getEnvironment()
   {
      // Do not synchronize.  This should only be written to asynchronously.
      return mEnvironment;
   }

   private ApplicationModule findUnreferencedAvailInstance()
   {
      ApplicationModule appModule = null;
      RecentlyUsedLinkedListElement elem = mUnrefAvailList.getMRUElement();

      // This loop will return the first unreferenced instance in the available
      // instance list.  The available instance list is ordered with the most
      // recently used instances last.
      if (elem != null)
      {
         appModule = (ApplicationModule)elem.getRefObject();
         Diagnostic.ASSERT(!isReferenced(appModule) && isAvailable(appModule)
            , "ILLEGAL STATE:  UNAVAILABLE OR REFERENCED APPMODULE IN AVAILABLE, UNREFERENCED LIST");
      }

      return appModule;
   }

   private int findAvailableInstance(SessionCookie cookie, SessionCookieInfo referencingCookieInfo)
   {
      int op = -1;

      synchronized(mLock)
      {
         ApplicationModule appModule = null;
         ApplicationModuleInfo appModuleInfo = null;
         appModule = getApplicationModuleForSession(cookie);

         if ((appModule != null) && (isAvailable(appModule)))
         {
            appModuleInfo = (ApplicationModuleInfo)mInstanceInfo.get(appModule);
            mReferencedList.touchElement(appModuleInfo);

            op = REUSE_REFERENCED_INSTANCE;
         }
         else
         {
            int recycleThreshold = getRecycleThreshold();
            appModule = findUnreferencedAvailInstance();

            if (appModule == null && (getResourceCount() >= recycleThreshold))
            {
               appModule = findReferencedAvailInstance();
            }

            // If we could not find a referenced instance
            if (appModule == null)
            {
               // Try to allocate a resource in the resource pool.
               appModule = (ApplicationModule)allocateResource();
            }

            // If we have successfully recycled an instance
            if (appModule != null)
            {
               // If the instance is not referenced
               if (!isReferenced(appModule))
               {
                  appModuleInfo = (ApplicationModuleInfo)mInstanceInfo.get(appModule);

                  op = RECYCLE_UNREFERENCED_INSTANCE;
               }
               else
               {
                  appModuleInfo = (ApplicationModuleInfo)mInstanceInfo.get(appModule);

                  // Remove the application module's session references
                  referencingCookieInfo.mCookie = getReferencingSessionCookie(appModule);
                  referencingCookieInfo.mLastUpdate = referencingCookieInfo.mCookie.getLastUpdate();

                  // Remove the instance from the referenced list.
                  mReferencedList.removeElement(appModuleInfo);

                  setApplicationModuleForSession(referencingCookieInfo.mCookie, null);
                  op = RECYCLE_REFERENCED_INSTANCE;
               }
            }
            else
            {
               op = CREATE_NEW_INSTANCE;
            }
         }

         if (appModule != null)
         {
            // Do this before associating the instance with the new session
            // cookie.
            referencingCookieInfo.mConnectionMetadata = appModuleInfo.mReferencingConnectionMetadata;

            // Only perform this block if an existing instance was located.
            // If we need to create a new instance then perform this block in
            // after the instance has been created below.  This is duplicated
            // so that we can move the instance creation outside of the
            // section that is blocking the pool.
            associateSessionCookie(appModule, cookie);

            // Be sure to set the application module as not available within
            // the synchronized block.  This will prevent other threads from
            // "stealing" the instance from the current thread once it releases
            // the lock
            setAvailable(appModule, false);

            // Immediately add the application module to the referenced instance list.
            mReferencedList.addElement(appModuleInfo);
         }

         referencingCookieInfo.mApplicationModule = appModule;
      } // release mLock

      return op;
   }

   private ApplicationModule findReferencedAvailInstance()
   {
      ApplicationModule appModule = null;
      RecentlyUsedLinkedListElement elem = mRefAvailList.getLRUElement();

      // This loop will return the first unreferenced instance in the available
      // instance list.  The available instance list is ordered with the most
      // recently used instances last.
      if (elem != null)
      {
         appModule = (ApplicationModule)elem.getRefObject();
         Diagnostic.ASSERT(isAvailable(appModule), "ILLEGAL STATE:  UNAVAILABLE APPMODULE IN REF,AVAIL LIST");
      }

      return appModule;
   }

   protected final Object getResourceDetails(Object resource)
   {
      SessionCookieInfo details = new SessionCookieInfo();
      details.mApplicationModule = (ApplicationModule)resource;
      details.mCookie = getReferencingSessionCookie(details.mApplicationModule);

      if (details.mCookie != null)
      {
         Diagnostic.println("Removing a referenced, available pool instance");

         // Remove the application module's session references
         details.mLastUpdate = details.mCookie.getLastUpdate();

         disassociateSessionCookie(details.mApplicationModule, details.mCookie);
      }
      else
      {
         Diagnostic.println("Removing an unreferenced, available pool instance");
      }

      return details;

   }

   /**
    * Return the least recently used available resource in the pool.
    * <p>
    * Applications should not use this method.
    *
    * @param refResource if not null returns the resource that was less recently
    *    used than the reference resource.
    */
   protected final Object seekLRUAvailableResource(Object refResource)
   {
      synchronized(mLock)
      {
         Object resource = null;
         if (refResource == null)
         {
            RecentlyUsedLinkedListElement elem = mUnrefAvailList.getLRUElement();

            if (elem == null)
            {
               elem = mRefAvailList.getLRUElement();
            }

            if (elem != null)
            {
               resource = elem.getRefObject();
            }
         }
         else
         {
            ApplicationModuleInfo info = (ApplicationModuleInfo)mInstanceInfo.get(refResource);
            
            RecentlyUsedLinkedListElement element = info.mUnrefAvailListElement;

            // if the element is not in one list then check the other.  the
            // two lists should be mutually exclusive.
            if (!element.isInList())
            {
               element = info.mRefAvailListElement;
            }
            
            if (element.isInList())
            {
               element = element.getLessRecentlyUsedElement();
               if (element != null)
               {
                  resource = element.getRefObject();
               }
            }
         }

         return resource;
      }
   }

   /**
    * Return the most recently used available resource in the pool.
    * <p>
    * Applications should not use this method.
    *
    * @param refResource if not null returns the resource that was more recently
    *    used than the reference resource.
    */
   protected final Object seekMRUAvailableResource(Object refResource)
   {
      synchronized(mLock)
      {
         Object resource = null;
         if (refResource == null)
         {
            RecentlyUsedLinkedListElement elem = mUnrefAvailList.getMRUElement();

            if (elem == null)
            {
               elem = mRefAvailList.getMRUElement();
            }

            if (elem != null)
            {
               resource = elem.getRefObject();
            }
         }
         else
         {
            ApplicationModuleInfo info = (ApplicationModuleInfo)mInstanceInfo.get(refResource);
            RecentlyUsedLinkedListElement element = info.mUnrefAvailListElement;

            // if the element is not in one list then check the other.  the
            // two lists should be mutually exclusive.
            if (!element.isInList())
            {
               element = info.mRefAvailListElement;
            }
            
            if (element.isInList())
            {
               element = element.getMoreRecentlyUsedElement();
               if (element != null)
               {
                  resource = element.getRefObject();
               }
            }
         }

         return resource;
      }
   }

   protected final void finalizeResource(Object resource, Object details)
   {
      super.finalizeResource(resource, details);

      if (!(details instanceof SessionCookieInfo))
      {
         return;
      }

      SessionCookieInfo cookieInfo = (SessionCookieInfo)details;

      ApplicationModule appModule = cookieInfo.mApplicationModule;
      SessionCookie referencingCookie = cookieInfo.mCookie;
      Date lastUpdate = cookieInfo.mLastUpdate;

      boolean isInstanceAlive = isInstanceAlive(appModule);
      if (!isInstanceAlive)
      {
         removeDeadInstance(appModule);
         return;
      }

      // 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.  There is a slight chance here that the referencing
      // session returns to the pool after we have released the pool lock
      // above, but before we can acquire the session cookie lock.
      // This will not result in an error but may cause the referencing
      // session to see old state.  This is a danger of disabling failover
      // support.
      if (referencingCookie != null)
      {
         synchronized(referencingCookie.getSyncLock())
         {
            // Only failover if the timestamps are still the same.  Otherwise
            // another thread must have slipped in and used the session
            // cookie.  Oh well.
            if (!referencingCookie.isFailoverEnabled()
               && referencingCookie.getLastUpdate().equals(lastUpdate))
            {
               // Cannot failover inside the application pool lock.  Failover
               // needs to acquire a session cookie lock.
               doFailover(appModule, referencingCookie);
            }
         }
      }
   }


   /**
    * Checks in an application instance that had previously been checked out.
    * This makes the application instance avalable for subsequent checkout
    * requests from this pool.
    * <p>
    * @param instance name of the application module instance to check in.
    * <p>
    * @deprecated Replaced by:
    *  {@link oracle.jbo.common.ampool.SessionCookie#releaseApplicationModule(boolean, boolean)}.
    * <p>
    * Application developers should invoke:
    *    <tt>SessionCookie.releaseApplicationModule(true, false)</tt>
    * instead of this method.  A session cookie instance may be acquired by
    * invoking:
    *    {@link #createSessionCookie(String, String, Properties)}.
    * <p>
    * This change was necessary to support the SessionCookie interface.  Please
    * see:
    *    {@link oracle.jbo.common.ampool.SessionCookie}
    * for more information about using SessionCookies with the application pool.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.SessionCookie#releaseApplicationModule(boolean, boolean)
    * @see oracle.jbo.common.ampool.SessionCookie#useApplicationModule()
    */
   public void checkin(ApplicationModule appModule)
   {
      // DO NOT ACQUIRE A POOL LOCK.
      // This may cause deadlock to occur between threads that are waiting
      // for the pool lock and those waiting for the session cookie lock (see
      // cookie.releaseApplicationModule(boolean, boolean) below.
      SessionCookie cookie = getReferencingSessionCookie(appModule);

      // Use the cookie to release the application module instead of invoking
      // the private doUnmanagedCheckin directly.  This is done to ensure that
      // the session cookie synchronization between multi-threaded requests
      // which are using the 3.2 API.
      cookie.releaseApplicationModule(true /* checkin */, false /* manageState */);
   }

   public void releaseApplicationModule(SessionCookie cookie, boolean manageState)
   {
      validateSessionCookieInPool(cookie);

      if (manageState)
      {
         // No need to synchronize in here.  The private method is synchronized
         // properly.
         doManagedCheckin(cookie);
      }
      else
      {
         // No need to synchronize in here.  The private method is synchronized
         // properly.
         doUnmanagedCheckin(cookie);
      }

      ((SessionCookieInfo)mSessionCookieInfo.get(cookie)).touch();
   }

   void resetApplicationModule(SessionCookie cookie, boolean cleanUpStateAndRelease)
   {
      // The cleanUpStateAndRelease flag indicates that the reset should clean 
      // up previously passivated state and release the application module to 
      // the pool.  The flag is true for most invocations of 
      // resetApplicationModule.  The flag may be false when 
      // ResetApplicationModule is invoked during cookie removal and the 
      // application module is already available and the passivated state may 
      // be requested by the timed out application client at a later time.
      //
      // Internal use only.  Unreferences a session cookie from an application
      // module.  Cleans up any transactional state that an application module
      // may reference.
      ApplicationModule appModule = null;

      synchronized(mLock)
      {
         validateSessionCookieInPool(cookie);

         appModule = getApplicationModuleForSession(cookie);

         if (appModule == null)
         {
            // the session is not referencing an ApplicationModule
            return;
         }

         // Just in case.  Mark the application module as not available
         setAvailable(appModule, false);
      }

      boolean isInstanceAlive = isInstanceAlive(appModule);
      if (!isInstanceAlive)
      {
         removeDeadInstance(appModule);

         // Now return.  The logic below is only necessary if the instance
         // is to remain in the pool.
         cookie.resetStateInternal();
         return;
      }

      // If we are not in release then the application module may have been
      // disconnected.  Reconnect before proceeding.
      if (!cleanUpStateAndRelease)
      {
         reconnect(appModule, cookie, false);
      }


      // Clean up any old session state that may be associated 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 (cleanUpStateAndRelease 
         && (cookie.getPassivationId() != SessionCookie.NULL_PASSIVATION_ID))
      {
         doPersistenceOperation(REMOVE_STATE, appModule, cookie.getPassivationId(), cookie);
         cookie.setPassivationId(SessionCookie.NULL_PASSIVATION_ID);
      }

      // 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.
      // A side effect of rollback should clear the VO and EO caches.
      //
      // Moved rollback into resetState on middle tier.  Reduce roundtrips.

      // Reset the non-transactional state of the application module.  By
      // default non-transactional state should be reset.  This is necessary to
      // support bacwards compatibility with 3.2.  If a developer has disabled
      // reset they must be aware that non-transaction application state like
      // dynamic view objects, where clauses, bind parameters, order by
      // clauses, view usage instances and application module usage instances
      // have not been reset.
      //
      // A developer may wish to disable reset if they would like to prevent
      // garbage collection of the application module, view usages, and their
      // related states.  Another benefit of disabling reset may be realized
      // through re-use of the cached JDBC prepared statements (assumes that
      // connection pooling has also been disabled).

      if (cookie.isConnectionPoolingEnabled())
      {
         // Disconnect.  Do not retain the application module state.
         // This will have the side effect of invoking resetState(true)
         disconnect(appModule, false, cookie);
      }
      else
      {
         appModule.resetState(true);
      }

      synchronized(mLock)
      {
         disassociateSessionCookie(appModule, cookie);

         cookie.resetStateInternal();

         SessionCookieInfo cookieInfo = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
         cookieInfo.mIsReferencingState = false;

         if (cleanUpStateAndRelease)
         {
            super.releaseResource(appModule, null);
         }
      } // release mLock
   }

   private void doUnmanagedCheckin(SessionCookie cookie)
   {
      synchronized(mLock)
      {
         ApplicationModule appModule = getApplicationModuleForSession(cookie);

         // Determine if the pool recognizes this application module
         // as having been checked out.  If not, throw an exception.
         if ((appModule == null) || isAvailable(appModule))
         {
            throw new ApplicationPoolException(
               AMPoolMessageBundle.class
               , AMPoolMessageBundle.EXC_AMPOOL_INVALID_CHECKIN
               , new Object[] {mName});
         }
      } // release mLock
      // It's okay to release the pool lock above because
      // we still have not set the application module as available

      // The cookie may have been cleaned up since the application module
      // was checked out.  Create new cookie.  JRS How?  Two possibilities for
      // this occuring:  1) The AM was not created by this pool instance, which
      // would result in many more serious issues, 2) the client attempted to
      // checkin an AM with session state for which a stateless checkin has
      // already been invoked.  Both of these conditions represent errors.
      // Throw an exception instead of creating a new cookie.
      if (cookie == null)
      {
         throw new ApplicationPoolException(
            AMPoolMessageBundle.class
            , AMPoolMessageBundle.EXC_AMPOOL_INVALID_CHECKIN
            , new Object[] {mName});
      }


      resetApplicationModule(cookie, true);
   }

   /**
    * Check-in the application module as being referenced by the invoking
    * session thread.
    * <p>
    * This method should be used by pool clients that wish to maintain logical
    * application module state while still sharing an application module
    * resource across requests.  The method returns a system generated session
    * id for the check-in that should be used as the unique application module
    * identifier by the client session.</p>
    * <p>
    * The application module will be passivated immediately if failover support
    * has been requested (default).  If failover support has been disable,
    * through the jbo.DoFailover system parameter, the application module will
    * not be passivated until it is re-used by a session other than the
    * session checking in the application module.</p>
    *
    * @param appModule the application module that will be checked in
    * <p>
    * @deprecated Replaced by:
    *  {@link oracle.jbo.common.ampool.SessionCookie#releaseApplicationModule(boolean, boolean)}.
    * <p>
    * Application developers should invoke:
    *    <tt>SessionCookie.releaseApplicationModule(true, true)</tt>
    * <p>
    * instead of this method.  A session cookie instance may be acquired by
    * invoking:
    *    {@link #createSessionCookie(String, String, Properties)}.
    * <p>
    * This change was necessary to support the SessionCookie interface.  Please
    * see:
    *    {@link oracle.jbo.common.ampool.SessionCookie}
    * <p>
    * for more information about using SessionCookies with the application pool.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.SessionCookie#releaseApplicationModule(boolean, boolean)
    * @see oracle.jbo.common.ampool.SessionCookie#useApplicationModule()
    */
   public String checkinWithSessionState(ApplicationModule appModule)
   {
      // DO NOT ACQUIRE A POOL LOCK.
      // This may cause deadlock to occur between threads that are waiting
      // for the pool lock and those waiting for the session cookie lock (see
      // cookie.releaseApplicationModule(boolean, boolean) below.
      SessionCookie cookie = getReferencingSessionCookie(appModule);

      // Use the cookie to release the application module instead of invoking
      // the private doManagedCheckin directly.  This is done to ensure that
      // the session cookie synchronization between multi-threaded requests
      // which are using the 3.2 API.
      StringBuffer sb = null;
      synchronized(cookie.getSyncLock())
      {
         cookie.releaseApplicationModule(true /* checkin */, true /* manageState */);

         sb = new StringBuffer(cookie.getSessionId());
         String value = cookie.getValue();
         if (value != null)
         {
            sb.append(SessionCookieImpl.COOKIE_SEPARATOR).append(value);
         }
      }

      return sb.toString();
   }

   private ApplicationModule doCheckout(SessionCookie cookie)
   {
      // Pass a return structure by reference to access the objects that are
      // located during findAvailInstance.  findAvailInstance is separated
      // from doCheckout because it contains most of the synchronized work
      // that is required to locate an application module instance for the
      // current thread.  It is also invoked recursively.
      SessionCookieInfo referencingCookieInfo = new SessionCookieInfo();
      int op = findAvailableInstance(cookie, referencingCookieInfo);

      // Synchronized the operations that must update shared data structures.
      // The "real" work is performed by the switch statement below.  This
      // is done to prevent the current thread from holding a lock on the pool
      // while the expensive application module connection/creation logic
      // is executed below.
      ApplicationModule appModule = referencingCookieInfo.mApplicationModule;
      SessionCookie referencingCookie = referencingCookieInfo.mCookie;
      ConnectionMetadata oldConnectionMetadata = referencingCookieInfo.mConnectionMetadata;
      Date lastUpdate = referencingCookieInfo.mLastUpdate;

      if (appModule != null)
      {
         boolean isInstanceAlive = isInstanceAlive(appModule);
         if (!isInstanceAlive)
         {
            // If the instance is dead then go ahead and remove it.
            removeDeadInstance(appModule);

            firePoolEvent(ApplicationPoolListener.CHECKOUT_FAILED);

            // Recursively invoke doCheckout
            return doCheckout(cookie);
         }
      }

      try
      {
         if (op == REUSE_REFERENCED_INSTANCE)
         {
            reuseReferencedInstance(cookie, appModule);

         }  // end REUSE_REFERENCED_INSTANCE
         else if (op == RECYCLE_REFERENCED_INSTANCE)
         {
            recycleReferencedInstance(cookie, appModule, oldConnectionMetadata, referencingCookie, lastUpdate);

         } // end RECYCLE_REFERENCED_INSTANCE
         else if (op == RECYCLE_UNREFERENCED_INSTANCE)
         {
            recycleUnreferencedInstance(cookie, appModule, oldConnectionMetadata);
         } // end RECYCLE_UNREFERENCED_INSTANCE
         else if (op == CREATE_NEW_INSTANCE)
         {
            try
            {
               Properties props = new Properties();
               props.put(SESSION_COOKIE, cookie);
               appModule = (ApplicationModule)createResource(props);
            }
            catch (java.lang.Exception ex)
            {
               if (ex instanceof RuntimeException)
               {
                  throw (RuntimeException)ex;
               }
               else
               {
                  throw new JboException(ex);
               }
            }

            connect(appModule, cookie);
            
            synchronized(mLock)
            {
               associateSessionCookie(appModule, cookie);

               // Be sure to set the application module as not available within
               // the synchronized block.  This will prevent other threads from
               // "stealing" the instance from the current thread once it releases
               // the lock
               setAvailable(appModule, false);

               // Immediately add the application module to the referenced instance list.
               mReferencedList.addElement((RecentlyUsedLinkedListElement)mInstanceInfo.get(appModule));
            }

            // If the passivation id is not null then the application module must
            // be activated because the client either:  1) passed a passivation id
            // in the cookieValue that was specified during checkout or, 2)
            // specified a session id for which a valid in-memory cookie existed
            // that referenced a passivation id.  Please note that this branch is
            // not executed if a referenced instance was re-used.
            int passivationId = cookie.getPassivationId();
            if (passivationId != SessionCookie.NULL_PASSIVATION_ID)
            {
               doActivate(passivationId, appModule, cookie);
            }
            else
            {
               prepareSession(appModule, cookie);
            }
         } // end CREATE_NEW_INSTANCE
      }
      catch (RuntimeException rex)
      {
         synchronized(mLock)
         {
            // Set the application module as available.  This is necessary
            // to prevent memory leaks from development time exceptions.
            // Doing this will force an application developer to deal with
            // development time exceptions, since a stale application module
            // will constantly be reused.
            if (appModule != null)
            {
               disassociateSessionCookie(appModule, cookie);
               setAvailable(appModule, true);
            }
         }
         
         firePoolEvent(ApplicationPoolListener.CHECKOUT_FAILED);

         ApplicationPoolException apex = new ApplicationPoolException(
            AMPoolMessageBundle.class
            , AMPoolMessageBundle.EXC_AMPOOL_CHECKOUT_FAILED
            , new Object[] {mName}
            , new Exception[] {rex}); 

         throw apex;
      }

      firePoolEvent(ApplicationPoolListener.INSTANCE_CHECKED_OUT);

      return appModule;
   }

   private void reuseReferencedInstance(SessionCookie cookie, ApplicationModule am)
   {
      Diagnostic.println("Reusing a cached session application module instance");

      SessionCookieInfo sessionCookieInfo = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
      ConnectionMetadata oldMetadata = sessionCookieInfo.mConnectionMetadata;
      
      reconnect(am, cookie, true);

      // 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 and the
      // application module transaction had been disconnected.
      if (cookie.isActivationRequired(am))
      {
         int passivationId = cookie.getPassivationId();
         doActivate(passivationId, am, cookie);
      }
      else if (oldMetadata != null)
      {
         ConnectionMetadata newMetadata = am.getTransaction().getConnectionMetadata();

         if ((newMetadata != null) && (newMetadata.getSignature() != oldMetadata.getSignature()))
         {
            prepareSession(am, cookie);

            sessionCookieInfo.mConnectionMetadata = newMetadata;
         }
      }
      
      firePoolEvent(ApplicationPoolListener.INSTANCE_REUSED);
   }

   private void recycleReferencedInstance(
      SessionCookie cookie
      , ApplicationModule am
      , ConnectionMetadata oldConnectionMetadata
      , SessionCookie referencingCookie
      , Date lastReferencingUpdate)
   {
      Diagnostic.println("Recycling a referenced, available pool instance");

      // 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.  There is a slight chance here that the referencing
      // session returns to the pool after we have released the pool lock
      // above, but before we can acquire the session cookie lock.
      // This will not result in an error but may cause the referencing
      // session to see old state.  This is a danger of disabling failover
      // support.
      //
      // Cannot failover inside the application pool lock.  Failover
      // needs to acquire a session cookie lock first.
      synchronized(referencingCookie.getSyncLock())
      {
         if (!referencingCookie.isFailoverEnabled()
            && !referencingCookie.isActivationRequired(am)
            && referencingCookie.getLastUpdate().equals(lastReferencingUpdate))
         {
            // Reconnect first.  This will prevent redundant connect/disconnect
            // logic from firing b/w doFailover and this method.
            reconnect(am, referencingCookie, true);

            // check again.  reconnect could have caused a failover.
            if (!referencingCookie.isActivationRequired(am))
            {
               doFailover(am, referencingCookie);
            }
         }
      }

      // 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 the referencing cookie and the existing cookie connection
      // metadata are not equal then do a disconnect/connect.
      // This is necessary because the JDBC credentials may be different
      // between the two sessions.
      SessionCookieInfo sessionCookieInfo = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
      if (!compareConnectionMetadata(oldConnectionMetadata, sessionCookieInfo.mConnectionMetadata))
      {
         disconnect(am, false, referencingCookie);
         connect(am, cookie);
      }
      else
      {
         // Otherwise, simply reconnect and then rollback the application
         // module state
         reconnect(am, cookie, false);

         // Rollback.  This is necessary to remove any
         // unposted transaction validation listeners which may still be
         // registered on a stateful application module's transaction.
         //
         // Moved rollback, cache clearing, to resetState on the middle tier.
         // Reload the AM state for the new session
         am.resetState(true);
      }

      // If the passivation id is not null then the application module must
      // be activated because the client either:  1) passed a passivation id
      // in the cookieValue that was specified during checkout or, 2)
      // specified a session id for which a valid in-memory cookie existed
      // that referenced a passivation id.  Please note that this branch is
      // not executed if a referenced instance was re-used.
      int passivationId = cookie.getPassivationId();
      if (passivationId != SessionCookie.NULL_PASSIVATION_ID)
      {
         doActivate(passivationId, am, cookie);
      }
      else
      {
         prepareSession(am, cookie);
      }

      firePoolEvent(ApplicationPoolListener.REFERENCED_INSTANCE_RECYCLED);
   }

   private void recycleUnreferencedInstance(
      SessionCookie cookie
      , ApplicationModule am
      , ConnectionMetadata oldConnectionMetadata)
   {
      Diagnostic.println("Recycling an unreferenced, available pool instance");

      // If the referencing cookie and the existing cookie environment
      // signatures are not equal then do a disconnect/connect.
      // This is necessary because the JDBC credentials may be different
      // between the two sessions.
      SessionCookieInfo sessionCookieInfo = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
      if (!compareConnectionMetadata(oldConnectionMetadata, sessionCookieInfo.mConnectionMetadata))
      {
         disconnect(am, false, cookie);
         connect(am, cookie);
      }
      else
      {
         reconnect(am, cookie, false);
      }

      // If the passivation id is not null then the application module must
      // be activated because the client either:  1) passed a passivation id
      // in the cookieValue that was specified during checkout or, 2)
      // specified a session id for which a valid in-memory cookie existed
      // that referenced a passivation id.  Please note that this branch is
      // not executed if a referenced instance was re-used.
      int passivationId = cookie.getPassivationId();
      if (passivationId != SessionCookie.NULL_PASSIVATION_ID)
      {
         doActivate(passivationId, am, cookie);
      }
      else
      {
         prepareSession(am, cookie);
      }
      
      firePoolEvent(ApplicationPoolListener.UNREFERENCED_INSTANCE_RECYCLED);
   }

   private void removeDeadInstance(ApplicationModule appModule)
   {
      Diagnostic.println("A dead application module instance was detected");
      Diagnostic.println("The application module instance was removed from the pool");

      try
      {
         removeResource(appModule);
      }
      catch(Exception ex)
      {
         // Eat the remove exception.  We don't care, we already
         // know that the instance is dead.
      }
   }

   private void doManagedCheckin(SessionCookie cookie)
   {
      ApplicationModule appModule = null;

      // Read from the shared application pool data structures inside the
      // synchronized block.  Then do the heavy lifting.
      synchronized(mLock)
      {
         appModule = getApplicationModuleForSession(cookie);

         // Determine if the pool recognizes this application module
         // as having been checked out.  If not, throw an exception.
         if (appModule == null || isAvailable(appModule))
         {
            throw new ApplicationPoolException(
               AMPoolMessageBundle.class
               , AMPoolMessageBundle.EXC_AMPOOL_INVALID_CHECKIN
               , new Object[] {mName});
         }
      } // release mLock
      // It's okay to release the pool lock above because
      // we still have not set the application module as available

      boolean doFailover = cookie.isFailoverEnabled();

      // 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);
      }

      // 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.
      if (cookie.isConnectionPoolingEnabled())
      {
         try
         {
            disconnect(appModule, true, cookie);
         }
         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);
               }

               // Rollback the application module to clean up the illegal
               // transactional state.  This is okay because we have already
               // passivated the transactional state.  If we do not perform a
               // rollback then an exception may occur when application
               // module state is activated.
               appModule.clearVOCaches(null, true);

               // Re-attempt to disconnect with database state retained.  This
               // should work this time because the transactional state has been
               // rolled back.
               disconnect(appModule, true, cookie);

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

      synchronized(mLock)
      {
         // Refresh the instance's position in the referenced list
         mReferencedList.touchElement((RecentlyUsedLinkedListElement)mInstanceInfo.get(appModule));

         super.releaseResource(appModule, null);
      } // release mLock
   }

   /**
    * @deprecated  Replaced by
    * {@link oracle.jbo.common.ampool.ConnectionStrategy#createApplicationModule(SessionCookie, EnvInfoProvider)}.
    * All extending logic that was implemented here should be implemented in a
    * custom ConnectionStrategy class that extends:
    *    {@link oracle.jbo.common.ampool.DefaultConnectionStrategy}.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.ConnectionStrategy#createApplicationModule(SessionCookie, EnvInfoProvider)
    */
   public ApplicationModule createNewInstance() throws Exception
   {
      // The internal blocks still invoke this deprecated API in order to
      // account for custom logic.
      return (ApplicationModule)createResource(null);
   }

   public Object instantiateResource(Properties properties)
   {
      SessionCookie cookie = null;
      ApplicationModule am = null;
      
      if ((properties != null) 
         && (null != (cookie = (SessionCookie)properties.get(SESSION_COOKIE))))
      {
         am = getConnectionStrategy().createApplicationModule(
            cookie
            , cookie.getEnvInfoProvider());
      }
      else
      {
         am = getConnectionStrategy().createApplicationModule(getEnvironment());
      }
      
      ApplicationModuleInfo info = new ApplicationModuleInfo(am);

      synchronized(mLock)
      {
         mInstanceInfo.put(am, info);

         // Don't set available yet.  This may cause a race condition if
         // this method has been invoked by doCheckout.  It is the responsiblity
         // of the client to set the application module as available when it
         // is ready to go.  Setting availability false just to make sure
         // that this instance cannot be "grabbed" by another thread after
         // it has been created but before it is available.
//         setAvailable(am, false);
         return am;
      } // release mLock
   }

   /**
    * Checks out stateless application instance from the pool. If the pool does
    * not have any available instances, it will create a new instance and return
    * it to the request thread.
    * <p>
    * @deprecated Replaced by:
    *   {@link oracle.jbo.common.ampool.SessionCookie#useApplicationModule()}.
    * <p>
    * Application developers should invoke:
    *    <tt>SessionCookie.useApplicationModule()</tt>
    * <p>
    * instead of this method.  A session cookie instance may be acquired by
    * invoking:
    *    {@link #createSessionCookie(String, String, Properties)}.
    * <p>
    * This change was necessary to support the SessionCookie interface.  Please
    * see:
    *    {@link oracle.jbo.common.ampool.SessionCookie}
    * for more information about using SessionCookies with the application pool.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.SessionCookie#useApplicationModule()
    * @see oracle.jbo.common.ampool.SessionCookie#releaseApplicationModule(boolean, boolean)
    */
   public ApplicationModule checkout() throws Exception
   {
      // DO NOT ACQUIRE A POOL LOCK.
      // This may cause deadlock to occur between threads that are waiting
      // for the pool lock and those waiting for the session cookie lock (see
      // cookie.useApplicationModule(boolean) below.
      SessionCookie cookie = createSessionCookie(getName(), getNextSessionId(), null);

      // Use the cookie to release the application module instead of invoking
      // the private doCheckout directly.  This is done to ensure that
      // the session cookie synchronization between multi-threaded requests
      // which are using the 3.2 API.  Do not down the session cookie mutex
      // since this was not performed in 3.2.
      return cookie.useApplicationModule(false /* lock */);
   }

   /**
    * @deprecated  Replaced by {@link #removeResources()}.  Method may be
    * confused with releaseResource.
    * <p>
    * @since 9.0.2 
    * @see #removeResources()
    */
   public void releaseInstances()
   {
      removeResources();
   }

   /**
    * @deprecated
    * @since 9.0.2 
    * @see #removeResource(Object)
    */
   protected void releaseInstance(ApplicationModule instance)
   {
      removeResource(instance);
   }

   public Object removeResource(Object resource)
   {
      // clean up the pool stat first.  this is important to prevent
      // a remove exception from putting the pool in an invalid state.
      ApplicationModule instance = null;
      synchronized(mLock)
      {
         instance = (ApplicationModule)super.removeResource(resource);

         SessionCookie cookie = getReferencingSessionCookie(instance);
         ApplicationModuleInfo info = 
            (ApplicationModuleInfo)mInstanceInfo.get(instance);
         if (cookie != null)
         {
            // Remove the instance from the referenced list.
            mReferencedList.removeElement(info);
            setApplicationModuleForSession(cookie, null);
         }

         mRefAvailList.removeElement(info.mRefAvailListElement);
         mUnrefAvailList.removeElement(info.mUnrefAvailListElement);

         mInstanceInfo.remove(instance);
      }

      // Perform the removal of the application module outside of the
      // synchronized block.  The remove may rollback and disconnect the
      // application module instance.
      instance.remove();

      return instance;
   }

   public ArrayList removeResources()
   {
      ArrayList resources = null;

      synchronized(mLock)
      {
         resources = super.removeResources();

         mInstanceInfo.clear();

         Iterator iter = mSessionCookieInfo.values().iterator();
         while (iter.hasNext())
         {
            ((SessionCookieInfo)iter.next()).mApplicationModule = null;
         }

         mReferencedList.reset();
         mRefAvailList.reset();
         mUnrefAvailList.reset();
      }

      int size = resources.size();

      for (int i = 0; i < size; i++)
      {
         ApplicationModule instance = (ApplicationModule)resources.get(i);
         instance.remove();
      }

      return resources;
   }

   public int getAvailableInstanceCount()
   {
      return getAvailableResourceCount();
   }

   /**
    * @deprecated Replaced by {@link #getAvailableInstanceCount()}
    * @since 9.0.2 
    * @see #getAvailableInstanceCount()
    */
   public int getAvailableNumPools()
   {
      return getAvailableResourceCount();
   }

   public int getInstanceCount()
   {
      return getResourceCount();
   }

   public ApplicationModule getInstance(int index)
   {
      return (ApplicationModule)getResource(index);
   }

   /**
    * Returns an application module for the specified session.  The session
    * id must have been generated by a previous call to:
    *    {@link #checkinWithSessionState(ApplicationModule)}
    * <p>
    * against this application module pool.
    * <p>
    * The session id should be used to re-create an application module state
    * from a previous request.
    * <p>
    * If an unrecognized session id is specified then the pool should return
    * a stateless application module instance.
    * <p>
    * @param sessionId a session identifier from a previous request
    *
    * @deprecated Replaced by:
    *    {@link oracle.jbo.common.ampool.SessionCookie#useApplicationModule()}.
    * <p>
    * Application developers should invoke:
    *   <tt>SessionCookie.useApplicationModule()</tt>
    * <p>
    * instead of this method.  A session cookie instance may be acquired by
    * invoking:
    *    {@link #createSessionCookie(String, String, Properties)}.
    * <p>
    * This change was necessary to support the SessionCookie interface.  Please
    * see:
    *    {@link oracle.jbo.common.ampool.SessionCookie}
    * <p>
    * for more information about using SessionCookies with the application pool.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.SessionCookie#useApplicationModule()
    * @see oracle.jbo.common.ampool.SessionCookie#releaseApplicationModule(boolean, boolean)
    */
   public ApplicationModule checkout(String cookieValue)
   {
      // DO NOT ACQUIRE A POOL LOCK.
      // This may cause deadlock to occur between threads that are waiting
      // for the pool lock and those waiting for the session cookie lock (see
      // cookie.useApplicationModule(boolean) below.
      String sessionId = SessionCookieImpl.parseSessionId(cookieValue);

      // Check to see if an existing cookie exists first.
      SessionCookie cookie =
         getSessionCookieFactory()
         .createSessionCookie(getName(), sessionId, this, null);

      synchronized(mLock)
      {
         SessionCookieInfo info = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
         if (info != null)
         {
            cookie = info.mCookie;
         }
      }

      if (cookie == null)
      { 
         cookie = createSessionCookie(getName(), sessionId, null);

         int passivationId = SessionCookieImpl.parsePassivationId(cookieValue);
         if (passivationId != SessionCookie.NULL_PASSIVATION_ID)
         {
            cookie.setPassivationId(passivationId);
         }
      }

      // Use the cookie to release the application module instead of invoking
      // the private doCheckout directly.  This is done to ensure that
      // the session cookie synchronization between multi-threaded requests
      // which are using the 3.2 API.  Do not down the session cookie mutex
      // since this was not performed in 3.2.
      return cookie.useApplicationModule(false /* lock */);
   }

   public ApplicationModule useApplicationModule(SessionCookie cookie, boolean checkout)
   {
      ApplicationModule rtnAppModule = null;

      synchronized(mLock)
      {
         validateSessionCookieInPool(cookie);

         ApplicationModule appModule = getApplicationModuleForSession(cookie);

         // If the cookie is referencing an application module instance and
         // the cookie that is referencing the application module is equal to
         // the cookie that was passed in and the application module instance
         // is checked out then the request has originated from a session has
         // already checked out the application module.  It is not
         // necessary to check out the application module instance again.
         if (appModule != null
            && getReferencingSessionCookie(appModule).equals(cookie)
            && !isAvailable(appModule))
         {
            rtnAppModule = appModule;
         }
      }

      if ((rtnAppModule == null) && checkout)
      {
         // No need to synchronize in here.  The private method is synchronized
         // properly.
         rtnAppModule = doCheckout(cookie);
      }

      ((SessionCookieInfo)mSessionCookieInfo.get(cookie)).touch();
      return rtnAppModule;
   }


   /**
    * @deprecated Replaced by {@link #getName()}.
    * @since 5.0
    * @see #getName()
    */
   public String getPoolName()
   {
      return getName();
   }

   public String getName()
   {
      // Do not synchronize.  mName is not mutable.
      return mName;
   }

   public Hashtable getUserData()
   {
      synchronized(mLock)
      {
         return mUserData;
      }
   }

   public void setUserData(Hashtable userData)
   {
      synchronized(mLock)
      {
         mUserData = userData;
      }
   }

   /**
    * Returns the user name.
    *
    * @deprecated This value should be passed to the pool connection strategy as
    * SessionCookie environment or by implementing an EnvInfoProvider.  The
    * value may be acquired from the ApplicationPool or SessionCookie
    * environment by using the <tt>Configuration.DB_USERNAME_PROPERTY</tt> key.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.ConnectionStrategy
    * @see oracle.jbo.common.ampool.EnvInfoProvider
    * @see oracle.jbo.common.ampool.SessionCookie
    */
   public String getUserName()
   {
      synchronized(mLock)
      {
         return mUserName;
      }
   }

   /**
    * @deprecated This value should be passed to the pool connection strategy as
    * SessionCookie environment or by implementing an EnvInfoProvider.  The
    * value may be set in the ApplicationPool or SessionCookie
    * environment by using the <tt>Configuration.DB_USERNAME_PROPERTY</tt> key.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.ConnectionStrategy
    * @see oracle.jbo.common.ampool.EnvInfoProvider
    * @see oracle.jbo.common.ampool.SessionCookie
    */
   public void setUserName(String userName)
   {
      synchronized(mLock)
      {
         mUserName = userName;
      }
   }

  /**
   * Returns the password.
   *
   * @deprecated This value should be passed to the pool connection strategy as
   * SessionCookie environment or by implementing an EnvInfoProvider.  The
   * value may be acquired from the ApplicationPool or SessionCookie
   * environment by using the <tt>Configuration.DB_PASSWORD_PROPERTY</tt> key.
   * <p>
   * @since 9.0.2
   * @see oracle.jbo.common.ampool.ConnectionStrategy
   * @see oracle.jbo.common.ampool.EnvInfoProvider
   * @see oracle.jbo.common.ampool.SessionCookie
   */
   public String getPassword()
   {
      synchronized(mLock)
      {
         return mPassword;
      }
   }

   /**
    * @deprecated This value should be passed to the pool connection strategy as
    * SessionCookie environment or by implementing an EnvInfoProvider.  The
    * value may be set in the ApplicationPool or SessionCookie
    * environment by using the <tt>Configuration.DB_PASSWORD_PROPERTY</tt> key.
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.ConnectionStrategy
    * @see oracle.jbo.common.ampool.EnvInfoProvider
    * @see oracle.jbo.common.ampool.SessionCookie
    */
   public void setPassword(String password)
   {
      synchronized(mLock)
      {
         mPassword = password;
      }
   }

   private void readObject(ObjectInputStream in)
      throws IOException, ClassNotFoundException
   {
      try
      {
         in.defaultReadObject();
      }
      catch (java.io.NotActiveException naex)
      {
         // Only thrown if defaultReadObject is not invoked from readObject
      }

      try
      {
         PoolMgr.getInstance().addResourcePool(mName, this);
         initialize();
      }
      catch (oracle.jbo.JboException jex)
      {
         // If a JboException is thrown then the pool must already exist.
      }
   }

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

         if (snapId >= 0)
         {
            Transaction txn = null;
            boolean wasConnected = true;
            ApplicationModule current = null;
            ApplicationModuleInfo instanceInfo = null;

            ArrayList resources = getResources();

            for(int i = 0 ; i < resources.size(); i++)
            {
               current = (ApplicationModule)resources.get(i);
               instanceInfo = (ApplicationModuleInfo)mInstanceInfo.get(current);

               if (current != instance)
               {
                  // Only apply the change set if the application module references
                  // some transactional state.  Whether or not the application
                  // module references transactional state is determined by
                  // checking if the application module is referenced by a
                  // session cookie.  An application module will only be referenced
                  // by a session cookie if it is currently in use or if it has
                  // been released with state management enabled.  Be sure not
                  // to apply the change set if forced activation is required
                  // because this implies that the transactional state of the
                  // application module has already been cleared.  We do not
                  // have to deal with unreferenced application modules b/c
                  // those application modules should already have had their
                  // transactional states cleared.
                  if (instanceInfo.mReferencingSessionCookie != null
                     && !instanceInfo.mReferencingSessionCookie.isActivationRequired(current))
                  {
                     txn = current.getTransaction();
                     if (!txn.isConnected())
                     {
                        // If the txn is not connected and the instance is a member
                        // of the referenced list then it must have been released
                        // to the pool with the state management enabled.  Go ahead
                        // and reconnect the application module instance so that we
                        // may update the state of its caches.
                        wasConnected = false;
                        txn.reconnect();
                     }

                     txn.applyChangeSet(snapId);

                     if (!wasConnected)
                     {
                        txn.disconnect(true);
                     }

                     wasConnected = true;
                  }
               }
            }

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

   private void resetCookieInfo(SessionCookie cookie)
   {
      SessionCookieInfo info = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
      info.mApplicationModule = null;
   }

   private ApplicationModule getApplicationModuleForSession(SessionCookie cookie)
   {
      SessionCookieInfo info = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
      return info.mApplicationModule;
   }

   private void setApplicationModuleForSession(SessionCookie cookie, ApplicationModule applicationModule)
   {
      SessionCookieInfo info = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
      info.mApplicationModule = applicationModule;
      info.mIsReferencingState = true;
   }

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

      return info.mReferencingSessionCookie;
   }

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

      return (info.mReferencingSessionCookie != null);
   }

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

      info.mReferencingSessionCookie = cookie;

      if (cookie != null)
      {
         SessionCookieInfo sessionCookieInfo = (SessionCookieInfo)mSessionCookieInfo.get(cookie);

         // If the session metadata is not null then the session has already
         // been connected once.  Okay to reset the referencing connection
         // metadata now.  Otherwise, this is a new session which has not already
         // been connected.  Maintain the existing referencing connection
         // metadata.  The referencing connection metadata will be set properly
         // if it is necessary to invoke connect.  Otherwise, it is okay to
         // maintain the same value.  The use case is as follows:
         //
         // 1.  Session one creates and connects an application module instance.  At
         // this point the session one and application module one connectionMetadata
         // attributes are not null and equal.
         //
         // 2.  Session one releases the application module statelessly.  At this
         // point the session one and application module one connectionMetadata
         // attributes are not null and equal.
         //
         // 3.  Session two requests an application module instance.  Session two
         // has not been connected so, without the if statement below, the
         // application module connection metadata attribute would be reset.
         if (sessionCookieInfo.mConnectionMetadata != null)
         {
            info.mReferencingConnectionMetadata = sessionCookieInfo.mConnectionMetadata;
         }
      }
   
      setApplicationModuleForSession(cookie, appModule);
   }

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

      info.mReferencingSessionCookie = null;

      mReferencedList.removeElement(info);

      if (info.mRefAvailListElement.isInList())
      {
         // transition from the refavail list to the unrefavail list
         mRefAvailList.removeElement(info.mRefAvailListElement);
         mUnrefAvailList.touchElement(info.mUnrefAvailListElement);
      }

      // Remove the internal application module reference that is held
      // be the cookie.
      resetCookieInfo(cookie);
   }

   private void doActivate(int passivationId, ApplicationModule appModule, SessionCookie cookie)
   {
      doPersistenceOperation(ACTIVATE_STATE, appModule, passivationId, cookie);

      cookie.setActivationRequired(false);

      firePoolEvent(ApplicationPoolListener.STATE_ACTIVATED);
   }

   void doFailover(ApplicationModule appModule, SessionCookie cookie)
   {
      doFailover(appModule, cookie, true);
   }
   
   private void doFailover(ApplicationModule appModule, SessionCookie cookie, boolean reconnect)
   {
      int reservedPassivationId = cookie.getReservedPassivationId();
      int oldPassivationId = cookie.getPassivationId();
      int passivationId = SessionCookie.NULL_PASSIVATION_ID;

      // If cookie activation is required then the session state was already
      // failed over properly.  doFailover may be invoked even if the state
      // has already been failed over if the pool had to failover the session
      // application module in order to persist some database state
      // (i.e. the state of some database cursors) at the end of a request when
      // connection pooling was enabled, but failover was disabled.  If the
      // application module for this session is recycled then there is no
      // need to failover the application state a second time.
      if (cookie.isActivationRequired(appModule))
      {
         return;
      }

      boolean wasConnected = true;
      if (reconnect)
      {
         wasConnected = reconnect(appModule, cookie, true);
      }

      // check again.  reconnect may have passivated.
      if (cookie.isActivationRequired(appModule))
      {
         return;
      }

      if (reservedPassivationId != SessionCookie.NULL_PASSIVATION_ID)
      {
         passivationId = appModule.passivateState(reservedPassivationId, null);

         // Reset the reserved passivation id after the application module
         // has been properly passivated with the reserved passivation id
         cookie.setReservedPassivationId(SessionCookie.NULL_PASSIVATION_ID);
      }
      else
      {
         passivationId = appModule.passivateState(null);
      }


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

      // 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?).
      if (oldPassivationId != SessionCookie.NULL_PASSIVATION_ID)
      {
         doPersistenceOperation(REMOVE_STATE, appModule, oldPassivationId, cookie);
      }

      if (!wasConnected)
      {
         // Always retain the state.
         disconnect(appModule, true, cookie);
      }

      firePoolEvent(ApplicationPoolListener.STATE_PASSIVATED);
   }

   /**
    * Performs tests to determine if the application module instance is still
    * alive.  This is only invoked for an application module instance when
    * it is recycled/reused.  The default implementation tests the application
    * module by invoking the remote <tt>getSession().getVersion()</tt>.
    * <p>
    * This method may be overriden to provide a custom test.
    */
   protected boolean isInstanceAlive(ApplicationModule instance)
   {
      boolean isInstanceAlive = true;
      try
      {
         // This is a very inexpensive method that will force a round-trip
         // to the middle tier.
         instance.getSession().getVersion();
      }
      catch (Exception ex)
      {
         Diagnostic.printStackTrace(ex);
         isInstanceAlive = false;
      }

      return isInstanceAlive;
   }

   public boolean validateSessionCookieInPool(SessionCookie cookie)
   {
      synchronized(mLock)
      {
         if (mSessionCookieInfo.get(cookie) == null)
         {
            throw new ApplicationPoolException(
               AMPoolMessageBundle.class
               , AMPoolMessageBundle.EXC_AMPOOL_INVALID_HANDLE
               , new Object[] {
                  cookie.getSessionId()
                  , cookie.getApplicationId()
                  , getName()});
         }
      }

      return validateSessionCookie(cookie);
   }

   public boolean validateSessionCookie(SessionCookie cookie)
   {
      // Compare the pool signature of the cookie with the signature of the pool
      // If the two signatures are the same then the cookie must have been
      // created by an instance of this pool and is consequently a valid
      // handle to the pool.
      if (getSignature() != cookie.getPoolSignature())
      {
         throw new ApplicationPoolException(
            AMPoolMessageBundle.class
            , AMPoolMessageBundle.EXC_AMPOOL_INVALID_HANDLE
            , new Object[] {cookie.getSessionId(), cookie.getApplicationId(), getName()});
      }

      return true;
   }

   protected void setAvailable(Object appModule, boolean isAvailable)
   {
      synchronized(mLock)
      {
         ApplicationModuleInfo info = (ApplicationModuleInfo)mInstanceInfo.get(appModule);

         if (isAvailable)
         {
            // if the ApplicationModule is in the referenced list
            if (info.isInList())
            {
               mUnrefAvailList.removeElement(info.mUnrefAvailListElement);
               mRefAvailList.touchElement(info.mRefAvailListElement);
            }
            else
            {
               mRefAvailList.removeElement(info.mRefAvailListElement);
               mUnrefAvailList.touchElement(info.mUnrefAvailListElement);
            }
         }
         else
         {
            mRefAvailList.removeElement(info.mRefAvailListElement);
            mUnrefAvailList.removeElement(info.mUnrefAvailListElement);
         }
         
         super.setAvailable(appModule, isAvailable);
      }
   }

   public void setAvailable(ApplicationModule appModule)
   {
      super.setAvailable(appModule);
   }

   /**
    * @deprecated Implementation detail.  This method has been made protected.
    * @since 9.0.2 
    */
   public boolean isAvailable(ApplicationModule appModule)
   {
      return super.isAvailable(appModule);
   }

   public long getCreationTimeMillis(ApplicationModule appModule)
   {
      return super.getCreationTimeMillis(appModule);
   }

   public long getTimeToCreateMillis(ApplicationModule appModule)
   {
      return super.getTimeToCreateMillis(appModule);
   }

   protected int getMinAvailableSize()
   {
      return getProperty(
         PropertyMetadata.ENV_AMPOOL_MIN_AVAIL_SIZE.pName
         , getEnvironment()
         , Integer.parseInt(PropertyMetadata.ENV_AMPOOL_MIN_AVAIL_SIZE.pDefault));
   }

   protected int getMaxAvailableSize()
   {
      return getProperty(
         PropertyMetadata.ENV_AMPOOL_MAX_AVAIL_SIZE.pName
         , getEnvironment()
         , Integer.parseInt(PropertyMetadata.ENV_AMPOOL_MAX_AVAIL_SIZE.pDefault));
   }

   protected int getMaxInactiveAge()
   {
      return getProperty(
         PropertyMetadata.ENV_AMPOOL_MAX_INACTIVE_AGE.pName
         , getEnvironment()
         , Integer.parseInt(PropertyMetadata.ENV_AMPOOL_MAX_INACTIVE_AGE.pDefault));
   }

   protected int getRecycleThreshold()
   {
      return getProperty(
         PropertyMetadata.ENV_POOL_RECYCLE_THRESHOLD.pName
         , getEnvironment()
         , Integer.parseInt(PropertyMetadata.ENV_POOL_RECYCLE_THRESHOLD.pDefault));
   }

   protected ResourcePoolLogger createPoolLogger()
   {
      return new ApplicationPoolLogger(this);
   }

   /**
    * Determines how long a request should wait for an available application
    * module instance when the maximum pool size has been reached.  Applications
    * may override this method to configure the wait time.
    */
   protected long getMaxWaitTime()
   {
      return MAX_WAIT_TIME;
   }

   /**
    * Invoked by the application pool implementation when an application module
    * instance is recycled for use by a session different than the session that
    * had previously used that instance.  The oldConnectionMetadata represents
    * the JDBC connection metadata that was used to establish the JDBC connection
    * for the session that previously used the application module instance.  The
    * newConnectionMetadata represents the JDBC connection metadata of the session
    * for which the application module is being recycled.  The newConnectionMetadata
    * will be null if this is the first time that the session has acquired an
    * application module instance from the pool.
    * <p>
    * If compareConnectionMetadata returns true then the pool assumes that the
    * oldConnectionMetadata and the newConnectionMetadata are equal and does
    * not attempt to connect the application module with the new session
    * connection metadata.  If compareConnectionMetadata returns false then the
    * pool assumes that the oldConnectionMetadata and the newConnectionMetadata
    * are note equal and will attempt to connect the application module using
    * the new connection metadata.
    * <p>
    * If it is known at design time that the connection metadata will not change
    * then applications may set the property, jbo.ampool.dynamicjdbccredentials, 
    * equal to false.  This will prevent the potential overhead of performing a
    * disconnect/connect whenever a new session causes the pool to recycle an 
    * application module instance.
    * <p>
    *
    * @returns true if the connection metadata are equal
    *          false if the connection metadata are not equal
    */
   protected boolean compareConnectionMetadata(ConnectionMetadata oldConnectionMetadata, ConnectionMetadata newConnectionMetadata)
   {
      boolean isEqual = false;
      if (oldConnectionMetadata != null)
      {
         // If the pool has been declared not to require dynamic JDBC credentials
         // then go ahead and return true.
         if (!isDynamicJDBCCredentials())
         {
            isEqual = true;
         }
         else
         {
            isEqual =  oldConnectionMetadata.equals(newConnectionMetadata);
         }
      }
      return isEqual;
   }

   public Object useResource(Properties properties)
   {
     // The application pool overrides ResourcePool.useResource.  The
     // application pool requires an enhanced interface which accepts a session
     // identifier in order to manage session state between pool requests.  The
     // application pool still uses the resource pool to manage available
     // resources.  However, the application pool acquires and returns these
     // resources by using internal resource pool APIs.
     return null;
   }

   public void releaseResource(Object resource, Properties properties)
   {
     // The application pool overrides ResourcePool.useResource.  The
     // application pool requires an enhanced interface which accepts a session
     // identifier in order to manage session state between pool requests.  The
     // application pool still uses the resource pool to manage available
     // resources.  However, the application pool acquires and returns these
     // resources by using internal resource pool APIs.
   }

   /**
    * @deprecated  This property is specific to the SessionCookie.  Extending
    * logic should be migrated to a custom extension of:
    *    {@link oracle.jbo.common.ampool.SessionCookieImpl#isFailoverEnabled()}
    * <p>
    * @since 9.0.2
    * @see oracle.jbo.common.ampool.SessionCookie#isFailoverEnabled()
    */
   protected boolean isDoFailover()
   {
      return true;
   }

   public boolean isDynamicJDBCCredentials()
   {
      return getProperty(
         PropertyMetadata.ENV_AMPOOL_DYNAMIC_JDBC_CREDENTIALS.pName
         , mEnvironment
         , Boolean.valueOf(PropertyMetadata.ENV_AMPOOL_DYNAMIC_JDBC_CREDENTIALS.pDefault)
            .booleanValue());
   }

   /**
    * @deprecated  This property is specific to the SessionCookie.  Extending
    * logic should be migrated to a custom extension of:
    *    {@link oracle.jbo.common.ampool.SessionCookieImpl#isConnectionPoolingEnabled()}
    * <p>
    * @since 9.0.2 
    * @see oracle.jbo.common.ampool.SessionCookie#isConnectionPoolingEnabled()
    */
   protected boolean isDoConnectionPooling()
   {
      return false;
   }

   /**
    * 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.
    *
    * @deprecated  Replaced by {@link oracle.jbo.common.ampool.ConnectionStrategy#connect(ApplicationModule, SessionCookie)}.
    * All extending logic that was implemented here should be implemented in a
    * custom ConnectionStrategy class that extends {@link oracle.jbo.common.ampool.DefaultConnectionStrategy}.
    * @since 5.0
    * @see oracle.jbo.ConnectionStrategy#connect(ApplicationModule, SessionCookie)
    */
   protected void connect(ApplicationModule appModule, Hashtable environment)
   {
   }

   private void initializeCookieJDBCProps(SessionCookie cookie)
   {
      try
      {
         // Check the usernames, password, and connectString attributes of the
         // pool.  If they are not null then set the environment with those
         // values.  This is required for backwards compatibility since 5.0.
         if ((getUserName() != null)
            && (cookie.getEnvironment(ConnectionStrategy.DB_USERNAME_PROPERTY)
               == null))
         {
            cookie.setEnvironment(
               ConnectionStrategy.DB_USERNAME_PROPERTY
               , getUserName());
         }

         if ((getPassword() != null)
            && (cookie.getEnvironment(ConnectionStrategy.DB_PASSWORD_PROPERTY)
               == null))
         {
            cookie.setEnvironment(
               ConnectionStrategy.DB_PASSWORD_PROPERTY
               , getPassword());
         }

         if ((getConnectString() != null)
            && (cookie.getEnvironment(ConnectionStrategy.DB_CONNECT_STRING_PROPERTY) == null))
         {
            cookie.setEnvironment(
               ConnectionStrategy.DB_CONNECT_STRING_PROPERTY
               , getConnectString());
         }
      }
      catch(ApplicationPoolException apex)
      {
         // Ignore the application pool exception.  The cookie must already
         // be active.
      }

   }

   private void connect(ApplicationModule appModule, SessionCookie cookie)
   {
      initializeCookieJDBCProps(cookie);

      getConnectionStrategy().connect(appModule, cookie, cookie.getEnvInfoProvider());

      // the application module may not be connected.  see 2374088.
      if (appModule.getTransaction().isConnected())
      {
         connect(appModule, cookie.getEnvironment());

         ConnectionMetadata connectionMetadata = appModule.getTransaction().getConnectionMetadata();
         SessionCookieInfo sessionCookieInfo = (SessionCookieInfo)mSessionCookieInfo.get(cookie);
         sessionCookieInfo.mConnectionMetadata = connectionMetadata;

         synchronized(mLock)
         {
            // Make sure that the referencing connection metadata is properly 
            // updated.
            ApplicationModuleInfo applicationModuleInfo = 
               (ApplicationModuleInfo)mInstanceInfo.get(appModule);

            applicationModuleInfo.mReferencingConnectionMetadata = 
               connectionMetadata;
         }
      }
   }

   /**
    * 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.
    * 
    * @deprecated  Replaced by {@link oracle.jbo.common.ampool.ConnectionStrategy#reconnect(ApplicationModule, SessionCookie)}.
    * All extending logic that was implemented here should be implemented in a
    * custom ConnectionStrategy class that extends {@link oracle.jbo.common.ampool.DefaultConnectionStrategy}.
    * @since 5.0
    * @see oracle.jbo.ConnectionStrategy#reconnect(ApplicationModule, SessionCookie)
    */
   protected void reconnect(ApplicationModule appModule, Hashtable environment)
   {
   }

   private boolean reconnect(ApplicationModule appModule, SessionCookie cookie, boolean retainState)
   {
      try
      {
         getConnectionStrategy().reconnect(appModule, cookie, cookie.getEnvInfoProvider());
      }
      catch (JboException ex)
      {
         // see bug 2337750.  if the database has failed then reconnect will
         // attempt to disconnect with state retained.  If open cursors exist
         // then this will fail.  passivate the appModule state, if necessary,
         // and then reattempt to reconnect.
         
         // Disconnect the application module without retaining AM state if
         // a database state exception is thrown.
         if (CSMessageBundle.EXC_DATABASE_STATE_EXISTS.equals(ex.getErrorCode()))
         {
            if (retainState)
            {
               // If the application module was not already passivated then
               // passivate.
               if (!cookie.isFailoverEnabled())
               {
                  Diagnostic.println("Passivating the application module state.");
                  doFailover(appModule, cookie, false);

                  // Mark the cookie's doActivate flag to indicate that activation
                  // must occur upon checkout.
                  cookie.setActivationRequired(true);
               }
            }
            
            disconnect(appModule, false, cookie);
               
            // Re-attempt reconnect.
            getConnectionStrategy().reconnect(
               appModule, cookie, cookie.getEnvInfoProvider());

         }
         else
         {
            throw ex;
         }
      }
      
      reconnect(appModule, cookie.getEnvironment());

      // It is necessary to check the sessionCookieInfo metadata in case an
      // application has provided a custom compareConnectionMetadata 
      // implementation that prevents the session connection metadata from being
      // set because the application module is never reconnected.  
      SessionCookieInfo sessionCookieInfo = (SessionCookieInfo)mSessionCookieInfo.get(cookie);

      if (sessionCookieInfo.mConnectionMetadata == null)
      {
         ConnectionMetadata connectionMetadata = appModule.getTransaction().getConnectionMetadata();
         sessionCookieInfo.mConnectionMetadata = connectionMetadata;
      }

      // returns true if the transaction was already connected.  false if it
      // was necessary to connect the transaction.
      // if the property was not set by the connection strategy
      // then we know that the strategy had to reconnect the transaction.  Otherwise,
      // the transaction was already connected.
       return (cookie.getUserData().remove(DefaultConnectionStrategy.RECONNECT_REQUIRED) == null);
      
   }

   /**
    * 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.
    *
    * @deprecated  Replaced by {@link oracle.jbo.common.ampool.ConnectionStrategy#disconnect(ApplicationModule, boolean, SessionCookie)}.
    * All extending logic that was implemented here should be implemented in a
    * custom ConnectionStrategy class that extends {@link oracle.jbo.common.ampool.DefaultConnectionStrategy}.
    * @since 5.0
    * @see oracle.jbo.ConnectionStrategy#disconnect(ApplicationModule, boolean, SessionCookie)
    */
   protected void disconnect(ApplicationModule appModule, boolean retainState, Hashtable environment)
   {
   }

   private void disconnect(ApplicationModule appModule, boolean retainState, SessionCookie cookie)
   {
      initializeCookieJDBCProps(cookie);

      getConnectionStrategy().disconnect(appModule, retainState, cookie);
      disconnect(appModule, retainState, cookie.getEnvironment());      
   }

   protected String getConnectionStrategyClassName()
   {
      return getProperty(
         PropertyMetadata.ENV_AMPOOL_CONNECTION_STRATEGY_CLASS_NAME.pName
         , getEnvironment()
         , PropertyMetadata.ENV_AMPOOL_CONNECTION_STRATEGY_CLASS_NAME.pDefault);
   }

   protected String getSessionCookieFactoryClassName()
   {
      return getProperty(
         PropertyMetadata.ENV_AMPOOL_COOKIE_FACTORY_CLASS_NAME.pName
         , getEnvironment()
         , PropertyMetadata.ENV_AMPOOL_COOKIE_FACTORY_CLASS_NAME.pDefault);
   }

   HashMap getInstanceInfos()
   {
      synchronized(mLock)
      {
         return (HashMap)mInstanceInfo.clone();
      }
   }

   HashMap getSessionCookieInfos()
   {
      synchronized(mLock)
      {
         return (HashMap)mSessionCookieInfo.clone();
      }
   }

   private String getProperty(String name, Hashtable environment, String defaultValue)
   {
      String rtn = (String)environment.get(name);

      if (rtn == null)
      {
         rtn = JboEnvUtil.getProperty(name, defaultValue);
      }

      return rtn;
   }

   private boolean getProperty(String name, Hashtable environment, boolean defaultValue)
   {
      boolean rtn = true;

      String value = (String)environment.get(name);
      if (value != null)
      {
         rtn = Boolean.valueOf(value).booleanValue();
      }
      else
      {
         rtn = JboEnvUtil.getPropertyAsBoolean(name, defaultValue);
      }

      return rtn;
   }

   private int getProperty(String name, Hashtable environment, int defaultValue)
   {
      int rtn = -1;

      String value = (String)environment.get(name);
      if (value != null)
      {
         rtn = Integer.parseInt(value);
      }
      else
      {
         rtn = JboEnvUtil.getPropertyAsInt(name, defaultValue);
      }

      return rtn;
   }

   private void doPersistenceOperation(byte opId, ApplicationModule appModule, int pId, SessionCookie cookie)
   {
      try
      {
         if (opId == REMOVE_STATE)
         {
            appModule.removeState(pId);
         }
         else if (opId == ACTIVATE_STATE)
         {
            EnvInfoProvider prov = cookie.getEnvInfoProvider();
            Hashtable env = cookie.getEnvironment();
            if (prov != null)
            {
               prov.getInfo(null, env);
            }

            appModule.activateState(
               pId
               , false
               , new SessionData(env, cookie.getUserData()));
         }         
      }
      catch(oracle.jbo.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(pId)
               .append(", was not located").toString());
         }
         else
         {
            throw pcex;
         }
      }
      catch(oracle.jbo.JboSerializationException jsex)
      {
         Object[] details = jsex.getDetails();
         if ((details == null) || (details.length == 0))
         {
            // Eat the serialization exception if it occurs and the details
            // stack is empty.  An exception is thrown if the passivation file
            // for a passivation id is not located.  This exception may be
            // differentiated from other passivation exceptions because
            // is has an empty stack trace.
            //
            // The passivation file may not be located if it has been removed
            // by the application administrator.
            Diagnostic.println(new StringBuffer(32)
               .append("The passivation record for passivate id, ")
               .append(pId)
               .append(", was not located or was empty").toString());
         }
         else if ((jsex.getErrorCode() != null)
            && (jsex.getErrorCode().equals(CSMessageBundle.EXC_AM_ACTIVATE))
            && (details[0] instanceof java.lang.IndexOutOfBoundsException))
         {
            // Eat the serialization exception if it occurs and the first
            // details element is an index out of bounds exception.  An
            // exception is thrown if the specified memory index is empty.
            //
            // The specified memory index may be empty if the client
            // is connecting to a different VM instance than the one that
            // was used to generate the in-memory cookie value.
            Diagnostic.println(new StringBuffer(32)
               .append("The passivation record for passivate id, ")
               .append(pId)
               .append(", was not located in memory").toString());
         }
         else
         {
            throw jsex;
         }
      }
   }

   private void prepareSession(ApplicationModule appModule, SessionCookie cookie)
   {
      EnvInfoProvider prov = cookie.getEnvInfoProvider();
      Hashtable env = cookie.getEnvironment();
      if (prov != null)
      {
         prov.getInfo(null, env);
      }

      appModule.prepareSession(
         new SessionData(env, cookie.getUserData()));
   }

   class SessionCookieInfo
   {
      // This class is a simple structure for maintaining internal session
      // cookie information.  The external session cookie class does not
      // manage this information because it is specific to the application
      // pool implementation.
      private SessionCookie      mCookie;
      Date                       mLastUpdate;
      private ApplicationModule  mApplicationModule  = null;
      private ConnectionMetadata mConnectionMetadata = null;
      boolean                    mIsReferencingState = false;

      SessionCookieInfo()
      {
      }

      SessionCookieInfo(SessionCookie cookie)
      {
         mCookie = cookie;
         mLastUpdate = new Date(System.currentTimeMillis());

         if (cookie.getPassivationId() != SessionCookie.NULL_PASSIVATION_ID)
         {
            mIsReferencingState = true;
         }
      }

      void touch()
      {
         mLastUpdate.setTime(System.currentTimeMillis());
      }
   }

   class ApplicationModuleInfo extends RecentlyUsedLinkedListElement
   {
      // This class is a simple structure for maintaining internal application
      // module information.  The application module class does not
      // manage this information because it is specific to the application
      // pool implementation.
      ApplicationModuleInfo(ApplicationModule appModule)
      {
         super(appModule);
         mRefAvailListElement = new RecentlyUsedLinkedListElement(appModule);
         mUnrefAvailListElement = new RecentlyUsedLinkedListElement(appModule);
      }

      // Initialize the application module as being checked out.  It must
      // be explicitly after createInstance has been invoked before it can
      // be used.
      SessionCookie               mReferencingSessionCookie      = null;
      ConnectionMetadata          mReferencingConnectionMetadata = null;
      final RecentlyUsedLinkedListElement mRefAvailListElement;
      final RecentlyUsedLinkedListElement mUnrefAvailListElement;
   }
}
