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

com.sun.ejb.containers.StatefulSessionContainer Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
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.
 */
// Portions Copyright [2016-2021] [Payara Foundation and/or its affiliates]

package com.sun.ejb.containers;

import com.sun.appserv.util.cache.CacheListener;
import com.sun.ejb.ComponentContext;
import com.sun.ejb.Container;
import com.sun.ejb.EJBUtils;
import com.sun.ejb.EjbInvocation;
import com.sun.ejb.InvocationInfo;
import com.sun.ejb.MethodLockInfo;
import com.sun.ejb.base.stats.HAStatefulSessionStoreMonitor;
import com.sun.ejb.base.stats.StatefulSessionStoreMonitor;
import com.sun.ejb.containers.EJBContextImpl.BeanState;
import com.sun.ejb.containers.util.cache.LruSessionCache;
import com.sun.ejb.monitoring.probes.EjbCacheProbeProvider;
import com.sun.ejb.monitoring.stats.EjbCacheStatsProvider;
import com.sun.ejb.monitoring.stats.EjbMonitoringStatsProvider;
import com.sun.ejb.monitoring.stats.EjbMonitoringUtils;
import com.sun.ejb.monitoring.stats.StatefulSessionBeanStatsProvider;
import com.sun.ejb.spi.container.SFSBContainerCallback;
import com.sun.ejb.spi.container.StatefulEJBContext;
import com.sun.ejb.spi.sfsb.util.SFSBUUIDUtil;
import com.sun.ejb.spi.sfsb.util.SFSBVersionManager;
import com.sun.enterprise.admin.monitor.callflow.ComponentType;
import com.sun.enterprise.container.common.impl.EntityManagerFactoryWrapper;
import com.sun.enterprise.container.common.impl.EntityManagerWrapper;
import com.sun.enterprise.container.common.impl.PhysicalEntityManagerWrapper;
import com.sun.enterprise.container.common.spi.util.IndirectlySerializable;
import com.sun.enterprise.container.common.spi.util.SerializableObjectFactory;
import com.sun.enterprise.deployment.EntityManagerReferenceDescriptor;
import com.sun.enterprise.deployment.LifecycleCallbackDescriptor;
import com.sun.enterprise.deployment.LifecycleCallbackDescriptor.CallbackType;
import com.sun.enterprise.deployment.MethodDescriptor;
import com.sun.enterprise.security.SecurityManager;
import com.sun.enterprise.transaction.api.JavaEETransaction;
import com.sun.enterprise.util.Utility;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;

import jakarta.ejb.ConcurrentAccessException;
import jakarta.ejb.ConcurrentAccessTimeoutException;
import jakarta.ejb.CreateException;
import jakarta.ejb.EJBException;
import jakarta.ejb.EJBObject;
import jakarta.ejb.IllegalLoopbackException;
import jakarta.ejb.NoSuchObjectLocalException;
import jakarta.ejb.RemoveException;
import jakarta.ejb.SessionBean;
import jakarta.ejb.SessionSynchronization;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceContextType;
import jakarta.persistence.SynchronizationType;
import jakarta.transaction.Status;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;

import org.glassfish.api.invocation.ComponentInvocation;
import org.glassfish.ejb.LogFacade;
import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
import org.glassfish.ejb.deployment.descriptor.EjbRemovalInfo;
import org.glassfish.ejb.deployment.descriptor.EjbSessionDescriptor;
import org.glassfish.ejb.deployment.descriptor.runtime.CheckpointAtEndOfMethodDescriptor;
import org.glassfish.ejb.deployment.descriptor.runtime.IASEjbExtraDescriptors;
import org.glassfish.flashlight.provider.ProbeProviderFactory;
import org.glassfish.ha.store.api.BackingStore;
import org.glassfish.ha.store.api.BackingStoreException;
import org.glassfish.ha.store.util.SimpleMetadata;
import org.glassfish.logging.annotation.LogMessageInfo;

import static com.sun.ejb.containers.EJBContextImpl.BeanState.DESTROYED;
import static com.sun.ejb.containers.EJBContextImpl.BeanState.INVOKING;
import static com.sun.ejb.containers.EJBContextImpl.BeanState.PASSIVATED;
import static com.sun.ejb.containers.EJBContextImpl.BeanState.READY;
import static com.sun.ejb.spi.sfsb.util.SFSBVersionManager.NO_VERSION;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.WARNING;
import static jakarta.persistence.SynchronizationType.SYNCHRONIZED;

