FileDocCategorySizeDatePackage
EntityContainer.javaAPI DocGlassfish v2 API116297Thu Jun 07 18:29:30 BST 2007com.sun.ejb.containers

EntityContainer.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.ejb.containers;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.io.*;
import java.rmi.RemoteException;

import java.util.*;
import java.util.logging.*;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;

import javax.ejb.*;
import javax.transaction.*;

import com.sun.appserv.util.cache.Constants;
import com.sun.appserv.util.cache.Cache;
import com.sun.appserv.util.cache.BaseCache;
import com.sun.appserv.util.cache.LruCache;
import com.sun.appserv.util.cache.CacheListener;

import com.sun.ejb.*;
import com.sun.ejb.portable.ObjrefEnumeration;
import com.sun.ejb.containers.util.pool.*;
import com.sun.ejb.containers.util.cache.EJBObjectCache;
import com.sun.ejb.containers.util.cache.EJBObjectCacheListener;
import com.sun.ejb.containers.util.cache.FIFOEJBObjectCache;
import com.sun.ejb.containers.util.cache.UnboundedEJBObjectCache;
import com.sun.ejb.containers.util.ContainerWorkPool;

import com.sun.enterprise.*;
import com.sun.enterprise.deployment.*;
import com.sun.enterprise.deployment.runtime.IASEjbExtraDescriptors;
import com.sun.enterprise.deployment.runtime.BeanCacheDescriptor;
import com.sun.enterprise.deployment.runtime.BeanPoolDescriptor;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.log.Log;
import com.sun.enterprise.appverification.factory.AppVerification;
import com.sun.enterprise.admin.monitor.callflow.ComponentType;

import com.sun.logging.*;

import com.sun.enterprise.admin.monitor.*;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.serverbeans.*;
import com.sun.enterprise.server.ServerContext;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.util.io.FileUtils;

import com.sun.ejb.spi.stats.EntityBeanStatsProvider;

import com.sun.ejb.spi.container.BeanStateSynchronization; 

import com.sun.enterprise.distributedtx.J2EETransaction;

/**
 * This class implements the Container interface for EntityBeans.
 * It is responsible for instance & lifecycle management for BMP & CMP
 * EntityBeans.
 * The EntityContainer implements option B of the commit-time options
 * described in the EJB2.0 spec section 10.5.9
 * It also implements optimistic concurrency (i.e. multiple non-exclusive
 * bean instances per primary key) when there are multiple concurrent
 * transactions on a EntityBean.
 * <p>
 * The following sequence of actions happens for the EntityContainer,
 * for each EJB lifecycle stage (note: getEJBObject, getContext,
 * releaseContext, preInvokeTx, postInvokeTx are called from BaseContainer).
 * 1. EJB Creation
 * homeImpl.create, container.getContext,
 * container.preInvokeTx, ejb.ejbCreate, container.postCreate,
 * ejb.ejbPostCreate, container.postInvokeTx, container.releaseContext
 * 2. EJB Finding
 * homeImpl.find---, container.getContext, container.preInvokeTx,
 * ejb.ejbFind---, container.postFind, container.postInvokeTx,
 * container.releaseContext
 * 3. EJB Invocation
 * container.getEJBObject, ejbObject.someMethod, container.getContext,
 * container.preInvokeTx, ejb.someMethod, container.postInvokeTx,
 * container.releaseContext
 * <P>
 * State Management: The EntityContainer manages collections of EJBs
 * in different states.
 * The 5 states of an EntityBean (an EJB can be in only 1 state at a time):
 * <UL>
 * <LI> 1. POOLED : does not have identity. EJBs in the POOLED state are
 * all identical, hence are maintained in a java.util.Vector,
 * whose size is maintained below a HIGH_WATER_MARK (currently 100).
 * <LI> 2. READY : ready for invocations, no transaction in progress.
 * EJBs in the READY state are associated with a primary key.
 * To enhance reuse of EJB instances, only one READY
 * EJB per primary key is stored. READY EJBs are managed by the
 * ejbstore/EntityStore class. READY EJBs are looked up using a key consisting
 * of the primary key and a null transaction context.
 * <LI> 3. INVOKING : processing an invocation.
 * EJBs in the INVOKING state are not stored anywhere. Before transitioning
 * from READY or INCOMPLETE_TX to INVOKING, the EJB is removed from the
 * EntityStore.
 * <LI> 4. INCOMPLETE_TX : ready for invocations, transaction in progress.
 * EJBs in the INCOMPLETE_TX state are associated with a primary key.
 * INCOMPLETE_TX EJBs are managed by the ejbstore/EntityStore class.
 * INCOMPLETE_TX EJBs are looked up using a composite key consisting
 * of the primary key and the transaction context.
 * <LI> 5. DESTROYED : does not exist.
 * </UL>
 * All READY bean instances are stored in the readyStore.
 * All INCOMPLETE_TX bean instances are stored in the ActiveTxCache.
 * Beans in the READY state are stored with key = ejbObject.
 * Beans in the INCOMPLETE_TX state are stored with key = ejbObject+Tx.
 * Instances in INVOKING state which have transactions associated
 * with them are also in ActiveTxCache.
 * All POOLED instances are stored in the pooledEJBs vector.
 *
 * Note on locking order: if both ready/ActiveTxCache and context are to be
 * locked, always acquire the context lock first, then the Store lock.
 * Note on locking order: if both ready/ActiveTxCache and ejbObject need
 * locks, always acquire the ejbObject lock first, then the Store lock.
 *
 * @author Mahesh Kannan
 * @author Shanker N
 * @author Pramod Gopinath
 */

