All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.persistence.ejb.entitybean.container.EntityContainer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation.
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.persistence.ejb.entitybean.container;

import com.sun.appserv.util.cache.BaseCache;
import com.sun.appserv.util.cache.Cache;
import com.sun.appserv.util.cache.CacheListener;
import com.sun.appserv.util.cache.Constants;
import com.sun.appserv.util.cache.LruCache;
import com.sun.ejb.ComponentContext;
import com.sun.ejb.EJBUtils;
import com.sun.ejb.EjbInvocation;
import com.sun.ejb.InvocationInfo;
import com.sun.ejb.containers.BaseContainer;
import com.sun.ejb.containers.EJBContextImpl;
import com.sun.ejb.containers.EJBContextImpl.BeanState;
import com.sun.ejb.containers.EJBHomeInvocationHandler;
import com.sun.ejb.containers.EJBLocalHomeInvocationHandler;
import com.sun.ejb.containers.EJBLocalObjectImpl;
import com.sun.ejb.containers.EJBLocalRemoteObject;
import com.sun.ejb.containers.EJBObjectImpl;
import com.sun.ejb.containers.util.pool.AbstractPool;
import com.sun.ejb.containers.util.pool.NonBlockingPool;
import com.sun.ejb.containers.util.pool.ObjectFactory;
import com.sun.ejb.monitoring.probes.EjbCacheProbeProvider;
import com.sun.ejb.monitoring.stats.EjbCacheStatsProvider;
import com.sun.ejb.monitoring.stats.EjbCacheStatsProviderDelegate;
import com.sun.ejb.monitoring.stats.EjbMonitoringStatsProvider;
import com.sun.ejb.monitoring.stats.EjbMonitoringUtils;
import com.sun.ejb.monitoring.stats.EjbPoolStatsProvider;
import com.sun.ejb.portable.EJBMetaDataImpl;
import com.sun.ejb.portable.ObjrefEnumeration;
import com.sun.ejb.spi.container.BeanStateSynchronization;
import com.sun.enterprise.admin.monitor.callflow.ComponentType;
import com.sun.enterprise.deployment.MethodDescriptor;
import com.sun.enterprise.deployment.runtime.BeanPoolDescriptor;
import com.sun.enterprise.security.SecurityManager;
import com.sun.enterprise.transaction.api.JavaEETransaction;
import com.sun.logging.LogDomains;

import jakarta.ejb.CreateException;
import jakarta.ejb.EJBContext;
import jakarta.ejb.EJBException;
import jakarta.ejb.EJBHome;
import jakarta.ejb.EJBLocalHome;
import jakarta.ejb.EJBLocalObject;
import jakarta.ejb.EJBObject;
import jakarta.ejb.EntityBean;
import jakarta.ejb.FinderException;
import jakarta.ejb.NoSuchEntityException;
import jakarta.ejb.NoSuchObjectLocalException;
import jakarta.ejb.RemoveException;
import jakarta.transaction.Status;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.glassfish.api.invocation.ComponentInvocation;
import org.glassfish.ejb.config.EjbContainer;
import org.glassfish.ejb.deployment.descriptor.EjbCMPEntityDescriptor;
import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
import org.glassfish.ejb.deployment.descriptor.EjbEntityDescriptor;
import org.glassfish.ejb.deployment.descriptor.runtime.BeanCacheDescriptor;
import org.glassfish.ejb.deployment.descriptor.runtime.IASEjbExtraDescriptors;
import org.glassfish.flashlight.provider.ProbeProviderFactory;
import org.glassfish.persistence.ejb.entitybean.container.cache.EJBObjectCache;
import org.glassfish.persistence.ejb.entitybean.container.cache.EJBObjectCacheListener;
import org.glassfish.persistence.ejb.entitybean.container.cache.FIFOEJBObjectCache;
import org.glassfish.persistence.ejb.entitybean.container.cache.UnboundedEJBObjectCache;
import org.glassfish.persistence.ejb.entitybean.container.spi.ReadOnlyEJBHome;
import org.glassfish.persistence.ejb.entitybean.container.spi.ReadOnlyEJBLocalHome;
import org.glassfish.persistence.ejb.entitybean.container.stats.EntityBeanStatsProvider;

