| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.openejb.core.stateful; |
| |
| import org.apache.openejb.ApplicationException; |
| import org.apache.openejb.BeanContext; |
| import org.apache.openejb.ContainerType; |
| import org.apache.openejb.InterfaceType; |
| import org.apache.openejb.InvalidateReferenceException; |
| import org.apache.openejb.OpenEJBException; |
| import org.apache.openejb.ProxyInfo; |
| import org.apache.openejb.RpcContainer; |
| import org.apache.openejb.SystemException; |
| import org.apache.openejb.cdi.CdiEjbBean; |
| import org.apache.openejb.core.ExceptionType; |
| import org.apache.openejb.core.InstanceContext; |
| import org.apache.openejb.core.Operation; |
| import org.apache.openejb.core.ThreadContext; |
| import org.apache.openejb.core.interceptor.InterceptorData; |
| import org.apache.openejb.core.interceptor.InterceptorStack; |
| import org.apache.openejb.core.stateful.Cache.CacheFilter; |
| import org.apache.openejb.core.stateful.Cache.CacheListener; |
| import org.apache.openejb.core.transaction.BeanTransactionPolicy; |
| import org.apache.openejb.core.transaction.BeanTransactionPolicy.SuspendedTransaction; |
| import org.apache.openejb.core.transaction.EjbTransactionUtil; |
| import org.apache.openejb.core.transaction.EjbUserTransaction; |
| import org.apache.openejb.core.transaction.JtaTransactionPolicy; |
| import org.apache.openejb.core.transaction.TransactionPolicy; |
| import org.apache.openejb.core.transaction.TransactionPolicy.TransactionSynchronization; |
| import org.apache.openejb.loader.SystemInstance; |
| import org.apache.openejb.monitoring.LocalMBeanServer; |
| import org.apache.openejb.monitoring.ManagedMBean; |
| import org.apache.openejb.monitoring.ObjectNameBuilder; |
| import org.apache.openejb.monitoring.StatsInterceptor; |
| import org.apache.openejb.persistence.EntityManagerAlreadyRegisteredException; |
| import org.apache.openejb.persistence.JtaEntityManagerRegistry; |
| import org.apache.openejb.spi.SecurityService; |
| import org.apache.openejb.util.Duration; |
| import org.apache.openejb.util.Index; |
| import org.apache.openejb.util.LogCategory; |
| import org.apache.openejb.util.Logger; |
| |
| import javax.ejb.ConcurrentAccessTimeoutException; |
| import javax.ejb.EJBAccessException; |
| import javax.ejb.EJBContext; |
| import javax.ejb.EJBException; |
| import javax.ejb.EJBHome; |
| import javax.ejb.EJBLocalHome; |
| import javax.ejb.RemoveException; |
| import javax.ejb.SessionBean; |
| import javax.ejb.SessionContext; |
| import javax.enterprise.context.Dependent; |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| import javax.naming.Context; |
| import javax.naming.NamingException; |
| import javax.persistence.EntityManager; |
| import javax.persistence.EntityManagerFactory; |
| import javax.transaction.Transaction; |
| import java.lang.management.ManagementFactory; |
| import java.lang.reflect.Method; |
| import java.rmi.NoSuchObjectException; |
| import java.rmi.RemoteException; |
| import java.rmi.dgc.VMID; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.locks.Lock; |
| |
| import static org.apache.openejb.core.ExceptionType.APPLICATION_ROLLBACK; |
| import static org.apache.openejb.core.ExceptionType.SYSTEM; |
| import static org.apache.openejb.core.transaction.EjbTransactionUtil.createTransactionPolicy; |
| import static org.apache.openejb.core.transaction.EjbTransactionUtil.handleApplicationException; |
| import static org.apache.openejb.core.transaction.EjbTransactionUtil.handleSystemException; |
| |
| public class StatefulContainer implements RpcContainer { |
| private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, "org.apache.openejb.util.resources"); |
| |
| private final Object containerID; |
| private final SecurityService securityService; |
| private final Duration accessTimeout; |
| |
| // todo this should be part of the constructor |
| protected final JtaEntityManagerRegistry entityManagerRegistry = SystemInstance.get().getComponent(JtaEntityManagerRegistry.class); |
| |
| /** |
| * Index used for getDeployments() and getDeploymentInfo(deploymentId). |
| */ |
| protected final Map<Object, BeanContext> deploymentsById = new HashMap<Object, BeanContext>(); |
| |
| protected final Cache<Object, Instance> cache; |
| private final ConcurrentHashMap<Object, Instance> checkedOutInstances = new ConcurrentHashMap<Object, Instance>(); |
| private final SessionContext sessionContext; |
| |
| public StatefulContainer(Object id, SecurityService securityService, Cache<Object, Instance> cache) { |
| this(id, securityService, cache, new Duration(-1, TimeUnit.MILLISECONDS)); |
| } |
| |
| public StatefulContainer(Object id, SecurityService securityService, Cache<Object, Instance> cache, Duration accessTimeout) { |
| this.containerID = id; |
| this.securityService = securityService; |
| this.cache = cache; |
| cache.setListener(new StatefulCacheListener()); |
| this.accessTimeout = accessTimeout; |
| sessionContext = new StatefulContext(this.securityService, new StatefulUserTransaction(new EjbUserTransaction(), entityManagerRegistry)); |
| } |
| |
| private Map<Method, MethodType> getLifecycleMethodsOfInterface(BeanContext beanContext) { |
| Map<Method, MethodType> methods = new HashMap<Method, MethodType>(); |
| |
| try { |
| methods.put(BeanContext.Removable.class.getDeclaredMethod("$$remove"), MethodType.REMOVE); |
| } catch (NoSuchMethodException e) { |
| throw new IllegalStateException("Internal code change: BeanContext.Removable.$$remove() method was deleted", e); |
| } |
| |
| List<Method> removeMethods = beanContext.getRemoveMethods(); |
| for (Method removeMethod : removeMethods) { |
| methods.put(removeMethod, MethodType.REMOVE); |
| |
| for (Class businessLocal : beanContext.getBusinessLocalInterfaces()) { |
| try { |
| Method method = businessLocal.getMethod(removeMethod.getName(), removeMethod.getParameterTypes()); |
| methods.put(method, MethodType.REMOVE); |
| } catch (NoSuchMethodException thatsFine) { |
| } |
| } |
| |
| for (Class businessRemote : beanContext.getBusinessRemoteInterfaces()) { |
| try { |
| Method method = businessRemote.getMethod(removeMethod.getName(), removeMethod.getParameterTypes()); |
| methods.put(method, MethodType.REMOVE); |
| } catch (NoSuchMethodException thatsFine) { |
| } |
| } |
| } |
| |
| Class legacyRemote = beanContext.getRemoteInterface(); |
| if (legacyRemote != null) { |
| try { |
| Method method = legacyRemote.getMethod("remove"); |
| methods.put(method, MethodType.REMOVE); |
| } catch (NoSuchMethodException thatsFine) { |
| } |
| } |
| |
| Class legacyLocal = beanContext.getLocalInterface(); |
| if (legacyLocal != null) { |
| try { |
| Method method = legacyLocal.getMethod("remove"); |
| methods.put(method, MethodType.REMOVE); |
| } catch (NoSuchMethodException thatsFine) { |
| } |
| } |
| |
| Class businessLocalHomeInterface = beanContext.getBusinessLocalInterface(); |
| if (businessLocalHomeInterface != null) { |
| for (Method method : BeanContext.BusinessLocalHome.class.getMethods()) { |
| if (method.getName().startsWith("create")) { |
| methods.put(method, MethodType.CREATE); |
| } else if (method.getName().equals("remove")) { |
| methods.put(method, MethodType.REMOVE); |
| } |
| } |
| } |
| |
| Class businessLocalBeanHomeInterface = beanContext.getBusinessLocalBeanInterface(); |
| if (businessLocalBeanHomeInterface != null) { |
| for (Method method : BeanContext.BusinessLocalBeanHome.class.getMethods()) { |
| if (method.getName().startsWith("create")) { |
| methods.put(method, MethodType.CREATE); |
| } else if (method.getName().equals("remove")) { |
| methods.put(method, MethodType.REMOVE); |
| } |
| } |
| } |
| |
| Class businessRemoteHomeInterface = beanContext.getBusinessRemoteInterface(); |
| if (businessRemoteHomeInterface != null) { |
| for (Method method : BeanContext.BusinessRemoteHome.class.getMethods()) { |
| if (method.getName().startsWith("create")) { |
| methods.put(method, MethodType.CREATE); |
| } else if (method.getName().equals("remove")) { |
| methods.put(method, MethodType.REMOVE); |
| } |
| } |
| } |
| |
| Class homeInterface = beanContext.getHomeInterface(); |
| if (homeInterface != null) { |
| for (Method method : homeInterface.getMethods()) { |
| if (method.getName().startsWith("create")) { |
| methods.put(method, MethodType.CREATE); |
| } else if (method.getName().equals("remove")) { |
| methods.put(method, MethodType.REMOVE); |
| } |
| } |
| } |
| |
| Class localHomeInterface = beanContext.getLocalHomeInterface(); |
| if (localHomeInterface != null) { |
| for (Method method : localHomeInterface.getMethods()) { |
| if (method.getName().startsWith("create")) { |
| methods.put(method, MethodType.CREATE); |
| } else if (method.getName().equals("remove")) { |
| methods.put(method, MethodType.REMOVE); |
| } |
| } |
| } |
| return methods; |
| } |
| |
| public static enum MethodType { |
| CREATE, REMOVE, BUSINESS |
| } |
| |
| public ContainerType getContainerType() { |
| return ContainerType.STATEFUL; |
| } |
| |
| public Object getContainerID() { |
| return containerID; |
| } |
| |
| public synchronized BeanContext[] getBeanContexts() { |
| return deploymentsById.values().toArray(new BeanContext[deploymentsById.size()]); |
| } |
| |
| public synchronized BeanContext getBeanContext(Object deploymentID) { |
| return deploymentsById.get(deploymentID); |
| } |
| |
| public void start(BeanContext beanContext) throws OpenEJBException { |
| } |
| |
| public void stop(BeanContext beanContext) throws OpenEJBException { |
| } |
| |
| public synchronized void undeploy(final BeanContext beanContext) throws OpenEJBException { |
| Data data = (Data) beanContext.getContainerData(); |
| |
| MBeanServer server = LocalMBeanServer.get(); |
| for (ObjectName objectName : data.jmxNames) { |
| try { |
| server.unregisterMBean(objectName); |
| } catch (Exception e) { |
| logger.error("Unable to unregister MBean "+objectName); |
| } |
| } |
| |
| deploymentsById.remove(beanContext.getDeploymentID()); |
| beanContext.setContainer(null); |
| beanContext.setContainerData(null); |
| |
| cache.removeAll(new CacheFilter<Instance>() { |
| public boolean matches(Instance instance) { |
| return beanContext == instance.beanContext; |
| } |
| }); |
| } |
| |
| public synchronized void deploy(BeanContext beanContext) throws OpenEJBException { |
| Map<Method, MethodType> methods = getLifecycleMethodsOfInterface(beanContext); |
| |
| deploymentsById.put(beanContext.getDeploymentID(), beanContext); |
| beanContext.setContainer(this); |
| Data data = new Data(new Index<Method, MethodType>(methods)); |
| beanContext.setContainerData(data); |
| |
| // Create stats interceptor |
| StatsInterceptor stats = new StatsInterceptor(beanContext.getBeanClass()); |
| beanContext.addSystemInterceptor(stats); |
| |
| MBeanServer server = LocalMBeanServer.get(); |
| |
| ObjectNameBuilder jmxName = new ObjectNameBuilder("openejb.management"); |
| jmxName.set("J2EEServer", "openejb"); |
| jmxName.set("J2EEApplication", null); |
| jmxName.set("EJBModule", beanContext.getModuleID()); |
| jmxName.set("StatefulSessionBean", beanContext.getEjbName()); |
| jmxName.set("j2eeType", ""); |
| jmxName.set("name", beanContext.getEjbName()); |
| |
| // register the invocation stats interceptor |
| try { |
| ObjectName objectName = jmxName.set("j2eeType", "Invocations").build(); |
| server.registerMBean(new ManagedMBean(stats), objectName); |
| data.jmxNames.add(objectName); |
| } catch (Exception e) { |
| logger.error("Unable to register MBean ", e); |
| } |
| |
| try { |
| final Context context = beanContext.getJndiEnc(); |
| context.bind("comp/EJBContext", sessionContext); |
| } catch (NamingException e) { |
| throw new OpenEJBException("Failed to bind EJBContext", e); |
| } |
| |
| beanContext.set(EJBContext.class, this.sessionContext); |
| } |
| |
| /** |
| * @deprecated use invoke signature without 'securityIdentity' argument. |
| */ |
| public Object invoke(Object deployID, Method callMethod, Object[] args, Object primKey, Object securityIdentity) throws OpenEJBException { |
| return invoke(deployID, null, callMethod.getDeclaringClass(), callMethod, args, primKey); |
| } |
| |
| public Object invoke(Object deployID, Class callInterface, Method callMethod, Object[] args, Object primKey) throws OpenEJBException { |
| return invoke(deployID, null, callInterface, callMethod, args, primKey); |
| } |
| |
| public Object invoke(Object deployID, InterfaceType type, Class callInterface, Method callMethod, Object[] args, Object primKey) throws OpenEJBException { |
| BeanContext beanContext = this.getBeanContext(deployID); |
| |
| if (beanContext == null) throw new OpenEJBException("Deployment does not exist in this container. Deployment(id='"+deployID+"'), Container(id='"+containerID+"')"); |
| |
| // Use the backup way to determine call type if null was supplied. |
| if (type == null) type = beanContext.getInterfaceType(callInterface); |
| |
| Data data = (Data) beanContext.getContainerData(); |
| MethodType methodType = data.getMethodIndex().get(callMethod); |
| methodType = (methodType != null) ? methodType : MethodType.BUSINESS; |
| |
| switch (methodType) { |
| case CREATE: |
| return createEJBObject(beanContext, callMethod, args, type); |
| case REMOVE: |
| return removeEJBObject(beanContext, primKey, callInterface, callMethod, args, type); |
| default: |
| return businessMethod(beanContext, primKey, callInterface, callMethod, args, type); |
| } |
| } |
| |
| protected ProxyInfo createEJBObject(BeanContext beanContext, Method callMethod, Object[] args, InterfaceType interfaceType) throws OpenEJBException { |
| // generate a new primary key |
| Object primaryKey = newPrimaryKey(); |
| |
| |
| ThreadContext createContext = new ThreadContext(beanContext, primaryKey); |
| ThreadContext oldCallContext = ThreadContext.enter(createContext); |
| try { |
| // Security check |
| checkAuthorization(callMethod, interfaceType); |
| |
| // Create the extended entity managers for this instance |
| Index<EntityManagerFactory, JtaEntityManagerRegistry.EntityManagerTracker> entityManagers = createEntityManagers(beanContext); |
| |
| // Register the newly created entity managers |
| if (entityManagers != null) { |
| try { |
| entityManagerRegistry.addEntityManagers((String) beanContext.getDeploymentID(), primaryKey, entityManagers); |
| } catch (EntityManagerAlreadyRegisteredException e) { |
| throw new EJBException(e); |
| } |
| } |
| |
| createContext.setCurrentOperation(Operation.CREATE); |
| createContext.setCurrentAllowedStates(null); |
| |
| // Start transaction |
| TransactionPolicy txPolicy = createTransactionPolicy(createContext.getBeanContext().getTransactionType(callMethod, interfaceType), createContext); |
| |
| Instance instance = null; |
| try { |
| // Create new instance |
| |
| try { |
| final InstanceContext context = beanContext.newInstance(); |
| |
| // Wrap-up everthing into a object |
| instance = new Instance(beanContext, primaryKey, context.getBean(), context.getCreationalContext(), context.getInterceptors(), entityManagers); |
| |
| } catch (Throwable throwable) { |
| ThreadContext callContext = ThreadContext.getThreadContext(); |
| handleSystemException(callContext.getTransactionPolicy(), throwable, callContext); |
| throw new IllegalStateException(throwable); // should never be reached |
| } |
| |
| // add to cache |
| cache.add(primaryKey, instance); |
| |
| // instance starts checked-out |
| checkedOutInstances.put(primaryKey, instance); |
| |
| // Register for synchronization callbacks |
| registerSessionSynchronization(instance, createContext); |
| |
| // Invoke create for legacy beans |
| if (!callMethod.getDeclaringClass().equals(BeanContext.BusinessLocalHome.class) && |
| !callMethod.getDeclaringClass().equals(BeanContext.BusinessRemoteHome.class) && |
| !callMethod.getDeclaringClass().equals(BeanContext.BusinessLocalBeanHome.class)) { |
| |
| // Setup for business invocation |
| Method createOrInit = beanContext.getMatchingBeanMethod(callMethod); |
| createContext.set(Method.class, createOrInit); |
| |
| // Initialize interceptor stack |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, createOrInit, Operation.CREATE, new ArrayList<InterceptorData>(), new HashMap<String, Object>()); |
| |
| // Invoke |
| if (args == null){ |
| interceptorStack.invoke(); |
| } else { |
| interceptorStack.invoke(args); |
| } |
| } |
| } catch (Throwable e) { |
| handleException(createContext, txPolicy, e); |
| } finally { |
| // un register EntityManager |
| unregisterEntityManagers(instance, createContext); |
| |
| afterInvoke(createContext, txPolicy, instance); |
| } |
| |
| return new ProxyInfo(beanContext, primaryKey); |
| } finally { |
| ThreadContext.exit(oldCallContext); |
| } |
| } |
| |
| protected Object newPrimaryKey() { |
| return new VMID(); |
| } |
| |
| protected Object removeEJBObject(BeanContext beanContext, Object primKey, Class callInterface, Method callMethod, Object[] args, InterfaceType interfaceType) throws OpenEJBException { |
| if (primKey == null) throw new NullPointerException("primKey is null"); |
| |
| final Class scope = beanContext.get(CdiEjbBean.class).getScope(); |
| if (callMethod.getDeclaringClass() != BeanContext.Removable.class && scope != Dependent.class) { |
| throw new UnsupportedOperationException("Can not call EJB Statefull Bean Remove Method without scoped @Dependent. Found scope: @" + scope.getSimpleName()); |
| } |
| |
| |
| final boolean internalRemove = BeanContext.Removable.class == callMethod.getDeclaringClass(); |
| |
| ThreadContext callContext = new ThreadContext(beanContext, primKey); |
| ThreadContext oldCallContext = ThreadContext.enter(callContext); |
| try { |
| // Security check |
| if (!internalRemove) checkAuthorization(callMethod, interfaceType); |
| |
| // If a bean managed transaction is active, the bean can not be removed |
| if (interfaceType.isComponent()) { |
| Instance instance = checkedOutInstances.get(primKey); |
| |
| /** |
| * According to EJB 3.0 "4.4.4 Restrictions for Transactions" any remove methods |
| * from home or component interfaces must not be allowed if the bean instance is |
| * in a transaction. Unfortunately, the Java EE 5 TCK has tests that ignore the |
| * restrictions in 4.4.4 and expect beans in transactions can be removed via their |
| * home or component interface. The test to see if the bean instance implements |
| * javax.ejb.SessionBean is a workaround for passing the TCK while the tests in |
| * question can be challenged or the spec can be changed/updated. |
| */ |
| if (instance != null && instance.bean instanceof javax.ejb.SessionBean) { |
| throw new ApplicationException(new RemoveException("A stateful EJB enrolled in a transaction can not be removed")); |
| } |
| } |
| |
| // Start transaction |
| TransactionPolicy txPolicy = createTransactionPolicy(callContext.getBeanContext().getTransactionType(callMethod, interfaceType), callContext); |
| |
| Object returnValue = null; |
| boolean retain = false; |
| Instance instance = null; |
| Method runMethod = null; |
| try { |
| // Obtain instance |
| instance = obtainInstance(primKey, callContext, callMethod); |
| |
| // Resume previous Bean transaction if there was one |
| if (txPolicy instanceof BeanTransactionPolicy){ |
| // Resume previous Bean transaction if there was one |
| SuspendedTransaction suspendedTransaction = instance.getBeanTransaction(); |
| if (suspendedTransaction != null) { |
| instance.setBeanTransaction(null); |
| BeanTransactionPolicy beanTxEnv = (BeanTransactionPolicy) txPolicy; |
| beanTxEnv.resumeUserTransaction(suspendedTransaction); |
| } |
| } |
| |
| if (!internalRemove) { |
| // Register the entity managers |
| registerEntityManagers(instance, callContext); |
| |
| // Register for synchronization callbacks |
| registerSessionSynchronization(instance, callContext); |
| |
| // Setup for remove invocation |
| callContext.setCurrentOperation(Operation.REMOVE); |
| callContext.setCurrentAllowedStates(null); |
| callContext.setInvokedInterface(callInterface); |
| runMethod = beanContext.getMatchingBeanMethod(callMethod); |
| callContext.set(Method.class, runMethod); |
| |
| // Do not pass arguments on home.remove(remote) calls |
| Class<?> declaringClass = callMethod.getDeclaringClass(); |
| if (declaringClass.equals(EJBHome.class) || declaringClass.equals(EJBLocalHome.class)){ |
| args = new Object[]{}; |
| } |
| |
| // Initialize interceptor stack |
| List<InterceptorData> interceptors = beanContext.getMethodInterceptors(runMethod); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, runMethod, Operation.REMOVE, interceptors, instance.interceptors); |
| |
| // Invoke |
| if (args == null){ |
| returnValue = interceptorStack.invoke(); |
| } else { |
| returnValue = interceptorStack.invoke(args); |
| } |
| } |
| } catch (InvalidateReferenceException e) { |
| throw e; |
| } catch (Throwable e) { |
| if (interfaceType.isBusiness()) { |
| retain = beanContext.retainIfExeption(runMethod); |
| handleException(callContext, txPolicy, e); |
| } else { |
| try { |
| handleException(callContext, txPolicy, e); |
| } catch (ApplicationException ae){ |
| // Don't throw application exceptions for non-business interface removes |
| } |
| } |
| } finally { |
| if (!retain) { |
| try { |
| callContext.setCurrentOperation(Operation.PRE_DESTROY); |
| List<InterceptorData> callbackInterceptors = beanContext.getCallbackInterceptors(); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, null, Operation.PRE_DESTROY, callbackInterceptors, instance.interceptors); |
| interceptorStack.invoke(); |
| } catch (Throwable t) { |
| final String logMessage = "An unexpected exception occured while invoking the preDestroy method on the Stateful SessionBean instance: " |
| + (null != instance ? instance.bean.getClass().getName() : beanContext.getBeanClass().getName()); |
| logger.error(logMessage, t); |
| |
| } finally { |
| callContext.setCurrentOperation(Operation.REMOVE); |
| } |
| |
| discardInstance(callContext); |
| } |
| |
| // un register EntityManager |
| Map<EntityManagerFactory, JtaEntityManagerRegistry.EntityManagerTracker> unregisteredEntityManagers = unregisterEntityManagers(instance, callContext); |
| |
| // Commit transaction |
| afterInvoke(callContext, txPolicy, instance); |
| |
| // Un register and close extended persistence contexts |
| /* |
| 7.6.2 Container-managed Extended Persistence Context |
| A container-managed extended persistence context can only be initiated within the scope of a stateful |
| session bean. It exists from the point at which the stateful session bean that declares a dependency on an |
| entity manager of type PersistenceContextType.EXTENDED is created, and is said to be bound |
| to the stateful session bean. The dependency on the extended persistence context is declared by means |
| of the PersistenceContext annotation or persistence-context-ref deployment descriptor element. |
| The persistence context is closed by the container when the @Remove method of the stateful session |
| bean completes (or the stateful session bean instance is otherwise destroyed). |
| */ |
| closeEntityManagers(unregisteredEntityManagers); |
| } |
| |
| return returnValue; |
| } finally { |
| ThreadContext.exit(oldCallContext); |
| } |
| } |
| |
| protected Object businessMethod(BeanContext beanContext, Object primKey, Class callInterface, Method callMethod, Object[] args, InterfaceType interfaceType) throws OpenEJBException { |
| ThreadContext callContext = new ThreadContext(beanContext, primKey); |
| ThreadContext oldCallContext = ThreadContext.enter(callContext); |
| try { |
| // Security check |
| checkAuthorization(callMethod, interfaceType); |
| |
| // Start transaction |
| TransactionPolicy txPolicy = createTransactionPolicy(callContext.getBeanContext().getTransactionType(callMethod, interfaceType), callContext); |
| |
| Object returnValue = null; |
| Instance instance = null; |
| try { |
| // Obtain instance |
| instance = obtainInstance(primKey, callContext, callMethod); |
| |
| // Resume previous Bean transaction if there was one |
| if (txPolicy instanceof BeanTransactionPolicy){ |
| SuspendedTransaction suspendedTransaction = instance.getBeanTransaction(); |
| if (suspendedTransaction != null) { |
| instance.setBeanTransaction(null); |
| BeanTransactionPolicy beanTxEnv = (BeanTransactionPolicy) txPolicy; |
| beanTxEnv.resumeUserTransaction(suspendedTransaction); |
| } |
| } |
| |
| // Register the entity managers |
| registerEntityManagers(instance, callContext); |
| |
| // Register for synchronization callbacks |
| registerSessionSynchronization(instance, callContext); |
| |
| // Setup for business invocation |
| callContext.setCurrentOperation(Operation.BUSINESS); |
| callContext.setCurrentAllowedStates(null); |
| callContext.setInvokedInterface(callInterface); |
| Method runMethod = beanContext.getMatchingBeanMethod(callMethod); |
| callContext.set(Method.class, runMethod); |
| |
| // Initialize interceptor stack |
| List<InterceptorData> interceptors = beanContext.getMethodInterceptors(runMethod); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, runMethod, Operation.BUSINESS, interceptors, instance.interceptors); |
| |
| // Invoke |
| returnValue = interceptorStack.invoke(args); |
| } catch (Throwable e) { |
| handleException(callContext, txPolicy, e); |
| } finally { |
| // un register EntityManager |
| unregisterEntityManagers(instance, callContext); |
| |
| // Commit transaction |
| afterInvoke(callContext, txPolicy, instance); |
| } |
| return returnValue; |
| } finally { |
| ThreadContext.exit(oldCallContext); |
| } |
| } |
| |
| private Instance obtainInstance(Object primaryKey, ThreadContext callContext, Method callMethod) throws OpenEJBException { |
| if (primaryKey == null) { |
| throw new SystemException(new NullPointerException("Cannot obtain an instance of the stateful session bean with a null session id")); |
| } |
| |
| Transaction currentTransaction = getTransaction(callContext); |
| |
| // Find the instance |
| Instance instance; |
| synchronized (primaryKey) { |
| instance = checkedOutInstances.get(primaryKey); |
| if (instance == null) { |
| try { |
| instance = cache.checkOut(primaryKey); |
| } catch (OpenEJBException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new SystemException("Unexpected load exception", e); |
| } |
| |
| // Did we find the instance? |
| if (instance == null) { |
| throw new InvalidateReferenceException(new NoSuchObjectException("Not Found")); |
| } |
| |
| |
| // remember instance until it is returned to the cache |
| checkedOutInstances.put(primaryKey, instance); |
| } |
| } |
| |
| Duration accessTimeout = getAccessTimeout(instance.beanContext, callMethod); |
| |
| final Lock currLock = instance.getLock(); |
| final boolean lockAcquired; |
| if (accessTimeout == null || accessTimeout.getTime() < 0) { |
| // wait indefinitely for a lock |
| currLock.lock(); |
| lockAcquired = true; |
| } else if (accessTimeout.getTime() == 0) { |
| // concurrent calls are not allowed, lock only once |
| lockAcquired = currLock.tryLock(); |
| } else { |
| // try to get a lock within the specified period. |
| try { |
| lockAcquired = currLock.tryLock(accessTimeout.getTime(), accessTimeout.getUnit()); |
| } catch (InterruptedException e) { |
| throw new ApplicationException("Unable to get lock.", e); |
| } |
| } |
| // Did we acquire the lock to the current execution? |
| if (!lockAcquired) { |
| throw new ApplicationException(new ConcurrentAccessTimeoutException("Unable to get lock.")); |
| } |
| |
| if (instance.getTransaction() != null) { |
| if (!instance.getTransaction().equals(currentTransaction) && !instance.getLock().tryLock()) { |
| throw new ApplicationException(new RemoteException("Instance is in a transaction and cannot be invoked outside that transaction. See EJB 3.0 Section 4.4.4")); |
| } |
| } else { |
| instance.setTransaction(currentTransaction); |
| } |
| |
| // Mark the instance in use so we can detect reentrant calls |
| instance.setInUse(true); |
| return instance; |
| } |
| |
| private Duration getAccessTimeout(BeanContext beanContext, Method callMethod) { |
| callMethod = beanContext.getMatchingBeanMethod(callMethod); |
| |
| Duration accessTimeout = beanContext.getAccessTimeout(callMethod); |
| if (accessTimeout == null) { |
| accessTimeout = beanContext.getAccessTimeout(); |
| if (accessTimeout == null) { |
| accessTimeout = this.accessTimeout; |
| } |
| } |
| return accessTimeout; |
| } |
| |
| private Transaction getTransaction(ThreadContext callContext) { |
| TransactionPolicy policy = callContext.getTransactionPolicy(); |
| |
| Transaction currentTransaction = null; |
| if (policy instanceof JtaTransactionPolicy) { |
| JtaTransactionPolicy jtaPolicy = (JtaTransactionPolicy) policy; |
| |
| currentTransaction = jtaPolicy.getCurrentTransaction(); |
| } |
| return currentTransaction; |
| } |
| |
| private void releaseInstance(Instance instance) { |
| // Don't pool if the bean has been undeployed |
| if (instance.beanContext.isDestroyed()) return; |
| |
| // verify the instance is not associated with a bean-managed transaction |
| if (instance.getBeanTransaction() != null) { |
| new IllegalStateException("Instance has an active bean-managed transaction"); |
| } |
| |
| // no longer in use |
| instance.setInUse(false); |
| |
| if (instance.getTransaction() == null) { |
| synchronized (instance.primaryKey) { |
| // return to cache |
| cache.checkIn(instance.primaryKey); |
| |
| // no longer checked out |
| checkedOutInstances.remove(instance.primaryKey); |
| } |
| } |
| } |
| |
| private void discardInstance(ThreadContext threadContext) { |
| Object primaryKey = threadContext.getPrimaryKey(); |
| if (primaryKey == null) { |
| return; |
| } |
| |
| Instance instance = checkedOutInstances.remove(primaryKey); |
| cache.remove(primaryKey); |
| |
| if (null != instance && null != instance.creationalContext) { |
| instance.creationalContext.release(); |
| } |
| } |
| |
| private void checkAuthorization(Method callMethod, InterfaceType interfaceType) throws ApplicationException { |
| boolean authorized = securityService.isCallerAuthorized(callMethod, interfaceType); |
| if (!authorized) { |
| throw new ApplicationException(new EJBAccessException("Unauthorized Access by Principal Denied")); |
| } |
| } |
| |
| private void handleException(ThreadContext callContext, TransactionPolicy txPolicy, Throwable e) throws ApplicationException { |
| if (e instanceof ApplicationException) { |
| throw (ApplicationException) e; |
| } |
| |
| ExceptionType type = callContext.getBeanContext().getExceptionType(e); |
| if (type == SYSTEM) { |
| discardInstance(callContext); |
| handleSystemException(txPolicy, e, callContext); |
| } else { |
| handleApplicationException(txPolicy, e, type == APPLICATION_ROLLBACK); |
| } |
| } |
| |
| private void afterInvoke(ThreadContext callContext, TransactionPolicy txPolicy, Instance instance) throws OpenEJBException { |
| try { |
| if (instance != null && txPolicy instanceof BeanTransactionPolicy) { |
| // suspend the currently running transaction if any |
| SuspendedTransaction suspendedTransaction = null; |
| try { |
| BeanTransactionPolicy beanTxEnv = (BeanTransactionPolicy) txPolicy; |
| suspendedTransaction = beanTxEnv.suspendUserTransaction(); |
| } catch (SystemException e) { |
| handleSystemException(txPolicy, e, callContext); |
| } finally { |
| instance.setBeanTransaction(suspendedTransaction); |
| } |
| } |
| } finally { |
| if (instance != null) { |
| instance.setInUse(false); |
| } |
| EjbTransactionUtil.afterInvoke(txPolicy, callContext); |
| if (instance != null) { |
| instance.releaseLock(); |
| } |
| } |
| } |
| |
| private Index<EntityManagerFactory, JtaEntityManagerRegistry.EntityManagerTracker> createEntityManagers(BeanContext beanContext) { |
| // create the extended entity managers |
| Index<EntityManagerFactory, Map> factories = beanContext.getExtendedEntityManagerFactories(); |
| Index<EntityManagerFactory, JtaEntityManagerRegistry.EntityManagerTracker> entityManagers = null; |
| if (factories != null && factories.size() > 0) { |
| entityManagers = new Index<EntityManagerFactory, JtaEntityManagerRegistry.EntityManagerTracker>(new ArrayList<EntityManagerFactory>(factories.keySet())); |
| for (Map.Entry<EntityManagerFactory, Map> entry : factories.entrySet()) { |
| EntityManagerFactory entityManagerFactory = entry.getKey(); |
| Map properties = entry.getValue(); |
| |
| |
| JtaEntityManagerRegistry.EntityManagerTracker entityManagerTracker = entityManagerRegistry.getInheritedEntityManager(entityManagerFactory); |
| EntityManager entityManager; |
| if (entityManagerTracker == null) { |
| if (properties != null) { |
| entityManager = entityManagerFactory.createEntityManager(properties); |
| } else { |
| entityManager = entityManagerFactory.createEntityManager(); |
| } |
| entityManagerTracker = new JtaEntityManagerRegistry.EntityManagerTracker(entityManager); |
| } else { |
| entityManagerTracker.incCounter(); |
| } |
| entityManagers.put(entityManagerFactory, entityManagerTracker); |
| } |
| } |
| return entityManagers; |
| } |
| |
| private void registerEntityManagers(Instance instance, ThreadContext callContext) throws OpenEJBException { |
| if (entityManagerRegistry == null) return; |
| |
| BeanContext beanContext = callContext.getBeanContext(); |
| |
| // get the factories |
| Index<EntityManagerFactory, Map> factories = beanContext.getExtendedEntityManagerFactories(); |
| if (factories == null) return; |
| |
| // get the managers for the factories |
| Map<EntityManagerFactory, JtaEntityManagerRegistry.EntityManagerTracker> entityManagers = instance.getEntityManagers(factories); |
| if (entityManagers == null) return; |
| |
| // register them |
| try { |
| entityManagerRegistry.addEntityManagers((String) beanContext.getDeploymentID(), instance.primaryKey, entityManagers); |
| } catch (EntityManagerAlreadyRegisteredException e) { |
| throw new EJBException(e); |
| } |
| } |
| |
| private Map<EntityManagerFactory, JtaEntityManagerRegistry.EntityManagerTracker> unregisterEntityManagers(Instance instance, ThreadContext callContext) { |
| if (entityManagerRegistry == null) return null; |
| if (instance == null) return null; |
| |
| BeanContext beanContext = callContext.getBeanContext(); |
| |
| // register them |
| return entityManagerRegistry.removeEntityManagers((String) beanContext.getDeploymentID(), instance.primaryKey); |
| } |
| |
| private void closeEntityManagers(Map<EntityManagerFactory,JtaEntityManagerRegistry.EntityManagerTracker> unregisteredEntityManagers) { |
| if (unregisteredEntityManagers == null) return; |
| |
| // iterate throughout all EM to close EntityManager |
| for (JtaEntityManagerRegistry.EntityManagerTracker entityManagerTracker : unregisteredEntityManagers.values()) { |
| if(entityManagerTracker.decCounter() == 0) { |
| entityManagerTracker.getEntityManager().close(); |
| } |
| } |
| } |
| |
| private void registerSessionSynchronization(Instance instance, ThreadContext callContext) { |
| TransactionPolicy txPolicy = callContext.getTransactionPolicy(); |
| if (txPolicy == null) { |
| throw new IllegalStateException("ThreadContext does not contain a TransactionEnvironment"); |
| } |
| |
| SessionSynchronizationCoordinator coordinator = (SessionSynchronizationCoordinator) txPolicy.getResource(SessionSynchronizationCoordinator.class); |
| if (coordinator == null) { |
| coordinator = new SessionSynchronizationCoordinator(txPolicy); |
| txPolicy.registerSynchronization(coordinator); |
| txPolicy.putResource(SessionSynchronizationCoordinator.class, coordinator); |
| } |
| |
| // SessionSynchronization are only enabled for beans after CREATE that are not bean-managed and implement the SessionSynchronization interface |
| boolean synchronize = callContext.getCurrentOperation() != Operation.CREATE && |
| callContext.getBeanContext().isSessionSynchronized() && |
| txPolicy.isTransactionActive(); |
| |
| coordinator.registerSessionSynchronization(instance, callContext.getBeanContext(), callContext.getPrimaryKey(), synchronize); |
| } |
| |
| /** |
| * SessionSynchronizationCoordinator handles afterBegin, beforeCompletion and afterCompletion callbacks. |
| * |
| * This class also is responsible for calling releaseInstance after the transaction completes. |
| */ |
| private class SessionSynchronizationCoordinator implements TransactionSynchronization { |
| private final Map<Object, Synchronization> registry = new HashMap<Object, Synchronization>(); |
| private final TransactionPolicy txPolicy; |
| |
| private SessionSynchronizationCoordinator(TransactionPolicy txPolicy) { |
| this.txPolicy = txPolicy; |
| } |
| |
| public class Synchronization { |
| private final Instance instance; |
| |
| private boolean callSessionSynchronization; |
| |
| public Synchronization(Instance instance) { |
| this.instance = instance; |
| } |
| |
| public synchronized boolean isCallSessionSynchronization() { |
| return callSessionSynchronization; |
| } |
| |
| public synchronized boolean setCallSessionSynchronization(boolean synchronize) { |
| boolean oldValue = this.callSessionSynchronization; |
| this.callSessionSynchronization = synchronize; |
| return oldValue; |
| } |
| |
| } |
| |
| private void registerSessionSynchronization(Instance instance, BeanContext beanContext, Object primaryKey, boolean synchronize) { |
| |
| Synchronization synchronization = registry.get(primaryKey); |
| |
| if (synchronization == null){ |
| synchronization = new Synchronization(instance); |
| registry.put(primaryKey, synchronization); |
| } |
| |
| boolean wasSynchronized = synchronization.setCallSessionSynchronization(synchronize); |
| |
| // check if afterBegin has already been invoked or if this is not a session synchronization bean |
| if (wasSynchronized || !synchronize) { |
| return; |
| } |
| |
| // Invoke afterBegin |
| ThreadContext callContext = new ThreadContext(instance.beanContext, instance.primaryKey, Operation.AFTER_BEGIN); |
| callContext.setCurrentAllowedStates(null); |
| ThreadContext oldCallContext = ThreadContext.enter(callContext); |
| try { |
| |
| List<InterceptorData> interceptors = beanContext.getCallbackInterceptors(); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, null, Operation.AFTER_BEGIN, interceptors, instance.interceptors); |
| interceptorStack.invoke(); |
| |
| } catch (Exception e) { |
| String message = "An unexpected system exception occured while invoking the afterBegin method on the SessionSynchronization object"; |
| |
| // [1] Log the exception or error |
| logger.error(message, e); |
| |
| // Caller handles transaction rollback and discardInstance |
| |
| // [4] throw the java.rmi.RemoteException to the client |
| throw new RuntimeException(message, e); |
| } finally { |
| ThreadContext.exit(oldCallContext); |
| } |
| } |
| |
| public void beforeCompletion() { |
| for (Synchronization synchronization : registry.values()) { |
| |
| Instance instance = synchronization.instance; |
| |
| // don't call beforeCompletion when transaction is marked rollback only |
| if (txPolicy.isRollbackOnly()) return; |
| |
| // only call beforeCompletion on beans with session synchronization |
| if (!synchronization.isCallSessionSynchronization()) continue; |
| |
| // Invoke beforeCompletion |
| ThreadContext callContext = new ThreadContext(instance.beanContext, instance.primaryKey, Operation.BEFORE_COMPLETION); |
| callContext.setCurrentAllowedStates(null); |
| ThreadContext oldCallContext = ThreadContext.enter(callContext); |
| try { |
| instance.setInUse(true); |
| |
| BeanContext beanContext = instance.beanContext; |
| List<InterceptorData> interceptors = beanContext.getCallbackInterceptors(); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, null, Operation.BEFORE_COMPLETION, interceptors, instance.interceptors); |
| interceptorStack.invoke(); |
| |
| instance.setInUse(false); |
| } catch (InvalidateReferenceException e) { |
| // exception has alredy been handled |
| } catch (Exception e) { |
| String message = "An unexpected system exception occured while invoking the beforeCompletion method on the SessionSynchronization object"; |
| |
| // [1] Log the exception or error |
| logger.error(message, e); |
| |
| // [2] Mark the transaction for rollback. |
| txPolicy.setRollbackOnly(e); |
| |
| // [3] Discard the instance |
| discardInstance(callContext); |
| |
| // [4] throw the java.rmi.RemoteException to the client |
| throw new RuntimeException(message, e); |
| } finally { |
| ThreadContext.exit(oldCallContext); |
| } |
| } |
| } |
| |
| public void afterCompletion(Status status) { |
| Throwable firstException = null; |
| for (Synchronization synchronization : registry.values()) { |
| |
| Instance instance = synchronization.instance; |
| |
| ThreadContext callContext = new ThreadContext(instance.beanContext, instance.primaryKey, Operation.AFTER_COMPLETION); |
| callContext.setCurrentAllowedStates(null); |
| ThreadContext oldCallContext = ThreadContext.enter(callContext); |
| try { |
| instance.setInUse(true); |
| if (synchronization.isCallSessionSynchronization()) { |
| |
| BeanContext beanContext = instance.beanContext; |
| List<InterceptorData> interceptors = beanContext.getCallbackInterceptors(); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, null, Operation.AFTER_COMPLETION, interceptors, instance.interceptors); |
| interceptorStack.invoke(status == Status.COMMITTED); |
| } |
| instance.setTransaction(null); |
| releaseInstance(instance); |
| } catch (InvalidateReferenceException inv) { |
| // exception has alredy been handled |
| } catch (Throwable e) { |
| String message = "An unexpected system exception occured while invoking the afterCompletion method on the SessionSynchronization object"; |
| |
| // [1] Log the exception or error |
| logger.error(message, e); |
| |
| // Transaction is complete so can not be rolled back |
| |
| // [3] Discard the instance |
| discardInstance(callContext); |
| |
| // [4] throw throw first exception to the client |
| if (firstException == null) firstException = e; |
| } finally { |
| ThreadContext.exit(oldCallContext); |
| } |
| } |
| |
| if (firstException != null) { |
| throw new RuntimeException("An unexpected system exception occured while invoking the afterCompletion method on the SessionSynchronization object", firstException); |
| } |
| } |
| } |
| |
| public class StatefulCacheListener implements CacheListener<Instance> { |
| public void afterLoad(Instance instance) throws SystemException, ApplicationException { |
| BeanContext beanContext = instance.beanContext; |
| |
| ThreadContext threadContext = new ThreadContext(instance.beanContext, instance.primaryKey, Operation.ACTIVATE); |
| ThreadContext oldContext = ThreadContext.enter(threadContext); |
| try { |
| Method remove = instance.bean instanceof SessionBean ? SessionBean.class.getMethod("ejbActivate") : null; |
| |
| List<InterceptorData> callbackInterceptors = beanContext.getCallbackInterceptors(); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, remove, Operation.ACTIVATE, callbackInterceptors, instance.interceptors); |
| |
| interceptorStack.invoke(); |
| } catch (Throwable callbackException) { |
| discardInstance(threadContext); |
| handleSystemException(threadContext.getTransactionPolicy(), callbackException, threadContext); |
| } finally { |
| ThreadContext.exit(oldContext); |
| } |
| } |
| |
| public void beforeStore(Instance instance) { |
| BeanContext beanContext = instance.beanContext; |
| |
| ThreadContext threadContext = new ThreadContext(beanContext, instance.primaryKey, Operation.PASSIVATE); |
| ThreadContext oldContext = ThreadContext.enter(threadContext); |
| try { |
| Method passivate = instance.bean instanceof SessionBean ? SessionBean.class.getMethod("ejbPassivate") : null; |
| |
| List<InterceptorData> callbackInterceptors = beanContext.getCallbackInterceptors(); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, passivate, Operation.PASSIVATE, callbackInterceptors, instance.interceptors); |
| |
| interceptorStack.invoke(); |
| |
| } catch (Throwable e) { |
| logger.error("An unexpected exception occured while invoking the ejbPassivate method on the Stateful SessionBean instance", e); |
| } finally { |
| ThreadContext.exit(oldContext); |
| } |
| } |
| |
| public void timedOut(Instance instance) { |
| BeanContext beanContext = instance.beanContext; |
| |
| ThreadContext threadContext = new ThreadContext(beanContext, instance.primaryKey, Operation.PRE_DESTROY); |
| threadContext.setCurrentAllowedStates(null); |
| ThreadContext oldContext = ThreadContext.enter(threadContext); |
| try { |
| Method remove = instance.bean instanceof SessionBean ? SessionBean.class.getMethod("ejbRemove") : null; |
| |
| List<InterceptorData> callbackInterceptors = beanContext.getCallbackInterceptors(); |
| InterceptorStack interceptorStack = new InterceptorStack(instance.bean, remove, Operation.PRE_DESTROY, callbackInterceptors, instance.interceptors); |
| |
| interceptorStack.invoke(); |
| } catch (Throwable e) { |
| logger.error("An unexpected exception occured while invoking the ejbRemove method on the timed-out Stateful SessionBean instance", e); |
| } finally { |
| logger.info("Removing the timed-out stateful session bean instance " + instance.primaryKey); |
| ThreadContext.exit(oldContext); |
| } |
| } |
| } |
| |
| private static class Data { |
| private final Index<Method, MethodType> methodIndex; |
| private final List<ObjectName> jmxNames = new ArrayList<ObjectName>(); |
| |
| private Data(Index<Method, MethodType> methodIndex) { |
| this.methodIndex = methodIndex; |
| } |
| |
| public Index<Method, MethodType> getMethodIndex() { |
| return methodIndex; |
| } |
| |
| } |
| } |