public class EntityContainer
    extends BaseContainer
    implements CacheListener, EntityBeanStatsProvider
{
    protected static final Logger _logger =
        LogDomains.getLogger(LogDomains.EJB_LOGGER);
    
    private ThreadLocal ejbServant = new ThreadLocal() {
        protected Object initialValue() {
            return null;
        }
    };
    protected static final LocalStringManagerImpl localStrings =
    new LocalStringManagerImpl(EntityContainer.class);
    
    static final int POOLED=1, READY=2, INVOKING=3,
    INCOMPLETE_TX=4, DESTROYED=5;
    protected static final int HIGH_WATER_MARK=100;
    
    private static final int DEFAULT_TX_CACHE_BUCKETS = 16;

    // table of EJBObjects, indexed by primary key.
    // Note: Hashtable methods are synchronized.
    protected EJBObjectCache ejbObjectStore;
    
    // table of EJBLocalObjectImpls, indexed by primary key.
    // Note: Hashtable methods are synchronized.
    protected EJBObjectCache ejbLocalObjectStore;
    
    //protected  LIFOChannel channel = null;
    protected Stack			passivationCandidates = new Stack();
    
    // table of EJBs (Contexts) in READY state, key is primary key
    protected Cache readyStore;
    
    //Pool of free EntityContexts
    protected AbstractPool	entityCtxPool;
    
    protected boolean isReentrant;
    protected boolean isContainerManagedPers;
    
    protected final float DEFAULT_LOAD_FACTOR = 0.75f;
    protected final int DEFAULT_CACHE_SIZE = 8192;
    protected int _maxBuckets = 8;
    
    protected IASEjbExtraDescriptors iased = null;
    protected BeanCacheDescriptor beanCacheDes = null;
    protected BeanPoolDescriptor beanPoolDes = null;
    protected Server svr = null;
    protected Config cfg = null;
    protected EjbContainer ejbContainer = null;
    boolean largeCache = false;
    
    CacheProperties cacheProp = null;
    PoolProperties poolProp = null;
    Object asyncTaskSemaphore = new Object();
    boolean addedASyncTask = false;
    
    // a timer task to trim the beans idle in readyStore
    protected IdleBeansPassivator	idleEJBObjectPassivator;
    protected IdleBeansPassivator	idleLocalEJBObjectPassivator;
    protected boolean				defaultCacheEJBO = true;
    
    IdleBeansPassivator idleBeansPassivator;
    boolean timerValid = true;
    long idleTimeout;
    
    
    protected int ejboRemoved;

    protected int	totalPassivations;
    protected int	totalPassivationErrors;

    private EntityCacheStatsProvider	cacheStatsProvider;

    static {
        _logger.log(Level.FINE," Loading Entitycontainer...");
    }
    
    /**
     * This constructor is called from the JarManager when a Jar is deployed.
     * @exception Exception on error
     */
    protected EntityContainer(EjbDescriptor desc, ClassLoader loader)
        throws Exception
    {
        super(desc, loader);
        EjbEntityDescriptor ed = (EjbEntityDescriptor)desc;
        isReentrant = ed.isReentrant();
        if ( ed.getPersistenceType().equals(
            EjbEntityDescriptor.BEAN_PERSISTENCE) ) {
            isContainerManagedPers = false;
        } else {
            isContainerManagedPers = true;
        }
        
        iased = ed.getIASEjbExtraDescriptors();
        if( iased != null) {
            beanCacheDes = iased.getBeanCache();
            beanPoolDes = iased.getBeanPool();
        }
        try {
            ServerContext sc = ApplicationServer.getServerContext();
	       //ROB: config changes
            //svr = ServerBeansFactory.getServerBean(sc.getConfigContext());
            cfg = ServerBeansFactory.getConfigBean(sc.getConfigContext()); 
        }  catch (ConfigException ex) {
            _logger.log(Level.WARNING, "ejb.entitycontainer_exception", ex);
        }
        //ROB: config changes
        //ejbContainer = svr.getEjbContainer();
        ejbContainer = cfg.getEjbContainer();

        super.setMonitorOn(ejbContainer.isMonitoringEnabled());
        
        createCaches();
        
        super.createCallFlowAgent(
                isContainerManagedPers ? ComponentType.CMP : ComponentType.BMP);
        _logger.log(Level.FINE,"[EntityContainer] Created EntityContainer: "
                + logParams[0]);
    }
    
    protected void preInitialize(EjbDescriptor desc, ClassLoader loader) {
        EjbEntityDescriptor ed = (EjbEntityDescriptor)desc;
        isReentrant = ed.isReentrant();
        if ( ed.getPersistenceType().equals(
            EjbEntityDescriptor.BEAN_PERSISTENCE) ) {
            isContainerManagedPers = false;
        } else {
            isContainerManagedPers = true;
        }
        _logger.log(Level.FINE,"[EntityContainer] preInitialize==>isContainerManagedPers: "
                + isContainerManagedPers);
    }
    
    /**
     * setup a timer task to trim timed out entries in the cache.
     * @param cache cache which is used to setup the timer task
     * @return the passivator object
     */
    public IdleBeansPassivator setupIdleBeansPassivator(Cache cache) 
        throws Exception {
        
        IdleBeansPassivator idleBeansPassivator =
            new IdleBeansPassivator(cache);

        ContainerFactoryImpl.getTimer().
            scheduleAtFixedRate(idleBeansPassivator, idleTimeout, idleTimeout);
        
        return idleBeansPassivator;
    }
    
    /**
     * cancel a timer task to trim timed out entries in the cache.
     */
    public void cancelTimerTasks() {
        timerValid = false;
        if (idleBeansPassivator != null) {
            try {
                idleBeansPassivator.cancel();
                idleBeansPassivator.cache  = null;
            } catch (Exception e) {
                _logger.log(Level.FINE, "[EntityContainer] cancelTimerTask: " + 
                    e);
            }
        }
        
        if (idleEJBObjectPassivator != null) {
            try {
                idleEJBObjectPassivator.cancel();
                idleEJBObjectPassivator.cache  = null;
            } catch (Exception e) {
                _logger.log(Level.FINE, "[EntityContainer] cancelTimerTask: " +
                    e);
            }
        }
        
        if (idleLocalEJBObjectPassivator != null) {
            try {
                idleLocalEJBObjectPassivator.cancel();
                idleLocalEJBObjectPassivator.cache  = null;
            } catch (Exception e) {
                _logger.log(Level.FINE, "[EntityContainer] cancelTimerTask: " +
                    e);
            }
        }
        
        this.idleEJBObjectPassivator    = null;
        this.idleLocalEJBObjectPassivator    = null;
        this.idleBeansPassivator = null;
    }
    
    protected InvocationInfo postProcessInvocationInfo(
            InvocationInfo invInfo) {
        Method method = invInfo.method;
        boolean isCMPField = isContainerManagedPers && invInfo.isBusinessMethod
                && invInfo.methodIntf.equals(MethodDescriptor.EJB_LOCAL);
        if (isCMPField) {
            String methodName = method.getName();
            isCMPField = methodName.startsWith("get") 
                    || methodName.startsWith("set");
            if (isCMPField) {
                try {
                    //ejbClass is the container-generated implementation class.
                    //Need to get its superclass, which is provided by the bean provider.
                    Method methodInBeanClass = ejbClass.getSuperclass().getMethod(
                            methodName, method.getParameterTypes());
                    isCMPField = Modifier.isAbstract(methodInBeanClass.getModifiers());
                } catch (NoSuchMethodException ignore) {
                    isCMPField = false;
                }
            }
        }
        invInfo.isTxRequiredLocalCMPField = isCMPField
                && (invInfo.txAttr == TX_REQUIRED);
        return invInfo;
    }
    
    /**
     * Called from the ContainerFactory during initialization.
     */
    protected void initializeHome()
        throws Exception
    {
        ObjectFactory entityCtxFactory = new EntityContextFactory(this);
        
        int steadyPoolSize = 0;
        int resizeQuantity = 10;
        int idleTimeoutInSeconds = Integer.MAX_VALUE-1;
        poolProp = new PoolProperties();
        
        super.initializeHome();

        entityCtxPool = new NonBlockingPool(ejbDescriptor.getName(),
        	entityCtxFactory, poolProp.steadyPoolSize,
            poolProp.poolResizeQuantity, poolProp.maxPoolSize,
            poolProp.poolIdleTimeoutInSeconds, loader);


	registerMonitorableComponents();
    }

    protected void registerMonitorableComponents() {
	registryMediator.registerProvider(this);
	registryMediator.registerProvider(entityCtxPool);
	if (readyStore != null) {
	    int confMaxCacheSize = cacheProp.maxCacheSize;
	    if (confMaxCacheSize <= 0) {
		confMaxCacheSize = Integer.MAX_VALUE;
	    }
	    this.cacheStatsProvider = new EntityCacheStatsProvider(
		    (BaseCache) readyStore, confMaxCacheSize);
	    registryMediator.registerProvider(cacheStatsProvider);
	}
        super.registerMonitorableComponents();
	super.populateMethodMonitorMap();
        _logger.log(Level.FINE, "[Entity Container] registered monitorable");
    }
    
    public void onReady() {
    }
    
    public String getMonitorAttributeValues() {
        StringBuffer sbuf = new StringBuffer();
	appendStats(sbuf);
	return sbuf.toString();
    }

    public void appendStats(StringBuffer sbuf) {
	sbuf.append("\nEntityContainer: ")
	    .append("CreateCount=").append(statCreateCount).append("; ")
	    .append("RemoveCount=").append(statRemoveCount).append("; ")
	    .append("PassQSize=")
	    .append(passivationCandidates.size()).append("]");
        Map stats = null;
        if (readyStore != null) {
            stats = readyStore.getStats();
        }
        appendStat(sbuf, "ReadyStore", stats);
        
        appendStat(sbuf, "EJBObjectStore", ejbObjectStore.getStats());
        appendStat(sbuf, "EJBLocalObjectStore",ejbLocalObjectStore.getStats());
    }

    /****************************/
    //Methods of EntityBeanStatsProvider

    public int getMaxCacheSize() {
	int maxSize = 0;
	if (readyStore != null) {
	    maxSize = (cacheProp.maxCacheSize <= 0)
		? Integer.MAX_VALUE
		: cacheProp.maxCacheSize;
	}

	return maxSize;
    }

    public int getSteadyPoolSize() {
	return entityCtxPool.getSteadyPoolSize();
    }

    public int getMaxPoolSize() {
	return entityCtxPool.getMaxPoolSize();
    }

    public long getPooledCount() {
	return entityCtxPool.getSize();
    }

    public long getReadyCount() {
	return (readyStore == null)
	    ? 0 
	    : readyStore.getEntryCount();
    }

    /****************************/
    
    private int getEjbObjectStoreSize() {
        return ejbObjectStore.getEntryCount();
    }
    
    /**
     * Implementation of BaseContainer method. This is never called.
     */
    EJBObjectImpl createEJBObjectImpl()
        throws CreateException, RemoteException
    {
        throw new EJBException(
            "INTERNAL ERROR: EntityContainer.createEJBObject() called");
    }
    
    EJBLocalObjectImpl createEJBLocalObjectImpl()
        throws CreateException
    {
        throw new EJBException(
          "INTERNAL ERROR: EntityContainer.createEJBLocalObjectImpl() called");
    }
    
    
    /**
     * Called only from ContainerFactory when a remote invocation
     * arrives for an EJB.
     */
    EJBObjectImpl getEJBObjectImpl(byte[] streamKey) {
        // First get the primary key of the EJB
        Object primaryKey;
        try {
            primaryKey = EJBUtils.deserializeObject(streamKey, loader, false);
        } catch ( Exception ex ) {
            throw new EJBException(ex);
        }
        
        return internalGetEJBObjectImpl(primaryKey, streamKey);
    }
    
    
    /**
     * Called from EJBLocalObjectImpl.getLocalObject() while deserializing
     * a local object reference.
     */
    EJBLocalObjectImpl getEJBLocalObjectImpl(Object key) {
        return internalGetEJBLocalObjectImpl(key);
    }
    
    /**
     * Called from BaseContainer.preInvoke which is called from the EJBObject
     * for local and remote invocations, and from the EJBHome for create/find.
     */
    protected ComponentContext _getContext(Invocation inv) {
        String name = inv.method.getName();
        
        if ( inv.invocationInfo.isCreateHomeFinder ) { 
            // create*, find*, home methods
            // Note: even though CMP finders dont need an instance,
            // we still return a pooled instance, so that the Tx demarcation
            // in BaseContainer.pre+postInvoke can work.
            
            // get any pooled EJB
            EntityContextImpl context = getPooledEJB();
            
            // we're sure that no concurrent thread can be using this
            // context, so no need to synchronize.
            context.setState(INVOKING);
            
            if ( inv.invocationInfo.startsWithCreate )
                preCreate(inv, context);
            else if ( inv.invocationInfo.startsWithFind )
                preFind(inv, context);


            context.setLastTransactionStatus(-1);
            context.incrementCalls();
            
            return context;
        }
        
        // If we came here, it means this is a business method
        // and there is an EJBObject/LocalObject.
        
        // If we would invoke the EJB with the client's Tx,
        // try to get an EJB with that incomplete Tx.
        EntityContextImpl context = null;
        if ( willInvokeWithClientTx(inv) )
            context = getEJBWithIncompleteTx(inv);
        if ( context == null )
            context = getReadyEJB(inv);
        
        synchronized ( context ) {
            if ( context.getState() == INVOKING && !isReentrant )
                throw new EJBException(
                    "EJB is already executing another request");
            if (context.getState() == POOLED ||
                context.getState() == DESTROYED) {
                // somehow a concurrent thread must have changed state.
                // this is an internal error.
                throw new EJBException("Internal error: unknown EJB state");
            }
            
            context.setState(INVOKING);
        }
        
        context.setLastTransactionStatus(-1);
        context.incrementCalls();
        // A business method may modify the bean's state
        context.setDirty(true);
        
        return context;
    }
    
    protected boolean willInvokeWithClientTx(Invocation inv) {
        int status = Status.STATUS_UNKNOWN;
        try {
            Integer preInvokeTxStatus = inv.getPreInvokeTxStatus();
            status = (preInvokeTxStatus != null) ?
                preInvokeTxStatus.intValue() : transactionManager.getStatus();
        } catch ( SystemException ex ) {
            throw new EJBException(ex);
        }
        if ( status != Status.STATUS_NO_TRANSACTION ) {
            int txAttr = inv.invocationInfo.txAttr;
            switch (txAttr) {
                case TX_SUPPORTS:
                case TX_REQUIRED:
                case TX_MANDATORY:
                    return true;
            }
        }
        return false;
    }
    
    
    
    /**
     * This is called from BaseContainer.postInvoke after
     * EntityContainer.preInvokeTx has been called.
     */
    public void releaseContext(Invocation inv) {
        EntityContextImpl context = (EntityContextImpl)inv.context;
        boolean decrementedCalls = false; // End of IAS 4661771
        
        if ( context.getState()==DESTROYED )
            return;
        
        try {
            if ( context.hasReentrantCall() ) {
                // For biz->biz or postCreate->biz, the bean instance will
                // remain in the incomplete-tx table.
                if ( inv.ejbObject.isRemoved() ) {
                    // biz -> remove case (biz method invoked reentrant remove)
                    // Remove from IncompleteTx table, to prevent further
                    // reentrant calls.
                    removeIncompleteTxEJB(context, true);
                    
                    // disconnect context from EJB(Local)Object so that
                    // context.getEJBObject() will throw exception.
                    if ( context.getEJBObjectImpl() != null ) {
                        // reset flag in case EJBObject is used again
                        context.getEJBObjectImpl().setRemoved(false);
                        context.setEJBObjectImpl(null);
                        context.setEJBStub(null);
                    }
                    if ( context.getEJBLocalObjectImpl() != null ) {
                        // reset flag in case EJBLocalObject is used again
                        context.getEJBLocalObjectImpl().setRemoved(false);
                        context.setEJBLocalObjectImpl(null);
                    }
                } else {
                    if ( context.getState() == INVOKING )  {
                        doFlush( inv );
                    }
                }
                
                // Note: at this point context.getState() is INVOKING.
            } else if ( context.getEJBObjectImpl()==null
                && context.getEJBLocalObjectImpl()==null ) {
                // This can only happen if the method was ejbFind
                // OR if the method was ejbCreate which threw an application
                // exception (so postCreate was not called)
                // OR after a biz method which called a reentrant remove.
                // So bean instance goes back into pool.
                // We dont care if any Tx has completed or not.
                //context.setTransaction(null);
                decrementedCalls = true;
                context.decrementCalls();
                if (!(inv.invocationInfo.startsWithCreate)) {
                    context.setTransaction(null);
                    addPooledEJB(context);
                }else if(context.getTransaction() == null) {
                    addPooledEJB(context);
                } else {
                    // Set the state to incomplete as the transaction
                    // is not done still and afterCompletion will
                    // handle stuff
                    context.setState(INCOMPLETE_TX);
                }
            } else if ( inv.ejbObject.isRemoved() ) {
                // EJBObject/LocalObject was removed, so bean instance
                // goes back into pool.
                // We dont care if any Tx has completed or not.
                removeIncompleteTxEJB(context, true);
                // unset the removed flag, in case the EJB(Local)Object
                // ref is held by the client and is used again
                if ( context.getEJBObjectImpl() != null )
                    context.getEJBObjectImpl().setRemoved(false);
                if ( context.getEJBLocalObjectImpl() != null )
                    context.getEJBLocalObjectImpl().setRemoved(false);
                
                decrementedCalls = true;
                context.decrementCalls();
                if(context.getTransaction() == null) {
                    addPooledEJB(context);
                } else {
                    // Set the state to incomplete as the transaction
                    // is not done still and afterCompletion will
                    // handle stuff
                    context.setState(INCOMPLETE_TX);
                }
                
            } else if ( context.getTransaction() == null ) {
                // biz methods and ejbCreate
                // Either the EJB was called with no tx,
                // or it was called with a tx which finished,
                // so afterCompletion was already called.
                
                // If no tx or tx committed, then move the EJB to READY state
                // else pool the bean
                int status = context.getLastTransactionStatus();
                decrementedCalls = true;
                context.decrementCalls();
                context.setLastTransactionStatus(-1);
                if ( status == -1 || status == Status.STATUS_COMMITTED
                || status == Status.STATUS_NO_TRANSACTION )
                    addReadyEJB(context);
                else
                    passivateAndPoolEJB(context);
            } else {
                // biz methods and ejbCreate
                // The EJB is still associated with a Tx.
                // It will already be in the INCOMPLETE_TX table.
                context.setState(INCOMPLETE_TX);

                doFlush( inv );
            }
        } catch ( Exception ex ) {
            _logger.log(Level.FINE, "ejb.release_context_exception",
                        logParams);
            _logger.log(Level.FINE, "",ex);
            throw new EJBException(ex);
        } finally {
            if (decrementedCalls == false) {
                context.decrementCalls();
            }
            context.touch();
        }
    }
    
    
    /**
     * Called from getContext before the ejb.ejbCreate is called
     */
    protected void preCreate(Invocation inv, EntityContextImpl context) {
	statCreateCount++;
    }
    
    
    /**
     * This is called from the generated "HelloEJBHomeImpl" create* method,
     * after ejb.ejbCreate() has been called and before ejb.ejbPostCreate()
     * is called.
     * Note: postCreate will not be called if ejbCreate throws an exception
     */
    public void postCreate(Invocation inv, Object primaryKey)
        throws CreateException
    {
        if ( primaryKey == null )
            throw new EJBException(
                "Null primary key returned by ejbCreate method");
        
        EntityContextImpl context = (EntityContextImpl)inv.context;
        EJBObjectImpl ejbObjImpl  = null;
        EJBLocalObjectImpl localObjImpl = null;
        
        if ( (isRemote) && (!inv.isLocal) ) {
            // remote invocation: create EJBObject
            ejbObjImpl = internalGetEJBObjectImpl(primaryKey, null, true);
            
            // associate the context with the ejbObject
            context.setEJBObjectImpl(ejbObjImpl);
            context.setEJBStub((EJBObject)ejbObjImpl.getStub());
        }
        
        if ( isLocal ) {
            // create EJBLocalObject irrespective of local/remote invocation
            // this is necessary to make EntityContext.getPrimaryKey and
            // EntityContext.getEJBObject work.
            localObjImpl = internalGetEJBLocalObjectImpl(primaryKey, true);
            
            // associate the context with the ejbLocalObject
            context.setEJBLocalObjectImpl(localObjImpl);
        }
        
        if ( inv.isLocal )
            inv.ejbObject = localObjImpl;
        else
            inv.ejbObject = ejbObjImpl;
        
        if ( context.getTransaction() != null ) {
            // Add EJB to INCOMPLETE_TX table so that concurrent/loopback
            // invocations will be correctly handled
            addIncompleteTxEJB(context);
        }
        
        context.setDirty(true); // ejbPostCreate could modify state
    }
    
    //Called from EJB(Local)HomeInvocationHandler
    //Note: preFind is already called from getContext
    protected Object invokeFindByPrimaryKey(Method method,
	    Invocation inv, Object[] args)
	throws Throwable
    {
	Object pKeys = super.invokeTargetBeanMethod(method,
	    inv, inv.ejb, args, null);
	return postFind(inv, pKeys, null);
    }
   
    /**
     * Called from getContext before the ejb.ejbFind* is called
     */
    protected void preFind(Invocation inv, EntityContextImpl context) {
        // if the finder is being invoked with the client's transaction,
        // call ejbStore on all dirty bean instances associated with that
        // transaction. This ensures that the finder results will include
        // all updates done previously in the client's tx.
        if ( willInvokeWithClientTx(inv) &&
        !inv.method.getName().equals("findByPrimaryKey") ) {
            Transaction tx = null;
            try {
                tx = transactionManager.getTransaction();
            } catch ( SystemException ex ) {
                throw new EJBException(ex);
            }
            
            storeAllBeansInTx( tx );
        }
        
    }
    
    /**
     * Called from CMP PersistentManager
     */
    public void preSelect() 
      throws javax.ejb.EJBException {
	// if the ejbSelect is being invoked with the client's transaction,
        // call ejbStore on all dirty bean instances associated with that
        // transaction. This ensures that the select results will include
        // all updates done previously in the client's tx.
	_logger.fine(" inside preSelect...");
	Transaction tx = null;
	try {
	    _logger.fine("PRESELECT : getting transaction...");
	    tx = transactionManager.getTransaction();
	} catch ( SystemException ex ) {
	    throw new EJBException(ex);
	}
	_logger.fine("PRESELECT : calling storeAllBeansInTx()...");
	storeAllBeansInTx( tx );                
    }    

    /**
     * Convert a collection of primary keys to a collection of EJBObjects.
     * (special case: single primary key).
     * Note: the order of input & output collections must be maintained.
     * Null values are preserved in both the single primary key return
     * and collection-valued return cases.
     *
     * This is called from the generated "HelloEJBHomeImpl" find* method,
     * after ejb.ejbFind**() has been called.
     * Note: postFind will not be called if ejbFindXXX throws an exception
     */
    public Object postFind(Invocation inv, Object primaryKeys, 
        Object[] findParams)
        throws FinderException
    {
                
        if ( primaryKeys instanceof Enumeration ) {
            // create Enumeration of objrefs from Enumeration of primaryKeys
            Enumeration e = (Enumeration)primaryKeys;
            // this is a portable Serializable Enumeration
            ObjrefEnumeration objrefs = new ObjrefEnumeration();
            while ( e.hasMoreElements() ) {
                Object primaryKey = e.nextElement();
                Object ref;
                if( primaryKey != null ) {
                    if ( inv.isLocal )
                        ref = getEJBLocalObjectForPrimaryKey(primaryKey);
                    else
                        ref = getEJBObjectStub(primaryKey, null);
                    objrefs.add(ref);
                } else {
                    objrefs.add(null);
                }
            }
            return objrefs;
        } else if ( primaryKeys instanceof Collection ) {
            // create Collection of objrefs from Collection of primaryKeys
            Collection c = (Collection)primaryKeys;
            Iterator it = c.iterator();
            ArrayList objrefs = new ArrayList();  // a Serializable Collection
            while ( it.hasNext() ) {
                Object primaryKey = it.next();
                Object ref;
                if( primaryKey != null ) {
                    if ( inv.isLocal )
                        ref = getEJBLocalObjectForPrimaryKey(primaryKey);
                    else
                        ref = getEJBObjectStub(primaryKey, null);
                    objrefs.add(ref);
                } else {
                    objrefs.add(null);
                }
            }
            return objrefs;
        } else {
            if( primaryKeys != null ) {
                if ( inv.isLocal )
                    return getEJBLocalObjectForPrimaryKey(primaryKeys);
                else
                    return getEJBObjectStub(primaryKeys, null);
            } else {
                return null;
            }
        }
    }
    
    /**
     * Called only from the Persistence Manager for EJB2.0 CMP EntityBeans.
     * This is a private API between the PM and Container because there
     * is no standard API defined in EJB2.0 for the PM to get an EJBObject
     * for a primary key (home.findByPrimaryKey cant be used because it may
     * not run in the same tx).
     */
    public EJBObject getEJBObjectForPrimaryKey(Object pkey) {
        // create stub without creating EJBObject
        return getEJBObjectStub(pkey, null);
    }

    /**
     * Called only from the Persistence Manager for EJB2.0 CMP EntityBeans.
     * Called only during cascade delete......
     * This is a private API between the PM and Container because there
     * is no standard API defined in EJB2.0 for the PM to get an EJBLocalObject
     * for a primary key (findByPrimaryKey cant be used because it may
     * not run in the same tx).
     * 
     * Example 1:
     *  A cascadeDeletes B and B calls getA() (expected return value: null)
     *
     *  In the above case, getA() eventualy calls getEJBLocalObjectForPrimaryKey(PK_of_A, Ctx_of_B)
     *  We first check if B is in the process of being cascade deleted by checking the 
     *  cascadeDeleteBeforeEJBRemove flag. If this flag is true, only then we bother to check if
     *  the Context associated with the PK_of_A in this transaction is marked for cascade delete
     *  which can be figured out by checking isCascadeDeleteAfterSuperEJBRemove() in A's context.
     *  If A is marked for cascade delete then we return null else the EJBLocalObject associated
     *  with A.
     *  
     * Example 2:
     *  C cascadeDeletes B and B calls getA() (expected return value: EJBLocalObject for PK_of_A)
     *
     *  In the above case, getA() eventualy calls getEJBLocalObjectForPrimaryKey(PK_of_A, Ctx_of_B)
     *  We first check if B is in the process of being cascade deleted by checking the 
     *  cascadeDeleteBeforeEJBRemove flag. This flag will be true, and hence we check if
     *  the Context associated with the PK_of_A in this transaction is marked for cascade delete
     *  which can be figured out by checking isCascadeDeleteAfterSuperEJBRemove() in A's context.
     *  In this case this flag will be false and hcen we return the ejbLocalObject
     * Example 2:
     *  B is *NOT* cascade deleted and B calls getA() (expected return value: EJBLocalObject for PK_of_A)
     *
     *  In the above case, getA() eventualy calls getEJBLocalObjectForPrimaryKey(PK_of_A, Ctx_of_B)
     *  We first check if B is in the process of being cascade deleted by checking the 
     *  cascadeDeleteBeforeEJBRemove flag. This flag will be FALSE, and hence we do not make
     *  any further check and return the EJBLocalObject associated with A
     *
     * @param pkey The primary key for which the EJBLocalObject is required
     * @param ctx The context associated with the bean from which the accessor method is invoked
     * @return The EJBLocalObject associated with the PK or null if it is cascade deleted.
     *
     */
    public EJBLocalObject getEJBLocalObjectForPrimaryKey
        (Object pkey, EJBContext ctx) {

        EntityContextImpl context = (EntityContextImpl) ctx;
        EJBLocalObjectImpl ejbLocalObjectImpl = 
            internalGetEJBLocalObjectImpl(pkey);

        if (context.isCascadeDeleteBeforeEJBRemove()) {
            J2EETransaction current = null;
            try {
                current = (J2EETransaction) transactionManager.getTransaction();
            } catch ( SystemException ex ) {
                throw new EJBException(ex);
            }
	    ActiveTxCache activeTxCache = (current == null) ? null :
		(ActiveTxCache) (current.getActiveTxCache());
            if (activeTxCache != null) {
		EntityContextImpl ctx2 = (EntityContextImpl)
			activeTxCache.get(this, pkey);
		if ((ctx2 != null) && 
		    (ctx2.isCascadeDeleteAfterSuperEJBRemove())) {
		    return null;
		}
	    }
	    return (EJBLocalObject) ejbLocalObjectImpl.getClientObject();
        }

        return (EJBLocalObject) ejbLocalObjectImpl.getClientObject();
    }

    /**
     * Called only from the Persistence Manager for EJB2.0 CMP EntityBeans.
     * This is a private API between the PM and Container because there
     * is no standard API defined in EJB2.0 for the PM to get an EJBLocalObject
     * for a primary key (findByPrimaryKey cant be used because it may
     * not run in the same tx).
     */
    public EJBLocalObject getEJBLocalObjectForPrimaryKey(Object pkey) {
        EJBLocalObjectImpl localObjectImpl = 
            internalGetEJBLocalObjectImpl(pkey);
	return (localObjectImpl != null) ? 
            (EJBLocalObject) localObjectImpl.getClientObject() : null;
    }
    
    // Called from EJBHomeImpl.remove(primaryKey),
    // EJBLocalHomeImpl.remove(primaryKey)
    protected void removeBean(Object primaryKey, Method removeMethod, 
        boolean local)
        throws RemoveException, EJBException, RemoteException
    {
        EJBLocalRemoteObject ejbo;
        if ( local ) {
            ejbo = internalGetEJBLocalObjectImpl(primaryKey, false, true);
        }
        else { // may be remote-only bean
            ejbo = internalGetEJBObjectImpl(primaryKey, null, false, true);
        }
        removeBean(ejbo, removeMethod, local);
    }
    
    // Called from EJBObjectImpl.remove, EJBLocalObjectImpl.remove,
    // and removeBean above.
    protected void removeBean(EJBLocalRemoteObject ejbo, Method removeMethod,
            boolean local)
        throws RemoveException, EJBException, RemoteException
    {
        Invocation i = new Invocation();
        i.ejbObject = ejbo;
        i.isLocal = local;
        i.method = removeMethod;
        
        // Method must be a remove method defined on one of :
        // javax.ejb.EJBHome, javax.ejb.EJBObject, javax.ejb.EJBLocalHome,
        // javax.ejb.EJBLocalObject
        Class declaringClass = removeMethod.getDeclaringClass();
        i.isHome = ( (declaringClass == javax.ejb.EJBHome.class) ||
                     (declaringClass == javax.ejb.EJBLocalHome.class) );

        try {
            preInvoke(i);
            removeBean(i);
        } catch(Exception e) {
            _logger.log(Level.SEVERE,"ejb.preinvoke_exception",logParams);
            _logger.log(Level.SEVERE,"",e);
            i.exception = e;
        } finally {
            postInvoke(i);
        }
        
        if(i.exception != null) {
            if(i.exception instanceof RemoveException) {
                throw (RemoveException)i.exception;
            }
            else if(i.exception instanceof RuntimeException) {
                throw (RuntimeException)i.exception;
            }
            else if(i.exception instanceof Exception) {
                throw new EJBException((Exception)i.exception);
            }
            else {
                EJBException ejbEx = new EJBException();
                ejbEx.initCause(i.exception);
                throw ejbEx;
            }
        }
    }
    
    
    /**
     * container.preInvoke() must already be done.
     * So this will be called with the proper Tx context.
     * @exception RemoveException if an error occurs while removing the bean
     */
    protected void removeBean(Invocation inv)
        throws RemoveException
    {
        try {
	    statRemoveCount++;
            // Note: if there are concurrent invocations/transactions in
            // progress for this ejbObject, they will be serialized along with
            // this remove by the database. So we optimistically do ejbRemove.
            
            // call ejbRemove on the EJB
            // the EJB is allowed to veto the remove by throwing RemoveException
            EntityBean ejb = (EntityBean)inv.ejb;
            EntityContextImpl context = (EntityContextImpl)inv.context;
            callEJBRemove(ejb, context);
            
            // inv.ejbObject could be a EJBObject or a EJBLocalObject
            Object primaryKey = inv.ejbObject.getKey();
            if ( isRemote ) {
                removeEJBObjectFromStore(primaryKey);
                
                // Mark EJB as removed. Now releaseContext will add bean to pool
                if ( context.getEJBObjectImpl() != null ) {
                    context.getEJBObjectImpl().setRemoved(true);
                }
            }
            
            if ( isLocal ) {
                // Remove the EJBLocalObject from ejbLocalObjectStore
                ejbLocalObjectStore.remove(primaryKey);
                // Mark EJB as removed. Now releaseContext will add bean to pool
                if ( context.getEJBLocalObjectImpl() != null ) {
                    context.getEJBLocalObjectImpl().setRemoved(true);
                }
            }
            
            // Remove any timers for this entity bean identity.
            EJBTimerService ejbTimerService =
                containerFactory.getEJBTimerService();
            if( (ejbTimerService != null) && isTimedObject() ) {
                ejbTimerService.cancelEntityBeanTimers(getContainerId(),
                                                       primaryKey);
            } 

        } catch ( RemoveException ex ) {
            if(_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE,"ejb.local_remove_exception",logParams);
                _logger.log(Level.FINE,"",ex);
            }
            throw ex;
        }
        catch ( Exception ex ) {
            if(_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE,"ejb.remove_bean_exception",logParams);
                _logger.log(Level.FINE,"",ex);
            }
            throw new EJBException(ex);
        }
    }
    
    private void removeEJBObjectFromStore(Object primaryKey) {
        removeEJBObjectFromStore(primaryKey, true);
    }
    
    private void removeEJBObjectFromStore(Object primaryKey, boolean decrementRefCount) {
        // Remove the EJBObject from ejbObjectStore so future lookups
        // in internalGetEJBObject will not get it.
        EJBObjectImpl ejbObjImpl = 
            (EJBObjectImpl)ejbObjectStore.remove(primaryKey, decrementRefCount);
                                                 
        if ( ejbObjImpl != null ) {
            synchronized ( ejbObjImpl ) {
                // disconnect the EJBObject from the ProtocolManager
                // so that no remote invocations can reach the EJBObject
                remoteHomeRefFactory.destroyReference(ejbObjImpl.getStub(), 
                                                  ejbObjImpl.getEJBObject());
            }
        }
    }
    
    /**
     * Remove a bean. Used by the PersistenceManager.
     * This is needed because the PM's remove must bypass tx/security checks.
     */
    public void removeBeanUnchecked(EJBLocalObject localObj) {
        // First convert client EJBLocalObject to EJBLocalObjectImpl
        EJBLocalObjectImpl localObjectImpl = 
            EJBLocalObjectImpl.toEJBLocalObjectImpl(localObj);
        internalRemoveBeanUnchecked(localObjectImpl, true);
    }
    
    
    /**
     * Remove a bean. Used by the PersistenceManager.
     * This is needed because the PM's remove must bypass tx/security checks.
     */
    public void removeBeanUnchecked(Object primaryKey) {
        EJBLocalRemoteObject ejbo;
        if ( isLocal ) {
            ejbo = internalGetEJBLocalObjectImpl(primaryKey);
            internalRemoveBeanUnchecked(ejbo, true);
        }
        else { // remote-only bean
            ejbo = internalGetEJBObjectImpl(primaryKey, null);
            internalRemoveBeanUnchecked(ejbo, false);
        }
    }
    
    /**
     * Remove a bean. Used by the PersistenceManager.
     * This is needed because the PM's remove must bypass tx/security checks.
     */
    private void internalRemoveBeanUnchecked(
    EJBLocalRemoteObject localRemoteObj, boolean local) {
        Invocation inv = new Invocation();
        inv.ejbObject = localRemoteObj;
        inv.isLocal = local;
        Method method=null;
        try {
            method = EJBLocalObject.class.getMethod("remove", NO_PARAMS);
        } catch ( NoSuchMethodException e ) {
            _logger.log(Level.FINE, 
                "Exception in internalRemoveBeanUnchecked()", e);
        }
        inv.method = method;
        
        inv.invocationInfo = (InvocationInfo) invocationInfoMap.get(method);
        
        try {
            // First get a bean instance on which ejbRemove can be invoked.
            // This code must be in sync with getContext().
            // Can't call getContext() directly because it does stuff
            // based on remove's txAttr.
            // Assume there is a tx on the current thread.
            EntityContextImpl context = getEJBWithIncompleteTx(inv);
            if ( context == null ) {
                context = getReadyEJB(inv);
            }
            
            synchronized ( context ) {
                if ( context.getState() == INVOKING && !isReentrant ) {
                    throw new EJBException(
                        "EJB is already executing another request");
                }
                if (context.getState() == POOLED ||
                    context.getState() == DESTROYED) {
                    // somehow a concurrent thread must have changed state.
                    // this is an internal error.
                    throw new EJBException("Internal error: unknown EJB state");
                }
                
                context.setState(INVOKING);
            }
            inv.context = context;
            context.setLastTransactionStatus(-1);
            context.incrementCalls();
            
            inv.instance = inv.ejb = context.getEJB();
            inv.container = this;
            invocationManager.preInvoke(inv);
            
            // call ejbLoad if necessary
            useClientTx(context.getTransaction(), inv);
            
            try {
                context.setCascadeDeleteBeforeEJBRemove(true);
                removeBean(inv);
            } catch ( Exception ex ) {
                _logger.log(Level.FINE, 
                    "Exception in internalRemoveBeanUnchecked()", ex);
                // if system exception mark the tx for rollback
                inv.exception = checkExceptionClientTx(context, ex);
            }
            if ( inv.exception != null ) {
                throw inv.exception;
            }
        }
        catch ( RuntimeException ex ) {
            throw ex;
        }
        catch ( Exception ex ) {
            throw new EJBException(ex);
        }
        catch ( Throwable ex ) {
            EJBException ejbEx = new EJBException();
            ejbEx.initCause(ex);
            throw ejbEx;
        }
        finally {
            invocationManager.postInvoke(inv);
            releaseContext(inv);
        }
    }
    
    /**
     * Discard the bean instance. The bean's persistent state is not removed.
     * This is usually called when the bean instance throws a system exception,
     * from BaseContainer.postInvokeTx, getReadyEJB,
     * afterBegin, beforeCompletion, passivateEJB.
     */
    void forceDestroyBean(EJBContextImpl ctx) {
        // Something bad happened (such as a RuntimeException),
        // so kill the bean and let it be GC'ed
        // Note: EJB2.0 section 18.3.1 says that discarding an EJB
        // means that no methods other than finalize() should be invoked on it.
        
        if ( ctx.getState() == DESTROYED ) {
            entityCtxPool.destroyObject(null);
            return;
        }
        
        EntityContextImpl context = (EntityContextImpl)ctx;
        EntityBean ejb = (EntityBean)context.getEJB();
        // Start of IAS 4661771
        synchronized ( context ) {
            try {
                Object primaryKey = context.getPrimaryKey();
                if ( primaryKey != null ) {
                    if ( context.getTransaction() != null ) {
                        Transaction txCurrent = context.getTransaction();
			ActiveTxCache activeTxCache = (ActiveTxCache) 
			    (((J2EETransaction) txCurrent).getActiveTxCache());
                        if (activeTxCache !=  null) {
			    // remove the context from the store
			    activeTxCache.remove(this, primaryKey);
			}
                    }
                    
                    // remove the context from readyStore as well
                    removeContextFromReadyStore(primaryKey, context);
                    
                    if (context.getEJBObjectImpl() != null) {
                        removeEJBObjectFromStore(primaryKey);
                    }
                    if (context.getEJBLocalObjectImpl() != null) {
                        ejbLocalObjectStore.remove(primaryKey);
                    }
                    
                }
            } catch ( Exception ex ) {
                _logger.log(Level.FINE, "Exception in forceDestroyBean()", ex);
            } finally {
                try {
                    //Very importatnt to set the state as destroyed otherwise
                    //	the pool.destroy might wrongly call unsetEntityContext
                    context.setState(DESTROYED);
                    entityCtxPool.destroyObject(context);
                } catch (Exception ex) {
                    _logger.log(Level.FINE, "Exception in forceDestroyBean()", 
                        ex);
                }
            }
        }
        // End of IAS 4661771
    }
    
    
    // Called before invoking a bean with no Tx or with a new Tx.
    // Check if the bean is associated with an unfinished tx.
    protected void checkUnfinishedTx(Transaction prevTx, Invocation inv) {
                                     
        try {
            if ( (prevTx != null) &&
                 prevTx.getStatus() != Status.STATUS_NO_TRANSACTION ) {
                // An unfinished tx exists for the bean.
                // so we cannot invoke the bean with no Tx or a new Tx.
                throw new IllegalStateException(
                  "Bean is associated with a different unfinished transaction");
            }
        } catch (SystemException ex) {
            throw new EJBException(ex);
        }
    }
    
    
    /**
     * Check if the given EJBObject/LocalObject has been removed.
     * Called before executing non-business methods of EJBLocalObject.
     * @exception NoSuchObjectLocalException if the object has been removed.
     */
    void checkExists(EJBLocalRemoteObject ejbObj) {
        // Need to call ejbLoad to see if persistent state is removed.
        // However, the non-business methods dont have a transaction attribute.
        // So do nothing for now.
    }
    
    // Called from BaseContainer.SyncImpl
    void afterBegin(EJBContextImpl ctx) {
        // Note: EntityBeans are not allowed to be TX_BEAN_MANAGED
        if ( ctx.getState() == DESTROYED )
            return;
        
        EntityContextImpl context  = (EntityContextImpl)ctx;
        
        if ( context.getEJBObjectImpl() != null
             || context.getEJBLocalObjectImpl() != null ) {
            // ejbLoad needed only for business methods and removes
            
            // Add EJB to INCOMPLETE_TX table so that concurrent/loopback
            // invocations will be correctly handled
            if ( context.getTransaction() != null ) {
                addIncompleteTxEJB(context);
            }
            
            // need to call ejbLoad since there can be more than
            // one active EJB instance per primaryKey. (Option B in 9.11.5).
            EntityBean e = (EntityBean)context.getEJB();
            try {
                callEJBLoad(e, context, true);
            } catch ( NoSuchEntityException ex ) {
                _logger.log(Level.FINE, "Exception in afterBegin()", ex);
                // Error during ejbLoad, so discard bean: EJB2.0 18.3.3
                forceDestroyBean(context);
                
                throw new NoSuchObjectLocalException(
         "NoSuchEntityException thrown by ejbLoad, EJB instance discarded", ex);
            } catch ( Exception ex ) {
                // Error during ejbLoad, so discard bean: EJB2.0 18.3.3
                forceDestroyBean(context);
                
                throw new EJBException(ex);
            }
            
            context.setNewlyActivated(false);
        }
    }
    
    // Called from BaseContainer.SyncImpl.beforeCompletion, postInvokeNoTx
    void beforeCompletion(EJBContextImpl ctx) {
        if ( ctx.getState() == DESTROYED ) {
            return;
        }
        
        EntityContextImpl context = (EntityContextImpl)ctx;
        EJBLocalRemoteObject ejbObjImpl = context.getEJBObjectImpl();
        EJBLocalRemoteObject ejbLocalObjImpl = context.getEJBLocalObjectImpl();
        
        // Call ejbStore as required by diagram in EJB2.0 section 10.9.4
        // home methods, finders and remove dont need ejbStore
        if ( ((ejbObjImpl != null) && !ejbObjImpl.isRemoved())
             || ((ejbLocalObjImpl != null) && !ejbLocalObjImpl.isRemoved()) ) {
            if ( context.isDirty() ) {
                enlistResourcesAndStore(context);
            }
        }
    }
    
    
    // Called from beforeCompletion and preFind
    private void enlistResourcesAndStore(EntityContextImpl context) {
        EntityBean e = (EntityBean)context.getEJB();
        // NOTE : Use invocation instead of ComponentInvocation since
        // the context is available.  It is needed in case ejbStore/ejbLoad
        // makes use of EJB timer service in order to perform operations allowed
        // checks
        Invocation inv = new Invocation(e, this);
        inv.context = context;
        invocationManager.preInvoke(inv);
        
        try {
            transactionManager.enlistComponentResources();
            
            callEJBStore(e, context);
            
        } catch ( NoSuchEntityException ex ) {
            // Error during ejbStore, so discard bean: EJB2.0 18.3.3
            forceDestroyBean(context);
            
            throw new NoSuchObjectLocalException(
        "NoSuchEntityException thrown by ejbStore, EJB instance discarded", ex);
        } catch ( Exception ex ) {
            // Error during ejbStore, so discard bean: EJB2.0 18.3.3
            forceDestroyBean(context);
            throw new EJBException(ex);
        } finally {
            invocationManager.postInvoke(inv);
        }
    }
    
    
    // Called from BaseContainer.SyncImpl.afterCompletion
    // at the end of a transaction.
    // Note: this can be called possibly asynchronously because
    // of transaction timeout
    // Note: this can be called before releaseContext (if container
    // completed the tx in BaseContainer.postInvokeTx), or it can
    // be called after releaseContext (if client completed the tx after
    // getting reply from bean). So whatever is done here *MUST* be
    // consistent with releaseContext, and the bean should end up in
    // the correct state.
    void afterCompletion(EJBContextImpl ctx, int status) {
        if ( ctx.getState() == DESTROYED ) {
            return;
        }

        if (super.isUndeployed()) {
	    transactionManager.ejbDestroyed(ctx);
	    return;
	}

        EntityContextImpl context = (EntityContextImpl)ctx;
        EJBLocalRemoteObject ejbObjImpl = context.getEJBObjectImpl();
        EJBLocalRemoteObject ejbLocalObjImpl = context.getEJBLocalObjectImpl();

        // home methods, finders and remove dont need this
        if ( ((ejbObjImpl != null) && !ejbObjImpl.isRemoved())
             || ((ejbLocalObjImpl != null) && !ejbLocalObjImpl.isRemoved()) ) {
            // Remove bean from ActiveTxCache table if its there.
            // No need to remove it from txBeanTable because the table
            // gets updated in ContainerFactoryImpl.removeContainerSync.

	    //removeIncompleteTxEJB(context, false);
            
            context.setTransaction(null);
            context.setLastTransactionStatus(status);

            context.setCascadeDeleteAfterSuperEJBRemove(false);
            context.setCascadeDeleteBeforeEJBRemove(false);
            
            // Move context to ready state if tx commited, else to pooled state
            if ( context.getState() != INVOKING ) {
                if ( (status == Status.STATUS_COMMITTED)
                     || (status == Status.STATUS_NO_TRANSACTION) ) {
                    addReadyEJB(context);
                } else {
                    passivateAndPoolEJB(context);
                }
            }
        } else if ((ejbObjImpl == null) && (ejbLocalObjImpl == null)) {
            // This happens if an ejbcreate has an exception, in that case
            // we remove bean from ActiveTxCache table if its there.
            // and return it to the pool
            //removeIncompleteTxEJB(context, false);
            
            context.setTransaction(null);
            context.setLastTransactionStatus(status);

            context.setCascadeDeleteAfterSuperEJBRemove(false);
            context.setCascadeDeleteBeforeEJBRemove(false);
            
            if ( context.getState() != INVOKING ) {
                addPooledEJB(context);
            }
	} else if ( ((ejbObjImpl != null) && ejbObjImpl.isRemoved())
	    || ((ejbLocalObjImpl != null) && ejbLocalObjImpl.isRemoved()) )
	{
	    //removeIncompleteTxEJB(context, false);
	    context.setTransaction(null);
	    context.setLastTransactionStatus(status);

	    if (context.getState() == INCOMPLETE_TX) {
		addPooledEJB(context);
	    }
	}
    }
    
    
    // Called from BaseContainer just before invoking a business method
    // whose tx attribute is TX_NEVER / TX_NOT_SUPPORTED / TX_SUPPORTS without
    // a client tx.
    void preInvokeNoTx(Invocation inv) {
        EntityContextImpl context = (EntityContextImpl)inv.context;
        
        if ( context.getState() == DESTROYED ) {
            return;
        }
        
        if ( context.isNewlyActivated() && 
            !inv.invocationInfo.isCreateHomeFinder ) {
            // follow EJB2.0 section 12.1.6.1
            EntityBean e = (EntityBean)context.getEJB();
            try {
                callEJBLoad(e, context, false);
            } catch ( NoSuchEntityException ex ) {
                // Error during ejbLoad, so discard bean: EJB2.0 18.3.3
                forceDestroyBean(context);
                
                throw new NoSuchObjectLocalException(
         "NoSuchEntityException thrown by ejbLoad, EJB instance discarded", ex);
            } catch ( Exception ex ) {
                // Error during ejbLoad, so discard bean: EJB2.0 18.3.3
                forceDestroyBean(context);
                
                throw new EJBException(ex);
            }
            
            context.setNewlyActivated(false);
        }
    }
    
    // Called from BaseContainer after invoking a method with tx attribute
    // NotSupported or Never or Supports without client tx.
    void postInvokeNoTx(Invocation inv) {
        // This calls ejbStore to allow bean to flush any state to database.
        // This is also sufficient for compliance with EJB2.0 section 12.1.6.1
        // (ejbStore must be called between biz method and ejbPassivate).
        beforeCompletion((EJBContextImpl)inv.context);
    }
    
    
    
    // CacheListener interface
    public void trimEvent(Object primaryKey, Object context) {
        boolean addTask = false;
        synchronized (asyncTaskSemaphore) {
            passivationCandidates.add(context);
            if (addedASyncTask == true) {
                return;
            }
            addTask = addedASyncTask = true;
        }
        
        try {
            ASyncPassivator work = new ASyncPassivator();
            ContainerWorkPool.addLast(work);
        } catch (Exception ex) {
            addedASyncTask = false;
            _logger.log(Level.WARNING, "ejb.add_cleanup_task_error",ex);
        }
    }
    
    private class ASyncPassivator
        implements com.sun.enterprise.util.threadpool.Servicable
    {
        
        public void prolog() { }
        
        public void epilog() { }
        
        public void service() { run(); }
        
        public void run() {
            final Thread currentThread = Thread.currentThread();
            final ClassLoader previousClassLoader = 
                currentThread.getContextClassLoader();
            final ClassLoader myClassLoader = loader;
            
            try {
                //We need to set the context class loader for this 
                //(deamon) thread!!      
                if(System.getSecurityManager() == null) {
                    currentThread.setContextClassLoader(loader);
                } else {
                    java.security.AccessController.doPrivileged(
                            new java.security.PrivilegedAction() {
                        public java.lang.Object run() {
                            currentThread.setContextClassLoader(loader);
                            return null;
                        }
                    }
                    );
                }
                
                ComponentContext ctx = null;
                do {
                    synchronized (asyncTaskSemaphore) {
                        int sz = passivationCandidates.size() - 1;
                        if (sz > 0) {
                            ctx = 
                          (ComponentContext) passivationCandidates.remove(sz-1);
                        } else {
                            return;
                        }
                    }
                    
                    if (ctx != null) {
                        passivateEJB(ctx);
			totalPassivations++;
                    }
                } while (ctx != null);
            } catch (Throwable th) {
		totalPassivationErrors++;
                th.printStackTrace();
            } finally {
                synchronized (asyncTaskSemaphore) {
                    addedASyncTask = false;
                }
                if(System.getSecurityManager() == null) {
                    currentThread.setContextClassLoader(previousClassLoader);
                } else {
                    java.security.AccessController.doPrivileged(
                            new java.security.PrivilegedAction() {
                        public java.lang.Object run() {
                            currentThread.setContextClassLoader(previousClassLoader);
                            return null;
                        }
                    }
                    );
                }
            }
        }
    }
    
    // Called from AbstractCache 
    boolean passivateEJB(ComponentContext ctx) {
        if (containerState != CONTAINER_STARTED) {
            return false;
        }
        
        EntityContextImpl context = (EntityContextImpl)ctx;
        
        if (context.getState() != READY) {
            return false;
        }
        
        if(_logger.isLoggable(Level.FINEST)) {
            _logger.log(Level.FINEST,"EntityContainer.passivateEJB(): context = (" +
                ctx + ")");
        }
        EntityBean ejb = (EntityBean)context.getEJB();
        
        Invocation inv = new Invocation(ejb, this, context);
        inv.method = ejbPassivateMethod;
        
        Object pkey = context.getPrimaryKey();
        boolean wasPassivated = false;
        
        // check state after locking ctx
        if ( context.getState() != READY )
            return false;
        try {
            invocationManager.preInvoke(inv);
            
            // remove EJB from readyStore
            removeContextFromReadyStore(pkey, context);
            
            // no Tx needed for ejbPassivate
            ejb.ejbPassivate();
            
            wasPassivated = true;
        } catch ( Exception ex ) {
            _logger.log(Level.FINE, "Exception in passivateEJB()", ex);
            // Error during ejbStore/Passivate, discard bean: EJB2.0 18.3.3
            forceDestroyBean(context);
            return false;
        } finally {
            invocationManager.postInvoke(inv);
        }
        
        // Remove the ejbObject/LocalObject from ejbObject/LocalObjectStore
        // If a future invocation arrives for them, they'll get recreated.
        if ( isRemote ) {
            removeEJBObjectFromStore(pkey);
        }
        if ( isLocal ) {
            ejbLocalObjectStore.remove(pkey);
        }
        
        // Note: ejbStore and ejbPassivate need the primarykey
        // so we should dissociate the context from EJBObject only
        // after calling ejbStore and ejbPassivate.
        synchronized (context) {
            addPooledEJB(context);
        }
        
        return wasPassivated;
    }
    
    
    
    /***************************************************************************
     * The following are private methods for implementing internal logic
     * for lifecyle and state management, in a reusable way.
     **************************************************************************/
    
    
    // called from postCreate, postFind,
    // getEJBLocalObjectForPrimaryKey, removeBean
    protected EJBLocalObjectImpl internalGetEJBLocalObjectImpl
        (Object primaryKey) {
        return internalGetEJBLocalObjectImpl(primaryKey, false, 
                                             defaultCacheEJBO);
    }
    
    protected EJBLocalObjectImpl internalGetEJBLocalObjectImpl
        (Object primaryKey, boolean incrementRefCount)
    {
        return internalGetEJBLocalObjectImpl(primaryKey, incrementRefCount, 
            defaultCacheEJBO);
    }
    
    protected EJBLocalObjectImpl internalGetEJBLocalObjectImpl
        (Object primaryKey, boolean incrementRefCount, boolean cacheEJBO) {
        // check if the EJBLocalObject exists in the store.
        try {
            EJBLocalObjectImpl localObjImpl = (EJBLocalObjectImpl)
                ejbLocalObjectStore.get(primaryKey, incrementRefCount);
            if ( localObjImpl == null ) {

                localObjImpl = instantiateEJBLocalObjectImpl();
                
                // associate the EJBLocalObjectImpl with the primary key
                localObjImpl.setKey(primaryKey);
                
                // add the EJBLocalObjectImpl to ejbLocalObjectStore
                if (incrementRefCount || cacheEJBO) {
                    ejbLocalObjectStore.put(primaryKey, localObjImpl, 
                        incrementRefCount);
                }
            }
            return localObjImpl;
        } catch ( Exception ex ) {
            _logger.log(Level.SEVERE,"ejb.get_ejb_local_object_exception",
                        logParams);
            _logger.log(Level.SEVERE,"",ex);
            throw new EJBException(ex);
        }
    }
    
    // called from postFind, getEJBObjectForPrimaryKey, 
    // EntityContextImpl.getEJBObject()
    EJBObject getEJBObjectStub(Object primaryKey, byte[] streamKey) {	
        // primary key cant be null, streamkey may be null

        // check if the EJBObject exists in the store.
        try {
            EJBObjectImpl ejbObjImpl = 
                (EJBObjectImpl) ejbObjectStore.get(primaryKey);
            if ( (ejbObjImpl != null) && (ejbObjImpl.getStub() != null) ) {
                return (EJBObject) ejbObjImpl.getStub();
            }

            // create a new stub without creating the EJBObject itself
            if ( streamKey == null ) {
                streamKey = EJBUtils.serializeObject(primaryKey, false);
            }
            EJBObject ejbStub = (EJBObject)
                remoteHomeRefFactory.createRemoteReference(streamKey);
                                                           
            return ejbStub;
        } catch ( Exception ex ) {
            _logger.log(Level.FINE,"", ex);
            throw new EJBException(ex);
        }
    }

    // called from getEJBObject, postCreate, removeBean,
    //             postFind, getEJBObjectForPrimaryKey
    protected EJBObjectImpl internalGetEJBObjectImpl(Object primaryKey, 
                                                     byte[] streamKey) {
        return internalGetEJBObjectImpl(primaryKey, streamKey, false, 
                                        defaultCacheEJBO);
    }
    
    protected EJBObjectImpl internalGetEJBObjectImpl(Object primaryKey, 
            byte[] streamKey, boolean incrementRefCount)
    {
        return internalGetEJBObjectImpl
            (primaryKey, streamKey, incrementRefCount, defaultCacheEJBO);
    }
    
    
    // called from getEJBObject, postCreate, postFind,
    // getEJBObjectForPrimaryKey, removeBean
    protected EJBObjectImpl internalGetEJBObjectImpl(Object primaryKey,
        byte[] streamKey, boolean incrementRefCount, boolean cacheEJBO) {
        // primary key cant be null, streamkey may be null
        
        // check if the EJBContext/EJBObject exists in the store.
        try {
            
            EJBObjectImpl ejbObjImpl = (EJBObjectImpl) 
                ejbObjectStore.get(primaryKey, incrementRefCount);

            if ( (ejbObjImpl != null) && (ejbObjImpl.getStub() != null) ) {
                return ejbObjImpl;
            }
            
            // check if the EJBContext/EJBObject exists in threadlocal
            // This happens if ejbo is in the process of being created.
            // This is necessary to prevent infinite recursion
            // because PRO.narrow calls is_a which calls the
            // ProtocolMgr which calls getEJBObject.
            ejbObjImpl = (EJBObjectImpl) ejbServant.get();
            if ( ejbObjImpl != null ) {
                return ejbObjImpl;
            }
            
            // create the EJBObject.
            ejbObjImpl = instantiateEJBObjectImpl();
            
            // associate the EJBObject with the primary key
            ejbObjImpl.setKey(primaryKey);
            
            // set ejbo in thread local to help recursive calls find the ejbo
            ejbServant.set(ejbObjImpl);
            
            // "Connect" the EJBObject to the Protocol Manager
            
            if ( streamKey == null ) {
                streamKey = EJBUtils.serializeObject(primaryKey, false);
            }
            EJBObject ejbStub = (EJBObject)
                remoteHomeRefFactory.createRemoteReference(streamKey);
                                                           
            ejbObjImpl.setStub(ejbStub);
            ejbServant.set(null);
            
            if ((incrementRefCount || cacheEJBO)) {
                EJBObjectImpl ejbo1 = 
                    (EJBObjectImpl) ejbObjectStore.put(primaryKey, ejbObjImpl, 
                        incrementRefCount);
                if ((ejbo1 != null) && (ejbo1 != ejbObjImpl)) {
                    remoteHomeRefFactory.destroyReference(ejbObjImpl.getStub(), 
                                                      ejbObjImpl);
                    ejbObjImpl = ejbo1;
                }
            }
            
            return ejbObjImpl;
        }
        catch ( Exception ex ) {
            _logger.log(Level.FINE, "ejb.get_ejb_context_exception", logParams);
            _logger.log(Level.FINE,"",ex);
            throw new EJBException(ex);
        }
    } //internalGetEJBObject(..)
    
    
    // called from getContext and getReadyEJB
    protected EntityContextImpl getPooledEJB() {
        try {
            return (EntityContextImpl) entityCtxPool.getObject(true, null);
        } catch (com.sun.ejb.containers.util.pool.PoolException inEx) {
            throw new EJBException(inEx);
        }
    }
    
    // called from passivateAndPoolEJB, releaseContext, passivateEJB
    // Note: addPooledEJB is idempotent: i.e. even if it is called multiple
    // times with the same context, the context is added only once.
    protected void addPooledEJB(EntityContextImpl context) {
        if ( context.getState() == POOLED ) {
            return;
        }
        // we're sure that no concurrent thread can be using this
        // context, so no need to synchronize.
        context.setEJBLocalObjectImpl(null);
        context.setEJBObjectImpl(null);
        context.setEJBStub(null);
        context.setState(POOLED);
	context.clearCachedPrimaryKey();
        
        //context.cacheEntry = null;
        entityCtxPool.returnObject(context);
        
    }
    
    // called from addReadyEJB and afterCompletion
    protected void passivateAndPoolEJB(EntityContextImpl context) {
        if ( context.getState() == DESTROYED || context.getState() == POOLED )
            return;
        
        // if ( context.isPooled() ) {
        // context.isPooled(false);
        // return;
        // }
        EntityBean ejb = (EntityBean) context.getEJB();
        synchronized ( context ) {
            Invocation inv = new Invocation(ejb, this, context);
            inv.method = ejbPassivateMethod;
            invocationManager.preInvoke(inv);
            
            try {
                ejb.ejbPassivate();
            } catch ( Exception ex ) {
                _logger.log(Level.FINE,"Exception in passivateAndPoolEJB()",ex);
                forceDestroyBean(context);
                return;
            } finally {
                invocationManager.postInvoke(inv);
            }
            
            // remove EJB(Local)Object from ejb(Local)ObjectStore
            
            
            Object primaryKey = context.getPrimaryKey();
            if ( isRemote ) {
                removeEJBObjectFromStore(primaryKey);
            }
            if ( isLocal ) {
                ejbLocalObjectStore.remove(primaryKey);
            }
            
            addPooledEJB(context);
        }
    }
    
    
    /**
     * Called from getContext and getEJBWithIncompleteTx
     * Get an EJB in the ready state (i.e. which is not doing any
     * invocations and doesnt have any incomplete Tx), for the
     * ejbObject provided in the Invocation.
     * Concurrent invocations should get *different* instances.
     */
    protected EntityContextImpl activateEJBFromPool(Object primaryKey, 
        Invocation inv) {
        EntityContextImpl context = null;
        // get a pooled EJB and activate it.
        context = getPooledEJB();
        
        // we're sure that no concurrent thread can be using this
        // context, so no need to synchronize.
        
        // set EJBObject/LocalObject for the context
        if ( inv.isLocal ) {
            EJBLocalObjectImpl localObjImpl = 
                internalGetEJBLocalObjectImpl(primaryKey, true);
            inv.ejbObject = localObjImpl;
            context.setEJBLocalObjectImpl(localObjImpl);
            // No need to create/set EJBObject if this EJB isRemote too.
		    // This saves remote object creation overhead.
		    // The EJBObject and stub will get created lazily if needed
		    // when EntityContext.getEJBObjectImpl is called.
        } else { // remote invocation
            EJBObjectImpl ejbObjImpl = 
                internalGetEJBObjectImpl(primaryKey, null, true);
            inv.ejbObject = ejbObjImpl;
            context.setEJBObjectImpl(ejbObjImpl);
            context.setEJBStub((EJBObject)ejbObjImpl.getStub());
            
            if ( isLocal ) {
                // Create EJBLocalObject so EntityContext methods work
                context.setEJBLocalObjectImpl(
                    internalGetEJBLocalObjectImpl(primaryKey, true));
            }
        }
        
        context.setState(READY);
        
        EntityBean ejb = (EntityBean)context.getEJB();
        
        Invocation inv2 = new Invocation(ejb, this, context);
        inv2.method = ejbActivateMethod;
        invocationManager.preInvoke(inv2);
        
        try {
            ejb.ejbActivate();
            
            // Note: ejbLoad will be called during preInvokeTx
            // since this EJB instance is being associated with
            // a Tx for the first time.
            
        } catch ( Exception ex ) {
            // Error during ejbActivate, discard bean: EJB2.0 18.3.3
            forceDestroyBean(context);
            throw new EJBException(ex);
        } finally {
            invocationManager.postInvoke(inv2);
        }
        
        context.setNewlyActivated(true);
        //recycler.initSoftRef(context);
        
        afterNewlyActivated(context);
        
        return context;
    } //getReadyEJB(inv)
    
    
    // called from releaseContext, afterCompletion
    
    
    /**
     * Get an EJB instance for this EJBObject and current client Tx
     * Called only from getContext.
     * Return null if there no INCOMPLETE_TX bean for the pkey & tx.
     */
    private EntityContextImpl getEJBWithIncompleteTx(Invocation inv) {
        // We need to make sure that two concurrent client
        // invocations with same primary key and same client tx
        // get the SAME EJB instance.
        // So we need to maintain exactly one copy of an EJB's state
        // per transaction.
        
        J2EETransaction current = null;
        try {
            current = (J2EETransaction) transactionManager.getTransaction();
        } catch ( SystemException ex ) {
            throw new EJBException(ex);
        }
        
        EntityContextImpl ctx = null;
	if (current != null) {
	    ActiveTxCache activeTxCache = (ActiveTxCache) 
		current.getActiveTxCache();
	    ctx = (activeTxCache == null)
		    ? null : activeTxCache.get(this, inv.ejbObject.getKey());
        inv.foundInTxCache = (ctx != null);
	}
	
	return ctx;
    }
    
    
    /**
     * Called only from afterBegin.
     * This EJB is invoked either with client's tx (in which case
     * it would already be in table), or with new tx (in which case
     * it would not be in table).
     */
    private void addIncompleteTxEJB(EntityContextImpl context) {
        J2EETransaction current = (J2EETransaction) context.getTransaction();
        if ( current == null ) {
            return;
        }
        if ( (context.getEJBObjectImpl() == null) &&
             (context.getEJBLocalObjectImpl() == null) ) {
            return;
        }

        // Its ok to add this context without checking if its already there.
	ActiveTxCache activeTxCache = (ActiveTxCache) current.getActiveTxCache();
	if (activeTxCache == null) {
	    activeTxCache = new ActiveTxCache(DEFAULT_TX_CACHE_BUCKETS);
	    current.setActiveTxCache(activeTxCache);
	}

	activeTxCache.add(context);
        
        Vector beans = containerFactory.getBeans(current);
        beans.add(context);
    }
    
    /**
     * Called from releaseContext if ejb is removed, from afterCompletion,
     * and from passivateEJB.
     */
    protected void removeIncompleteTxEJB(EntityContextImpl context,
                                         boolean updateTxBeanTable)
    {
        J2EETransaction current = (J2EETransaction) context.getTransaction();

        if (current == null) {
            return;
        }
        if ( (context.getEJBObjectImpl() == null) &&
             (context.getEJBLocalObjectImpl() == null) ) {
            return;
        }
        
	ActiveTxCache activeTxCache = (ActiveTxCache) 
	    (((J2EETransaction) current).getActiveTxCache());
	if (activeTxCache != null) {
	    activeTxCache.remove(this, context.getPrimaryKey());
	}

        if ( updateTxBeanTable ) {
            Vector beans = containerFactory.getBeans(current);
            beans.remove(context); // this is a little expensive...
        }
    }
    
    /**
     * a TimerTask class to trim a given cache of timedout entries
     */
    private class IdleBeansPassivator
        extends java.util.TimerTask
    {
        Cache cache;
        
        IdleBeansPassivator(Cache cache) {
            this.cache = cache;
        }
        
        public void run() {
            if (timerValid) {
                cache.trimExpiredEntries(Integer.MAX_VALUE);
            }
        }

	public boolean cancel() {
	    cache = null;
	    return super.cancel();
	}
    }
    
    
    // Key for INCOMPLETE_TX beans which contains ejbObject + Tx
    private class EJBTxKey {
        
        Transaction  tx; // may be null
        Object       primaryKey;
        int          pkHashCode;
        
        EJBTxKey(Object primaryKey, Transaction tx) {
            this.tx = tx;
            this.primaryKey = primaryKey;
            this.pkHashCode = primaryKey.hashCode();
        }
        
        public final int hashCode() {
            // Note: this hashcode need not be persistent across
            // activations of this process.
            // Return the primaryKey's hashCode. The Hashtable will
            // then search for the Tx through the bucket for
            // the primaryKey's hashCode.
            
            //return primaryKey.hashCode();
            return pkHashCode;
        }
        
        public final boolean equals(Object obj) {
            if ( !(obj instanceof EJBTxKey) ) {
                return false;
            }
            EJBTxKey other = (EJBTxKey) obj;
            try {
                // Note: tx may be null if the EJB is not associated with
                // an incomplete Tx.
                if ( primaryKey.equals(other.primaryKey) ) {
                    if ( (tx == null) && (other.tx == null) ) {
                        return true;
                    } else if ( (tx != null) && (other.tx != null)
                                && tx.equals(other.tx) ) {
                        return true;
                    } else  {
                        return false;
                    }
                } else {
                    return false;
                }
            } catch ( Exception ex ) {
                _logger.log(Level.FINE, "Exception in equals()", ex);
                return false;
            }
        }
        
    }
    
    protected class CacheProperties {
        
        int maxCacheSize ;
        int numberOfVictimsToSelect ;
        int cacheIdleTimeoutInSeconds ;
        String victimSelectionPolicy;
        int removalTimeoutInSeconds;
        
        public CacheProperties() {
            numberOfVictimsToSelect = 
                new Integer(ejbContainer.getCacheResizeQuantity()).intValue();
            maxCacheSize=new Integer(ejbContainer.getMaxCacheSize()).intValue();
            cacheIdleTimeoutInSeconds = 
            new Integer(ejbContainer.getCacheIdleTimeoutInSeconds()).intValue();
            victimSelectionPolicy = ejbContainer.getVictimSelectionPolicy();
            removalTimeoutInSeconds = 
            new Integer(ejbContainer.getRemovalTimeoutInSeconds()).intValue();
            
            if(beanCacheDes != null) {
                int temp = 0;
                if((temp = beanCacheDes.getResizeQuantity()) != -1) {
                    numberOfVictimsToSelect = temp;
                }
                
                if((temp = beanCacheDes.getMaxCacheSize()) != -1) {
                    maxCacheSize = temp;
                }
                
                if ((temp = beanCacheDes.getCacheIdleTimeoutInSeconds()) != -1)
                {
                    cacheIdleTimeoutInSeconds = temp;
                }
                
                if (( beanCacheDes.getVictimSelectionPolicy()) != null) {
                    victimSelectionPolicy = 
                        beanCacheDes.getVictimSelectionPolicy();
                }
                if ((temp = beanCacheDes.getRemovalTimeoutInSeconds()) != -1) {
                    removalTimeoutInSeconds = temp;
                }
            }
        }
    } //CacheProperties
    
    private class PoolProperties {
        int maxPoolSize;
        int poolIdleTimeoutInSeconds;
        //    	int maxWaitTimeInMillis;
        int poolResizeQuantity;
        int steadyPoolSize;
        
        public PoolProperties() {
            
            maxPoolSize = new Integer(ejbContainer.getMaxPoolSize()).intValue();
            poolIdleTimeoutInSeconds = new Integer(
                ejbContainer.getPoolIdleTimeoutInSeconds()).intValue();
            poolResizeQuantity = new Integer(
                ejbContainer.getPoolResizeQuantity()).intValue();
            steadyPoolSize = new Integer(
                ejbContainer.getSteadyPoolSize()).intValue();
            if(beanPoolDes != null) {
                int temp = 0;
                if (( temp = beanPoolDes.getMaxPoolSize()) != -1) {
                    maxPoolSize = temp;
                }
                if (( temp = beanPoolDes.getPoolIdleTimeoutInSeconds()) != -1) {
                    poolIdleTimeoutInSeconds = temp;
                }
                if (( temp = beanPoolDes.getPoolResizeQuantity()) != -1) {
                    poolResizeQuantity = temp;
                }
                if (( temp = beanPoolDes.getSteadyPoolSize()) != -1) {
                    steadyPoolSize = temp;
                }
            }
        }
    } //PoolProperties
    
    
    boolean isIdentical(EJBObjectImpl ejbObjImpl, EJBObject other)
        throws RemoteException
    {
        if ( other == ejbObjImpl.getStub() ) {
            return true;
        } else {
            try {
                // EJBObject may be a remote object.
                // Compare homes. See EJB2.0 spec section 9.8.
                if ( !protocolMgr.isIdentical(ejbHomeStub,
                                              other.getEJBHome()))
                    return false;
                
                // Compare primary keys.
                if (!ejbObjImpl.getPrimaryKey().equals(other.getPrimaryKey())) {
                    return false;
                }
                
                return true;
            } catch ( Exception ex ) {
                _logger.log(Level.INFO, "ejb.ejb_comparison_exception",
                            logParams);
                _logger.log(Level.INFO, "", ex);
                throw new RemoteException("Exception in isIdentical()", ex);
            }
        }
    }
    
    
    protected void callEJBLoad(EntityBean ejb, EntityContextImpl context,
                               boolean activeTx)
        throws Exception
    {
        try {
            context.setInEjbLoad(true);
            ejb.ejbLoad();
            // Note: no need to do context.setDirty(false) because ejbLoad is
            // called immediately before a business method.
        } catch(Exception e) {
            throw e;
        } finally {
            context.setInEjbLoad(false);
        }
    }
    
    protected void callEJBStore(EntityBean ejb, EntityContextImpl context)
        throws Exception
    {
        try {
            context.setInEjbStore(true);
            ejb.ejbStore();
        } finally {
            context.setInEjbStore(false);
            context.setDirty(false); // bean's state is in sync with DB
        }
    }
    
    protected void callEJBRemove(EntityBean ejb, EntityContextImpl context)
        throws Exception
    {
        Exception exc = null;
        try {
            context.setInEjbRemove(true);
            ejb.ejbRemove();
        } catch ( Exception ex ) {
            exc = ex;
            throw ex;
        } finally {
            context.setInEjbRemove(false);
            context.setDirty(false); // bean is removed so doesnt need ejbStore
            if ( AppVerification.doInstrument() ) {
                AppVerification.getInstrumentLogger().doInstrumentForEjb(
                ejbDescriptor, ejbRemoveMethod, exc);
            }
        }
    }
     
    void doTimerInvocationInit(Invocation inv, RuntimeTimerState timerState)
        throws Exception
    {
        Object primaryKey = timerState.getTimedObjectPrimaryKey();
        if( isRemote ) {
            inv.ejbObject = internalGetEJBObjectImpl(primaryKey, null);
            inv.isLocal = false;
        } else {
            inv.ejbObject = internalGetEJBLocalObjectImpl(primaryKey);
            inv.isLocal = true;
        }
        if( inv.ejbObject == null ) {
            throw new Exception("Timed object identity (" + primaryKey +
                " ) no longer exists " );
        }
    }
    
    public void undeploy() {
        
        //Change the container state to ensure that all new invocations will be rejected
        super.setUndeployedState();
        
        String ejbName = ejbDescriptor.getName();
        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE,"[EntityContainer]: Undeploying " + ejbName +
                " ...");
        } 
        // destroy all EJBObject refs
        
        try {
            Iterator elements = ejbObjectStore.values();
            while ( elements.hasNext() ) {
                EJBObjectImpl ejbObjImpl = (EJBObjectImpl) elements.next();
                try {
                    if ( isRemote ) {
                        remoteHomeRefFactory.destroyReference
                            (ejbObjImpl.getStub(), ejbObjImpl.getEJBObject());
                                                    
                    }
                } catch ( Exception ex ) {
                    _logger.log(Level.FINE, "Exception in undeploy()", ex);
                }
            }
            
            ejbObjectStore.destroy();  //store must set the listern to null
            ejbObjectStore = null;
            
            ejbLocalObjectStore.destroy(); //store must set the listern to null
            ejbLocalObjectStore = null;
            
            // destroy all EJB instances in readyStore
            destroyReadyStoreOnUndeploy(); //cache must set the listern to null
            
            // destroy all EJB instances in ActiveTxCache
	    /*
            synchronized ( incompleteTxStore ) {
                Iterator beans = incompleteTxStore.values();
                while ( beans.hasNext() ) {
                    EJBContextImpl ctx = (EJBContextImpl)beans.next();
                    transactionManager.ejbDestroyed(ctx);
                }
            }
	    */
            
            entityCtxPool.close();
            
            // stops the idle bean passivator and also removes the link
            // to the cache; note that cancel() method of timertask
            // does not remove the task from the timer's queue
            if (idleBeansPassivator != null) {
                try {
                    idleBeansPassivator.cancel();
                } catch (Exception e) {
                    _logger.log(Level.FINE,
                                "[EntityContainer] cancelTimerTask: ", e);
                }
                this.idleBeansPassivator.cache  = null;
            }
	    cancelTimerTasks();
        }
        finally {
            
            super.undeploy();
            
            // helps garbage collection
            this.ejbObjectStore         = null;
            this.ejbLocalObjectStore    = null;
            this.passivationCandidates  = null;
            this.readyStore             = null;
            this.entityCtxPool          = null;
            this.iased                  = null;
            this.beanCacheDes           = null;
            this.beanPoolDes            = null;
            this.svr                    = null;
            this.cfg                    = null;
            this.ejbContainer           = null;
            this.cacheProp              = null;
            this.poolProp               = null;
            this.asyncTaskSemaphore     = null;
            this.idleBeansPassivator    = null;
            
        }
        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE," [EntityContainer]: Successfully Undeployed " +
                ejbName);
        }
    }
    
    protected void afterNewlyActivated(EntityContextImpl context) {
        //Noop for EntityContainer
    }
    
    protected EntityContextImpl createEntityContextInstance(EntityBean ejb,
            EntityContainer entityContainer)
    {
        return new EntityContextImpl(ejb, entityContainer);
    }
    
    private class EntityContextFactory
        implements ObjectFactory
    {
        private EntityContainer entityContainer;
        
        public EntityContextFactory(EntityContainer entityContainer) {
            this.entityContainer = entityContainer;
        }
        
        public Object create(Object param) {
            EntityContextImpl entityCtx = null;
            ComponentInvocation ci = null;
            try {
                // Create new bean. The constructor is not allowed
                // to do a JNDI access (see EJB2.0 section 10.5.5),
                // so no need to call invocationMgr before instantiation.
                EntityBean ejb = (EntityBean) ejbClass.newInstance();
                
                // create EntityContext
                entityCtx = createEntityContextInstance(ejb, entityContainer);

                ci = new ComponentInvocation(ejb, entityContainer,entityCtx);
                invocationManager.preInvoke(ci);
                
                // setEntityContext may be called with or without a Tx
                // spec 9.4.2
                ejb.setEntityContext(entityCtx);

                // NOTE : Annotations are *not* supported for entity beans
                // so we do not invoke the injection manager for this instance.

            } catch (Exception ex ) {
                throw new EJBException("Could not create Entity EJB", ex);
            } finally {
                if ( ci != null ) {
                    invocationManager.postInvoke(ci);
                }
            }
            
            entityCtx.touch();
            return entityCtx;
        }
        
        
        public void destroy(Object object) {
            if (object == null) {
                //means that this is called through forceDestroyBean
                //So no need to anything, as we cannot call unsetEntityCtx etc..
                return;
            }
            
            EntityContextImpl context = (EntityContextImpl) object;
            EntityBean ejb = (EntityBean)context.getEJB();
            if (context.getState() != DESTROYED) {
                ComponentInvocation ci = new ComponentInvocation(ejb, entityContainer, context);
                invocationManager.preInvoke(ci);
                
                // kill the bean and let it be GC'ed
                try {
                    synchronized ( context ) {
                        context.setEJBLocalObjectImpl(null);
                        context.setEJBObjectImpl(null);
                        context.setEJBStub(null);
                        context.setState(DESTROYED);
                        //context.cacheEntry = null;
                        context.setInUnsetEntityContext(true);
                        
                        try {
                            ejb.unsetEntityContext();
                        } catch ( Exception ex ) {
                            _logger.log(Level.FINE, 
                                "Exception in ejb.unsetEntityContext()", ex);
                        }
                        
                        // tell the TM to release resources held by the bean
                        transactionManager.ejbDestroyed(context);
                    }
                } finally {
                    invocationManager.postInvoke(ci);
                }
            } else {
                //Called from forceDestroyBean
                try {
                    synchronized ( context ) {
                        context.setEJBLocalObjectImpl(null);
                        context.setEJBObjectImpl(null);
                        context.setEJBStub(null);
                        context.setState(DESTROYED);
                        //context.cacheEntry = null;
                        
                        // mark the context's transaction for rollback
                        Transaction tx = context.getTransaction();
                        if ( tx != null && tx.getStatus() != 
                            Status.STATUS_NO_TRANSACTION ) {
                            context.getTransaction().setRollbackOnly();
                        }
                        
                        // tell the TM to release resources held by the bean
                        transactionManager.ejbDestroyed(context);
                        
                    }
                } catch (Exception ex) {
                    _logger.log(Level.FINE, "Exception in destroy()", ex);
                }
            }
        }
        
    } //class EntityContextFactory
    
    private static void appendStat(StringBuffer sbuf, String header, Map map) {
        
        sbuf.append("\n\t[").append(header).append(": ");
        if (map != null) {
            Iterator iter = map.keySet().iterator();
            
            while (iter.hasNext()) {
                String name = (String)iter.next();
                sbuf.append(name).append("=").append(map.get(name))
		    .append("; ");
            }
        } else {
            sbuf.append("NONE");
        }
        sbuf.append("]");
    }
    
    private void createCaches() throws Exception {

        cacheProp = new CacheProperties();
        
        int cacheSize = cacheProp.maxCacheSize;
        int numberOfVictimsToSelect = cacheProp.numberOfVictimsToSelect;
        float loadFactor = DEFAULT_LOAD_FACTOR;
        idleTimeout = cacheProp.cacheIdleTimeoutInSeconds * 1000;
        
        createReadyStore(cacheSize, numberOfVictimsToSelect, loadFactor, 
                         idleTimeout);
        
        createEJBObjectStores(cacheSize, numberOfVictimsToSelect, 
                              idleTimeout);
        
    }
    
    protected void createReadyStore(int cacheSize, int numberOfVictimsToSelect,
            float loadFactor, long idleTimeout) throws Exception
    {
        idleTimeout = (idleTimeout <= 0) ? -1 : idleTimeout;
        if (cacheSize <= 0 && idleTimeout <= 0) {
            readyStore = new BaseCache();
            cacheSize = DEFAULT_CACHE_SIZE;
            readyStore.init(cacheSize, loadFactor, null);
        } else {
            cacheSize = (cacheSize <= 0) ? DEFAULT_CACHE_SIZE : cacheSize;
            LruCache lru = new LruCache(DEFAULT_CACHE_SIZE);
            if (numberOfVictimsToSelect >= 0) {
                loadFactor = (float) (1.0 - (1.0 *
                                             numberOfVictimsToSelect/cacheSize));
            }
            lru.init(cacheSize, idleTimeout, loadFactor, null);
            readyStore = lru;
            readyStore.addCacheListener(this);
        }
        
        if (idleTimeout > 0) {
            idleBeansPassivator = setupIdleBeansPassivator(readyStore);
        }
    }
    
    protected void createEJBObjectStores(int cacheSize,
        int numberOfVictimsToSelect, long idleTimeout) throws Exception {
        
        EJBObjectCache lru = null;
        String ejbName = ejbDescriptor.getName();
        idleTimeout = (idleTimeout <= 0) ? -1 : idleTimeout;
        
        if (cacheSize <= 0 && idleTimeout <= 0) {
            ejbObjectStore = new UnboundedEJBObjectCache(ejbName);
            ejbObjectStore.init(DEFAULT_CACHE_SIZE, numberOfVictimsToSelect, 0L, 
                (float)1.0, null);
            
            ejbLocalObjectStore = new UnboundedEJBObjectCache(ejbName);
            ejbLocalObjectStore.init(DEFAULT_CACHE_SIZE,
                numberOfVictimsToSelect, 0L, (float)1.0, null);
        } else {
            cacheSize = (cacheSize <= 0) ? DEFAULT_CACHE_SIZE : cacheSize;
            ejbObjectStore = new FIFOEJBObjectCache(ejbName);
            ejbObjectStore.init(cacheSize, numberOfVictimsToSelect, idleTimeout,
                (float)1.0, null);
            ejbObjectStore.setEJBObjectCacheListener(
                new EJBObjectCacheVictimHandler());
            
            ejbLocalObjectStore = new FIFOEJBObjectCache(ejbName);
            ejbLocalObjectStore.init(cacheSize, numberOfVictimsToSelect, 
                idleTimeout, (float)1.0, null);
            ejbLocalObjectStore.setEJBObjectCacheListener(
                new LocalEJBObjectCacheVictimHandler());
        }
        
        if (idleTimeout > 0) {
            idleEJBObjectPassivator = setupIdleBeansPassivator(ejbObjectStore);
            idleLocalEJBObjectPassivator = 
                setupIdleBeansPassivator(ejbLocalObjectStore);
        }
    }
    
    protected EntityContextImpl getReadyEJB(Invocation inv) {
        Object primaryKey = inv.ejbObject.getKey();
        EntityContextImpl context = null;
        // Try and get an EJB instance for this primaryKey from the
        // readyStore
        context = (EntityContextImpl)readyStore.remove(primaryKey);
        if (context == null || context.getState() != READY) {
            context = activateEJBFromPool(primaryKey, inv);
        }
        return context;
    } //getReadyEJB(inv)
    
    protected void addReadyEJB(EntityContextImpl context) {
        // add to the cache (can have multiple instances of beans per key)
        Object primaryKey = context.getPrimaryKey();
        context.setState(READY);
        readyStore.add(primaryKey, context);
    }
    
    protected void destroyReadyStoreOnUndeploy() {
        if (readyStore == null) {
            return;
        }
        
        // destroy all EJB instances in readyStore
        synchronized ( readyStore ) {
            
            Iterator beans = readyStore.values();
            while ( beans.hasNext() ) {
                EJBContextImpl ctx = (EJBContextImpl)beans.next();
                transactionManager.ejbDestroyed(ctx);
            }
        }
        readyStore.destroy();
        readyStore = null;
    }
    
    protected void removeContextFromReadyStore(Object primaryKey, 
        EntityContextImpl context) {
        readyStore.remove(primaryKey, context);
    }
    
    protected void doFlush( Invocation inv ) {
        if( !inv.invocationInfo.flushEnabled ||
            inv.exception != null )  {
            return;
        }

        if( !isContainerManagedPers ) {
            //NEED TO INTERNATIONALIZE THIS WARNING MESSAGE
            _logger.log(Level.WARNING, 
                "Cannot turn on flush-enabled-at-end-of-method for a bean with Bean Managed Persistence");
            return;
        }

        InvocationInfo invInfo = inv.invocationInfo;
        Throwable exception = inv.exception;
        EntityContextImpl context = (EntityContextImpl)inv.context;
        Transaction tx = context.getTransaction();

        //Since postInvoke(Tx) has been called before the releaseContext, the transaction
        //could be committed or rolledback. In that case there is no point to call flush
        if( tx == null) {
            return;
        }

        //return w/o doing anything if the transaction is marked for rollback 
        try {
            if( context.getRollbackOnly() ) {
                return;
            }
        } catch( Throwable ex ) {
            _logger.log(Level.WARNING, "Exception when calling getRollbackOnly", ex);
            return;
        }

        if ( invInfo.isBusinessMethod ) {
            try {
                //Store the state of all the beans that are part of this transaction
                storeAllBeansInTx( tx );
            } catch( Throwable ex ) {
                inv.exception = ex;
                return;
            }
        }

        try {
            BeanStateSynchronization pmcontract = (BeanStateSynchronization)inv.ejb;
            pmcontract.ejb__flush();
        } catch( Throwable ex ) {
            //check the type of the method and create the corresponding exception
            if( invInfo.startsWithCreate ) {
                CreateException ejbEx = new CreateException();
                ejbEx.initCause(ex);
                inv.exception = ejbEx;
            } else if( invInfo.startsWithRemove ) {
                RemoveException ejbEx = new RemoveException();
                ejbEx.initCause(ex);
                inv.exception = ejbEx;
            } else {
                EJBException ejbEx = new EJBException();
                ejbEx.initCause(ex);
                inv.exception = ejbEx;
            }

            return;
        }

    } //doFlush(...)

    private void storeAllBeansInTx(Transaction tx) {
        // Call ejbStore on all entitybeans in tx for all EntityContainers
        Vector beans = containerFactory.getBeans(tx);
        if ( beans.isEmpty() ) {
            // No beans associated with the current transaction
            return;
        }

        Iterator itr = beans.iterator();
        while ( itr.hasNext() ) {
            EntityContextImpl ctx = (EntityContextImpl)itr.next();
            if ( ctx.getState() == INCOMPLETE_TX && ctx.isDirty() ) {
                // Call ejbStore on the bean
                // Note: the bean may be in a different container instance
                EntityContainer cont = (EntityContainer)ctx.getContainer();
                cont.enlistResourcesAndStore(ctx);
            }
        }
    }


    protected class LocalEJBObjectCacheVictimHandler
        implements EJBObjectCacheListener, 
            com.sun.enterprise.util.threadpool.Servicable
    {
        
        protected Object lock = new Object();
        protected boolean addedTask = false;
        protected ArrayList keys = new ArrayList(16);
        
        protected LocalEJBObjectCacheVictimHandler() {
        }
        
        //EJBObjectCacheListener interface
        public void handleOverflow(Object key) {
            doCleanup(key);
        }
        
        public void handleBatchOverflow(ArrayList paramKeys) {
            int size = paramKeys.size();
            synchronized (lock) {
                for (int i=0; i<size; i++) {
                    keys.add(paramKeys.get(i));
                }
                if (addedTask == true) {
                    return;
                }
                addedTask = true;
            }
            
            try {
                ContainerWorkPool.addLast(this);
            } catch (Exception ex) {
                _logger.log(Level.WARNING, "ejb.entity_add_async_task", ex);
                synchronized (lock) {
                    addedTask = false;
                }
            }
        }
        
        
        public void prolog() { }
        
        public void epilog() { }
        
        public void service() { run(); }
        
        public void run() {
            final Thread currentThread = Thread.currentThread();
            final ClassLoader previousClassLoader = 
                currentThread.getContextClassLoader();
            final ClassLoader myClassLoader = loader;
            
            try {
            //We need to set the context class loader for this (deamon) thread!!
                if(System.getSecurityManager() == null) {
                    currentThread.setContextClassLoader(loader);
                } else {
                    java.security.AccessController.doPrivileged(
                            new java.security.PrivilegedAction() {
                        public java.lang.Object run() {
                            currentThread.setContextClassLoader(loader);
                            return null;
                        }
                    }
                    );
                }
                
                ArrayList localKeys = null;
                do {
                    synchronized (lock) {
                        int size = keys.size();
                        if (size == 0) {
                            return;
                        }
                        
                        localKeys = keys;
                        keys = new ArrayList(16);
                    }
                    
                    int maxIndex = localKeys.size();
                    for (int i=0; i<maxIndex; i++) {
                        doCleanup(localKeys.get(i));
                    }
                } while (true);
                
            } catch (Throwable th) {
                th.printStackTrace();
            } finally {
                synchronized (lock) {
                    addedTask = false;
                }
                if(System.getSecurityManager() == null) {
                    currentThread.setContextClassLoader(previousClassLoader);
                } else {
                    java.security.AccessController.doPrivileged(
                            new java.security.PrivilegedAction() {
                        public java.lang.Object run() {
                            currentThread.setContextClassLoader(previousClassLoader);
                            return null;
                        }
                    }
                    );
                }
            }
        }
        
        protected void doCleanup(Object key) {
            ejbLocalObjectStore.remove(key, false);
        }
    } //LocalEJBObjectCacheVictimHandler{}
    
    protected class EJBObjectCacheVictimHandler
        extends LocalEJBObjectCacheVictimHandler
    {
        
        protected EJBObjectCacheVictimHandler() {
        }
        
        protected void doCleanup(Object key) {
            removeEJBObjectFromStore(key, false);
        }
    } //EJBObjectCacheVictimHandler{}
    


    class EntityCacheStatsProvider
	implements com.sun.ejb.spi.stats.EJBCacheStatsProvider
    {
	private BaseCache cache;
	private String configData;
	private int confMaxCacheSize;

	EntityCacheStatsProvider(BaseCache cache, int maxCacheSize) {
	    this.cache = cache;
	    this.confMaxCacheSize = maxCacheSize;
	}
    
	public int getCacheHits() {
	    return ((Integer) cache.getStatByName(
			Constants.STAT_BASECACHE_HIT_COUNT)).intValue();
	}
    
	public int getCacheMisses() {
	    return ((Integer) cache.getStatByName(
			Constants.STAT_BASECACHE_MISS_COUNT)).intValue();
	}
    
	public int getNumBeansInCache() {
	    return cache.getEntryCount();
	}
    
	public int getNumExpiredSessionsRemoved() {
	    return 0;
	}
    
	public int getNumPassivationErrors() {
	    return totalPassivationErrors;
	}
    
	public int getNumPassivations() {
	    return totalPassivations;
	}
    
	public int getNumPassivationSuccess() {
	    return totalPassivations - totalPassivationErrors;
	}

	public int getMaxCacheSize() {
	    return this.confMaxCacheSize;
	}

    	public void appendStats(StringBuffer sbuf) {
	    sbuf.append("[Cache: ")
		.append("Size=").append(getNumBeansInCache()).append("; ")
		.append("HitCount=").append(getCacheHits()).append("; ")
		.append("MissCount=").append(getCacheMisses()).append("; ")
		.append("Passivations=").append(getNumPassivations()).append("; ");
	    if (configData != null) {
		sbuf.append(configData);
	    }
	    sbuf.append("]");
	}

    }//End of class EntityCacheStatsProvider
    
}

//No need to sync...
class ActiveTxCache {

    private static Logger _logger =
        LogDomains.getLogger(LogDomains.EJB_LOGGER);

    private EntityContextImpl[]	    buckets;
    private int			    bucketMask;

    ActiveTxCache(int numBuckets) {
	this.bucketMask = numBuckets - 1;
	initialize();
    }

    EntityContextImpl get(BaseContainer container, Object pk) {
	int pkHashCode = pk.hashCode();
	int index = getIndex(pkHashCode);

	EntityContextImpl ctx = buckets[index];
	while (ctx != null) {
	    if (ctx.doesMatch(container, pkHashCode, pk)) {
		return ctx;
	    }
	    ctx = ctx._getNext();
	}

	return null;
    }

    void add(EntityContextImpl ctx) {
	ctx.cachePrimaryKey();
	int index = getIndex(ctx._getPKHashCode());
	ctx._setNext(buckets[index]);
	buckets[index] = ctx;
    }

    EntityContextImpl remove(BaseContainer container, Object pk) {
	int pkHashCode = pk.hashCode();
	int index = getIndex(pkHashCode);

	EntityContextImpl ctx = buckets[index];
	for (EntityContextImpl prev = null; ctx != null; ctx = ctx._getNext()) {
	    if (ctx.doesMatch(container, pkHashCode, pk)) {
		if (prev == null) {
		    buckets[index] = ctx._getNext();
		} else {
		    prev._setNext(ctx._getNext());
		}
		ctx._setNext(null);
		break;
	    }
	    prev = ctx;
	}

	return ctx;
    }

    //One remove method is enough
    EntityContextImpl remove(Object pk, EntityContextImpl existingCtx) {
	int pkHashCode = pk.hashCode();
	int index = getIndex(pkHashCode);

	EntityContextImpl ctx = buckets[index];
	for (EntityContextImpl prev = null; ctx != null; ctx = ctx._getNext()) {
	    if (ctx == existingCtx) {
		if (prev == null) {
		    buckets[index] = ctx._getNext();
		} else {
		    prev._setNext(ctx._getNext());
		}
		ctx._setNext(null);
		break;
	    }
	    prev = ctx;
	}

	return ctx;
    }

    private void initialize() {
	buckets = new EntityContextImpl[bucketMask+1];
    }

    private final int getIndex(int hashCode) {
	return (hashCode & bucketMask);
    }

}