/**
 * This class provides container functionality specific to stateful
 * SessionBeans.
 * At deployment time, one instance of the StatefulSessionContainer is created
 * for each stateful SessionBean type (i.e. deployment descriptor) in a JAR.
 * 

* The 5 states of a Stateful EJB (an EJB can be in only 1 state at a time): * 1. PASSIVE : has been passivated * 2. READY : ready for invocations, no transaction in progress * 3. INVOKING : processing an invocation * 4. INCOMPLETE_TX : ready for invocations, transaction in progress * 5. DESTROYED : does not exist * * @author Mahesh Kannan */ public final class StatefulSessionContainer extends BaseContainer implements CacheListener, SFSBContainerCallback { private static final Logger _logger = LogFacade.getLogger(); @LogMessageInfo( message = "[SFSBContainer] Exception while initializing SessionSynchronization methods", level = "WARNING") private static final String EXCEPTION_WHILE_INITIALIZING_SESSION_SYNCHRONIZATION = "AS-EJB-00012"; @LogMessageInfo( message = "[SFSBContainer] Exception while loading checkpoint info", level = "WARNING") private static final String EXCEPTION_WHILE_LOADING_CHECKPOINT = "AS-EJB-00013"; @LogMessageInfo( message = "Exception creating ejb object : [{0}]", level = "WARNING") private static final String CREATE_EJBOBJECT_EXCEPTION = "AS-EJB-00014"; @LogMessageInfo( message = "Exception creating ejb local object [{0}]", level = "WARNING") private static final String CREATE_EJBLOCALOBJECT_EXCEPTION = "AS-EJB-00015"; @LogMessageInfo( message = "Couldn't update timestamp for: [{0}]; Exception: [{1}]", level = "WARNING") private static final String COULDNT_UPDATE_TIMESTAMP_FOR_EXCEPTION = "AS-EJB-00016"; @LogMessageInfo( message = "Cannot register bean for checkpointing", level = "WARNING") private static final String CANNOT_REGISTER_BEAN_FOR_CHECKPOINTING = "AS-EJB-00017"; @LogMessageInfo( message = "Error during checkpoint ([{0}]. Key: [{1}]) [{2}]", level = "WARNING") private static final String ERROR_DURING_CHECKPOINT_3PARAMs = "AS-EJB-00018"; @LogMessageInfo( message = "sfsb checkpoint error. Name: [{0}]", level = "WARNING") private static final String SFSB_CHECKPOINT_ERROR_NAME = "AS-EJB-00019"; @LogMessageInfo( message = "sfsb checkpoint error. Key: [{0}]", level = "WARNING") private static final String SFSB_CHECKPOINT_ERROR_KEY = "AS-EJB-00020"; @LogMessageInfo( message = "Exception in afterCompletion : [{0}]", level = "INFO") private static final String AFTER_COMPLETION_EXCEPTION = "AS-EJB-00021"; @LogMessageInfo( message = "1. passivateEJB() returning because containerState: [{0}]", level = "WARNING") private static final String PASSIVATE_EJB_RETURNING_BECAUSE_CONTAINER_STATE = "AS-EJB-00022"; @LogMessageInfo( message = "Extended EM not serializable. Exception: [{0}]", level = "WARNING") private static final String EXTENDED_EM_NOT_SERIALIZABLE = "AS-EJB-00023"; @LogMessageInfo( message = "Error during passivation: [{0}]; [{1}]", level = "WARNING") private static final String ERROR_DURING_PASSIVATION = "AS-EJB-00024"; @LogMessageInfo( message = "Error during passivation of [{0}]", level = "WARNING") private static final String PASSIVATION_ERROR_1PARAM = "AS-EJB-00025"; @LogMessageInfo( message = "sfsb passivation error. Key: [{0}]", level = "WARNING") private static final String SFSB_PASSIVATION_ERROR_1PARAM = "AS-EJB-00026"; @LogMessageInfo( message = "Error during Stateful Session Bean activation for key [{0}]", level = "SEVERE", cause = "A problem occurred while the container was activating a stateful session bean. " + "One possible cause is that the bean code threw a system exception from its ejbActivate method.", action = "Check the stack trace to see whether the exception was thrown from the ejbActivate method " + "and if so double-check the application code to determine what caused the exception.") private static final String SFSB_ACTIVATION_ERROR = "AS-EJB-00028"; @LogMessageInfo( message = "[{0}]: Error during backingStore.shutdown()", level = "WARNING") private static final String ERROR_DURING_BACKING_STORE_SHUTDOWN = "AS-EJB-00029"; @LogMessageInfo( message = "[{0}]: Error during onShutdown()", level = "WARNING") private static final String ERROR_DURING_ON_SHUTDOWN = "AS-EJB-00030"; @LogMessageInfo( message = "[{0}]: Error while undeploying ctx. Key: [{1}]", level = "WARNING") private static final String ERROR_WHILE_UNDEPLOYING_CTX_KEY = "AS-EJB-00031"; @LogMessageInfo( message = "Cannot add idle bean cleanup task", level = "WARNING") private static final String ADD_CLEANUP_TASK_ERROR = "AS-EJB-00032"; @LogMessageInfo( message = "Got exception during removeExpiredSessions (but the reaper thread is still alive)", level = "WARNING") private static final String GOT_EXCEPTION_DURING_REMOVE_EXPIRED_SESSIONS = "AS-EJB-00033"; @LogMessageInfo( message = "Error during checkpoint(, but session not destroyed)", level = "WARNING") private static final String ERROR_DURING_CHECKPOINT_SESSION_ALIVE = "AS-EJB-00034"; @LogMessageInfo( message = "Error during checkpoint", level = "WARNING") private static final String ERROR_DURING_CHECKPOINT = "AS-EJB-00035"; @LogMessageInfo( message = "Cache is shutting down, {0} stateful session beans will not be restored after restarting " + "since passivation is disabled", level = "INFO") private static final String SFSB_NOT_RESTORED_AFTER_RESTART = "AS-EJB-00050"; @LogMessageInfo( message = "Exception in backingStore.size()", level = "WARNING") private static final String ERROR_WHILE_BACKSTORE_SIZE_ACCESS = "AS-EJB-00063"; // We do not want too many ORB task for passivation public static final int MIN_PASSIVATION_BATCH_COUNT = 8; private final static long CONCURRENCY_NOT_ALLOWED = 0; private final static long BLOCK_INDEFINITELY = -1; private final ArrayList passivationCandidates = new ArrayList(); private final Object asyncTaskSemaphore = new Object(); private int asyncTaskCount = 0; private int asyncCummTaskCount = 0; private int passivationBatchCount = MIN_PASSIVATION_BATCH_COUNT; private int containerTrimCount = 0; private LruSessionCache sessionBeanCache; private BackingStore backingStore; private SFSBUUIDUtil uuidGenerator; private final ArrayList scheduledTimerTasks = new ArrayList(); private int statMethodReadyCount = 0; private final String ejbName; private boolean isHAEnabled; private int removalGracePeriodInSeconds; private final InvocationInfo postConstructInvInfo; private final InvocationInfo preDestroyInvInfo; private final InvocationInfo postActivateInvInfo; private final InvocationInfo prePassivateInvInfo; private StatefulSessionStoreMonitor sfsbStoreMonitor; private final String traceInfoPrefix; private SFSBVersionManager sfsbVersionManager; private Method afterBeginMethod; private Method beforeCompletionMethod; private Method afterCompletionMethod; private final boolean isPassivationCapable; /* * Cache for keeping ref count for shared extended entity manager. * The key in this map is the physical entity manager */ private static final Map extendedEMReferenceCountMap = new HashMap<>(); private static final Map eemKey2EEMMap = new HashMap<>(); /** * This constructor is called from the JarManager when a Jar is deployed. * * @throws Exception on error */ public StatefulSessionContainer(EjbDescriptor desc, ClassLoader loader, SecurityManager sm) throws Exception { this(ContainerType.STATEFUL, desc, loader, sm); } public StatefulSessionContainer(ContainerType conType, EjbDescriptor desc, ClassLoader loader, SecurityManager sm) throws Exception { super(conType, desc, loader, sm); super.createCallFlowAgent(ComponentType.SFSB); this.ejbName = desc.getName(); this.traceInfoPrefix = "sfsb-" + ejbName + ": "; postConstructInvInfo = getLifecycleCallbackInvInfo(ejbDescriptor.getPostConstructDescriptors()); preDestroyInvInfo = getLifecycleCallbackInvInfo(ejbDescriptor.getPreDestroyDescriptors()); EjbSessionDescriptor sfulDesc = (EjbSessionDescriptor) ejbDescriptor; postActivateInvInfo = getLifecycleCallbackInvInfo(sfulDesc.getPostActivateDescriptors()); prePassivateInvInfo = getLifecycleCallbackInvInfo(sfulDesc.getPrePassivateDescriptors()); isPassivationCapable = sfulDesc.isPassivationCapable(); } @Override public boolean isPassivationCapable() { return isPassivationCapable; } private InvocationInfo getLifecycleCallbackInvInfo(Set lifecycleCallbackDescriptors) throws Exception { InvocationInfo inv = new InvocationInfo(); inv.ejbName = ejbDescriptor.getName(); inv.methodIntf = MethodDescriptor.LIFECYCLE_CALLBACK; inv.txAttr = getTxAttrForLifecycleCallback( // lifecycleCallbackDescriptors, -1, Container.TX_NOT_SUPPORTED, Container.TX_REQUIRES_NEW); return inv; } @Override protected void initializeHome() throws Exception { super.initializeHome(); initSessionSyncMethods(); loadCheckpointInfo(); } @Override public void startApplication(boolean deploy) { super.startApplication(deploy); registerMonitorableComponents(); } private void initSessionSyncMethods() throws Exception { if (SessionSynchronization.class.isAssignableFrom(ejbClass)) { try { afterBeginMethod = ejbClass.getMethod("afterBegin"); beforeCompletionMethod = ejbClass.getMethod("beforeCompletion"); afterCompletionMethod = ejbClass.getMethod("afterCompletion", Boolean.TYPE); } catch (Exception e) { _logger.log(Level.WARNING, EXCEPTION_WHILE_INITIALIZING_SESSION_SYNCHRONIZATION, e); } } else { EjbSessionDescriptor sessionDesc = (EjbSessionDescriptor) ejbDescriptor; MethodDescriptor afterBeginMethodDesc = sessionDesc.getAfterBeginMethod(); if (afterBeginMethodDesc != null) { afterBeginMethod = afterBeginMethodDesc.getDeclaredMethod(sessionDesc); processSessionSynchMethod(afterBeginMethod); } MethodDescriptor beforeCompletionMethodDesc = sessionDesc.getBeforeCompletionMethod(); if (beforeCompletionMethodDesc != null) { beforeCompletionMethod = beforeCompletionMethodDesc.getDeclaredMethod(sessionDesc); processSessionSynchMethod(beforeCompletionMethod); } MethodDescriptor afterCompletionMethodDesc = sessionDesc.getAfterCompletionMethod(); if (afterCompletionMethodDesc != null) { afterCompletionMethod = afterCompletionMethodDesc.getDeclaredMethod(sessionDesc); if (afterCompletionMethod == null) { afterCompletionMethod = afterCompletionMethodDesc.getDeclaredMethod( // sessionDesc, new Class[] {Boolean.TYPE}); } processSessionSynchMethod(afterCompletionMethod); } } } private void processSessionSynchMethod(Method sessionSynchMethod) throws Exception { final Method methodAccessible = sessionSynchMethod; // SessionSynch method defined through annotation or ejb-jar.xml // can have any access modifier so make sure we have permission // to invoke it. final PrivilegedExceptionAction action = () -> { if (!methodAccessible.isAccessible()) { methodAccessible.setAccessible(true); } return null; }; AccessController.doPrivileged(action); } // 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 (inv.invocationInfo.isBusinessMethod && 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) { _logger.log(Level.FINE, "Exception in checkUnfinishedTx", ex); throw new EJBException(ex); } } protected void loadCheckpointInfo() { try { if (!isHAEnabled) { return; } for (InvocationInfo info : invocationInfoMap.values()) { info.checkpointEnabled = false; MethodDescriptor md = new MethodDescriptor(info.method, info.methodIntf); IASEjbExtraDescriptors extraDesc = ejbDescriptor.getIASEjbExtraDescriptors(); if (extraDesc != null) { CheckpointAtEndOfMethodDescriptor cpDesc = extraDesc.getCheckpointAtEndOfMethodDescriptor(); if (cpDesc != null) { info.checkpointEnabled = cpDesc.isCheckpointEnabledFor(md); } } if (info.checkpointEnabled) { _logger.log(Level.FINE, () -> "[SFSBContainer] " + info.method + " MARKED for end-of-method-checkpoint"); } } } catch (Exception ex) { _logger.log(Level.WARNING, EXCEPTION_WHILE_LOADING_CHECKPOINT, ex); } } @Override protected void registerMonitorableComponents() { super.registerMonitorableComponents(); cacheProbeListener = new EjbCacheStatsProvider( // sessionBeanCache, getContainerId(), // containerInfo.appName, containerInfo.modName, containerInfo.ejbName); cacheProbeListener.register(); try { ProbeProviderFactory probeFactory = ejbContainerUtilImpl.getProbeProviderFactory(); String invokerId = EjbMonitoringUtils.getInvokerId( // containerInfo.appName, containerInfo.modName, containerInfo.ejbName); cacheProbeNotifier = probeFactory.getProbeProvider(EjbCacheProbeProvider.class, invokerId); _logger.log(Level.FINE, () -> "Got ProbeProvider: " + cacheProbeNotifier.getClass().getName()); } catch (Exception ex) { cacheProbeNotifier = new EjbCacheProbeProvider(); _logger.log(Level.FINE, "Error getting the EjbMonitoringProbeProvider"); } if (isHAEnabled) { sfsbStoreMonitor = new HAStatefulSessionStoreMonitor(); } else { sfsbStoreMonitor = new StatefulSessionStoreMonitor(); } sessionBeanCache.setStatefulSessionStoreMonitor(sfsbStoreMonitor); _logger.log(Level.FINE, "[SFSBContainer] registered monitorable"); } public String getMonitorAttributeValues() { StringBuilder sbuf = new StringBuilder(); //sbuf.append(storeHelper.getMonitorAttributeValues()); sbuf.append(" { asyncTaskCount=").append(asyncTaskCount) .append("; asyncCummTaskCount=").append(asyncCummTaskCount) .append("; passivationBatchCount=").append(passivationBatchCount) .append("; passivationQSz=").append(passivationCandidates.size()) .append("; trimEventCount=").append(containerTrimCount) .append(" }"); return sbuf.toString(); } @Override protected EjbMonitoringStatsProvider getMonitoringStatsProvider(String appName, String modName, String ejbName) { StatefulSessionBeanStatsProvider statsProvider = // new StatefulSessionBeanStatsProvider(this, getContainerId(), appName, modName, ejbName); try { statsProvider.setPassiveCount(backingStore.size()); } catch (BackingStoreException e) { _logger.log(Level.WARNING, ERROR_WHILE_BACKSTORE_SIZE_ACCESS, e); } return statsProvider; } private static String convertCtxStateToString(SessionContextImpl sc) { switch (sc.getState()) { case PASSIVATED: return "PASSIVE"; case READY: return "READY"; case INVOKING: return "INVOKING"; case INCOMPLETE_TX: return "INCOMPLETE_TX"; case DESTROYED: return "DESTROYED"; default: return "UNKNOWN-STATE"; } } @Override protected boolean isIdentical(EJBObjectImpl ejbo, EJBObject other) throws RemoteException { if (other == ejbo.getStub()) { return true; } try { // other may be a stub for a remote object. return getProtocolManager().isIdentical(ejbo.getStub(), other); } catch (Exception ex) { throw new RemoteException("Error during isIdentical.", ex); } } /** * This is called from the generated "HelloEJBHomeImpl" create method * via EJBHomeImpl.createEJBObject. * Note: for stateful beans, the HelloEJBHomeImpl.create calls * ejbCreate on the new bean after createEJBObject() returns. * Return the EJBObject for the bean. */ @Override protected EJBObjectImpl createEJBObjectImpl() throws CreateException, RemoteException { try { SessionContextImpl context = createBeanInstance(); EJBObjectImpl ejbObjImpl = createEJBObjectImpl(context); afterInstanceCreation(context); return ejbObjImpl; } catch (Exception ex) { _logger.log(Level.WARNING, CREATE_EJBOBJECT_EXCEPTION, new Object[]{ejbDescriptor.getName(), ex}); if (ex instanceof EJBException) { throw (EJBException) ex; } CreateException ce = new CreateException("ERROR creating stateful SessionBean"); ce.initCause(ex); throw ce; } } @Override protected EJBObjectImpl createRemoteBusinessObjectImpl() throws CreateException, RemoteException { try { SessionContextImpl context = createBeanInstance(); EJBObjectImpl ejbBusinessObjImpl = createRemoteBusinessObjectImpl(context); afterInstanceCreation(context); return ejbBusinessObjImpl; } catch (Exception ex) { _logger.log(Level.WARNING, CREATE_EJBOBJECT_EXCEPTION, new Object[] {ejbDescriptor.getName(), ex}); if (ex instanceof EJBException) { throw (EJBException) ex; } CreateException ce = new CreateException("ERROR creating stateful SessionBean"); ce.initCause(ex); throw ce; } } /** * This is called from the generated "HelloEJBLocalHomeImpl" create method * via EJBLocalHomeImpl.createEJBObject. * Note: for stateful beans, the HelloEJBLocalHomeImpl.create calls * ejbCreate on the new bean after createEJBLocalObjectImpl() returns. * Return the EJBLocalObject for the bean. */ @Override protected EJBLocalObjectImpl createEJBLocalObjectImpl() throws CreateException { try { SessionContextImpl context = createBeanInstance(); EJBLocalObjectImpl localObjImpl = createEJBLocalObjectImpl(context); afterInstanceCreation(context); return localObjImpl; } catch (Exception ex) { _logger.log(Level.WARNING, CREATE_EJBLOCALOBJECT_EXCEPTION, new Object[] {ejbDescriptor.getName(), ex}); if (ex instanceof EJBException) { throw (EJBException) ex; } CreateException ce = new CreateException("ERROR creating stateful SessionBean"); ce.initCause(ex); throw ce; } } /** * Internal creation event for Local Business view of SFSB */ @Override EJBLocalObjectImpl createEJBLocalBusinessObjectImpl(boolean localBeanView) throws CreateException { try { SessionContextImpl context = createBeanInstance(); EJBLocalObjectImpl localBusinessObjImpl = // localBeanView // ? createOptionalEJBLocalBusinessObjectImpl(context) : createEJBLocalBusinessObjectImpl(context); afterInstanceCreation(context); return localBusinessObjImpl; } catch (Exception ex) { _logger.log(Level.WARNING, CREATE_EJBLOCALOBJECT_EXCEPTION, new Object[] {ejbDescriptor.getName(), ex}); if (ex instanceof EJBException) { throw (EJBException) ex; } CreateException ce = new CreateException("ERROR creating stateful SessionBean"); ce.initCause(ex); throw ce; } } @Override protected SessionContextImpl _constructEJBContextImpl(Object instance) { return new SessionContextImpl(instance, this); } @Override protected Object _constructEJBInstance() throws Exception { return sfsbSerializedClass == null ? ejbClass.newInstance() : sfsbSerializedClass.newInstance(); } @Override protected boolean suspendTransaction(EjbInvocation inv) throws Exception { SessionContextImpl sc = (SessionContextImpl) inv.context; return !inv.invocationInfo.isBusinessMethod && !sc.getInLifeCycleCallback(); } @Override protected boolean resumeTransaction(EjbInvocation inv) throws Exception { SessionContextImpl sc = (SessionContextImpl) inv.context; return !inv.invocationInfo.isBusinessMethod && !sc.getInLifeCycleCallback(); } /** * Create a new Session Bean and set Session Context. */ private SessionContextImpl createBeanInstance() throws Exception { EjbInvocation ejbInv = null; try { SessionContextImpl context = (SessionContextImpl) createEjbInstanceAndContext(); Object ejb = context.getEJB(); Object sessionKey = uuidGenerator.createSessionKey(); createExtendedEMs(context, sessionKey); // Need to do preInvoke because setSessionContext can access JNDI ejbInv = super.createEjbInvocation(ejb, context); invocationManager.preInvoke(ejbInv); // setSessionContext will be called without a Tx as required // by the spec, because the EJBHome.create would have been called // after the container suspended any client Tx. // setSessionContext is also called before createEJBObject because // the bean is not allowed to do EJBContext.getEJBObject here if (ejb instanceof SessionBean) { ((SessionBean) ejb).setSessionContext(context); } // Perform injection right after where setSessionContext // would be called. This is important since injection methods // have the same "operations allowed" permissions as // setSessionContext. injectEjbInstance(context); // Set the timestamp before inserting into bean store, else // Recycler might go crazy and remove this bean! context.touch(); // Add the EJB into the session store // and get the instanceKey for this EJB instance. // XXX The store operation could be avoided for local-only beans. sessionBeanCache.put(sessionKey, context); context.setInstanceKey(sessionKey); _logger.log(FINE, () -> "[SFSBContainer] Created " + "session: " + sessionKey); return context; } catch (Exception ex) { throw ex; } catch (Throwable t) { EJBException ejbEx = new EJBException(); ejbEx.initCause(t); throw ejbEx; } finally { if (ejbInv != null) { invocationManager.postInvoke(ejbInv); } } } private void createExtendedEMs(SessionContextImpl ctx, Object sessionKey) { Set emRefs = ejbDescriptor.getEntityManagerReferenceDescriptors(); Iterator iter = emRefs.iterator(); Set eemRefInfos = new HashSet<>(); while (iter.hasNext()) { EntityManagerReferenceDescriptor refDesc = iter.next(); if (refDesc.getPersistenceContextType() == PersistenceContextType.EXTENDED) { String unitName = refDesc.getUnitName(); EntityManagerFactory emf = EntityManagerFactoryWrapper.lookupEntityManagerFactory( // ComponentInvocation.ComponentInvocationType.EJB_INVOCATION, unitName, ejbDescriptor); if (emf == null) { throw new EJBException("EMF is null. Couldn't get extended EntityManager for" // + " refName: " + refDesc.getName() + "; unitname: " + unitName); } PhysicalEntityManagerWrapper physicalEntityManagerWrapper = findExtendedEMFromInvList(emf); if (physicalEntityManagerWrapper == null) { // We did not find an extended EM that we can inherit from. Create one try { EntityManager em = // emf.createEntityManager(refDesc.getSynchronizationType(), refDesc.getProperties()); physicalEntityManagerWrapper = // new PhysicalEntityManagerWrapper(em, refDesc.getSynchronizationType()); } catch (Throwable th) { EJBException ejbEx = new EJBException( // "Couldn't create EntityManager for refName: " + refDesc.getName() // + "; unitname: " + unitName); ejbEx.initCause(th); throw ejbEx; } } else { // We found an extended EM we can inherit from. Validate that sync type matches if (physicalEntityManagerWrapper.getSynchronizationType() != refDesc.getSynchronizationType()) { throw new EJBException( "The current invocation inherits a persistence context of synchronization type '" + physicalEntityManagerWrapper.getSynchronizationType() + "' where as it references a persistence context of synchronization type '" + refDesc.getSynchronizationType() // + "' refName: " + refDesc.getName() + " unitName: " + unitName); } } String emRefName = refDesc.getName(); long containerID = this.getContainerId(); EEMRefInfo refInfo = null; synchronized (extendedEMReferenceCountMap) { refInfo = extendedEMReferenceCountMap.get(physicalEntityManagerWrapper.getEM()); if (refInfo != null) { refInfo.refCount++; } else { refInfo = new EEMRefInfo(emRefName, refDesc.getUnitName(), refDesc.getSynchronizationType(), containerID, sessionKey, physicalEntityManagerWrapper.getEM(), emf); refInfo.refCount = 1; extendedEMReferenceCountMap.put(physicalEntityManagerWrapper.getEM(), refInfo); eemKey2EEMMap.put(refInfo.getKey(), refInfo.getEntityManager()); } } ctx.addExtendedEntityManagerMapping(emf, refInfo); eemRefInfos.add(refInfo); } } if (eemRefInfos.size() > 0) { ctx.setEEMRefInfos(eemRefInfos); } } private PhysicalEntityManagerWrapper findExtendedEMFromInvList(EntityManagerFactory emf) { ComponentInvocation compInv = invocationManager.getCurrentInvocation(); if (compInv != null) { if (compInv.getInvocationType() == ComponentInvocation.ComponentInvocationType.EJB_INVOCATION) { EjbInvocation ejbInv = (EjbInvocation) compInv; if (ejbInv.context instanceof SessionContextImpl) { SessionContextImpl ctxImpl = (SessionContextImpl) ejbInv.context; if (ctxImpl.container instanceof StatefulSessionContainer) { return ctxImpl.getExtendedEntityManager(emf); } } } } return null; } @Override public EntityManager lookupExtendedEntityManager(EntityManagerFactory emf) { PhysicalEntityManagerWrapper physicalEntityManagerWrapper = findExtendedEMFromInvList(emf); return physicalEntityManagerWrapper == null ? null : physicalEntityManagerWrapper.getEM(); } private void afterInstanceCreation(SessionContextImpl context) throws Exception { context.setState(BeanState.READY); EjbInvocation ejbInv = null; boolean inTx = false; try { // Need to do preInvoke because setSessionContext can access JNDI ejbInv = super.createEjbInvocation(context.getEJB(), context); invocationManager.preInvoke(ejbInv); // PostConstruct must be called after state set to something // other than CREATED inTx = callLifecycleCallbackInTxIfUsed(ejbInv, context, postConstructInvInfo, CallbackType.POST_CONSTRUCT); } catch (Throwable t) { EJBException ejbEx = new EJBException(); ejbEx.initCause(t); throw ejbEx; } finally { if (ejbInv != null) { try { invocationManager.postInvoke(ejbInv); if( inTx ) { // Call directly to report exception postInvokeTx(ejbInv); } } catch(Exception pie) { if (ejbInv.exception != null) { _logger.log(Level.FINE, "Exception during SFSB startup postInvoke ", pie); } else { ejbInv.exception = pie; CreateException creEx = new CreateException( "Initialization failed for Stateful Session Bean " + ejbDescriptor.getName()); creEx.initCause(pie); throw creEx; } } finally { context.setInLifeCycleCallback(false); } } } ejbProbeNotifier.ejbBeanCreatedEvent( // getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName); incrementMethodReadyStat(); } private EJBLocalObjectImpl createEJBLocalObjectImpl(SessionContextImpl context) throws Exception { if (context.getEJBLocalObjectImpl() != null) { return context.getEJBLocalObjectImpl(); } // create EJBLocalObject EJBLocalObjectImpl localObjImpl = instantiateEJBLocalObjectImpl(context.getInstanceKey()); // introduce context and EJBLocalObject to each other context.setEJBLocalObjectImpl(localObjImpl); localObjImpl.setContext(context); if (hasLocalBusinessView) { createEJBLocalBusinessObjectImpl(context); } if (hasOptionalLocalBusinessView) { createOptionalEJBLocalBusinessObjectImpl(context); } if (hasRemoteHomeView) { createEJBObjectImpl(context); // enable remote invocations too } if (hasRemoteBusinessView) { createRemoteBusinessObjectImpl(context); } return localObjImpl; } private EJBLocalObjectImpl createEJBLocalBusinessObjectImpl(SessionContextImpl context) throws Exception { if (context.getEJBLocalBusinessObjectImpl() != null) { return context.getEJBLocalBusinessObjectImpl(); } EJBLocalObjectImpl localBusinessObjImpl = instantiateEJBLocalBusinessObjectImpl(); context.setEJBLocalBusinessObjectImpl(localBusinessObjImpl); localBusinessObjImpl.setContext(context); localBusinessObjImpl.setKey(context.getInstanceKey()); if (hasOptionalLocalBusinessView) { createOptionalEJBLocalBusinessObjectImpl(context); } if (hasLocalHomeView) { createEJBLocalObjectImpl(context); } if (hasRemoteHomeView) { createEJBObjectImpl(context); // enable remote invocations too } if (hasRemoteBusinessView) { createRemoteBusinessObjectImpl(context); } return localBusinessObjImpl; } private EJBLocalObjectImpl createOptionalEJBLocalBusinessObjectImpl(SessionContextImpl context) throws Exception { if (context.getOptionalEJBLocalBusinessObjectImpl() != null) { return context.getOptionalEJBLocalBusinessObjectImpl(); } EJBLocalObjectImpl optionalLocalBusinessObjImpl = instantiateOptionalEJBLocalBusinessObjectImpl(); context.setOptionalEJBLocalBusinessObjectImpl(optionalLocalBusinessObjImpl); optionalLocalBusinessObjImpl.setContext(context); optionalLocalBusinessObjImpl.setKey(context.getInstanceKey()); if (hasLocalBusinessView) { createEJBLocalBusinessObjectImpl(context); } if (hasLocalHomeView) { createEJBLocalObjectImpl(context); } if (hasRemoteHomeView) { createEJBObjectImpl(context); // enable remote invocations too } if (hasRemoteBusinessView) { createRemoteBusinessObjectImpl(context); } return optionalLocalBusinessObjImpl; } // called from createEJBObject and activateEJB and createEJBLocalObjectImpl private EJBObjectImpl createEJBObjectImpl(SessionContextImpl context) throws Exception { if (context.getEJBObjectImpl() != null) { return context.getEJBObjectImpl(); } // create EJBObject and associate it with the key Object sessionKey = context.getInstanceKey(); EJBObjectImpl ejbObjImpl = instantiateEJBObjectImpl(null, sessionKey); // introduce context and EJBObject to each other context.setEJBObjectImpl(ejbObjImpl); ejbObjImpl.setContext(context); // connect the EJBObject to the ProtocolManager // (creates the client-side stub too) byte[] sessionOID = uuidGenerator.keyToByteArray(sessionKey); EJBObject ejbStub = (EJBObject) remoteHomeRefFactory.createRemoteReference(sessionOID); context.setEJBStub(ejbStub); ejbObjImpl.setStub(ejbStub); if (hasRemoteBusinessView) { createRemoteBusinessObjectImpl(context); } if (isLocal) { if (hasLocalHomeView) { // enable local home invocations too createEJBLocalObjectImpl(context); } if (hasLocalBusinessView) { // enable local business invocations too createEJBLocalBusinessObjectImpl(context); } if (hasOptionalLocalBusinessView) { createOptionalEJBLocalBusinessObjectImpl(context); } } return ejbObjImpl; } private EJBObjectImpl createRemoteBusinessObjectImpl(SessionContextImpl context) throws Exception { if (context.getEJBRemoteBusinessObjectImpl() != null) { return context.getEJBRemoteBusinessObjectImpl(); } // create EJBObject EJBObjectImpl ejbBusinessObjImpl = instantiateRemoteBusinessObjectImpl(); context.setEJBRemoteBusinessObjectImpl(ejbBusinessObjImpl); ejbBusinessObjImpl.setContext(context); Object sessionKey = context.getInstanceKey(); ejbBusinessObjImpl.setKey(sessionKey); // connect the Remote object to the ProtocolManager // (creates the client-side stub too) byte[] sessionOID = uuidGenerator.keyToByteArray(sessionKey); for (RemoteBusinessIntfInfo next : remoteBusinessIntfInfo.values()) { java.rmi.Remote stub = next.referenceFactory.createRemoteReference(sessionOID); ejbBusinessObjImpl.setStub(next.generatedRemoteIntf.getName(), stub); } if (hasRemoteHomeView) { createEJBObjectImpl(context); } if (isLocal) { if (hasLocalHomeView) { // enable local home invocations too createEJBLocalObjectImpl(context); } if (hasLocalBusinessView) { // enable local business invocations too createEJBLocalBusinessObjectImpl(context); } if (hasOptionalLocalBusinessView) { createOptionalEJBLocalBusinessObjectImpl(context); } } return ejbBusinessObjImpl; } // Called from EJBObjectImpl.remove, EJBLocalObjectImpl.remove, // EJBHomeImpl.remove(Handle). @Override protected void removeBean(EJBLocalRemoteObject ejbo, Method removeMethod, boolean local) throws RemoveException, EJBException { EjbInvocation ejbInv = super.createEjbInvocation(); ejbInv.ejbObject = ejbo; ejbInv.isLocal = local; ejbInv.isRemote = !local; ejbInv.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(); ejbInv.isHome = (declaringClass == jakarta.ejb.EJBHome.class) || (declaringClass == jakarta.ejb.EJBLocalHome.class); try { preInvoke(ejbInv); removeBean(ejbInv); } catch (Exception e) { _logger.log(Level.FINE, "Exception while running pre-invoke : ejbName = [{0}]", e); ejbInv.exception = e; } finally { postInvoke(ejbInv); } if (ejbInv.exception != null) { if (ejbInv.exception instanceof RemoveException) { throw (RemoveException) ejbInv.exception; } else if (ejbInv.exception instanceof RuntimeException) { throw (RuntimeException) ejbInv.exception; } else if (ejbInv.exception instanceof Exception) { throw new EJBException((Exception) ejbInv.exception); } else { EJBException ejbEx = new EJBException(); ejbEx.initCause(ejbInv.exception); throw ejbEx; } } } /** * Called from EJBObjectImpl.remove(). * Note: preInvoke and postInvoke are called for remove(). */ private void removeBean(EjbInvocation inv) throws RemoveException { // At this point the EJB's state is always INVOKING // because EJBObjectImpl.remove() called preInvoke(). try { ejbProbeNotifier.ejbBeanDestroyedEvent( // getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName); SessionContextImpl sc = (SessionContextImpl) inv.context; Transaction tc = sc.getTransaction(); if (tc != null && tc.getStatus() != Status.STATUS_NO_TRANSACTION) { // EJB2.0 section 7.6.4: remove must always be called without // a transaction. throw new RemoveException("Cannot remove EJB: transaction in progress"); } // call ejbRemove on the EJB _logger.log(FINE, () -> "[SFSBContainer] Removing session: " + sc.getInstanceKey()); sc.setInEjbRemove(true); try { destroyBean(inv, sc); } catch (Throwable t) { _logger.log(Level.FINE, "exception thrown from SFSB PRE_DESTROY", t); } finally { sc.setInEjbRemove(false); } forceDestroyBean(sc); } catch (EJBException ex) { _logger.log(Level.FINE, "EJBException in removing bean", ex); throw ex; } catch (RemoveException ex) { _logger.log(Level.FINE, "Remove exception while removing bean", ex); throw ex; } catch (Exception ex) { _logger.log(Level.FINE, "Some exception while removing bean", ex); throw new EJBException(ex); } } /** * Force destroy the EJB and rollback any Tx it was associated with * Called from removeBean, timeoutBean and BaseContainer.postInvokeTx. * Note: EJB2.0 section 18.3.1 says that discarding an EJB * means that no methods other than finalize() should be invoked on it. */ @Override protected void forceDestroyBean(EJBContextImpl ctx) { SessionContextImpl sc = (SessionContextImpl) ctx; synchronized (sc) { if (sc.getState() == EJBContextImpl.BeanState.DESTROYED) { return; } // mark context as destroyed so no more invocations happen on it sc.setState(BeanState.DESTROYED); cleanupInstance(ctx); _logger.log(FINE, () -> "[SFSBContainer] (Force)Destroying session: " + sc.getInstanceKey()); Transaction prevTx = sc.getTransaction(); try { if (prevTx != null && prevTx.getStatus() != Status.STATUS_NO_TRANSACTION) { prevTx.setRollbackOnly(); } } catch (SystemException ex) { throw new EJBException(ex); } catch (IllegalStateException ex) { throw new EJBException(ex); } // remove the bean from the session store Object sessionKey = sc.getInstanceKey(); sessionBeanCache.remove(sessionKey, sc.existsInStore()); if (isRemote) { if (hasRemoteHomeView) { // disconnect the EJBObject from the context and vice versa EJBObjectImpl ejbObjImpl = sc.getEJBObjectImpl(); ejbObjImpl.clearContext(); ejbObjImpl.setRemoved(true); sc.setEJBObjectImpl(null); // disconnect the EJBObject from the ProtocolManager // so that no remote invocations can reach the EJBObject remoteHomeRefFactory.destroyReference(ejbObjImpl.getStub(), ejbObjImpl.getEJBObject()); } if (hasRemoteBusinessView) { EJBObjectImpl ejbBusinessObjImpl = sc.getEJBRemoteBusinessObjectImpl(); ejbBusinessObjImpl.clearContext(); ejbBusinessObjImpl.setRemoved(true); sc.setEJBRemoteBusinessObjectImpl(null); for (RemoteBusinessIntfInfo next : remoteBusinessIntfInfo.values()) { // disconnect from the ProtocolManager // so that no remote invocations can get through next.referenceFactory.destroyReference( ejbBusinessObjImpl.getStub(next.generatedRemoteIntf.getName()), ejbBusinessObjImpl.getEJBObject(next.generatedRemoteIntf.getName())); } } } if (isLocal) { if (hasLocalHomeView) { // disconnect the EJBLocalObject from the context // and vice versa EJBLocalObjectImpl localObjImpl = sc.getEJBLocalObjectImpl(); localObjImpl.clearContext(); localObjImpl.setRemoved(true); sc.setEJBLocalObjectImpl(null); } if (hasLocalBusinessView) { // disconnect the EJBLocalObject from the context // and vice versa EJBLocalObjectImpl localBusinessObjImpl = sc.getEJBLocalBusinessObjectImpl(); localBusinessObjImpl.clearContext(); localBusinessObjImpl.setRemoved(true); sc.setEJBLocalBusinessObjectImpl(null); } if (hasOptionalLocalBusinessView) { EJBLocalObjectImpl optionalLocalBusinessObjImpl = sc.getOptionalEJBLocalBusinessObjectImpl(); optionalLocalBusinessObjImpl.clearContext(); optionalLocalBusinessObjImpl.setRemoved(true); sc.setOptionalEJBLocalBusinessObjectImpl(null); } } destroyExtendedEMsForContext(sc); // tell the TM to release resources held by the bean transactionManager.componentDestroyed(sc); } } private void destroyExtendedEMsForContext(SessionContextImpl sc) { for (PhysicalEntityManagerWrapper emWrapper : sc.getExtendedEntityManagers()) { synchronized (extendedEMReferenceCountMap) { EntityManager em = emWrapper.getEM(); if (extendedEMReferenceCountMap.containsKey(em)) { EEMRefInfo refInfo = extendedEMReferenceCountMap.get(em); if (refInfo.refCount > 1) { refInfo.refCount--; _logger.log(Level.FINE, "Decremented RefCount ExtendedEM em: " + em); } else { _logger.log(Level.FINE, "DESTROYED ExtendedEM em: " + em); refInfo = extendedEMReferenceCountMap.remove(em); eemKey2EEMMap.remove(refInfo.getKey()); try { em.close(); } catch (Throwable th) { _logger.log(Level.FINE, "Exception during em.close()", th); } } } } } } @Override public boolean userTransactionMethodsAllowed(ComponentInvocation inv) { if (!isBeanManagedTran) { return false; } if (inv instanceof EjbInvocation) { SessionContextImpl sc = (SessionContextImpl) ((EjbInvocation) inv).context; // This will prevent setSessionContext access to // UserTransaction methods. return sc.getInstanceKey() != null; } return true; } public void removeTimedoutBean(EJBContextImpl ctx) { // check if there is an invocation in progress for // this instance. synchronized (ctx) { if (ctx.getState() != BeanState.INVOKING) { try { // call ejbRemove on the bean ctx.setInEjbRemove(true); destroyBean(null, ctx); } catch (Throwable t) { _logger.log(Level.FINE, "ejbRemove exception", t); } finally { ctx.setInEjbRemove(false); } if (_logger.isLoggable(FINE)) { SessionContextImpl sc = (SessionContextImpl) ctx; _logger.log(FINE, "[SFSBContainer] Removing TIMEDOUT session: " + sc.getInstanceKey()); } forceDestroyBean(ctx); } } } /** * Called when a remote invocation arrives for an EJB. * * @throws NoSuchObjectLocalException if the target object does not exist */ private SessionContextImpl _getContextForInstance(byte[] instanceKey) { Serializable sessionKey = (Serializable) uuidGenerator.byteArrayToKey(instanceKey, 0, -1); _logger.log(FINE, () -> "[SFSBContainer] Got request for: " + sessionKey); while (true) { SessionContextImpl sc = (SessionContextImpl) sessionBeanCache.lookupEJB(sessionKey, this, null); if (sc == null) { // EJB2.0 section 7.6 // Note: the NoSuchObjectLocalException gets converted to a // remote exception by the protocol manager. throw new NoSuchObjectLocalException("Invalid Session Key ( " + sessionKey + ")"); } synchronized (sc) { switch (sc.getState()) { case PASSIVATED: // Next cache.lookup() == different_ctx case DESTROYED: // Next cache.lookup() == null break; default: return sc; } } } } @Override protected EJBObjectImpl getEJBObjectImpl(byte[] instanceKey) { SessionContextImpl sc = _getContextForInstance(instanceKey); return sc.getEJBObjectImpl(); } @Override EJBObjectImpl getEJBRemoteBusinessObjectImpl(byte[] instanceKey) { SessionContextImpl sc = _getContextForInstance(instanceKey); return sc.getEJBRemoteBusinessObjectImpl(); } /** * Called from EJBLocalObjectImpl.getLocalObject() while deserializing * a local object reference. */ @Override protected EJBLocalObjectImpl getEJBLocalObjectImpl(Object sessionKey) { // Create an EJBLocalObject reference which // is *not* associated with a SessionContext. That way, the // session bean context lookup will be done lazily whenever // the reference is actually accessed. This avoids I/O in the // case that the reference points to a passivated session bean. // It's also consistent with the deserialization approach used // throughout the container. e.g. a timer reference is deserialized // from its handle without checking it against the timer database. EJBLocalObjectImpl localObjImpl; try { localObjImpl = instantiateEJBLocalObjectImpl(sessionKey); } catch (Exception ex) { EJBException ejbEx = new EJBException(); ejbEx.initCause(ex); throw ejbEx; } return localObjImpl; } @Override EJBLocalObjectImpl getEJBLocalBusinessObjectImpl(Object sessionKey) { // Create an EJBLocalObject reference which // is *not* associated with a SessionContext. That way, the // session bean context lookup will be done lazily whenever // the reference is actually accessed. This avoids I/O in the // case that the reference points to a passivated session bean. // It's also consistent with the deserialization approach used // throughout the container. e.g. a timer reference is deserialized // from its handle without checking it against the timer database. try { EJBLocalObjectImpl localBusinessObjImpl = instantiateEJBLocalBusinessObjectImpl(); localBusinessObjImpl.setKey(sessionKey); return localBusinessObjImpl; } catch (Exception ex) { EJBException ejbEx = new EJBException(); ejbEx.initCause(ex); throw ejbEx; } } @Override EJBLocalObjectImpl getOptionalEJBLocalBusinessObjectImpl(Object sessionKey) { // Create an EJBLocalObject reference which // is *not* associated with a SessionContext. That way, the // session bean context lookup will be done lazily whenever // the reference is actually accessed. This avoids I/O in the // case that the reference points to a passivated session bean. // It's also consistent with the deserialization approach used // throughout the container. e.g. a timer reference is deserialized // from its handle without checking it against the timer database. try { EJBLocalObjectImpl localBusinessObjImpl = instantiateOptionalEJBLocalBusinessObjectImpl(); localBusinessObjImpl.setKey(sessionKey); return localBusinessObjImpl; } catch (Exception ex) { EJBException ejbEx = new EJBException(); ejbEx.initCause(ex); throw ejbEx; } } /** * Check if the given EJBObject/LocalObject has been removed. * * @throws NoSuchObjectLocalException if the object has been removed. */ @Override protected void checkExists(EJBLocalRemoteObject ejbObj) { if (ejbObj.isRemoved()) { throw new NoSuchObjectLocalException("Bean has been removed"); } } /** * Called from preInvoke which is called from the EJBObject * for local and remote invocations. */ @Override public ComponentContext _getContext(EjbInvocation ejbInvocation) { EJBLocalRemoteObject ejbObject = ejbInvocation.ejbObject; Serializable sessionKey = (Serializable) ejbObject.getKey(); logTraceInfo(ejbInvocation, sessionKey, "Trying to get context"); SessionContextImpl sessionContext = getSessionContext(ejbInvocation, ejbObject, sessionKey); MethodLockInfo lockInfo = ejbInvocation.invocationInfo.methodLockInfo; boolean allowSerializedAccess = isAllowSerializedAccess(lockInfo); if (allowSerializedAccess) { aquireStatefulLockOrThrow(ejbInvocation, sessionContext, lockInfo); // Explicitly set state to track that we're holding the lock for this invocation. // No matter what we need to ensure that the lock is released. In some // cases releaseContext() isn't called so for safety we'll have more than one // place that can potentially release the lock. The invocation state will ensure // we don't accidently unlock too many times. ejbInvocation.setHoldingSFSBSerializedLock(true); } SessionContextImpl context = null; try { synchronized (sessionContext) { SessionContextImpl newSessionContext = sessionContext; if (sessionContext.getState() == PASSIVATED) { // This is possible if the EJB was passivated after // the last lookupEJB. Try to activate it again. newSessionContext = (SessionContextImpl) sessionBeanCache.lookupEJB(sessionKey, this, ejbObject); if (newSessionContext == null) { logTraceInfo(ejbInvocation, sessionKey, "Context does not exist"); // EJB2.0 section 7.6 throw new NoSuchObjectLocalException("The EJB does not exist. key: " + sessionKey); } // Swap any stateful lock that was set on the original sc newSessionContext.setStatefulWriteLock(sessionContext); } // Acquire the lock again, in case a new session context was returned. synchronized (newSessionContext) { // newSessionContext could be same as sc // Check & set the state of the EJB if (newSessionContext.getState() == DESTROYED) { logTraceInfo(ejbInvocation, sessionKey, "Got destroyed context"); throw new NoSuchObjectLocalException("The EJB does not exist. session-key: " + sessionKey); } else if (newSessionContext.getState() == INVOKING) { handleConcurrentInvocation(allowSerializedAccess, ejbInvocation, newSessionContext, sessionKey); } if (newSessionContext.getState() == READY) { decrementMethodReadyStat(); } if (isHAEnabled) { doVersionCheck(ejbInvocation, sessionKey, sessionContext); } newSessionContext.setState(INVOKING); context = newSessionContext; } } // Touch the context here so timestamp is set & timeout is prevented context.touch(); if (context.existsInStore() && removalGracePeriodInSeconds > 0) { updateLastPersistedTime(context, sessionKey); } logTraceInfo(ejbInvocation, context, "Got Context!!"); } catch (RuntimeException t) { // ReleaseContext isn't called if this method throws an exception, // so make sure to release any sfsb lock releaseSFSBSerializedLock(ejbInvocation, sessionContext); throw t; } return context; } private void updateLastPersistedTime(SessionContextImpl context, Serializable sessionKey) { long now = System.currentTimeMillis(); long threshold = now - (removalGracePeriodInSeconds * 1000L); if (context.getLastPersistedAt() <= threshold) { try { backingStore.updateTimestamp(sessionKey, now); context.setLastPersistedAt(System.currentTimeMillis()); } catch (BackingStoreException sfsbEx) { _logger.log(WARNING, COULDNT_UPDATE_TIMESTAMP_FOR_EXCEPTION, new Object[] {sessionKey, sfsbEx}); _logger.log(FINE, "Couldn't update timestamp for: " + sessionKey, sfsbEx); } } } private void aquireStatefulLockOrThrow(EjbInvocation ejbInvocation, SessionContextImpl sessionContext, MethodLockInfo lockInfo) { if (isBlockWithTimeout(lockInfo)) { try { if (!isAquireLockWithTimeout(sessionContext, lockInfo)) { throw new ConcurrentAccessTimeoutException( // "Serialized access attempt on method " + ejbInvocation.beanMethod // + " for ejb " + ejbDescriptor.getName() // + " timed out after " + lockInfo.getTimeout() + " " + lockInfo.getTimeUnit()); } } catch (InterruptedException ie) { throw (ConcurrentAccessTimeoutException) new ConcurrentAccessTimeoutException( "Serialized access attempt on method " + ejbInvocation.beanMethod // + " for ejb " + ejbDescriptor.getName() // + " was interrupted within " + +lockInfo.getTimeout() + " " + lockInfo.getTimeUnit() // ).initCause(ie); } } else { sessionContext.getStatefulWriteLock().lock(); } } private boolean isAquireLockWithTimeout(SessionContextImpl sessionContext, MethodLockInfo lockInfo) throws InterruptedException { return sessionContext.getStatefulWriteLock().tryLock(lockInfo.getTimeout(), lockInfo.getTimeUnit()); } private boolean isAllowSerializedAccess(MethodLockInfo lockInfo) { return lockInfo == null || lockInfo.getTimeout() != CONCURRENCY_NOT_ALLOWED; } private boolean isBlockWithTimeout(MethodLockInfo lockInfo) { return lockInfo != null && lockInfo.getTimeout() != BLOCK_INDEFINITELY; } private SessionContextImpl getSessionContext(EjbInvocation ejbInvocation, EJBLocalRemoteObject ejbObject, Serializable sessionKey) { SessionContextImpl sessionContext = ejbObject.getContext(); if (sessionContext == null) { // This is possible if the EJB was destroyed or passivated. // Try to activate it again. sessionContext = (SessionContextImpl) sessionBeanCache.lookupEJB(sessionKey, this, ejbObject); } if (sessionContext == null || sessionContext.getState() == DESTROYED) { logTraceInfo(ejbInvocation, sessionKey, "Context already destroyed"); // EJB2.0 section 7.6 throw new NoSuchObjectLocalException("The EJB does not exist. session-key: " + sessionKey); } return sessionContext; } @Override public boolean isHAEnabled() { return isHAEnabled; } private void doVersionCheck(EjbInvocation ejbInvocation, Object sessionKey, SessionContextImpl sessionContext) { EJBLocalRemoteObject ejbObject = ejbInvocation.ejbObject; long clientVersion = NO_VERSION; if (!ejbInvocation.isLocal && sfsbVersionManager != null) { clientVersion = sfsbVersionManager.getRequestClientVersion(); sfsbVersionManager.clearRequestClientVersion(); sfsbVersionManager.clearResponseClientVersion(); } if (ejbObject != null) { if (clientVersion == NO_VERSION) { clientVersion = ejbObject.getSfsbClientVersion(); } long ctxVersion = sessionContext.getVersion(); if (_logger.isLoggable(FINE)) { _logger.log(FINE, "doVersionCheck(): for: {" // + ejbDescriptor.getName() + "." + ejbInvocation.method.getName() + " <=> " + sessionKey + "} clientVersion: " + clientVersion + " == " + ctxVersion); } if (clientVersion > ctxVersion) { throw new NoSuchObjectLocalException("Found only a stale version " // + " clientVersion: " + clientVersion // + " contextVersion: " + ctxVersion); } } } private void handleConcurrentInvocation(boolean allowSerializedAccess, EjbInvocation inv, SessionContextImpl sc, Object sessionKey) { logTraceInfo(inv, sessionKey, "Another invocation in progress"); if (allowSerializedAccess) { // Check for loopback call to avoid deadlock. // TODO: It's not entirely clear why this check is there, since the StatefulWriteLock is a // a reentrant lock that is only granted to the current thread. holdCount > 1 // can only be true if the same thread holds the lock, but why would this deadlock? // What is this exactly protecting against? if (sc.getStatefulWriteLock().getHoldCount() > 1) { String calleeEjbName = inv.invocationInfo.ejbName; String callerEjbName = getCallerEjbName(); if (!calleeEjbName.equals(callerEjbName)) { throw new IllegalLoopbackException( // "Illegal Reentrant Access : Attempt to make a loopback call on method '" + inv.beanMethod + "' for stateful session bean " + ejbDescriptor.getName()); } } } else { String errMsg = "Concurrent Access attempt on method '" + inv.beanMethod + "' of SessionBean '" + ejbDescriptor.getName() + "' is prohibited. SFSB instance is executing another request. " + "[session-key: " + sessionKey + "]"; ConcurrentAccessException conEx = new ConcurrentAccessException(errMsg); if (inv.isBusinessInterface) { throw conEx; } // there is an invocation in progress for this instance // throw an exception (EJB2.0 section 7.5.6). throw new EJBException(conEx); } } private String getCallerEjbName() { // Current invocation represents the invocation of the bean (if any) that calls this container ComponentInvocation callerInvocation = invocationManager.getCurrentInvocation(); if (callerInvocation instanceof EjbInvocation) { return ((EjbInvocation) callerInvocation).invocationInfo.ejbName; } return null; } @Override protected void postInvokeTx(EjbInvocation inv) throws Exception { // Intercept postInvokeTx call to perform any @Remove logic // before tx commits. super.postInvokeTx() must *always* // be called. // If this was an invocation of a remove-method if (inv.invocationInfo.removalInfo != null) { InvocationInfo invInfo = inv.invocationInfo; EjbRemovalInfo removeInfo = invInfo.removalInfo; if (retainAfterRemoveMethod(inv, removeInfo)) { // Do nothing } else { // If there is a tx, remove bean from ContainerSynch so it // won't receive any SessionSynchronization callbacks. // We delay the PreDestroy callback and instance destruction // until releaseContext so that PreDestroy won't run within // the business method's tx. SessionContextImpl sc = (SessionContextImpl) inv.context; Transaction tx = sc.getTransaction(); if (tx != null) { ejbContainerUtilImpl.getContainerSync(tx).removeBean(sc); } } } super.postInvokeTx(inv); } /** * Should only be called when a method is known to be a remove method. * * @return true if the removal should be skipped, false otherwise. */ private boolean retainAfterRemoveMethod(EjbInvocation inv, EjbRemovalInfo rInfo) { return rInfo.getRetainIfException() && isApplicationException(inv.exceptionFromBeanMethod); } /** * Called from preInvoke which is called from the EJBObject for local and * remote invocations. */ @Override public void releaseContext(EjbInvocation inv) { SessionContextImpl sc = (SessionContextImpl) inv.context; // Make sure everything is within try block so we can be assured that // any instance lock is released in the finally block. try { // check if the bean was destroyed if (sc.getState() == BeanState.DESTROYED) { return; } // we're sure that no concurrent thread can be using this // context, so no need to synchronize. Transaction tx = sc.getTransaction(); // If this was an invocation of a remove-method if (inv.invocationInfo.removalInfo != null) { InvocationInfo invInfo = inv.invocationInfo; EjbRemovalInfo removeInfo = invInfo.removalInfo; if (retainAfterRemoveMethod(inv, removeInfo)) { _logger.log(Level.FINE, () -> "Skipping destruction of SFSB " + invInfo.ejbName // + " after @Remove method " + invInfo.method // + " due to (retainIfException == true) and exception " + inv.exception); } else { try { destroyBean(inv, sc); } catch (Throwable t) { _logger.log(Level.FINE, "@Remove.preDestroy exception", t); } // Explicitly null out transaction association in bean's context. // Otherwise, forceDestroyBean() will mark that tx for rollback, // which could incorrectly rollback a client-propagated transaction. sc.setTransaction(null); forceDestroyBean(sc); // The bean has been detroyed so just skip any remaining processing. return; } } if (tx == null || tx.getStatus() == Status.STATUS_NO_TRANSACTION) { // The Bean executed with no tx, or with a tx and // container.afterCompletion() was already called. if (sc.getState() != BeanState.READY) { if (sc.isAfterCompletionDelayed()) { // ejb.afterCompletion was not called yet // because of container.afterCompletion may have // been called concurrently with this invocation. logTraceInfo(inv, sc, "Calling delayed afterCompletion"); callEjbAfterCompletion(sc, sc.getCompletedTxStatus()); } if (sc.getState() != BeanState.DESTROYED) { // callEjbAfterCompletion could make state as DESTROYED sc.setState(BeanState.READY); handleEndOfMethodCheckpoint(sc, inv); } } if ((sc.getState() != BeanState.DESTROYED) && isHAEnabled) { syncClientVersion(inv, sc); } } else { if ((sc.getState() != BeanState.DESTROYED) && isHAEnabled) { syncClientVersion(inv, sc); } sc.setState(BeanState.INCOMPLETE_TX); logTraceInfo(inv, sc, "Marking state == INCOMPLETE_TX"); } } catch (SystemException ex) { throw new EJBException(ex); } finally { releaseSFSBSerializedLock(inv, sc); } } private void releaseSFSBSerializedLock(EjbInvocation inv, SessionContextImpl sc) { if (inv.holdingSFSBSerializedLock()) { inv.setHoldingSFSBSerializedLock(false); sc.getStatefulWriteLock().unlock(); } } @Override protected void afterBegin(EJBContextImpl context) { // TX_BEAN_MANAGED EJBs cannot implement SessionSynchronization // Do not call afterBegin if it is a transactional lifecycle callback if (isBeanManagedTran || ((SessionContextImpl) context).getInLifeCycleCallback()) { return; } // Note: this is only called for business methods. // For SessionBeans non-business methods are never called with a Tx. Object ejb = context.getEJB(); if (afterBeginMethod != null ) { try { afterBeginMethod.invoke(ejb, null); } catch (Exception ex) { // Error during afterBegin, so discard bean: EJB2.0 18.3.3 forceDestroyBean(context); throw new EJBException("Error during SessionSynchronization.afterBegin(), EJB instance discarded", ex); } } //Register CMT Beans for end of Tx Checkpointing //Note:- We will never reach here for TX_BEAN_MANAGED if (isHAEnabled) { try { ContainerSynchronization cSync = ejbContainerUtilImpl.getContainerSync(context.getTransaction()); cSync.registerForTxCheckpoint((SessionContextImpl) context); } catch (jakarta.transaction.RollbackException rollEx) { _logger.log(Level.WARNING, CANNOT_REGISTER_BEAN_FOR_CHECKPOINTING, rollEx); } catch (jakarta.transaction.SystemException sysEx) { _logger.log(Level.WARNING, CANNOT_REGISTER_BEAN_FOR_CHECKPOINTING, sysEx); } } } @Override protected void beforeCompletion(EJBContextImpl context) { // SessionSync calls on TX_BEAN_MANAGED SessionBeans // are not allowed // Do not call beforeCompletion if it is a transactional lifecycle callback if (isBeanManagedTran || beforeCompletionMethod == null || ((SessionContextImpl) context).getInLifeCycleCallback()) { return; } Object ejb = context.getEJB(); // No need to check for a concurrent invocation // because beforeCompletion can only be called after // all business methods are completed. EjbInvocation inv = super.createEjbInvocation(ejb, context); invocationManager.preInvoke(inv); try { transactionManager.enlistComponentResources(); beforeCompletionMethod.invoke(ejb, null); } catch (Exception ex) { // Error during beforeCompletion, so discard bean: EJB2.0 18.3.3 try { forceDestroyBean(context); } catch (Exception e) { _logger.log(Level.FINE, "error destroying bean", e); } throw new EJBException("Error during SessionSynchronization.beforeCompletion, EJB instance discarded", ex); } finally { invocationManager.postInvoke(inv); } } // Called from SyncImpl.afterCompletion // May be called asynchronously during tx timeout // or on the same thread as tx.commit @Override protected void afterCompletion(EJBContextImpl context, int status) { if (context.getState() == BeanState.DESTROYED) { return; } SessionContextImpl sc = (SessionContextImpl) context; boolean committed = (status == Status.STATUS_COMMITTED) || (status == Status.STATUS_NO_TRANSACTION); sc.setTransaction(null); // Do not call afterCompletion if it is a transactional lifecycle callback if (sc.getInLifeCycleCallback()) { return; } // SessionSync calls on TX_BEAN_MANAGED SessionBeans // are not allowed. if (!isBeanManagedTran && afterCompletionMethod != null) { // Check for a concurrent invocation // because afterCompletion can be called asynchronously // during rollback because of transaction timeout if (sc.getState() == BeanState.INVOKING && !sc.isTxCompleting()) { // Cant invoke ejb.afterCompletion now because there is // already some invocation in progress on the ejb. sc.setAfterCompletionDelayed(true); sc.setCompletedTxStatus(committed); logTraceInfo(sc, "AfterCompletion delayed"); return; } callEjbAfterCompletion(sc, committed); } //callEjbAfterCompletion can set state as DESTROYED if (sc.getState() != BeanState.DESTROYED) { if (isHAEnabled) { if (isBeanManagedTran) { sc.setTxCheckpointDelayed(true); logTraceInfo(sc, "(BMT) Checkpoint delayed"); } } else { if (!isBeanManagedTran) { logTraceInfo(sc, "Released context"); sc.setState(BeanState.READY); incrementMethodReadyStat(); } } } } SimpleMetadata getSFSBBeanState(SessionContextImpl sc) { //No need to synchronize SimpleMetadata simpleMetadata = null; try { if (containerState != CONTAINER_STARTED && containerState != CONTAINER_STOPPED) { _logger.log(Level.FINE, () -> "getSFSBBeanState() returning because containerState: " + containerState); return null; } if (sc.getState() == BeanState.DESTROYED) { return null; } Object ejb = sc.getEJB(); EjbInvocation ejbInv = createEjbInvocation(ejb, sc); invocationManager.preInvoke(ejbInv); boolean needToDoPostInvokeTx = false; boolean destroyBean = false; synchronized (sc) { try { needToDoPostInvokeTx = callLifecycleCallbackInTxIfUsed(ejbInv, sc, prePassivateInvInfo, CallbackType.PRE_PASSIVATE); sc.setLastPersistedAt(System.currentTimeMillis()); long newCtxVersion = sc.incrementAndGetVersion(); byte[] serializedState = serializeContext(sc); simpleMetadata = new SimpleMetadata(// sc.getVersion(), System.currentTimeMillis(), removalGracePeriodInSeconds * 1000L, serializedState); simpleMetadata.setVersion(newCtxVersion); needToDoPostInvokeTx = callLifecycleCallbackInTxIfUsed( // ejbInv, sc, postActivateInvInfo, CallbackType.POST_ACTIVATE); //Do not set sc.setExistsInStore() here } catch (java.io.NotSerializableException serEx) { _logger.log(Level.WARNING, ERROR_DURING_CHECKPOINT_3PARAMs, new Object[] {ejbDescriptor.getName(), sc.getInstanceKey(), serEx}); _logger.log(Level.FINE, "sfsb checkpoint error. Key: " + sc.getInstanceKey(), serEx); destroyBean = true; } catch (Throwable ex) { _logger.log(Level.WARNING, SFSB_CHECKPOINT_ERROR_NAME, new Object[] {ejbDescriptor.getName()}); _logger.log(Level.WARNING, SFSB_CHECKPOINT_ERROR_KEY, new Object[] {sc.getInstanceKey(), ex}); destroyBean = true; } finally { invocationManager.postInvoke(ejbInv); completeLifecycleCallbackTxIfUsed(ejbInv, sc, needToDoPostInvokeTx); if (destroyBean) { try { forceDestroyBean(sc); } catch (Exception e) { _logger.log(Level.FINE, "error destroying bean", e); } } } } //synchronized } catch (Throwable th) { _logger.log(Level.WARNING, SFSB_CHECKPOINT_ERROR_NAME, new Object[]{ejbDescriptor.getName(), th}); } return simpleMetadata; } void txCheckpointCompleted(SessionContextImpl sc) { if (sc.getState() != BeanState.DESTROYED) { //We did persist this ctx in the store sc.setExistsInStore(true); sc.setState(BeanState.READY); incrementMethodReadyStat(); } } private void callEjbAfterCompletion(SessionContextImpl context, boolean status) { if (afterCompletionMethod == null) { return; } Object ejb = context.getEJB(); EjbInvocation ejbInv = createEjbInvocation(ejb, context); invocationManager.preInvoke(ejbInv); try { context.setInAfterCompletion(true); afterCompletionMethod.invoke(ejb, status); // reset flags context.setAfterCompletionDelayed(false); context.setTxCompleting(false); } catch (Exception ex) { Throwable realException = ex; if (ex instanceof InvocationTargetException) { realException = ((InvocationTargetException) ex).getTargetException(); } // Error during afterCompletion, so discard bean: EJB2.0 18.3.3 try { forceDestroyBean(context); } catch (Exception e) { _logger.log(Level.FINE, "error destroying bean", e); } _logger.log(Level.INFO, AFTER_COMPLETION_EXCEPTION, realException); // No use throwing an exception here, since the tx has already // completed, and afterCompletion may be called asynchronously // when there is no client to receive the exception. } finally { context.setInAfterCompletion(false); invocationManager.postInvoke(ejbInv); } } public boolean canPassivateEJB(ComponentContext context) { SessionContextImpl sc = (SessionContextImpl) context; return (sc.getState() == BeanState.READY); } // called asynchronously from the Recycler @Override public boolean passivateEJB(ComponentContext context) { SessionContextImpl sc = (SessionContextImpl) context; boolean success = false; try { if (ejbDescriptor.getApplication().getKeepStateResolved() == false) { if (containerState != CONTAINER_STARTED && containerState != CONTAINER_STOPPED) { _logger.log(Level.WARNING, PASSIVATE_EJB_RETURNING_BECAUSE_CONTAINER_STATE, containerState); return false; } } if (sc.getState() == BeanState.DESTROYED) { return false; } if (_logger.isLoggable(FINE)) { _logger.log(FINE, traceInfoPrefix // + "Passivating context " + sc.getInstanceKey() // + "; current-state = " + convertCtxStateToString(sc)); } Object ejb = sc.getEJB(); EjbInvocation ejbInv = createEjbInvocation(ejb, sc); invocationManager.preInvoke(ejbInv); success = false; boolean needToDoPostInvokeTx = false; boolean destroyBean = false; synchronized (sc) { try { // dont passivate if there is a Tx/invocation in progress // for this instance. if (!sc.canBePassivated()) { return false; } Serializable instanceKey = (Serializable) sc.getInstanceKey(); if (sessionBeanCache.eligibleForRemovalFromCache(sc, instanceKey)) { // remove the EJB since removal-timeout has elapsed sc.setState(BeanState.DESTROYED); needToDoPostInvokeTx = callLifecycleCallbackInTxIfUsed( // ejbInv, sc, preDestroyInvInfo, CallbackType.PRE_DESTROY); sessionBeanCache.remove(instanceKey, sc.existsInStore()); } else { // passivate the EJB sc.setState(BeanState.PASSIVATED); decrementMethodReadyStat(); needToDoPostInvokeTx = callLifecycleCallbackInTxIfUsed( // ejbInv, sc, prePassivateInvInfo, CallbackType.PRE_PASSIVATE); sc.setLastPersistedAt(System.currentTimeMillis()); boolean saved = false; try { saved = sessionBeanCache.passivateEJB(sc, instanceKey); } catch (EMNotSerializableException emNotSerEx) { _logger.log(Level.WARNING, EXTENDED_EM_NOT_SERIALIZABLE, emNotSerEx); _logger.log(Level.FINE, "Extended EM not serializable", emNotSerEx); saved = false; } if (!saved) { // TODO - add a flag to reactivate in the same tx // Complete previous tx completeLifecycleCallbackTxIfUsed(ejbInv, sc, needToDoPostInvokeTx); needToDoPostInvokeTx = callLifecycleCallbackInTxIfUsed( // ejbInv, sc, postActivateInvInfo, CallbackType.POST_ACTIVATE); sc.setState(BeanState.READY); incrementMethodReadyStat(); return false; } } // V2: sfsbStoreMonitor.incrementPassivationCount(true); cacheProbeNotifier.ejbBeanPassivatedEvent( // getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName, true); transactionManager.componentDestroyed(sc); decrementRefCountsForEEMs(sc); if (isRemote) { if (hasRemoteHomeView) { // disconnect the EJBObject from the EJB EJBObjectImpl ejbObjImpl = sc.getEJBObjectImpl(); ejbObjImpl.clearContext(); sc.setEJBObjectImpl(null); // disconnect the EJBObject from ProtocolManager // so that no state is held by ProtocolManager remoteHomeRefFactory.destroyReference(ejbObjImpl.getStub(), ejbObjImpl.getEJBObject()); } if (hasRemoteBusinessView) { // disconnect the EJBObject from the EJB EJBObjectImpl ejbBusinessObjImpl = sc.getEJBRemoteBusinessObjectImpl(); ejbBusinessObjImpl.clearContext(); sc.setEJBRemoteBusinessObjectImpl(null); for (RemoteBusinessIntfInfo next : remoteBusinessIntfInfo.values()) { next.referenceFactory.destroyReference( // ejbBusinessObjImpl.getStub(), // ejbBusinessObjImpl.getEJBObject(next.generatedRemoteIntf.getName())); } } } if (isLocal) { long version = sc.getVersion(); if (hasLocalHomeView) { // disconnect the EJBLocalObject from the EJB EJBLocalObjectImpl localObjImpl = sc.getEJBLocalObjectImpl(); localObjImpl.setSfsbClientVersion(version); localObjImpl.clearContext(); sc.setEJBLocalObjectImpl(null); } if (hasLocalBusinessView) { EJBLocalObjectImpl localBusinessObjImpl = sc.getEJBLocalBusinessObjectImpl(); localBusinessObjImpl.setSfsbClientVersion(version); localBusinessObjImpl.clearContext(); sc.setEJBLocalBusinessObjectImpl(null); } if (hasOptionalLocalBusinessView ) { EJBLocalObjectImpl optLocalBusObjImpl = sc.getOptionalEJBLocalBusinessObjectImpl(); optLocalBusObjImpl.setSfsbClientVersion(version); optLocalBusObjImpl.clearContext(); sc.setOptionalEJBLocalBusinessObjectImpl(null); } } logTraceInfo(sc, "Successfully passivated"); } catch (NotSerializableException nsEx) { // V2: sfsbStoreMonitor.incrementPassivationCount(false); cacheProbeNotifier.ejbBeanPassivatedEvent(getContainerId(), // containerInfo.appName, containerInfo.modName, containerInfo.ejbName, false); _logger.log(Level.WARNING, ERROR_DURING_PASSIVATION, new Object[] {sc, nsEx}); _logger.log(Level.FINE, "sfsb passivation error", nsEx); // Error during passivate, so discard bean: EJB2.0 18.3.3 destroyBean = true; } catch (Throwable ex) { // V2: sfsbStoreMonitor.incrementPassivationCount(false); cacheProbeNotifier.ejbBeanPassivatedEvent(getContainerId(), // containerInfo.appName, containerInfo.modName, containerInfo.ejbName, false); _logger.log(Level.WARNING, PASSIVATION_ERROR_1PARAM, new Object[] {ejbDescriptor.getName() + " <==> " + sc}); _logger.log(Level.WARNING, SFSB_PASSIVATION_ERROR_1PARAM, new Object[] {sc.getInstanceKey(), ex}); // Error during passivate, so discard bean: EJB2.0 18.3.3 destroyBean = true; } finally { invocationManager.postInvoke(ejbInv); completeLifecycleCallbackTxIfUsed(ejbInv, sc, needToDoPostInvokeTx); if (destroyBean) { try { forceDestroyBean(sc); } catch (Exception e) { _logger.log(Level.FINE, "error destroying bean", e); } } } } //synchronized } catch (Exception ex) { _logger.log(Level.WARNING, PASSIVATION_ERROR_1PARAM, new Object[] {ejbDescriptor.getName(), ex}); } return success; } @Override public int getPassivationBatchCount() { return this.passivationBatchCount; } public void setPassivationBatchCount(int count) { this.passivationBatchCount = count; } // called asynchronously from the Recycler @Override public boolean passivateEJB(StatefulEJBContext sfsbCtx) { return passivateEJB((ComponentContext) sfsbCtx.getSessionContext()); } public long getMethodReadyCount() { return statMethodReadyCount; } public long getPassiveCount() { return (sfsbStoreMonitor == null) ? 0 : sfsbStoreMonitor.getNumPassivations(); } // called from StatefulSessionStore @Override public void activateEJB(Object sessionKey, StatefulEJBContext sfsbCtx, Object cookie) { SessionContextImpl context = (SessionContextImpl) sfsbCtx.getSessionContext(); logTraceInfo(context, "Attempting to activate"); EJBLocalRemoteObject ejbObject = (EJBLocalRemoteObject) cookie; Object ejb = context.getEJB(); EjbInvocation ejbInv = createEjbInvocation(ejb, context); invocationManager.preInvoke(ejbInv); boolean needToDoPostInvokeTx = false; try { // we're sure that no concurrent thread can be using this bean // so no need to synchronize. // No need to call enlistComponentResources here because // ejbActivate executes in unspecified tx context (spec 6.6.1) // Set the timestamp here, else Recycler might remove this bean! context.touch(); context.setContainer(this); context.setState(BeanState.READY); incrementMethodReadyStat(); context.setInstanceKey(sessionKey); context.setExistsInStore(true); context.initializeStatefulWriteLock(); if (ejbObject == null) { // This MUST be a remote invocation if (hasRemoteHomeView) { createEJBObjectImpl(context); } else { createRemoteBusinessObjectImpl(context); } } else if (ejbObject instanceof EJBObjectImpl) { EJBObjectImpl eo = (EJBObjectImpl) ejbObject; ejbObject.setContext(context); ejbObject.setKey(sessionKey); byte[] sessionOID = uuidGenerator.keyToByteArray(sessionKey); if (eo.isRemoteHomeView()) { // introduce context and EJBObject to each other context.setEJBObjectImpl(eo); EJBObject ejbStub = (EJBObject) remoteHomeRefFactory.createRemoteReference(sessionOID); eo.setStub(ejbStub); context.setEJBStub(ejbStub); if (hasRemoteBusinessView) { createRemoteBusinessObjectImpl(context); } } else { context.setEJBRemoteBusinessObjectImpl(eo); for (RemoteBusinessIntfInfo next : remoteBusinessIntfInfo.values()) { java.rmi.Remote stub = next.referenceFactory.createRemoteReference(sessionOID); eo.setStub(next.generatedRemoteIntf.getName(), stub); } if (hasRemoteHomeView) { createEJBObjectImpl(context); } } if (isLocal) { // create localObj too if (hasLocalHomeView) { createEJBLocalObjectImpl(context); } if (hasLocalBusinessView) { createEJBLocalBusinessObjectImpl(context); } if (hasOptionalLocalBusinessView) { createOptionalEJBLocalBusinessObjectImpl(context); } } } else if (ejbObject instanceof EJBLocalObjectImpl) { EJBLocalObjectImpl elo = (EJBLocalObjectImpl) ejbObject; ejbObject.setContext(context); ejbObject.setKey(sessionKey); if (elo.isLocalHomeView()) { context.setEJBLocalObjectImpl(elo); if (hasLocalBusinessView) { createEJBLocalBusinessObjectImpl(context); } if (hasOptionalLocalBusinessView) { createOptionalEJBLocalBusinessObjectImpl(context); } } else if( elo.isOptionalLocalBusinessView() ) { context.setOptionalEJBLocalBusinessObjectImpl(elo); if (hasLocalBusinessView) { createEJBLocalBusinessObjectImpl(context); } if (hasLocalHomeView) { createEJBLocalObjectImpl(context); } } else { context.setEJBLocalBusinessObjectImpl(elo); if (hasLocalHomeView) { createEJBLocalObjectImpl(context); } if (hasOptionalLocalBusinessView) { createOptionalEJBLocalBusinessObjectImpl(context); } } if (hasRemoteHomeView) { // create remote obj too createEJBObjectImpl(context); } if (hasRemoteBusinessView) { createRemoteBusinessObjectImpl(context); } } //Now populate the EEM maps in this context repopulateEEMMapsInContext(sessionKey, context); try { needToDoPostInvokeTx = callLifecycleCallbackInTxIfUsed( // ejbInv, context, postActivateInvInfo, CallbackType.POST_ACTIVATE); } catch (Throwable th) { throw (EJBException) new EJBException("Error during activation" + sessionKey).initCause(th); } long now = System.currentTimeMillis(); try { backingStore.updateTimestamp((Serializable) sessionKey, now); context.setLastPersistedAt(now); } catch (BackingStoreException sfsbEx) { _logger.log(Level.WARNING, COULDNT_UPDATE_TIMESTAMP_FOR_EXCEPTION, new Object[]{sessionKey, sfsbEx}); _logger.log(Level.FINE, "Couldn't update timestamp for: " + sessionKey, sfsbEx); } logTraceInfo(context, "Successfully activated"); _logger.log(Level.FINE, () -> "Activated: " + sessionKey); } catch (Exception ex) { logTraceInfo(context, "Failed to activate"); _logger.log(Level.SEVERE, SFSB_ACTIVATION_ERROR, new Object[] {sessionKey, ex}); _logger.log(Level.SEVERE, "", ex); throw new EJBException("Unable to activate EJB for key: " + sessionKey, ex); } finally { invocationManager.postInvoke(ejbInv); completeLifecycleCallbackTxIfUsed(ejbInv, context, needToDoPostInvokeTx); } } @Override public byte[] serializeContext(StatefulEJBContext ctx) throws IOException { return serializeContext((SessionContextImpl)ctx.getSessionContext()); } @Override public Object deserializeData(byte[] data) throws Exception { Object o = ejbContainerUtilImpl.getJavaEEIOUtils().deserializeObject( // data, true, getClassLoader(), getApplicationId()); if (o instanceof SessionContextImpl) { deserializeContext((SessionContextImpl) o); } return o; } /*********************************************************************/ /*********** END SFSBContainerCallback methods *******************/ /*********************************************************************/ private void deserializeContext(SessionContextImpl ctx) throws Exception { if (ctx == null) { return; } final Object ejb = ctx.getEJB(); _logger.log(Level.FINE, () -> "StatefulSessionContainer.deserializeData: " + (ejb == null ? null : ejb.getClass())); if (ejb instanceof SerializableEJB) { SerializableEJB sejb = (SerializableEJB) ejb; try ( // ByteArrayInputStream bis = new ByteArrayInputStream(sejb.serializedFields); // ObjectInputStream ois = ejbContainerUtilImpl.getJavaEEIOUtils() // .createObjectInputStream(bis, true, getClassLoader(), getApplicationId())) { final Object newEjb = ejbClass.newInstance(); EJBUtils.deserializeObjectFields(newEjb, ois, ctx, false); ctx.setEJB(newEjb); } } } private byte[] serializeContext(SessionContextImpl ctx) throws IOException { final Object ejb = ctx.getEJB(); if (!(ejb instanceof Serializable || ejb.getClass().getName().equals(EJBUtils.getGeneratedSerializableClassName(ejbName)))) { ctx.setEJB(null); ctx.setEJB(new SerializableEJB(ejb)); } return ejbContainerUtilImpl.getJavaEEIOUtils().serializeObject(ctx, true); } private void decrementRefCountsForEEMs(SessionContextImpl context) { Collection allRefInfos = context.getAllEEMRefInfos(); for (EEMRefInfo refInfo : allRefInfos) { EEMRefInfoKey key = refInfo.getKey(); synchronized (extendedEMReferenceCountMap) { EEMRefInfo cachedRefInfo = extendedEMReferenceCountMap.get(refInfo.eem); if (cachedRefInfo != null) { cachedRefInfo.refCount--; if (cachedRefInfo.refCount == 0) { extendedEMReferenceCountMap.remove(refInfo.eem); eemKey2EEMMap.remove(key); } } } } } private void repopulateEEMMapsInContext(Object sessionKey, SessionContextImpl context) { Collection allRefInfos = context.getAllEEMRefInfos(); for (EEMRefInfo refInfo : allRefInfos) { EEMRefInfoKey key = refInfo.getKey(); synchronized (extendedEMReferenceCountMap) { EntityManager eMgr = eemKey2EEMMap.get(key); EEMRefInfo newRefInfo = null; if (eMgr != null) { EEMRefInfo cachedRefInfo = extendedEMReferenceCountMap.get(eMgr); //cachedRefInfo cannot be null context.addExtendedEntityManagerMapping(cachedRefInfo.getEntityManagerFactory(), cachedRefInfo); cachedRefInfo.refCount++; newRefInfo = cachedRefInfo; } else { //Deserialize em from the byte[] String emRefName = key.emRefName; String unitName = refInfo.getUnitName(); EntityManagerFactory emf = EntityManagerFactoryWrapper.lookupEntityManagerFactory( // ComponentInvocation.ComponentInvocationType.EJB_INVOCATION, unitName, ejbDescriptor); if (emf == null) { throw new EJBException( "EMF is null. Couldn't get extended EntityManager for refName: " + emRefName); } try ( // ByteArrayInputStream bis = new ByteArrayInputStream(refInfo.serializedEEM); ObjectInputStream ois = new ObjectInputStream(bis) // ) { eMgr = (EntityManager) ois.readObject(); newRefInfo = new EEMRefInfo( // emRefName, unitName, refInfo.getSynchronizationType(), // super.getContainerId(), sessionKey, eMgr, emf); newRefInfo.refCount = 1; extendedEMReferenceCountMap.put(eMgr, newRefInfo); eemKey2EEMMap.put(newRefInfo.getKey(), newRefInfo.getEntityManager()); } catch (Throwable th) { EJBException ejbEx = new EJBException( "Couldn't create EntityManager for refName: " + emRefName); ejbEx.initCause(th); throw ejbEx; } } context.addExtendedEntityManagerMapping(newRefInfo.getEntityManagerFactory(), newRefInfo); } } } @Override protected void validateEMForClientTx(EjbInvocation inv, JavaEETransaction clientJ2EETx) throws EJBException { SessionContextImpl sessionCtx = (SessionContextImpl) inv.context; Map entityManagerMap = sessionCtx.getExtendedEntityManagerMap(); for (Map.Entry entry : entityManagerMap.entrySet()) { EntityManagerFactory emf = entry.getKey(); // Make sure there is no Transactional persistence context // for the same EntityManagerFactory as this SFSB's // Extended persistence context for the propagated transaction. if (clientJ2EETx.getTxEntityManagerResource(emf) != null) { throw new EJBException( "There is an active transactional persistence context for the same EntityManagerFactory" + " as the current stateful session bean's extended persistence context"); } // Now see if there's already a *different* extended // persistence context within this transaction for the // same EntityManagerFactory. PhysicalEntityManagerWrapper physicalEM = // (PhysicalEntityManagerWrapper) clientJ2EETx.getExtendedEntityManagerResource(emf); if (physicalEM != null && entry.getValue().getEM() != physicalEM.getEM()) { throw new EJBException( "Detected two different extended persistence contexts for the same EntityManagerFactory" + " within a transaction"); } } } @Override protected void enlistExtendedEntityManagers(ComponentContext ctx) { if (ctx.getTransaction() == null) { return; } JavaEETransaction j2eeTx = (JavaEETransaction) ctx.getTransaction(); SessionContextImpl sessionCtx = (SessionContextImpl) ctx; Map entityManagerMap = // sessionCtx.getExtendedEntityManagerMap(); for (Map.Entry entry : entityManagerMap.entrySet()) { EntityManagerFactory emf = entry.getKey(); PhysicalEntityManagerWrapper extendedEm = entry.getValue(); PhysicalEntityManagerWrapper extendedEmAssociatedWithTx = // EntityManagerWrapper.getExtendedEntityManager(j2eeTx, emf); // If there's not already an EntityManager registered for // this extended EntityManagerFactory within the current tx if (extendedEmAssociatedWithTx == null) { j2eeTx.addExtendedEntityManagerMapping(emf, extendedEm); sessionCtx.setEmfRegisteredWithTx(emf, true); // Tell persistence provider to associate the extended // entity manager with the transaction. if(extendedEm.getSynchronizationType() == SYNCHRONIZED) { extendedEm.getEM().joinTransaction(); } } } } @Override protected void delistExtendedEntityManagers(ComponentContext ctx) { if (ctx.getTransaction() == null) { return; } SessionContextImpl sessionCtx = (SessionContextImpl) ctx; JavaEETransaction j2eeTx = (JavaEETransaction) sessionCtx.getTransaction(); Map entityManagerMap = // sessionCtx.getExtendedEntityManagerMap(); for (Map.Entry entry : entityManagerMap.entrySet()) { EntityManagerFactory emf = entry.getKey(); if (sessionCtx.isEmfRegisteredWithTx(emf)) { j2eeTx.removeExtendedEntityManagerMapping(emf); sessionCtx.setEmfRegisteredWithTx(emf, false); } } } @Override public void invokePeriodically(long delay, long periodicity, Runnable target) { java.util.Timer timer = ejbContainerUtilImpl.getTimer(); TimerTask timerTask = new PeriodicTask(super.loader, target, ejbContainerUtilImpl); timer.scheduleAtFixedRate(timerTask, delay, periodicity); scheduledTimerTasks.add(timerTask); } //Called from Cache implementation through ContainerCallback // when cache.undeploy() is invoked public void onUndeploy(StatefulEJBContext sfsbCtx) { undeploy((SessionContextImpl) sfsbCtx.getSessionContext()); } @Override protected String[] getPre30LifecycleMethodNames() { return new String[] {null, null, "ejbRemove", "ejbPassivate", "ejbActivate"}; } @Override protected void doConcreteContainerShutdown(boolean appBeingUndeployed) { cancelAllTimerTasks(); if (appBeingUndeployed && (ejbDescriptor.getApplication().getKeepStateResolved() == false)) { removeBeansOnUndeploy(); } else { if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "StatefulSessionContainer.doConcreteContainerShutdown() called with --keepstate=" + ejbDescriptor.getApplication().getKeepStateResolved()); } passivateBeansOnShutdown(); } } @Override protected Object intercept(EjbInvocation inv) throws Throwable { deserializeContext((SessionContextImpl) inv.context); return super.intercept(inv); } @Override public boolean intercept(CallbackType eventType, EJBContextImpl ctx) throws Throwable { deserializeContext((SessionContextImpl) ctx); return super.intercept(eventType, ctx); } private void passivateBeansOnShutdown() { ClassLoader origLoader = Utility.setContextClassLoader(loader); try { _logger.log(Level.FINE, "Passivating SFSBs before container shutdown"); if (!isPassivationCapable() && _logger.isLoggable(Level.INFO)) { _logger.log(Level.INFO, SFSB_NOT_RESTORED_AFTER_RESTART); } sessionBeanCache.shutdown(); while (true) { ComponentContext ctx = null; synchronized (asyncTaskSemaphore) { int sz = passivationCandidates.size(); if (sz > 0) { ctx = (ComponentContext) passivationCandidates.remove(sz - 1); } else { break; } } passivateEJB(ctx); } sessionBeanCache.destroy(); cacheProbeListener.unregister(); try { // backingStore will be null when passivation-capable is false if (backingStore != null) { backingStore.close(); } } catch (BackingStoreException sfsbEx) { _logger.log(Level.WARNING, ERROR_DURING_BACKING_STORE_SHUTDOWN, new Object[] {ejbName, sfsbEx}); } } catch (Throwable th) { _logger.log(Level.WARNING, ERROR_DURING_ON_SHUTDOWN, new Object[] {ejbName, th}); } finally { Utility.setContextClassLoader(origLoader); } } private void removeBeansOnUndeploy() { ClassLoader origLoader = Utility.setContextClassLoader(loader); long myContainerId = 0; try { myContainerId = getContainerId(); _logger.log(Level.FINE, "Removing SFSBs during application undeploy"); sessionBeanCache.setUndeployedState(); Iterator iter = sessionBeanCache.values(); while (iter.hasNext()) { SessionContextImpl ctx = (SessionContextImpl) iter.next(); invokePreDestroyAndUndeploy(ctx); } while (true) { SessionContextImpl ctx = null; synchronized (asyncTaskSemaphore) { int sz = passivationCandidates.size(); if (sz > 0) { ctx = (SessionContextImpl) passivationCandidates.remove(sz - 1); invokePreDestroyAndUndeploy(ctx); } else { break; } } } sessionBeanCache.destroy(); try { // backingStore will be null when passivation-capable is false if (backingStore != null) { backingStore.destroy(); } } catch (BackingStoreException sfsbEx) { _logger.log(Level.WARNING, ERROR_DURING_BACKING_STORE_SHUTDOWN, new Object[]{ejbName, sfsbEx}); } } finally { if (sfsbVersionManager != null) { sfsbVersionManager.removeAll(myContainerId); } if (origLoader != null) { Utility.setContextClassLoader(origLoader); } } } private void invokePreDestroyAndUndeploy(SessionContextImpl ctx) { try { ctx.setInEjbRemove(true); destroyBean(null, ctx); } catch (Throwable t) { _logger.log(Level.FINE, "exception thrown from SFSB PRE_DESTROY", t); } finally { ctx.setInEjbRemove(false); } try { this.undeploy(ctx); } catch (Exception ex) { _logger.log(Level.WARNING, ERROR_WHILE_UNDEPLOYING_CTX_KEY, new Object[] {ejbName, ctx.getInstanceKey()}); _logger.log(Level.FINE, "[" + ejbName + "]: Error while " + " undeploying ctx. Key: " + ctx.getInstanceKey(), ex); } } private void cancelAllTimerTasks() { try { int size = scheduledTimerTasks.size(); for (int i = 0; i < size; i++) { TimerTask task = (TimerTask) scheduledTimerTasks.get(i); task.cancel(); } } catch (Exception ex) { // note: exceptions were ignored originally, now they can be at least logged _logger.log(Level.FINEST, "cancelAllTimerTasks failed.", ex); } finally { scheduledTimerTasks.clear(); } } private void destroyBean(EjbInvocation ejbInv, EJBContextImpl ctx) { if (ejbInv == null) { ejbInv = createEjbInvocation(ctx.getEJB(), ctx); } boolean inTx = false; try { invocationManager.preInvoke(ejbInv); inTx = callLifecycleCallbackInTxIfUsed(ejbInv, ctx, preDestroyInvInfo, CallbackType.PRE_DESTROY); } catch (Throwable t) { _logger.log(Level.FINE, "exception thrown from SFSB PRE_DESTROY", t); } finally { invocationManager.postInvoke(ejbInv); completeLifecycleCallbackTxIfUsed(ejbInv, ctx, inTx); } } /** * Start transaction if necessary and invoke lifecycle callback */ private boolean callLifecycleCallbackInTxIfUsed(EjbInvocation ejbInv, EJBContextImpl ctx, // InvocationInfo invInfo, CallbackType callbackType) throws Throwable { boolean inTx = (invInfo.txAttr != -1 && invInfo.txAttr != Container.TX_BEAN_MANAGED); if (inTx) { ((SessionContextImpl) ctx).setInLifeCycleCallback(true); // Call preInvokeTx directly. InvocationInfo containing tx // attribute must be set prior to calling preInvoke ejbInv.transactionAttribute = invInfo.txAttr; ejbInv.invocationInfo = invInfo; preInvokeTx(ejbInv); enlistExtendedEntityManagers(ctx); } intercept(callbackType, ctx); return inTx; } /** * Complete transaction if necessary after lifecycle callback */ private void completeLifecycleCallbackTxIfUsed(EjbInvocation ejbInv, EJBContextImpl ctx, boolean usedTx) { if (usedTx) { delistExtendedEntityManagers(ctx); try { postInvokeTx(ejbInv); } catch(Exception pie) { _logger.log(Level.FINE, "SFSB postInvokeTx exception", pie); } ((SessionContextImpl)ctx).setInLifeCycleCallback(false); } } public void undeploy(SessionContextImpl ctx) { if (this != ctx.getContainer()) { return; } if (hasRemoteHomeView) { EJBObjectImpl ejbObjectImpl = ctx.getEJBObjectImpl(); if (ejbObjectImpl != null) { remoteHomeRefFactory.destroyReference(ejbObjectImpl.getStub(), ejbObjectImpl.getEJBObject()); } } if (hasRemoteBusinessView) { EJBObjectImpl ejbBusinessObjectImpl = ctx.getEJBRemoteBusinessObjectImpl(); if (ejbBusinessObjectImpl != null) { for (RemoteBusinessIntfInfo next : remoteBusinessIntfInfo.values()) { next.referenceFactory.destroyReference( ejbBusinessObjectImpl.getStub(next.generatedRemoteIntf.getName()), ejbBusinessObjectImpl.getEJBObject(next.generatedRemoteIntf.getName())); } } } sessionBeanCache.remove(ctx.getInstanceKey(), ctx.existsInStore()); destroyExtendedEMsForContext(ctx); transactionManager.componentDestroyed(ctx); } // CacheListener interface @Override public void trimEvent(Object primaryKey, Object context) { boolean addTask = false; synchronized (asyncTaskSemaphore) { containerTrimCount++; passivationCandidates.add(context); int requiredTaskCount = (passivationCandidates.size() / passivationBatchCount); addTask = (asyncTaskCount < requiredTaskCount); if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, // "qSize: " + passivationCandidates.size() // + "; batchCount: " + passivationBatchCount // + "; asyncTaskCount: " + asyncTaskCount // + "; requiredTaskCount: " + requiredTaskCount // + "; ADDED TASK ==> " + addTask); // } if (addTask == false) { return; } asyncTaskCount++; asyncCummTaskCount++; } try { ASyncPassivator work = new ASyncPassivator(); ejbContainerUtilImpl.addWork(work); } catch (Exception ex) { synchronized (asyncTaskSemaphore) { asyncTaskCount--; } _logger.log(Level.WARNING, 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; boolean decrementedTaskCount = false; try { // We need to set the context class loader for // this (deamon) thread!! if (System.getSecurityManager() == null) { currentThread.setContextClassLoader(myClassLoader); } else { PrivilegedAction action = () -> { currentThread.setContextClassLoader(myClassLoader); return null; }; AccessController.doPrivileged(action); } ComponentContext ctx = null; do { synchronized (asyncTaskSemaphore) { int sz = passivationCandidates.size(); if (sz > 0) { ctx = (ComponentContext) passivationCandidates.remove(sz - 1); } else { return; } } passivateEJB(ctx); } while (true); } catch (Throwable th) { th.printStackTrace(); } finally { if (!decrementedTaskCount) { synchronized (asyncTaskSemaphore) { asyncTaskCount--; } } if (System.getSecurityManager() == null) { currentThread.setContextClassLoader(previousClassLoader); } else { PrivilegedAction action = () -> { currentThread.setContextClassLoader(previousClassLoader); return null; }; AccessController.doPrivileged(action); } } } } public void setSFSBUUIDUtil(SFSBUUIDUtil util) { this.uuidGenerator = util; } public void setHAEnabled(boolean isHAEnabled) { this.isHAEnabled = isHAEnabled; } public void setSessionCache(LruSessionCache cache) { this.sessionBeanCache = cache; } public void setRemovalGracePeriodInSeconds(int val) { this.removalGracePeriodInSeconds = val; } public void removeExpiredSessions() { try { _logger.log(Level.FINE, "StatefulContainer Removing expired sessions...."); long val = 0; if (backingStore != null) { val = backingStore.removeExpired(this.removalGracePeriodInSeconds * 1000L); } if (cacheProbeNotifier != null) { cacheProbeNotifier.ejbExpiredSessionsRemovedEvent( // getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName, val); } _logger.log(Level.FINE, "StatefulContainer Removed " + val + " sessions...."); } catch (Exception sfsbEx) { _logger.log(Level.WARNING, GOT_EXCEPTION_DURING_REMOVE_EXPIRED_SESSIONS, sfsbEx); } } public void setSFSBVersionManager(SFSBVersionManager sfsbVersionManager) { this.sfsbVersionManager = sfsbVersionManager; } private void handleEndOfMethodCheckpoint(SessionContextImpl sc, EjbInvocation inv) { int txAttr = inv.invocationInfo.txAttr; switch (txAttr) { case TX_NEVER: case TX_SUPPORTS: case TX_NOT_SUPPORTED: if (inv.invocationInfo.checkpointEnabled) { checkpointEJB(sc); } break; case TX_BEAN_MANAGED: if (sc.isTxCheckpointDelayed() || inv.invocationInfo.checkpointEnabled) { checkpointEJB(sc); sc.setTxCheckpointDelayed(false); } break; default: if (inv.invocationInfo.isCreateHomeFinder) { if (inv.invocationInfo.checkpointEnabled) { checkpointEJB(sc); } } break; } if (sc.getState() != BeanState.DESTROYED) { sc.setState(BeanState.READY); incrementMethodReadyStat(); logTraceInfo(inv, sc.getInstanceKey(), "Released context"); } } private void syncClientVersion(EjbInvocation inv, SessionContextImpl sc) { EJBLocalRemoteObject ejbLRO = inv.ejbObject; if (ejbLRO != null) { ejbLRO.setSfsbClientVersion(sc.getVersion()); } if ((!inv.isLocal) && isHAEnabled) { long version = sc.getVersion(); if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Added [synced] version: " + version + " for key: " + sc.getInstanceKey()); } } } //methods for StatefulSessionBeanStatsProvider public int getMaxCacheSize() { return sessionBeanCache.getMaxCacheSize(); } public BackingStore getBackingStore() { return backingStore; } public void setBackingStore(BackingStore store) { this.backingStore = store; } private boolean checkpointEJB(SessionContextImpl sc) { boolean checkpointed = false; try { if (containerState != CONTAINER_STARTED && containerState != CONTAINER_STOPPED) { _logger.log(Level.FINE, "passivateEJB() returning because containerState: " + containerState); return false; } if (sc.getState() == BeanState.DESTROYED) { return false; } Object ejb = sc.getEJB(); long checkpointStartTime = -1; if ((sfsbStoreMonitor != null) && sfsbStoreMonitor.isMonitoringOn()) { checkpointStartTime = System.currentTimeMillis(); } EjbInvocation ejbInv = createEjbInvocation(ejb, sc); invocationManager.preInvoke(ejbInv); boolean needToDoPostInvokeTx = false; boolean destroyBean = false; synchronized (sc) { try { // dont passivate if there is a Tx/invocation in progress // for this instance. if (sc.getState() != BeanState.READY) { return false; } // passivate the EJB sc.setState(BeanState.PASSIVATED); decrementMethodReadyStat(); needToDoPostInvokeTx = callLifecycleCallbackInTxIfUsed( // ejbInv, sc, prePassivateInvInfo, CallbackType.PRE_PASSIVATE); sc.setLastPersistedAt(System.currentTimeMillis()); byte[] serializedState = null; try { long newCtxVersion = sc.incrementAndGetVersion(); serializedState = serializeContext(sc); SimpleMetadata beanState = new SimpleMetadata(sc.getVersion(), sc.getLastAccessTime(), removalGracePeriodInSeconds * 1000L, serializedState); beanState.setVersion(newCtxVersion); backingStore.save((Serializable) sc.getInstanceKey(), beanState, !sc.existsInStore()); //Now that we have successfully stored..... sc.setLastPersistedAt(System.currentTimeMillis()); sc.setExistsInStore(true); checkpointed = true; } catch (EMNotSerializableException emNotSerEx) { _logger.log(Level.WARNING, ERROR_DURING_CHECKPOINT_SESSION_ALIVE, emNotSerEx); } catch (NotSerializableException notSerEx) { throw notSerEx; } catch (Exception ignorableEx) { _logger.log(Level.WARNING, ERROR_DURING_CHECKPOINT, ignorableEx); } // TODO - add a flag to reactivate in the same tx // Complete previous tx completeLifecycleCallbackTxIfUsed(ejbInv, sc, needToDoPostInvokeTx); needToDoPostInvokeTx = callLifecycleCallbackInTxIfUsed( // ejbInv, sc, postActivateInvInfo, CallbackType.POST_ACTIVATE); sc.setState(BeanState.READY); incrementMethodReadyStat(); if (sfsbStoreMonitor != null) { sfsbStoreMonitor.setCheckpointSize(serializedState.length); sfsbStoreMonitor.incrementCheckpointCount(true); } } catch (Throwable ex) { if (sfsbStoreMonitor != null) { sfsbStoreMonitor.incrementCheckpointCount(false); } _logger.log(Level.WARNING, SFSB_CHECKPOINT_ERROR_NAME, new Object[] {ejbDescriptor.getName()}); _logger.log(Level.WARNING, SFSB_CHECKPOINT_ERROR_KEY, new Object[] {sc.getInstanceKey(), ex}); destroyBean = true; } finally { invocationManager.postInvoke(ejbInv); completeLifecycleCallbackTxIfUsed(ejbInv, sc, needToDoPostInvokeTx); if (destroyBean) { try { forceDestroyBean(sc); } catch (Exception e) { _logger.log(Level.FINE, "error destroying bean", e); } } if (checkpointStartTime != -1) { long timeSpent = System.currentTimeMillis() - checkpointStartTime; if( sfsbStoreMonitor != null ) { sfsbStoreMonitor.setCheckpointTime(timeSpent); } } } } //synchronized } catch (Exception ex) { _logger.log(Level.WARNING, PASSIVATION_ERROR_1PARAM, new Object[]{ejbDescriptor.getName(), ex}); } return checkpointed; } public void incrementMethodReadyStat() { statMethodReadyCount++; ejbProbeNotifier.methodReadyAddEvent( // getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName); } public void decrementMethodReadyStat() { statMethodReadyCount--; ejbProbeNotifier.methodReadyRemoveEvent( // getContainerId(), containerInfo.appName, containerInfo.modName, containerInfo.ejbName); } private void logTraceInfo(EjbInvocation inv, Object key, String message) { _logger.log(FINEST, () -> traceInfoPrefix + message + " for " + inv.method.getName() + "; key: " + key); } private void logTraceInfo(SessionContextImpl sc, String message) { _logger.log(FINEST, () -> traceInfoPrefix + message + " for key: " + sc.getInstanceKey() + "; " + System.identityHashCode(sc)); } static class EEMRefInfoKey implements Serializable { private final String emRefName; private final long containerID; private final Object instanceKey; private final int hc; EEMRefInfoKey(String en, long cid, Object ikey) { this.emRefName = en; this.containerID = cid; this.instanceKey = ikey; this.hc = instanceKey.hashCode(); } @Override public int hashCode() { return hc; } @Override public boolean equals(Object obj) { if (obj instanceof EEMRefInfoKey) { EEMRefInfoKey other = (EEMRefInfoKey) obj; return this.containerID == other.containerID // && this.emRefName.equals(other.emRefName) // && this.instanceKey.equals(other.instanceKey); } return false; } @Override public String toString() { return "<" + instanceKey + ":" + emRefName + ":" + containerID + ">"; } } static class EEMRefInfo implements IndirectlySerializable, SerializableObjectFactory { private transient int refCount = 0; private final String unitName; private final SynchronizationType synchronizationType; private final EEMRefInfoKey eemRefInfoKey; private byte[] serializedEEM; private transient EntityManager eem; private transient EntityManagerFactory emf; private int hc; EEMRefInfo(String emRefName, String uName, SynchronizationType synchronizationType, long containerID, Object instanceKey, EntityManager eem, EntityManagerFactory emf) { this.eemRefInfoKey = new EEMRefInfoKey(emRefName, containerID, instanceKey); this.eem = eem; this.emf = emf; this.unitName = uName; this.synchronizationType = synchronizationType; } EntityManager getEntityManager() { return eem; } EntityManagerFactory getEntityManagerFactory() { return this.emf; } EEMRefInfoKey getKey() { return eemRefInfoKey; } Object getSessionKey() { return eemRefInfoKey.instanceKey; } String getUnitName() { return unitName; } SynchronizationType getSynchronizationType() { return synchronizationType; } //Method of IndirectlySerializable @Override public SerializableObjectFactory getSerializableObjectFactory() throws IOException { //Serialize the eem into the serializedEEM try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos);) { oos.writeObject(eem); oos.flush(); bos.flush(); serializedEEM = bos.toByteArray(); } catch (NotSerializableException notSerEx) { throw new EMNotSerializableException(notSerEx.toString(), notSerEx); } catch (IOException ioEx) { throw new EMNotSerializableException(ioEx.toString(), ioEx); } return this; } //Method of SerializableObjectFactory @Override public Object createObject(long appUniqueId) throws IOException { return this; } } static class EMNotSerializableException extends NotSerializableException { public EMNotSerializableException(String className, Throwable th) { super(className); super.initCause(th); } } static class SerializableEJB implements IndirectlySerializable, SerializableObjectFactory { private byte[] serializedFields; SerializableEJB(Object ejb) throws IOException { //Serialize the ejb fields into the serializedFields try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = EjbContainerUtilImpl.getInstance().getJavaEEIOUtils().createObjectOutputStream(bos, true);) { EJBUtils.serializeObjectFields(ejb, oos, false); oos.flush(); bos.flush(); serializedFields = bos.toByteArray(); } } //Method of IndirectlySerializable @Override public SerializableObjectFactory getSerializableObjectFactory() throws IOException { return this; } //Method of SerializableObjectFactory @Override public Object createObject(long appUniqueId) throws IOException { return this; } } } class PeriodicTask extends java.util.TimerTask { AsynchronousTask task; EjbContainerUtil ejbContainerUtil; PeriodicTask(ClassLoader classLoader, Runnable target, EjbContainerUtil ejbContainerUtil) { this.task = new AsynchronousTask(classLoader, target); this.ejbContainerUtil = ejbContainerUtil; } @Override public void run() { if (!task.isExecuting()) { ejbContainerUtil.addWork(task); } } @Override public boolean cancel() { boolean cancelled = super.cancel(); this.task = null; return cancelled; } } class AsynchronousTask implements Runnable { ClassLoader loader; Runnable target; boolean executing; AsynchronousTask(ClassLoader cloassLoader, Runnable target) { this.loader = cloassLoader; this.target = target; this.executing = false; } boolean isExecuting() { return executing; } //This will be called with the correct ClassLoader @Override public void run() { ClassLoader prevCL = Thread.currentThread().getContextClassLoader(); try { Utility.setContextClassLoader(loader); target.run(); } finally { Utility.setContextClassLoader(prevCL); executing = false; } } // end run }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy