| /* |
| * 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.entity; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.InvocationTargetException; |
| import java.rmi.NoSuchObjectException; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Vector; |
| import javax.ejb.EJBAccessException; |
| import javax.ejb.EJBHome; |
| import javax.ejb.EJBLocalHome; |
| import javax.ejb.EJBLocalObject; |
| import javax.ejb.EJBObject; |
| import javax.ejb.EntityBean; |
| import javax.ejb.NoSuchEntityException; |
| import javax.ejb.Timer; |
| import javax.transaction.TransactionSynchronizationRegistry; |
| |
| import org.apache.openejb.ApplicationException; |
| import org.apache.openejb.BeanContext; |
| import org.apache.openejb.ContainerType; |
| import org.apache.openejb.OpenEJBException; |
| import org.apache.openejb.ProxyInfo; |
| import org.apache.openejb.SystemException; |
| import org.apache.openejb.RpcContainer; |
| import org.apache.openejb.InterfaceType; |
| import org.apache.openejb.core.ExceptionType; |
| import org.apache.openejb.core.Operation; |
| import org.apache.openejb.core.ThreadContext; |
| import org.apache.openejb.core.timer.EjbTimerService; |
| import org.apache.openejb.core.timer.EjbTimerServiceImpl; |
| import static org.apache.openejb.core.transaction.EjbTransactionUtil.handleApplicationException; |
| import static org.apache.openejb.core.transaction.EjbTransactionUtil.handleSystemException; |
| import static org.apache.openejb.core.transaction.EjbTransactionUtil.afterInvoke; |
| import static org.apache.openejb.core.transaction.EjbTransactionUtil.createTransactionPolicy; |
| import org.apache.openejb.core.transaction.TransactionPolicy; |
| import org.apache.openejb.core.transaction.TransactionType; |
| import org.apache.openejb.loader.SystemInstance; |
| import org.apache.openejb.spi.SecurityService; |
| import org.apache.openejb.util.LogCategory; |
| import org.apache.openejb.util.Logger; |
| |
| /** |
| * @org.apache.xbean.XBean element="bmpContainer" |
| */ |
| public class EntityContainer implements RpcContainer { |
| |
| private EntityInstanceManager instanceManager; |
| |
| private Map<String, BeanContext> deploymentRegistry = new HashMap<String, BeanContext>(); |
| |
| private Object containerID = null; |
| |
| public static Logger logger = Logger.getInstance(LogCategory.OPENEJB, "org.apache.openejb.util.resources"); |
| private SecurityService securityService; |
| |
| /** |
| * Tracks entity instances that have been "entered" so we can throw reentrancy exceptions. |
| */ |
| protected EntrancyTracker entrancyTracker; |
| |
| public EntityContainer(Object id, SecurityService securityService, int poolSize) throws OpenEJBException { |
| this.containerID = id; |
| this.securityService = securityService; |
| entrancyTracker = new EntrancyTracker(SystemInstance.get().getComponent(TransactionSynchronizationRegistry.class)); |
| |
| instanceManager = new EntityInstanceManager(this, securityService, poolSize); |
| } |
| |
| public synchronized BeanContext[] getBeanContexts() { |
| return deploymentRegistry.values().toArray(new BeanContext[deploymentRegistry.size()]); |
| } |
| |
| public synchronized BeanContext getBeanContext(Object deploymentID) { |
| String id = (String) deploymentID; |
| return deploymentRegistry.get(id); |
| } |
| |
| public ContainerType getContainerType() { |
| return ContainerType.BMP_ENTITY; |
| } |
| |
| public Object getContainerID() { |
| return containerID; |
| } |
| |
| public void deploy(BeanContext beanContext) throws OpenEJBException { |
| synchronized (this) { |
| deploymentRegistry.put((String) beanContext.getDeploymentID(), beanContext); |
| beanContext.setContainer(this); |
| } |
| instanceManager.deploy(beanContext); |
| |
| EjbTimerService timerService = beanContext.getEjbTimerService(); |
| if (timerService != null) { |
| timerService.start(); |
| } |
| } |
| |
| public void start(BeanContext info) throws OpenEJBException { |
| } |
| |
| public void stop(BeanContext info) throws OpenEJBException { |
| info.stop(); |
| } |
| |
| public void undeploy(BeanContext info) throws OpenEJBException { |
| EjbTimerService timerService = info.getEjbTimerService(); |
| if (timerService != null) { |
| timerService.stop(); |
| } |
| |
| instanceManager.undeploy(info); |
| |
| synchronized (this) { |
| String id = (String) info.getDeploymentID(); |
| deploymentRegistry.remove(id); |
| info.setContainer(null); |
| } |
| } |
| |
| /** |
| * @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); |
| |
| ThreadContext callContext = new ThreadContext(beanContext, primKey); |
| ThreadContext oldCallContext = ThreadContext.enter(callContext); |
| try { |
| boolean authorized = type == InterfaceType.TIMEOUT || getSecurityService().isCallerAuthorized(callMethod, type); |
| if (!authorized) |
| throw new org.apache.openejb.ApplicationException(new EJBAccessException("Unauthorized Access by Principal Denied")); |
| |
| Class declaringClass = callMethod.getDeclaringClass(); |
| String methodName = callMethod.getName(); |
| |
| if (EJBHome.class.isAssignableFrom(declaringClass) || EJBLocalHome.class.isAssignableFrom(declaringClass)) { |
| if (declaringClass != EJBHome.class && declaringClass != EJBLocalHome.class) { |
| |
| if (methodName.startsWith("create")) { |
| |
| return createEJBObject(callMethod, args, callContext, type); |
| } else if (methodName.startsWith("find")) { |
| |
| return findMethod(callMethod, args, callContext, type); |
| } else { |
| |
| return homeMethod(callMethod, args, callContext, type); |
| } |
| } else if (methodName.equals("remove")) { |
| removeEJBObject(callMethod, args, callContext, type); |
| return null; |
| } |
| } else if ((EJBObject.class == declaringClass || EJBLocalObject.class == declaringClass) && methodName.equals("remove")) { |
| removeEJBObject(callMethod, args, callContext, type); |
| return null; |
| } |
| |
| callContext.setCurrentOperation(type == InterfaceType.TIMEOUT ? Operation.TIMEOUT : Operation.BUSINESS); |
| Method runMethod = beanContext.getMatchingBeanMethod(callMethod); |
| |
| callContext.set(Method.class, runMethod); |
| |
| Object retValue = invoke(type, callMethod, runMethod, args, callContext); |
| |
| return retValue; |
| |
| } finally { |
| ThreadContext.exit(oldCallContext); |
| } |
| } |
| |
| private SecurityService getSecurityService() { |
| return securityService; |
| } |
| |
| public EntityInstanceManager getInstanceManager() { |
| return instanceManager; |
| } |
| |
| protected Object invoke(InterfaceType type, Method callMethod, Method runMethod, Object[] args, ThreadContext callContext) throws OpenEJBException { |
| BeanContext beanContext = callContext.getBeanContext(); |
| TransactionPolicy txPolicy = createTransactionPolicy(beanContext.getTransactionType(callMethod, type), callContext); |
| |
| EntityBean bean = null; |
| |
| Object returnValue = null; |
| entrancyTracker.enter(callContext.getBeanContext(), callContext.getPrimaryKey()); |
| try { |
| bean = instanceManager.obtainInstance(callContext); |
| |
| ejbLoad_If_No_Transaction(callContext, bean); |
| returnValue = runMethod.invoke(bean, args); |
| ejbStore_If_No_Transaction(callContext, bean); |
| instanceManager.poolInstance(callContext, bean, callContext.getPrimaryKey()); |
| } catch (Throwable e) { |
| handleException(txPolicy, e, callContext, bean); |
| } finally { |
| entrancyTracker.exit(callContext.getBeanContext(), callContext.getPrimaryKey()); |
| afterInvoke(txPolicy, callContext); |
| } |
| |
| return returnValue; |
| } |
| |
| public void ejbLoad_If_No_Transaction(ThreadContext callContext, EntityBean bean) throws Exception { |
| Operation orginalOperation = callContext.getCurrentOperation(); |
| if (orginalOperation == Operation.BUSINESS || orginalOperation == Operation.REMOVE) { |
| |
| TransactionPolicy callerTxPolicy = callContext.getTransactionPolicy(); |
| if (callerTxPolicy != null && callerTxPolicy.isTransactionActive()) { |
| return; |
| } |
| |
| BeanContext beanContext = callContext.getBeanContext(); |
| TransactionPolicy txPolicy = beanContext.getTransactionPolicyFactory().createTransactionPolicy(TransactionType.Supports); |
| try { |
| // double check we don't have an active transaction |
| if (!txPolicy.isTransactionActive()) { |
| callContext.setCurrentOperation(Operation.LOAD); |
| bean.ejbLoad(); |
| } |
| } catch (NoSuchEntityException e) { |
| instanceManager.discardInstance(callContext, bean); |
| throw new ApplicationException(new NoSuchObjectException("Entity not found: " + callContext.getPrimaryKey())/*.initCause(e)*/); |
| } catch (Exception e) { |
| instanceManager.discardInstance(callContext, bean); |
| throw e; |
| } finally { |
| callContext.setCurrentOperation(orginalOperation); |
| txPolicy.commit(); |
| } |
| |
| } |
| } |
| |
| public void ejbStore_If_No_Transaction(ThreadContext callContext, EntityBean bean) throws Exception { |
| Operation currentOp = callContext.getCurrentOperation(); |
| if (currentOp == Operation.BUSINESS) { |
| |
| TransactionPolicy callerTxPolicy = callContext.getTransactionPolicy(); |
| if (callerTxPolicy != null && callerTxPolicy.isTransactionActive()) { |
| return; |
| } |
| |
| BeanContext beanContext = callContext.getBeanContext(); |
| TransactionPolicy txPolicy = beanContext.getTransactionPolicyFactory().createTransactionPolicy(TransactionType.Supports); |
| try { |
| // double check we don't have an active transaction |
| if (!txPolicy.isTransactionActive()) { |
| callContext.setCurrentOperation(Operation.STORE); |
| bean.ejbStore(); |
| } |
| } catch (Exception e) { |
| instanceManager.discardInstance(callContext, bean); |
| throw e; |
| } finally { |
| callContext.setCurrentOperation(currentOp); |
| txPolicy.commit(); |
| } |
| } |
| } |
| |
| protected void didCreateBean(ThreadContext callContext, EntityBean bean) throws OpenEJBException { |
| } |
| |
| protected ProxyInfo createEJBObject(Method callMethod, Object[] args, ThreadContext callContext, InterfaceType type) throws OpenEJBException { |
| BeanContext beanContext = callContext.getBeanContext(); |
| |
| callContext.setCurrentOperation(Operation.CREATE); |
| |
| /* |
| * According to section 9.1.5.1 of the EJB 1.1 specification, the "ejbPostCreate(...) |
| * method executes in the same transaction context as the previous ejbCreate(...) method." |
| * |
| * For this reason the TransactionScopeHandler methods usally preformed by the invoke( ) |
| * operation must be handled here along with the call explicitly. |
| * This ensures that the afterInvoke() is not processed between the ejbCreate and ejbPostCreate methods to |
| * ensure that the ejbPostCreate executes in the same transaction context of the ejbCreate. |
| * This would otherwise not be possible if container-managed transactions were used because |
| * the TransactionScopeManager would attempt to commit the transaction immediately after the ejbCreate |
| * and before the ejbPostCreate had a chance to execute. Once the ejbPostCreate method execute the |
| * super classes afterInvoke( ) method will be executed committing the transaction if its a CMT. |
| */ |
| |
| TransactionPolicy txPolicy = createTransactionPolicy(beanContext.getTransactionType(callMethod, type), callContext); |
| |
| EntityBean bean = null; |
| Object primaryKey = null; |
| try { |
| // Get new ready instance |
| bean = instanceManager.obtainInstance(callContext); |
| |
| // Obtain the proper ejbCreate() method |
| Method ejbCreateMethod = beanContext.getMatchingBeanMethod(callMethod); |
| |
| // invoke the ejbCreate which returns the primary key |
| primaryKey = ejbCreateMethod.invoke(bean, args); |
| |
| didCreateBean(callContext, bean); |
| |
| // determine post create callback method |
| Method ejbPostCreateMethod = beanContext.getMatchingPostCreateMethod(ejbCreateMethod); |
| |
| // create a new context containing the pk for the post create call |
| ThreadContext postCreateContext = new ThreadContext(beanContext, primaryKey); |
| postCreateContext.setCurrentOperation(Operation.POST_CREATE); |
| |
| ThreadContext oldContext = ThreadContext.enter(postCreateContext); |
| try { |
| // Invoke the ejbPostCreate method on the bean instance |
| ejbPostCreateMethod.invoke(bean, args); |
| |
| // According to section 9.1.5.1 of the EJB 1.1 specification, the "ejbPostCreate(...) |
| // method executes in the same transaction context as the previous ejbCreate(...) method." |
| // |
| // The bean is first insterted using db.create( ) and then after ejbPostCreate( ) its |
| // updated using db.update(). This protocol allows for visablity of the bean after ejbCreate |
| // within the current trasnaction. |
| } finally { |
| ThreadContext.exit(oldContext); |
| } |
| |
| // update pool |
| instanceManager.poolInstance(callContext, bean, primaryKey); |
| } catch (Throwable e) { |
| handleException(txPolicy, e, callContext, bean); |
| } finally { |
| afterInvoke(txPolicy, callContext); |
| } |
| |
| return new ProxyInfo(beanContext, primaryKey); |
| |
| } |
| |
| protected Object findMethod(Method callMethod, Object[] args, ThreadContext callContext, InterfaceType type) throws OpenEJBException { |
| BeanContext beanContext = callContext.getBeanContext(); |
| callContext.setCurrentOperation(Operation.FIND); |
| Method runMethod = beanContext.getMatchingBeanMethod(callMethod); |
| Object returnValue = invoke(type, callMethod, runMethod, args, callContext); |
| |
| /* |
| * Find operations return either a single primary key or a collection of primary keys. |
| * The primary keys are converted to ProxyInfo objects. |
| */ |
| if (returnValue instanceof java.util.Collection) { |
| Iterator keys = ((Collection) returnValue).iterator(); |
| Vector<ProxyInfo> proxies = new Vector<ProxyInfo>(); |
| while (keys.hasNext()) { |
| Object primaryKey = keys.next(); |
| proxies.addElement(new ProxyInfo(beanContext, primaryKey)); |
| } |
| returnValue = proxies; |
| } else if (returnValue instanceof java.util.Enumeration) { |
| Enumeration keys = (Enumeration) returnValue; |
| Vector<ProxyInfo> proxies = new Vector<ProxyInfo>(); |
| while (keys.hasMoreElements()) { |
| Object primaryKey = keys.nextElement(); |
| proxies.addElement(new ProxyInfo(beanContext, primaryKey)); |
| } |
| returnValue = new org.apache.openejb.util.ArrayEnumeration(proxies); |
| } else |
| returnValue = new ProxyInfo(beanContext, returnValue); |
| |
| return returnValue; |
| } |
| |
| protected Object homeMethod(Method callMethod, Object[] args, ThreadContext callContext, InterfaceType type) throws OpenEJBException { |
| BeanContext beanContext = callContext.getBeanContext(); |
| callContext.setCurrentOperation(Operation.HOME); |
| Method runMethod = beanContext.getMatchingBeanMethod(callMethod); |
| return invoke(type, callMethod, runMethod, args, callContext); |
| } |
| |
| protected void didRemove(EntityBean bean, ThreadContext threadContext) throws OpenEJBException { |
| cancelTimers(threadContext); |
| } |
| |
| private void cancelTimers(ThreadContext threadContext) { |
| BeanContext beanContext = threadContext.getBeanContext(); |
| Object primaryKey = threadContext.getPrimaryKey(); |
| |
| // if we have a real timerservice, stop all timers. Otherwise, ignore... |
| if (primaryKey != null) { |
| EjbTimerService timerService = beanContext.getEjbTimerService(); |
| if (timerService != null && timerService instanceof EjbTimerServiceImpl) { |
| for (Timer timer : beanContext.getEjbTimerService().getTimers(primaryKey)) { |
| timer.cancel(); |
| } |
| } |
| } |
| } |
| |
| protected void removeEJBObject(Method callMethod, Object[] args, ThreadContext callContext, InterfaceType type) throws OpenEJBException { |
| callContext.setCurrentOperation(Operation.REMOVE); |
| |
| BeanContext beanContext = callContext.getBeanContext(); |
| TransactionPolicy txPolicy = createTransactionPolicy(beanContext.getTransactionType(callMethod, type), callContext); |
| |
| EntityBean bean = null; |
| try { |
| |
| bean = instanceManager.obtainInstance(callContext); |
| |
| ejbLoad_If_No_Transaction(callContext, bean); |
| bean.ejbRemove(); |
| didRemove(bean, callContext); |
| instanceManager.poolInstance(callContext, bean, callContext.getPrimaryKey()); |
| } catch (Throwable e) { |
| handleException(txPolicy, e, callContext, bean); |
| } finally { |
| afterInvoke(txPolicy, callContext); |
| } |
| } |
| |
| private void handleException(TransactionPolicy txPolicy, Throwable e, ThreadContext callContext, EntityBean bean) throws OpenEJBException { |
| ExceptionType type; |
| if (e instanceof InvocationTargetException) { |
| e = ((InvocationTargetException) e).getTargetException(); |
| type = callContext.getBeanContext().getExceptionType(e); |
| } else if (e instanceof ApplicationException) { |
| e = ((ApplicationException) e).getRootCause(); |
| type = ExceptionType.APPLICATION; |
| } else if (e instanceof SystemException) { |
| e = ((SystemException) e).getRootCause(); |
| type = ExceptionType.SYSTEM; |
| } else { |
| type = ExceptionType.SYSTEM; |
| } |
| |
| if (type == ExceptionType.SYSTEM) { |
| // System Exception |
| if (bean != null) { |
| try { |
| instanceManager.discardInstance(callContext, bean); |
| } catch (SystemException e1) { |
| logger.error("The instance manager encountered an unkown system exception while trying to discard the entity instance with primary key " + callContext.getPrimaryKey()); |
| } |
| } |
| handleSystemException(txPolicy, e, callContext); |
| } else { |
| // Application Exception |
| instanceManager.poolInstance(callContext, bean, callContext.getPrimaryKey()); |
| handleApplicationException(txPolicy, e, type == ExceptionType.APPLICATION_ROLLBACK); |
| } |
| } |
| } |