// Copyright (c) 1999, 2000 Oracle Corporation package oracle.jbo.common.ampool; import java.util.Vector; import java.util.Hashtable; import java.util.Properties; import java.util.Date; import java.io.Serializable; import java.io.ObjectInputStream; import java.io.IOException; import java.io.PrintWriter; import com.sun.java.util.collections.HashMap; import com.sun.java.util.collections.Collection; import com.sun.java.util.collections.Iterator; import com.sun.java.util.collections.ArrayList; import oracle.jbo.ApplicationModule; import oracle.jbo.Transaction; import oracle.jbo.CSMessageBundle; import oracle.jbo.JboException; import oracle.jbo.NotConnectedException; import oracle.jbo.ConnectionMetadata; import oracle.jbo.common.JBOClass; import oracle.jbo.common.Diagnostic; import oracle.jbo.common.PropertyMetadata; import oracle.jbo.common.JboEnvUtil; import oracle.jbo.client.Configuration; import oracle.jbo.pool.ResourcePool; import oracle.jbo.pool.ResourcePoolLogger; import oracle.jbo.pool.RecentlyUsedLinkedList; import oracle.jbo.pool.RecentlyUsedLinkedListElement; /** * 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. *
* The pool utilizes a singleton internal monitor object instance to synchronize * read/write access to shared data. *
* 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. *
* This strategy of course assumes that application module access from the same * session is properly synchronized. Consider the following scenario: *
* 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. *
* 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. *
* 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. *
* 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: *
* 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. *
* 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: *
* 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. *
* 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. *
* 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. *
* 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: *
* 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. *
* 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. *
*
* View definition of ApplicationPool
*
* View Implementation of ApplicationPoolImpl
*
*/ 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 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) { super.initialize(null); if (mInstanceInfo == null) { mInstanceInfo = new HashMap(10); } if (mReferencedList == null) { mReferencedList = 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; } } } } else if (!cookieExists) { cookie.setEnvironment(getEnvironment()); } 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) { 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) { mSessionCookieInfo.remove(cookie); setAvailable(appModule, true); } } } } } 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. Please * see (@link oracle.jbo.common.ampool.DefaultConnectionStrategy} for more information. * @since 5.0 * @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 = (ApplicationModule)seekMRUAvailableResource(null); // 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. while (appModule != null) { if (!isReferenced(appModule)) { break; } appModule = (ApplicationModule)seekLRUAvailableResource(appModule); } 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 listElement = mReferencedList.getLRUElement(); while (listElement != null) { if (isAvailable(listElement.getRefObject())) { appModule = (ApplicationModule)listElement.getRefObject(); break; } listElement = listElement.getMoreRecentlyUsedElement(); } return appModule; } protected 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; } protected 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); } } } } /** * @deprecated Replaced by {@link oracle.jbo.common.ampool.SessionCookie#releaseApplicationModule(boolean, boolean)}. * Application developers should invoke SessionCookie.releaseApplicationModule(true, false) * instead of this method. A session cookie instance may be acquired by invoking * {@link #createSessionCookie(String, String, Properties)}. *
* 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. * @since 5.0 * @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) { 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(); } private void resetApplicationModule(SessionCookie cookie, boolean inRelease) { // The release flag indicates that the reset is invoked by an application // module release. inRelease may be false when ResetApplicationModule is // is invoked during cookie removal. // 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) { appModule = getApplicationModuleForSession(cookie); // 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.resetState(); return; } // 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 (inRelease && (cookie.getPassivationId() != SessionCookie.NULL_PASSIVATION_ID)) { doPersistenceOperation(REMOVE_STATE, appModule, cookie.getPassivationId()); 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. appModule.getTransaction().rollback(); // 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.isResetNonTransactionalState()) { if (cookie.isConnectionPoolingEnabled()) { // Disconnect. Do not retain the application module state. disconnect(appModule, false, cookie); } else { // Else, simply reset the application module state. appModule.resetState(true); } } else { // If connection pooling has been enabled then disconnect with retain // state equal to true. The transactional state has already been // rolled back above, so specifying the retainState flag as true here // should result in the non-transactional state only being perserved // be the pool. if (cookie.isConnectionPoolingEnabled()) { disconnect(appModule, true /* retainState */, cookie); } } synchronized(mLock) { disassociateSessionCookie(appModule, cookie); cookie.resetState(); SessionCookieInfo cookieInfo = (SessionCookieInfo)mSessionCookieInfo.get(cookie); cookieInfo.mIsReferencingState = false; if (inRelease) { super.releaseResource(appModule, null); } } // release mLock } private void doUnmanagedCheckin(SessionCookie cookie) { validateSessionCookie(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); } /** * @deprecated Replaced by {@link oracle.jbo.common.ampool.SessionCookie#releaseApplicationModule(boolean, boolean)}. * Application developers should invoke SessionCookie.releaseApplicationModule(true, true) * instead of this method. A session cookie instance may be acquired by invoking * {@link #createSessionCookie(String, String, Properties)}. *
* 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. * @since 5.0 * @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) { validateSessionCookie(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); } } // 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) { 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"); reconnect(am, cookie); // 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()) { int passivationId = cookie.getPassivationId(); doActivate(passivationId, am, cookie); } 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() && referencingCookie.getLastUpdate().equals(lastReferencingUpdate)) { // Reconnect first. This will prevent redundant connect/disconnect // logic from firing b/w doFailover and this method. reconnect(am, referencingCookie); 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); // Rollback. This is necessary to remove any // unposted transaction validation listeners which may still be // registered on a stateful application module's transaction. am.getTransaction().rollback(); am.clearVOCaches(null, true); // Clear the entity caches as well. am.getTransaction().clearEntityCache(null); if (cookie.isResetNonTransactionalState()) { // 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); } 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); } // 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); } 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) { validateSessionCookie(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.getTransaction().rollback(); // 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}. * @since 5.0 * @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 } /** * @deprecated Replaced by {@link oracle.jbo.common.ampool.SessionCookie#useApplicationModule()}. * Application developers should invoke SessionCookie.useApplicationModule() * instead of this method. A session cookie instance may be acquired by invoking * {@link #createSessionCookie(String, String, Properties)}. *
* 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. * @since 5.0 * @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 oracle.jbo.pool.removeResources()}. Method * may be confused with releaseResource. * @since 5.0 * @see #removeResources() */ public void releaseInstances() { removeResources(); } /** * @deprecated * @since 5.0 * @see #removeResource(Object) */ protected void releaseInstance(ApplicationModule instance) { removeResource(instance); } public Object removeResource(Object resource) { 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); } 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(); } 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 5.0 * @see #getAvailableInstanceCount() */ public int getAvailableNumPools() { return getAvailableResourceCount(); } public int getInstanceCount() { return getResourceCount(); } public ApplicationModule getInstance(int index) { return (ApplicationModule)getResource(index); } /** * @deprecated Replaced by {@link oracle.jbo.common.ampool.SessionCookie#useApplicationModule()}. * Application developers should invoke SessionCookie.useApplicationModule() * instead of this method. A session cookie instance may be acquired by invoking * {@link #createSessionCookie(String, String, Properties)}. *
* 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. * @since 5.0 * @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) { validateSessionCookie(cookie); ApplicationModule rtnAppModule = null; synchronized(mLock) { 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 #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; } } /** * @deprecated This value should be passed to the pool connection strategy as * SessionCookie environment or by implementing an EnvInfoProvider. Please * see (@link oracle.jbo.common.ampool.DefaultConnectionStrategy} for more information. * @since 5.0 * @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. Please * see (@link oracle.jbo.common.ampool.DefaultConnectionStrategy} for more information. * @since 5.0 * @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; } } /** * @deprecated This value should be passed to the pool connection strategy as * SessionCookie environment or by implementing an EnvInfoProvider. Please * see (@link oracle.jbo.common.ampool.DefaultConnectionStrategy} for more information. * @since 5.0 * @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. Please * see (@link oracle.jbo.common.ampool.DefaultConnectionStrategy} for more information. * @since 5.0 * @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()) { 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); // 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.setActivationRequired(false); firePoolEvent(ApplicationPoolListener.STATE_ACTIVATED); } private void doFailover(ApplicationModule appModule, SessionCookie cookie) { 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()) { return; } boolean wasConnected = true; if (!appModule.getTransaction().isConnected()) { reconnect(appModule, cookie); wasConnected = false; } 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); } 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 getSession().getVersion(). *
* 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 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; } /** * @deprecated Implementation detail. Modifying an application module's * available status could result in concurrency issues. {@link #setAvailable(ApplicationModule)} * may be invoked to set an unused application module as available. * @since 5.0 * @see #setAvailable(appModule) */ public void setAvailable(ApplicationModule appModule, boolean isAvailable) { super.setAvailable(appModule, isAvailable); } public void setAvailable(ApplicationModule appModule) { super.setAvailable(appModule); } /** * @deprecated Implementation detail. This method has been made protected. * @since 5.0 */ 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. *
* 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. *
* 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. *
* * @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 ((newConnectionMetadata == null) || (!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} * @since 5.0 * @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} * @since 5.0 * @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 connect(ApplicationModule appModule, SessionCookie cookie) { Hashtable environment = cookie.getEnvironment(); // 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) && (environment.get(ConnectionStrategy.DB_USERNAME_PROPERTY) == null)) { environment.put(ConnectionStrategy.DB_USERNAME_PROPERTY, getUserName()); } if ((getPassword() != null) && (environment.get(ConnectionStrategy.DB_PASSWORD_PROPERTY) == null)) { environment.put(ConnectionStrategy.DB_PASSWORD_PROPERTY, getPassword()); } if ((getConnectString() != null) && (environment.get(ConnectionStrategy.DB_CONNECT_STRING_PROPERTY) == null)) { environment.put(ConnectionStrategy.DB_CONNECT_STRING_PROPERTY, getConnectString()); } try { cookie.setEnvironment(environment); } catch(ApplicationPoolException apex) { // Ignore the application pool exception. The cookie must already // be active. } getConnectionStrategy().connect(appModule, cookie, cookie.getEnvInfoProvider()); connect(appModule, environment); 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 void reconnect(ApplicationModule appModule, SessionCookie cookie) { getConnectionStrategy().reconnect(appModule, cookie, cookie.getEnvInfoProvider()); 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; } } /** * 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) { 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) { try { if (opId == REMOVE_STATE) { appModule.removeState(pId); } else if (opId == ACTIVATE_STATE) { appModule.activateState(pId, false); } } 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; } } } 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); } // 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; } }