org.glassfish.persistence.ejb.entitybean.container.EntityContainer Maven / Gradle / Ivy
Show all versions of payara-micro Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package 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.stats.EjbCacheStatsProvider;
import com.sun.ejb.monitoring.stats.EjbCacheStatsProviderDelegate;
import com.sun.ejb.monitoring.stats.EjbMonitoringStatsProvider;
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 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 jakarta.ejb.*;
import jakarta.transaction.Status;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;
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.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):
*
* - 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).
*
- 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.
*
- 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.
*
- 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.
*
- 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: "
+ logParams[0]);
}
@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);
int steadyPoolSize = 0;
int resizeQuantity = 10;
int idleTimeoutInSeconds = Integer.MAX_VALUE-1;
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;
}
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() {
StringBuilder sbuf = new StringBuilder();
appendStats(sbuf);
return sbuf.toString();
}
public void appendStats(StringBuilder 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, getApplicationId());
} 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",
logParams);
_logger.log(Level.FINE, "",ex);
throw new EJBException(ex);
} finally {
if (decrementedCalls == false) {
context.decrementCalls();
}
context.touch();
}
}
/**
* Called from getContext before the ejb.ejbCreate is called
*/
protected void preCreate(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, null);
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()) {
JavaEETransaction current = null;
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) ?
(EJBLocalObject) localObjectImpl.getClientObject() : null;
}
// Called from EJBHomeImpl.remove(primaryKey),
// EJBLocalHomeImpl.remove(primaryKey)
@Override
protected void doEJBHomeRemove(Object primaryKey, Method removeMethod, boolean local)
throws RemoveException, EJBException, RemoteException {
final 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",logParams);
_logger.log(Level.SEVERE,"",e);
i.exception = e;
} finally {
postInvoke(i);
}
if(i.exception != null) {
if(i.exception instanceof RemoveException) {
throw (RemoveException)i.exception;
}
else if(i.exception instanceof RuntimeException) {
throw (RuntimeException)i.exception;
}
else if(i.exception instanceof Exception) {
throw new EJBException((Exception)i.exception);
}
else {
EJBException ejbEx = new EJBException();
ejbEx.initCause(i.exception);
throw ejbEx;
}
}
}
/**
* container.preInvoke() must already be done.
* So this will be called with the proper Tx context.
* @exception RemoveException if an error occurs while removing the bean
*/
protected void removeBean(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",logParams);
_logger.log(Level.FINE,"",ex);
}
throw ex;
} catch ( Exception ex ) {
if(_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE,"entitybean.container.remove_bean_exception",logParams);
_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) {
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) {
throw (EJBException) new EJBException().initCause(ex);
} 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!!
if(System.getSecurityManager() == null) {
currentThread.setContextClassLoader(myClassLoader);
} else {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
@Override
public java.lang.Object run() {
currentThread.setContextClassLoader(myClassLoader);
return null;
}
}
);
}
ComponentContext ctx = null;
do {
synchronized (asyncTaskSemaphore) {
int sz = passivationCandidates.size() - 1;
if (sz > 0) {
ctx =
(ComponentContext) passivationCandidates.remove(sz-1);
} else {
return;
}
}
if (ctx != null) {
passivateEJB(ctx);
totalPassivations++;
}
} while (ctx != null);
} catch (Throwable th) {
totalPassivationErrors++;
th.printStackTrace();
} finally {
synchronized (asyncTaskSemaphore) {
addedASyncTask = false;
}
if(System.getSecurityManager() == null) {
currentThread.setContextClassLoader(previousClassLoader);
} else {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
@Override
public java.lang.Object run() {
currentThread.setContextClassLoader(previousClassLoader);
return null;
}
}
);
}
}
}
}
// 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",
logParams);
_logger.log(Level.SEVERE,"",ex);
throw new EJBException(ex);
}
}
// called from postFind, getEJBObjectForPrimaryKey,
// EntityContextImpl.getEJBObject()
EJBObject getEJBObjectStub(Object primaryKey, byte[] streamKey) {
// primary key cant be null, streamkey may be null
// check if the EJBObject exists in the store.
try {
EJBObjectImpl ejbObjImpl = (EJBObjectImpl) ejbObjectStore.get(primaryKey);
if (ejbObjImpl != null && ejbObjImpl.getStub() != null) {
return (EJBObject) ejbObjImpl.getStub();
}
// create a new stub without creating the EJBObject itself
if ( streamKey == null ) {
streamKey = EJBUtils.serializeObject(primaryKey, false);
}
EJBObject ejbStub = (EJBObject) remoteHomeRefFactory.createRemoteReference(streamKey);
return ejbStub;
} catch (Exception ex) {
_logger.log(Level.FINE,"", ex);
throw new EJBException(ex);
}
}
// called from getEJBObject, postCreate, removeBean,
// postFind, getEJBObjectForPrimaryKey
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", logParams);
_logger.log(Level.FINE,"",ex);
throw new EJBException(ex);
}
} //internalGetEJBObject(..)
// called from getContext and getReadyEJB
protected EntityContextImpl getPooledEJB() {
try {
return (EntityContextImpl) entityCtxPool.getObject(true, null);
} catch (com.sun.ejb.containers.util.pool.PoolException inEx) {
throw new EJBException(inEx);
}
}
// called from passivateAndPoolEJB, releaseContext, passivateEJB
// Note: addPooledEJB is idempotent: i.e. even if it is called multiple
// times with the same context, the context is added only once.
protected void addPooledEJB(EntityContextImpl context) {
if ( context.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", logParams);
_logger.log(Level.INFO, "", ex);
throw new RemoteException("Exception in isIdentical()", ex);
}
}
protected void callEJBLoad(EntityBean ejb, EntityContextImpl context, boolean activeTx) throws Exception {
try {
context.setInEjbLoad(true);
ejb.ejbLoad();
// Note: no need to do context.setDirty(false) because ejbLoad is
// called immediately before a business method.
} catch(Exception e) {
throw e;
} finally {
context.setInEjbLoad(false);
}
}
protected void callEJBStore(EntityBean ejb, EntityContextImpl context) throws Exception {
try {
context.setInEjbStore(true);
ejb.ejbStore();
} finally {
context.setInEjbStore(false);
context.setDirty(false); // bean's state is in sync with DB
}
}
protected void callEJBRemove(EntityBean ejb, EntityContextImpl context) throws Exception {
try {
ejb.ejbRemove();
} catch ( Exception ex ) {
throw ex;
} finally {
// bean is removed so doesnt need ejbStore
context.setDirty(false);
}
}
@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 {
EJBObjectCache lru = null;
String ejbName = ejbDescriptor.getName();
idleTimeout = (idleTimeout <= 0) ? -1 : idleTimeout;
if (cacheSize <= 0 && idleTimeout <= 0) {
ejbObjectStore = new UnboundedEJBObjectCache(ejbName);
ejbObjectStore.init(DEFAULT_CACHE_SIZE, numberOfVictimsToSelect, 0L, (float) 1.0, null);
ejbLocalObjectStore = new UnboundedEJBObjectCache(ejbName);
ejbLocalObjectStore.init(DEFAULT_CACHE_SIZE, numberOfVictimsToSelect, 0L, (float) 1.0, null);
} else {
cacheSize = (cacheSize <= 0) ? DEFAULT_CACHE_SIZE : cacheSize;
ejbObjectStore = new FIFOEJBObjectCache(ejbName);
ejbObjectStore.init(cacheSize, numberOfVictimsToSelect, idleTimeout, (float) 1.0, null);
ejbObjectStore.setEJBObjectCacheListener(new EJBObjectCacheVictimHandler());
ejbLocalObjectStore = new FIFOEJBObjectCache(ejbName);
ejbLocalObjectStore.init(cacheSize, numberOfVictimsToSelect, idleTimeout, (float) 1.0, null);
ejbLocalObjectStore.setEJBObjectCacheListener(new LocalEJBObjectCacheVictimHandler());
}
if (idleTimeout > 0) {
idleEJBObjectPassivator = setupIdleBeansPassivator(ejbObjectStore);
idleLocalEJBObjectPassivator = setupIdleBeansPassivator(ejbLocalObjectStore);
}
}
protected EntityContextImpl getReadyEJB(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