
org.glassfish.persistence.ejb.entitybean.container.EntityContainer Maven / Gradle / Ivy
/*
* 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);
}
}