/**
 * 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.
 * 

* 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 *

* 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): *

    *
  • 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). *
  • 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. *
  • 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. *
  • 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. *
  • 5. DESTROYED : does not exist. *
* 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 { private final ThreadLocal ejbServant = new ThreadLocal() { @Override protected Object initialValue() { return null; } }; static final Logger _logger = LogDomains.getLogger(EntityContainer.class, LogDomains.EJB_LOGGER); 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 EjbContainer ejbContainer; 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, SecurityManager sm) throws Exception { this(ContainerType.ENTITY, desc, loader, sm); } protected EntityContainer(ContainerType containerType, EjbDescriptor desc, ClassLoader loader, SecurityManager sm) throws Exception { super(containerType, desc, loader, sm); EjbEntityDescriptor ed = (EjbEntityDescriptor) desc; isReentrant = ed.isReentrant(); if (ed.getPersistenceType().equals(EjbEntityDescriptor.BEAN_PERSISTENCE)) { isContainerManagedPers = false; } else { isContainerManagedPers = true; } isBeanManagedTran = false; iased = ed.getIASEjbExtraDescriptors(); if (iased != null) { beanCacheDes = iased.getBeanCache(); beanPoolDes = iased.getBeanPool(); } ejbContainer = ejbContainerUtilImpl.getEjbContainer(); // TODO super.setMonitorOn(ejbContainer.isMonitoringEnabled()); createCaches(); super.createCallFlowAgent(isContainerManagedPers ? ComponentType.CMP : ComponentType.BMP); _logger.log(Level.FINE, "[EntityContainer] Created EntityContainer: {0}", containerInfo); } @Override 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); } @Override protected void setEJBMetaData() throws Exception { EjbEntityDescriptor ed = (EjbEntityDescriptor) ejbDescriptor; Class primaryKeyClass = loader.loadClass(ed.getPrimaryKeyClassName()); metadata = new EJBMetaDataImpl(ejbHomeStub, homeIntf, remoteIntf, primaryKeyClass); } @Override protected void validateTxAttr(MethodDescriptor md, int txAttr) throws EJBException { super.validateTxAttr(md, txAttr); // For EJB2.0 CMP EntityBeans, container is only required to support // REQUIRED/REQUIRES_NEW/MANDATORY, see EJB2.0 section 17.4.1. if (((EjbEntityDescriptor) ejbDescriptor).getPersistenceType().equals(EjbEntityDescriptor.CONTAINER_PERSISTENCE)) { EjbCMPEntityDescriptor e = (EjbCMPEntityDescriptor) ejbDescriptor; if (!e.getIASEjbExtraDescriptors().isIsReadOnlyBean() && e.isEJB20()) { if (txAttr != TX_REQUIRED && txAttr != TX_REQUIRES_NEW && txAttr != TX_MANDATORY) { throw new EJBException("Transaction attribute for EJB2.0 CMP EntityBeans" + " must be Required/RequiresNew/Mandatory"); } } } } @Override protected void adjustHomeTargetMethodInfo(InvocationInfo invInfo, String methodName, Class[] paramTypes) throws NoSuchMethodException { if (invInfo.startsWithCreate) { String extraCreateChars = methodName.substring("create".length()); invInfo.targetMethod2 = ejbClass.getMethod("ejbPostCreate" + extraCreateChars, paramTypes); } } @Override protected EJBHomeInvocationHandler getEJBHomeInvocationHandler(Class homeIntfClass) throws Exception { return new EntityBeanHomeImpl(ejbDescriptor, homeIntfClass); } @Override protected EJBLocalHomeInvocationHandler getEJBLocalHomeInvocationHandler(Class homeIntfClass) throws Exception { return new EntityBeanLocalHomeImpl(ejbDescriptor, homeIntfClass); } /** * 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); ejbContainerUtilImpl.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; } @Override 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. */ @Override protected void initializeHome() throws Exception { ObjectFactory entityCtxFactory = new EntityContextFactory(this); poolProp = new PoolProperties(this); super.initializeHome(); entityCtxPool = new NonBlockingPool(getContainerId(), ejbDescriptor.getName(), entityCtxFactory, poolProp.steadyPoolSize, poolProp.poolResizeQuantity, poolProp.maxPoolSize, poolProp.poolIdleTimeoutInSeconds, loader); registerMonitorableComponents(); } @Override protected void registerMonitorableComponents() { super.registerMonitorableComponents(); if (readyStore != null) { int confMaxCacheSize = cacheProp.maxCacheSize; if (confMaxCacheSize <= 0) { confMaxCacheSize = Integer.MAX_VALUE; } try { ProbeProviderFactory probeFactory = ejbContainerUtilImpl.getProbeProviderFactory(); String invokerId = EjbMonitoringUtils.getInvokerId(containerInfo.appName, containerInfo.modName, containerInfo.ejbName); cacheProbeNotifier = probeFactory.getProbeProvider(EjbCacheProbeProvider.class, invokerId); if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Got ProbeProvider: " + cacheProbeNotifier.getClass().getName()); } } catch (Exception ex) { cacheProbeNotifier = new EjbCacheProbeProvider(); if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Error getting the EjbCacheProbeProvider"); } } this.cacheStatsProvider = new EntityCacheStatsProvider((BaseCache) readyStore, confMaxCacheSize); // registryMediator.registerProvider(cacheStatsProvider); cacheProbeListener = new EjbCacheStatsProvider(cacheStatsProvider, getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName); cacheProbeListener.register(); } poolProbeListener = new EjbPoolStatsProvider(entityCtxPool, getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName); poolProbeListener.register(); _logger.log(Level.FINE, "[Entity Container] registered monitorable"); } @Override protected EjbMonitoringStatsProvider getMonitoringStatsProvider(String appName, String modName, String ejbName) { return new EntityBeanStatsProvider(this, getContainerId(), appName, modName, ejbName); } @Override public void onReady() { } /** * TODO 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(); } /** * Implementation of BaseContainer method. This is never called. */ @Override protected EJBObjectImpl createEJBObjectImpl() throws CreateException, RemoteException { throw new EJBException("INTERNAL ERROR: EntityContainer.createEJBObject() called"); } @Override protected EJBLocalObjectImpl createEJBLocalObjectImpl() throws CreateException { throw new EJBException("INTERNAL ERROR: EntityContainer.createEJBLocalObjectImpl() called"); } /** * Called when a remote EjbInvocation arrives for an EJB. */ @Override protected 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. */ @Override protected 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. */ @Override protected ComponentContext _getContext(EjbInvocation inv) { 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(BeanState.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.isInState(BeanState.INVOKING) && !isReentrant) { throw new EJBException("EJB is already executing another request"); } if (context.isInState(BeanState.POOLED) || context.isInState(BeanState.DESTROYED)) { // somehow a concurrent thread must have changed state. // this is an internal error. throw new EJBException("Internal error: unknown EJB state"); } context.setState(BeanState.INVOKING); } context.setLastTransactionStatus(-1); context.incrementCalls(); // A business method may modify the bean's state context.setDirty(true); return context; } protected boolean willInvokeWithClientTx(EjbInvocation 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. */ @Override public void releaseContext(EjbInvocation inv) { EntityContextImpl context = (EntityContextImpl) inv.context; boolean decrementedCalls = false; // End of IAS 4661771 if (context.isInState(BeanState.DESTROYED)) { return; } try { if (context.hasReentrantCall()) { // For biz->biz or postCreate->biz, the bean instance will // remain in the incomplete-tx table. if (containerStateManager.isRemovedEJBObject(inv)) { // biz -> remove case (biz method invoked reentrant remove) // Remove from IncompleteTx table, to prevent further // reentrant calls. removeIncompleteTxEJB(context, true); containerStateManager.disconnectContext(context); } else { if (context.isInState(BeanState.INVOKING)) { doFlush(inv); } } // Note: at this point context.getState() is INVOKING. } else if (containerStateManager.isNullEJBObject(context) && containerStateManager.isNullEJBLocalObject(context)) { // 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(BeanState.INCOMPLETE_TX); } } else if (containerStateManager.isRemovedEJBObject(inv)) { // 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 containerStateManager.markObjectRemoved(context, 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(BeanState.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(BeanState.INCOMPLETE_TX); doFlush(inv); } } catch (Exception ex) { _logger.log(Level.FINE, "entitybean.container.release_context_exception", containerInfo); _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(EjbInvocation inv, EntityContextImpl context) { ejbProbeNotifier.ejbBeanCreatedEvent(getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName); } /** * 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 */ @Override public void postCreate(EjbInvocation inv, Object primaryKey) throws CreateException { if (primaryKey == null) { throw new EJBException("Null primary key returned by ejbCreate method"); } if ((isRemote) && (!inv.isLocal)) { // remote EjbInvocation: create EJBObject EJBObjectImpl ejbObjImpl = internalGetEJBObjectImpl(primaryKey, null, true); // associate the context with the ejbObject containerStateManager.attachObject(inv, (EJBContextImpl) inv.context, ejbObjImpl, null); } if (isLocal) { // create EJBLocalObject irrespective of local/remote EjbInvocation // this is necessary to make EntityContext.getPrimaryKey and // EntityContext.getEJBObject work. EJBLocalObjectImpl localObjImpl = internalGetEJBLocalObjectImpl(primaryKey, true); // associate the context with the ejbLocalObject containerStateManager.attachObject(inv, (EJBContextImpl) inv.context, null, localObjImpl); } EntityContextImpl context = (EntityContextImpl) inv.context; 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 @Override protected Object invokeFindByPrimaryKey(Method method, EjbInvocation inv, Object[] args) throws Throwable { Object pKeys = super.invokeTargetBeanMethod(method, inv, inv.ejb, args); return postFind(inv, pKeys, null); } @Override protected void authorizeLocalGetPrimaryKey(EJBLocalRemoteObject ejbObj) throws EJBException { authorizeLocalMethod(BaseContainer.EJBLocalObject_getPrimaryKey); checkExists(ejbObj); } @Override protected void authorizeRemoteGetPrimaryKey(EJBLocalRemoteObject ejbObj) throws RemoteException { authorizeRemoteMethod(BaseContainer.EJBObject_getPrimaryKey); } /** * Called from getContext before the ejb.ejbFind* is called */ protected void preFind(EjbInvocation 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 */ @Override public void preSelect() throws jakarta.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 */ @Override public Object postFind(EjbInvocation 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). */ @Override 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. * */ @Override public EJBLocalObject getEJBLocalObjectForPrimaryKey(Object pkey, EJBContext ctx) { // EntityContextImpl should always be used in conjunction with EntityContainer so we can // always cast assert ctx instanceof EntityContextImpl; EntityContextImpl context = (EntityContextImpl) ctx; EJBLocalObjectImpl ejbLocalObjectImpl = internalGetEJBLocalObjectImpl(pkey); if (context.isCascadeDeleteBeforeEJBRemove()) { final JavaEETransaction current; try { current = (JavaEETransaction) transactionManager.getTransaction(); } catch (SystemException ex) { throw new EJBException(ex); } ActiveTxCache activeTxCache = current == null ? null : (ActiveTxCache) ejbContainerUtilImpl.getActiveTxCache(current); if (activeTxCache != null) { EntityContextImpl ctx2 = 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). */ @Override public EJBLocalObject getEJBLocalObjectForPrimaryKey(Object pkey) { EJBLocalObjectImpl localObjectImpl = internalGetEJBLocalObjectImpl(pkey); return localObjectImpl == null ? null : (EJBLocalObject) localObjectImpl.getClientObject(); } // Called from EJBHomeImpl.remove(primaryKey), // EJBLocalHomeImpl.remove(primaryKey) @Override protected void doEJBHomeRemove(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. @Override protected void removeBean(EJBLocalRemoteObject ejbo, Method removeMethod, boolean local) throws RemoveException, EJBException, RemoteException { EjbInvocation i = super.createEjbInvocation(); i.ejbObject = ejbo; i.isLocal = local; i.isRemote = !local; i.method = removeMethod; // Method must be a remove method defined on one of : // jakarta.ejb.EJBHome, jakarta.ejb.EJBObject, jakarta.ejb.EJBLocalHome, // jakarta.ejb.EJBLocalObject Class declaringClass = removeMethod.getDeclaringClass(); i.isHome = ((declaringClass == jakarta.ejb.EJBHome.class) || (declaringClass == jakarta.ejb.EJBLocalHome.class)); try { preInvoke(i); removeBean(i); } catch (Exception e) { _logger.log(Level.SEVERE, "entitybean.container.preinvoke_exception", containerInfo); _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(EjbInvocation inv) throws RemoveException { try { ejbProbeNotifier.ejbBeanDestroyedEvent(getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName); // 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 = getInvocationKey(inv); if (isRemote) { removeEJBObjectFromStore(primaryKey); } if (isLocal) { // Remove the EJBLocalObject from ejbLocalObjectStore ejbLocalObjectStore.remove(primaryKey); } // Mark EJB as removed. Now releaseContext will add bean to pool containerStateManager.markObjectRemoved(context, true); // Remove any timers for this entity bean identity. cancelTimers(primaryKey); } catch (RemoveException ex) { if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "entitybean.container.local_remove_exception", containerInfo); _logger.log(Level.FINE, "", ex); } throw ex; } catch (Exception ex) { if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "entitybean.container.remove_bean_exception", containerInfo); _logger.log(Level.FINE, "", ex); } throw new EJBException(ex); } } private void removeEJBObjectFromStore(Object primaryKey) { removeEJBObjectFromStore(primaryKey, true); } ComponentInvocation getCurrentInvocation() { return invocationManager.getCurrentInvocation(); } 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. */ @Override 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. */ @Override public void removeBeanUnchecked(Object primaryKey) { final 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) { EjbInvocation inv = super.createEjbInvocation(); inv.ejbObject = localRemoteObj; inv.isLocal = local; inv.isRemote = !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 = 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.isInState(BeanState.INVOKING) && !isReentrant) { throw new EJBException("EJB is already executing another request"); } if (context.isInState(BeanState.POOLED) || context.isInState(BeanState.DESTROYED)) { // somehow a concurrent thread must have changed state. // this is an internal error. throw new EJBException("Internal error: unknown EJB state"); } context.setState(BeanState.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. */ @Override protected 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. EntityContextImpl context = (EntityContextImpl) ctx; if (context.isInState(BeanState.DESTROYED)) { entityCtxPool.destroyObject(null); return; } // 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) ejbContainerUtilImpl.getActiveTxCache(txCurrent); if (activeTxCache != null) { // remove the context from the store activeTxCache.remove(this, primaryKey); } } // remove the context from readyStore as well removeContextFromReadyStore(primaryKey, context); if (!containerStateManager.isNullEJBObject(context)) { removeEJBObjectFromStore(primaryKey); } if (!containerStateManager.isNullEJBLocalObject(context)) { 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(BeanState.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. @Override protected void checkUnfinishedTx(Transaction prevTx, EjbInvocation 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. */ @Override protected 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 @Override protected void afterBegin(EJBContextImpl ctx) { EntityContextImpl context = (EntityContextImpl) ctx; // Note: EntityBeans are not allowed to be TX_BEAN_MANAGED if (context.isInState(BeanState.DESTROYED)) { return; } if (!containerStateManager.isNullEJBObject(context) || !containerStateManager.isNullEJBLocalObject(context)) { // 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 @Override protected void beforeCompletion(EJBContextImpl ctx) { EntityContextImpl context = (EntityContextImpl) ctx; if (context.isInState(BeanState.DESTROYED)) { return; } // Call ejbStore as required by diagram in EJB2.0 section 10.9.4 // home methods, finders and remove dont need ejbStore if ((!containerStateManager.isNullEJBObject(context) && !containerStateManager.isRemovedEJBObject(context)) || (!containerStateManager.isNullEJBLocalObject(context) && !containerStateManager.isRemovedEJBLocalObject(context))) { if (context.isDirty()) { enlistResourcesAndStore(context); } } } // Called from beforeCompletion and preFind private void enlistResourcesAndStore(EntityContextImpl context) { EntityBean e = (EntityBean) context.getEJB(); // NOTE : Use EjbInvocation 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 EjbInvocation inv = super.createEjbInvocation(e, 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. @Override protected void afterCompletion(EJBContextImpl ctx, int status) { EntityContextImpl context = (EntityContextImpl) ctx; if (context.isInState(BeanState.DESTROYED)) { return; } if (super.isUndeployed()) { transactionManager.componentDestroyed(ctx); return; } // home methods, finders and remove dont need this if (!containerStateManager.isRemovedEJBObject(context) || !containerStateManager.isRemovedEJBLocalObject(context)) { // 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.isInState(BeanState.INVOKING)) { if ((status == Status.STATUS_COMMITTED) || (status == Status.STATUS_NO_TRANSACTION)) { addReadyEJB(context); } else { passivateAndPoolEJB(context); } } } else if (containerStateManager.isNullEJBObject(context) && containerStateManager.isNullEJBLocalObject(context)) { // 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.isInState(BeanState.INVOKING)) { addPooledEJB(context); } } else if (containerStateManager.isRemovedEJBObject(context) || containerStateManager.isRemovedEJBLocalObject(context)) { // removeIncompleteTxEJB(context, false); context.setTransaction(null); context.setLastTransactionStatus(status); if (context.isInState(BeanState.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. @Override protected void preInvokeNoTx(EjbInvocation inv) { EntityContextImpl context = (EntityContextImpl) inv.context; if (context.isInState(BeanState.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. @Override protected void postInvokeNoTx(EjbInvocation 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); } @Override protected void adjustInvocationInfo(InvocationInfo invInfo, Method method, int txAttr, boolean flushEnabled, String methodIntf, Class originalIntf) throws EJBException { invInfo.isHomeFinder = isHomeFinder(method); } // Check if a method is a finder / home method. // Note: this method object is of the EJB's remote/home/local interfaces, // not the EJB class. private final boolean isHomeFinder(Method method) { Class methodClass = method.getDeclaringClass(); if (isRemote) { if ((hasRemoteHomeView && methodClass.isAssignableFrom(homeIntf)) && (methodClass != EJBHome.class) && (!method.getName().startsWith("create"))) { return true; } } if (isLocal) { // No need to check LocalBusiness view b/c home/finder methods // only apply to entity beans. if ((hasLocalHomeView && methodClass.isAssignableFrom(localHomeIntf)) && (methodClass != EJBLocalHome.class) && (!method.getName().startsWith("create"))) { return true; } } return false; } // CacheListener interface @Override public void trimEvent(Object primaryKey, Object context) { synchronized (asyncTaskSemaphore) { passivationCandidates.add(context); if (addedASyncTask == true) { return; } addedASyncTask = true; } try { ASyncPassivator work = new ASyncPassivator(); ejbContainerUtilImpl.addWork(work); } catch (Exception ex) { addedASyncTask = false; _logger.log(Level.WARNING, "entitybean.container.add_cleanup_task_error", ex); } } private class ASyncPassivator implements Runnable { @Override 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!! currentThread.setContextClassLoader(myClassLoader); 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; } currentThread.setContextClassLoader(previousClassLoader); } } } // Called from AbstractCache @Override protected boolean passivateEJB(ComponentContext ctx) { if (containerState != CONTAINER_STARTED) { return false; } EntityContextImpl context = (EntityContextImpl) ctx; if (!context.isInState(BeanState.READY)) { return false; } if (_logger.isLoggable(Level.FINEST)) { _logger.log(Level.FINEST, "EntityContainer.passivateEJB(): context = (" + ctx + ")"); } EntityBean ejb = (EntityBean) context.getEJB(); EjbInvocation inv = super.createEjbInvocation(ejb, context); inv.method = ejbPassivateMethod; Object pkey = context.getPrimaryKey(); boolean wasPassivated = false; // check state after locking ctx if (!context.isInState(BeanState.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 EjbInvocation 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) { // and associate the EJBLocalObjectImpl with the primary key localObjImpl = instantiateEJBLocalObjectImpl(primaryKey); // add the EJBLocalObjectImpl to ejbLocalObjectStore if (incrementRefCount || cacheEJBO) { ejbLocalObjectStore.put(primaryKey, localObjImpl, incrementRefCount); } } return localObjImpl; } catch (Exception ex) { _logger.log(Level.SEVERE, "entitybean.container.get_ejb_local_object_exception", containerInfo); _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 private EJBObjectImpl internalGetEJBObjectImpl(Object primaryKey, byte[] streamKey) { return internalGetEJBObjectImpl(primaryKey, streamKey, false, defaultCacheEJBO); } private EJBObjectImpl internalGetEJBObjectImpl(Object primaryKey, byte[] streamKey, boolean incrementRefCount) { return internalGetEJBObjectImpl(primaryKey, streamKey, incrementRefCount, defaultCacheEJBO); } // called from getEJBObject, postCreate, postFind, // getEJBObjectForPrimaryKey, removeBean private 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; } // 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); // create the EJBObject and associate it with the stub // and the primary key ejbObjImpl = instantiateEJBObjectImpl(ejbStub, primaryKey); 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, "entitybean.container.get_ejb_context_exception", containerInfo); _logger.log(Level.FINE, "", ex); throw new EJBException(ex); } } // internalGetEJBObject(..) // called from getContext and getReadyEJB protected EntityContextImpl getPooledEJB() { try { return (EntityContextImpl) entityCtxPool.getObject(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.isInState(BeanState.POOLED)) { return; } // we're sure that no concurrent thread can be using this // context, so no need to synchronize. containerStateManager.clearContext(context); context.setState(BeanState.POOLED); context.clearCachedPrimaryKey(); // context.cacheEntry = null; entityCtxPool.returnObject(context); } // called from addReadyEJB and afterCompletion protected void passivateAndPoolEJB(EntityContextImpl context) { if (context.isInState(BeanState.DESTROYED) || context.isInState(BeanState.POOLED)) { return; } // if ( context.isPooled() ) { // context.isPooled(false); // return; // } EntityBean ejb = (EntityBean) context.getEJB(); synchronized (context) { EjbInvocation inv = super.createEjbInvocation(ejb, 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 EjbInvocation. Concurrent * invocations should get *different* instances. */ protected EntityContextImpl activateEJBFromPool(Object primaryKey, EjbInvocation 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); containerStateManager.attachObject(inv, context, null, 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 EjbInvocation EJBObjectImpl ejbObjImpl = internalGetEJBObjectImpl(primaryKey, null, true); containerStateManager.attachObject(inv, context, ejbObjImpl, null); if (isLocal) { // Create EJBLocalObject so EntityContext methods work containerStateManager.attachObject(inv, context, null, internalGetEJBLocalObjectImpl(primaryKey, true)); } } context.setState(BeanState.READY); EntityBean ejb = (EntityBean) context.getEJB(); EjbInvocation inv2 = super.createEjbInvocation(ejb, 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(EjbInvocation 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. JavaEETransaction current = null; try { current = (JavaEETransaction) transactionManager.getTransaction(); } catch (SystemException ex) { throw new EJBException(ex); } EntityContextImpl ctx = null; if (current != null) { ActiveTxCache activeTxCache = (ActiveTxCache) ejbContainerUtilImpl.getActiveTxCache(current); ctx = (activeTxCache == null) ? null : activeTxCache.get(this, getInvocationKey(inv)); 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) { JavaEETransaction current = (JavaEETransaction) context.getTransaction(); if (current == null) { return; } if ((containerStateManager.isNullEJBObject(context)) && (containerStateManager.isNullEJBLocalObject(context))) { return; } // Its ok to add this context without checking if its already there. ActiveTxCache activeTxCache = (ActiveTxCache) ejbContainerUtilImpl.getActiveTxCache(current); if (activeTxCache == null) { activeTxCache = new ActiveTxCache(DEFAULT_TX_CACHE_BUCKETS); ejbContainerUtilImpl.setActiveTxCache(current, activeTxCache); } activeTxCache.add(context); Vector beans = ejbContainerUtilImpl.getBeans(current); beans.add(context); } /** * Called from releaseContext if ejb is removed, from afterCompletion, and from passivateEJB. */ protected void removeIncompleteTxEJB(EntityContextImpl context, boolean updateTxBeanTable) { JavaEETransaction current = (JavaEETransaction) context.getTransaction(); if (current == null) { return; } if ((containerStateManager.isNullEJBObject(context)) && (containerStateManager.isNullEJBLocalObject(context))) { return; } ActiveTxCache activeTxCache = (ActiveTxCache) ejbContainerUtilImpl.getActiveTxCache(current); if (activeTxCache != null) { activeTxCache.remove(this, context.getPrimaryKey()); } if (updateTxBeanTable) { Vector beans = ejbContainerUtilImpl.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; } @Override public void run() { if (timerValid) { cache.trimExpiredEntries(Integer.MAX_VALUE); } } @Override public boolean cancel() { cache = null; return super.cancel(); } } // Key for INCOMPLETE_TX beans which contains ejbObject + Tx private static 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(); } @Override 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; } @Override 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 static class CacheProperties { int maxCacheSize; int numberOfVictimsToSelect; int cacheIdleTimeoutInSeconds; public CacheProperties(EntityContainer entityContainer) { numberOfVictimsToSelect = Integer.parseInt(entityContainer.ejbContainer.getCacheResizeQuantity()); maxCacheSize = Integer.parseInt(entityContainer.ejbContainer.getMaxCacheSize()); cacheIdleTimeoutInSeconds = Integer.parseInt(entityContainer.ejbContainer.getCacheIdleTimeoutInSeconds()); if (entityContainer.beanCacheDes != null) { int temp = 0; if ((temp = entityContainer.beanCacheDes.getResizeQuantity()) != -1) { numberOfVictimsToSelect = temp; } if ((temp = entityContainer.beanCacheDes.getMaxCacheSize()) != -1) { maxCacheSize = temp; } if ((temp = entityContainer.beanCacheDes.getCacheIdleTimeoutInSeconds()) != -1) { cacheIdleTimeoutInSeconds = temp; } } } } // CacheProperties private static class PoolProperties { int maxPoolSize; int poolIdleTimeoutInSeconds; // int maxWaitTimeInMillis; int poolResizeQuantity; int steadyPoolSize; public PoolProperties(EntityContainer entityContainer) { maxPoolSize = Integer.parseInt(entityContainer.ejbContainer.getMaxPoolSize()); poolIdleTimeoutInSeconds = Integer.parseInt(entityContainer.ejbContainer.getPoolIdleTimeoutInSeconds()); poolResizeQuantity = Integer.parseInt(entityContainer.ejbContainer.getPoolResizeQuantity()); steadyPoolSize = Integer.parseInt(entityContainer.ejbContainer.getSteadyPoolSize()); if (entityContainer.beanPoolDes != null) { int temp = 0; if ((temp = entityContainer.beanPoolDes.getMaxPoolSize()) != -1) { maxPoolSize = temp; } if ((temp = entityContainer.beanPoolDes.getPoolIdleTimeoutInSeconds()) != -1) { poolIdleTimeoutInSeconds = temp; } if ((temp = entityContainer.beanPoolDes.getPoolResizeQuantity()) != -1) { poolResizeQuantity = temp; } if ((temp = entityContainer.beanPoolDes.getSteadyPoolSize()) != -1) { steadyPoolSize = temp; } } } } // PoolProperties @Override protected boolean isIdentical(EJBObjectImpl ejbObjImpl, EJBObject other) throws RemoteException { if (other == ejbObjImpl.getStub()) { return true; } try { // EJBObject may be a remote object. // Compare homes. See EJB2.0 spec section 9.8. if (!getProtocolManager().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, "entitybean.container.ejb_comparison_exception", containerInfo); _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 { try { // TODO - check if it is needed: context.setInEjbRemove(true); ejb.ejbRemove(); } catch (Exception ex) { throw ex; } finally { // TODO - check if it is needed: context.setInEjbRemove(false); context.setDirty(false); // bean is removed so doesnt need ejbStore /* * TODO if ( AppVerification.doInstrument() ) { AppVerification.getInstrumentLogger().doInstrumentForEjb( ejbDescriptor, * ejbRemoveMethod, exc); } */ } } @Override protected void doTimerInvocationInit(EjbInvocation inv, Object primaryKey) throws Exception { if (isRemote) { inv.ejbObject = internalGetEJBObjectImpl(primaryKey, null); inv.isRemote = true; } else { inv.ejbObject = internalGetEJBLocalObjectImpl(primaryKey); inv.isLocal = true; } if (inv.ejbObject == null) { throw new Exception("Timed object identity (" + primaryKey + " ) no longer exists "); } } @Override protected void doConcreteContainerShutdown(boolean appBeingUndeployed) { 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 entityCtxPool.close(); poolProbeListener.unregister(); if (cacheProbeListener != null) { cacheProbeListener.unregister(); } // 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 { // 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.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 final EntityContainer entityContainer; public EntityContextFactory(EntityContainer entityContainer) { this.entityContainer = entityContainer; } @Override public Object create(Object param) { EntityContextImpl entityCtx = null; EjbInvocation ejbInv = 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); ejbInv = entityContainer.createEjbInvocation(ejb, entityCtx); invocationManager.preInvoke(ejbInv); // 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 (ejbInv != null) { invocationManager.postInvoke(ejbInv); } } entityCtx.touch(); return entityCtx; } @Override 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.isInState(BeanState.DESTROYED)) { EjbInvocation ci = entityContainer.createEjbInvocation(ejb, context); invocationManager.preInvoke(ci); // kill the bean and let it be GC'ed try { synchronized (context) { containerStateManager.clearContext(context); context.setState(BeanState.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.componentDestroyed(context); } } finally { invocationManager.postInvoke(ci); } } else { // Called from forceDestroyBean try { synchronized (context) { containerStateManager.clearContext(context); context.setState(BeanState.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.componentDestroyed(context); } } catch (Exception ex) { _logger.log(Level.FINE, "Exception in destroy()", ex); } } } } // class EntityContextFactory private void createCaches() throws Exception { cacheProp = new CacheProperties(this); int cacheSize = cacheProp.maxCacheSize; int numberOfVictimsToSelect = cacheProp.numberOfVictimsToSelect; float loadFactor = DEFAULT_LOAD_FACTOR; idleTimeout = cacheProp.cacheIdleTimeoutInSeconds * 1000L; 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 { 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(EjbInvocation inv) { Object primaryKey = getInvocationKey(inv); EntityContextImpl context = null; // Try and get an EJB instance for this primaryKey from the // readyStore context = (EntityContextImpl) readyStore.remove(primaryKey); if (context == null || !context.isInState(BeanState.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(BeanState.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.componentDestroyed(ctx); } } readyStore.destroy(); readyStore = null; } protected void removeContextFromReadyStore(Object primaryKey, EntityContextImpl context) { readyStore.remove(primaryKey, context); } @Override protected void addProxyInterfacesSetClass(Set proxyInterfacesSet, boolean local) { if (ejbDescriptor.getIASEjbExtraDescriptors().isIsReadOnlyBean()) { if (local) { proxyInterfacesSet.add(ReadOnlyEJBLocalHome.class); } else { proxyInterfacesSet.add(ReadOnlyEJBHome.class); } } } @Override protected void doFlush(EjbInvocation 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; 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 = ejbContainerUtilImpl.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.isInState(BeanState.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, Runnable { protected Object lock = new Object(); protected boolean addedTask = false; protected ArrayList keys = new ArrayList(16); protected LocalEJBObjectCacheVictimHandler() { } // EJBObjectCacheListener interface @Override public void handleOverflow(Object key) { doCleanup(key); } @Override 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 { ejbContainerUtilImpl.addWork(this); } catch (Exception ex) { _logger.log(Level.WARNING, "entitybean.container.entity_add_async_task", ex); synchronized (lock) { addedTask = false; } } } @Override 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!! currentThread.setContextClassLoader(myClassLoader); 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; } currentThread.setContextClassLoader(previousClassLoader); } } protected void doCleanup(Object key) { ejbLocalObjectStore.remove(key, false); } } // LocalEJBObjectCacheVictimHandler{} protected class EJBObjectCacheVictimHandler extends LocalEJBObjectCacheVictimHandler { protected EJBObjectCacheVictimHandler() { } @Override protected void doCleanup(Object key) { removeEJBObjectFromStore(key, false); } } class EntityCacheStatsProvider implements EjbCacheStatsProviderDelegate { private final BaseCache cache; private final int confMaxCacheSize; EntityCacheStatsProvider(BaseCache cache, int maxCacheSize) { this.cache = cache; this.confMaxCacheSize = maxCacheSize; } @Override public int getCacheHits() { return ((Integer) cache.getStatByName(Constants.STAT_BASECACHE_HIT_COUNT)).intValue(); } @Override public int getCacheMisses() { return ((Integer) cache.getStatByName(Constants.STAT_BASECACHE_MISS_COUNT)).intValue(); } @Override public int getNumBeansInCache() { return cache.getEntryCount(); } @Override public int getNumExpiredSessionsRemoved() { return 0; } @Override public int getNumPassivationErrors() { return totalPassivationErrors; } @Override public int getNumPassivations() { return totalPassivations; } @Override public int getNumPassivationSuccess() { return totalPassivations - totalPassivationErrors; } @Override public int getMaxCacheSize() { return this.confMaxCacheSize; } @Override 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("; ]"); } }// End of class EntityCacheStatsProvider } //No need to sync... class ActiveTxCache { private EntityContextImpl[] buckets; private final 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); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy