com.sun.ejb.containers.StatefulSessionContainer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* 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
}