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

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

There is a newer version: 6.2024.6
Show newest version
/*
 * 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): *

    *
  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: " + 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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy