You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by da...@apache.org on 2006/12/01 04:43:00 UTC

svn commit: r481138 - in /incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb: ./ core/ core/mdb/

Author: dain
Date: Thu Nov 30 19:42:58 2006
New Revision: 481138

URL: http://svn.apache.org/viewvc?view=rev&rev=481138
Log:
Finished initial mdb container implementation

Added:
    incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContext.java
    incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbInstanceFactory.java
Modified:
    incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/DeploymentInfo.java
    incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/CoreDeploymentInfo.java
    incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointFactory.java
    incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointHandler.java
    incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContainer.java

Modified: incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/DeploymentInfo.java
URL: http://svn.apache.org/viewvc/incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/DeploymentInfo.java?view=diff&rev=481138&r1=481137&r2=481138
==============================================================================
--- incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/DeploymentInfo.java (original)
+++ incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/DeploymentInfo.java Thu Nov 30 19:42:58 2006
@@ -18,6 +18,7 @@
 
 import java.lang.reflect.Method;
 import java.util.Collection;
+import java.util.Map;
 
 public interface DeploymentInfo {
 
@@ -73,7 +74,19 @@
 
     Class getMdbInterface();
 
+    Map<String, String> getActivationProperties();
+
     ClassLoader getClassLoader();
+
+    Method getPostActivate();
+
+    Method getPostConstruct();
+
+    Method getPreDestroy();
+
+    Method getPrePassivate();
+
+    void setContainer(Container container);
 
     public interface BusinessLocalHome extends javax.ejb.EJBLocalHome {
         Object create();

Modified: incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/CoreDeploymentInfo.java
URL: http://svn.apache.org/viewvc/incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/CoreDeploymentInfo.java?view=diff&rev=481138&r1=481137&r2=481138
==============================================================================
--- incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/CoreDeploymentInfo.java (original)
+++ incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/CoreDeploymentInfo.java Thu Nov 30 19:42:58 2006
@@ -106,6 +106,7 @@
     private final Map<Method, Method> methodMap = new HashMap<Method, Method>();
     private final Map<String, List<String>> securityRoleReferenceMap = new HashMap<String, List<String>>();
     private String jarPath;
+    private final Map<String, String> activationProperties = new HashMap<String, String>();
 
     public Class getInterface(InterfaceType interfaceType) {
         switch(interfaceType){
@@ -173,10 +174,11 @@
         }
     }
 
-    public CoreDeploymentInfo(DeploymentContext context, Class beanClass, Class mdbInterface) throws SystemException {
+    public CoreDeploymentInfo(DeploymentContext context, Class beanClass, Class mdbInterface, Map<String, String> activationProperties) throws SystemException {
         this.context = context;
         this.beanClass = beanClass;
         this.mdbInterface = mdbInterface;
+        this.activationProperties.putAll(activationProperties);
         this.componentType = BeanType.MESSAGE_DRIVEN;
 
         if (MessageDrivenBean.class.isAssignableFrom(beanClass)){
@@ -197,8 +199,8 @@
         this.containerData = containerData;
     }
 
-    public void setContainer(Container cont) {
-        container = cont;
+    public void setContainer(Container container) {
+        this.container = container;
     }
 
     public BeanType getComponentType() {
@@ -305,6 +307,15 @@
 
     public Class getMdbInterface() {
         return mdbInterface;
+    }
+
+    public Map<String, String> getActivationProperties() {
+        return activationProperties;
+    }
+
+    public void setActivationProperties(Map<String, String> activationProperties) {
+        this.activationProperties.clear();
+        this.activationProperties.putAll(activationProperties);
     }
 
     public Class getPrimaryKeyClass() {

Modified: incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointFactory.java
URL: http://svn.apache.org/viewvc/incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointFactory.java?view=diff&rev=481138&r1=481137&r2=481138
==============================================================================
--- incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointFactory.java (original)
+++ incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointFactory.java Thu Nov 30 19:42:58 2006
@@ -20,6 +20,7 @@
 import org.apache.openejb.DeploymentInfo;
 
 import javax.resource.spi.UnavailableException;
+import javax.resource.spi.ActivationSpec;
 import javax.resource.spi.endpoint.MessageEndpoint;
 import javax.resource.spi.endpoint.MessageEndpointFactory;
 import javax.transaction.xa.XAResource;
@@ -27,20 +28,28 @@
 import java.lang.reflect.Proxy;
 
 public class EndpointFactory implements MessageEndpointFactory {
-    private final MdbContainer mdbContainer;
+    private final ActivationSpec activationSpec;
+    private final MdbContainer container;
     private final DeploymentInfo deploymentInfo;
+    private final MdbInstanceFactory instanceFactory;
     private final ClassLoader classLoader;
     private final Class[] interfaces;
 
-    public EndpointFactory(MdbContainer mdbContainer, DeploymentInfo mdbDeploymentInfo) {
-        this.mdbContainer = mdbContainer;
-        this.deploymentInfo = mdbDeploymentInfo;
-        this.classLoader = mdbDeploymentInfo.getClassLoader();
-        interfaces = new Class[]{mdbDeploymentInfo.getMdbInterface(), MessageEndpoint.class};
+    public EndpointFactory(ActivationSpec activationSpec, MdbContainer container, DeploymentInfo deploymentInfo, MdbInstanceFactory instanceFactory) {
+        this.activationSpec = activationSpec;
+        this.container = container;
+        this.deploymentInfo = deploymentInfo;
+        this.instanceFactory = instanceFactory;
+        classLoader = deploymentInfo.getClassLoader();
+        interfaces = new Class[]{deploymentInfo.getMdbInterface(), MessageEndpoint.class};
+    }
+
+    public ActivationSpec getActivationSpec() {
+        return activationSpec;
     }
 
     public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException {
-        EndpointHandler endpointHandler = new EndpointHandler(mdbContainer, deploymentInfo, null, xaResource);
+        EndpointHandler endpointHandler = new EndpointHandler(container, deploymentInfo, instanceFactory, xaResource);
         MessageEndpoint messageEndpoint = (MessageEndpoint) Proxy.newProxyInstance(classLoader, interfaces, endpointHandler);
         return messageEndpoint;
     }

Modified: incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointHandler.java
URL: http://svn.apache.org/viewvc/incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointHandler.java?view=diff&rev=481138&r1=481137&r2=481138
==============================================================================
--- incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointHandler.java (original)
+++ incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/EndpointHandler.java Thu Nov 30 19:42:58 2006
@@ -18,66 +18,64 @@
 package org.apache.openejb.core.mdb;
 
 import org.apache.openejb.DeploymentInfo;
+import org.apache.openejb.SystemException;
+import org.apache.openejb.ApplicationException;
 
-import javax.ejb.EJBException;
-import javax.resource.ResourceException;
+import javax.resource.spi.UnavailableException;
+import javax.resource.spi.ApplicationServerInternalException;
+import javax.resource.spi.endpoint.MessageEndpoint;
 import javax.transaction.xa.XAResource;
+import javax.ejb.EJBException;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.util.Arrays;
 
-/**
- * Container for the local interface of a Message Driven Bean.
- * This container owns implementations of EJBLocalHome and EJBLocalObject
- * that can be used by a client in the same classloader as the server.
- * <p/>
- * The implementation of the interfaces is generated using cglib FastClass
- * proxies to avoid the overhead of native Java reflection.
- * <p/>
- * <p/>
- * <p/>
- * <p/>
- * The J2EE connector and EJB specifications are not clear on what happens when beforeDelivery or
- * afterDelivery throw an exception, so here is what we have decided:
- * <p/>
- * Exception from beforeDelivery:
- * if container started TX, roll it back
- * reset class loader to adapter classloader
- * reset state to STATE_NONE
- * <p/>
- * Exception from delivery method:
- * if container started TX, roll it back
- * reset class loader to adapter classloader
- * if state was STATE_BEFORE_CALLED, set state to STATE_ERROR so after can still be called
- * <p/>
- * Exception from afterDelivery:
- * if container started TX, roll it back
- * reset class loader to adapter classloader
- * reset state to STATE_NONE
- * <p/>
- * One subtle side effect of this is if the adapter ignores an exception from beforeDelivery and
- * continues with delivery and afterDelivery, the delivery will be treated as a single standalone
- * delivery and the afterDelivery will throw an IllegalStateException.
- *
- * @version $Revision: 451417 $ $Date: 2006-09-29 13:13:22 -0700 (Fri, 29 Sep 2006) $
- */
-public class EndpointHandler implements InvocationHandler {
+public class EndpointHandler implements InvocationHandler, MessageEndpoint {
     private static enum State {
-        NONE, BEFORE_CALLED, METHOD_CALLED
+        /**
+         * The handler has been initialized and is ready for invoation
+         */
+        NONE,
+
+        /**
+         * The beforeDelivery method has been called, and the next method called must be a message delivery method
+         * or release.
+         */
+        BEFORE_CALLED,
+
+        /**
+         * The message delivery method has been called successfully, and the next method called must be afterDelivery
+         * or release.
+         */
+        METHOD_CALLED,
+
+        /**
+         * The message delivery threw a system exception, and the next method called must be afterDelivery
+         * or release.  This state notified the afterDelivery method that the instace must be replaced with a new
+         * instance.
+         */
+        SYSTEM_EXCEPTION,
+
+        /**
+         * This message endpoint handler has been released and can no longer be used.
+         */
+        RELEASED
     }
 
     private final MdbContainer container;
     private final DeploymentInfo deployment;
-    private final Object instance;
+    private final MdbInstanceFactory instanceFactory;
+    private final XAResource xaResource;
 
-    private boolean released = false;
     private State state = State.NONE;
-    private ClassLoader adapterClassLoader;
+    private Object instance;
 
-    public EndpointHandler(MdbContainer container, DeploymentInfo deployment, Object instance, XAResource xaResource) {
+    public EndpointHandler(MdbContainer container, DeploymentInfo deployment, MdbInstanceFactory instanceFactory, XAResource xaResource) throws UnavailableException {
         this.container = container;
         this.deployment = deployment;
-        this.instance = instance;
+        this.instanceFactory = instanceFactory;
+        this.xaResource = xaResource;
+        instance = instanceFactory.createInstance();
     }
 
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
@@ -112,29 +110,24 @@
 
     }
 
-    public void beforeDelivery(Method method) throws NoSuchMethodException, ResourceException {
+    public void beforeDelivery(Method method) throws ApplicationServerInternalException {
         // verify current state
-        if (released) throw new IllegalStateException("Proxy has been released");
         switch (state) {
+            case RELEASED:
+                throw new IllegalStateException("Message endpoint factory has been released");
             case BEFORE_CALLED:
                 throw new IllegalStateException("beforeDelivery can not be called again until message is delivered and afterDelivery is called");
             case METHOD_CALLED:
+            case SYSTEM_EXCEPTION:
                 throw new IllegalStateException("The last message delivery must be completed with an afterDeliver before beforeDeliver can be called again");
         }
 
-        // call afterDelivery on the container
-        installAppClassLoader();
+        // call beforeDelivery on the container
         try {
-            container.beforeDelivery(deployment.getDeploymentID(), instance, method);
-        } catch (NoSuchMethodException e) {
-            restoreAdapterClassLoader();
-            throw e;
-        } catch (ResourceException e) {
-            restoreAdapterClassLoader();
-            throw e;
-        } catch (Throwable throwable) {
-            restoreAdapterClassLoader();
-            throw new ResourceException(throwable);
+            container.beforeDelivery(deployment.getDeploymentID(), instance, method, xaResource);
+        } catch (SystemException se) {
+            Throwable throwable = (se.getRootCause() != null) ? se.getRootCause() : se;
+            throw new ApplicationServerInternalException(throwable);
         }
 
         // before completed successfully we are now ready to invoke bean
@@ -143,60 +136,65 @@
 
     public Object deliverMessage(Method method, Object[] args) throws Throwable {
         // verify current state
-        if (released) throw new IllegalStateException("Proxy has been released");
         switch (state) {
+            case RELEASED:
+                throw new IllegalStateException("Message endpoint factory has been released");
             case BEFORE_CALLED:
                 state = State.METHOD_CALLED;
             case METHOD_CALLED:
+            case SYSTEM_EXCEPTION:
                 throw new IllegalStateException("The last message delivery must be completed with an afterDeliver before another message can be delivered");
         }
 
 
         // if beforeDelivery was not called, call it now
-        if (state == State.NONE) {
+        boolean callBeforeAfter = (state == State.NONE);
+        if (callBeforeAfter) {
             try {
-                container.beforeDelivery(deployment.getDeploymentID(), instance, method);
-            } catch (Throwable throwable) {
-                if (throwable instanceof EJBException) {
-                    throw (EJBException) throwable;
-                }
-                throw (EJBException) new EJBException().initCause(throwable);
+                beforeDelivery(method);
+            } catch (ApplicationServerInternalException e) {
+                throw (EJBException) new EJBException().initCause(e.getCause());
             }
         }
 
-        boolean exceptionThrown = false;
+        Throwable throwable = null;
+        Object value = null;
         try {
-            Object value = container.invoke(instance, method, args);
-            return value;
-        } catch (Throwable throwable) {
-            exceptionThrown = true;
-            throw throwable;
+            // deliver the message
+            value = container.invoke(instance, method, args);
+        } catch (SystemException se) {
+            throwable = (se.getRootCause() != null) ? se.getRootCause() : se;
+            state = State.SYSTEM_EXCEPTION;
+        } catch (ApplicationException ae) {
+            throwable = (ae.getRootCause() != null) ? ae.getRootCause() : ae;
         } finally {
             // if the adapter is not using before/after, we must call afterDelivery to clean up
-            if (state == State.NONE) {
+            if (callBeforeAfter) {
                 try {
-                    container.afterDelivery(instance);
-                } catch (Throwable throwable) {
-                    // if bean threw an exception, do not override that exception
-                    if (!exceptionThrown) {
-                        EJBException ejbException;
-                        if (throwable instanceof EJBException) {
-                            ejbException = (EJBException) throwable;
-                        } else {
-                            ejbException = new EJBException();
-                            ejbException.initCause(throwable);
-                        }
-                        throw ejbException;
-                    }
+                    afterDelivery();
+                } catch (ApplicationServerInternalException e) {
+                    throwable = throwable == null ? e.getCause() : throwable;
+                } catch (UnavailableException e) {
+                    throwable = throwable == null ? e : throwable;
                 }
             }
         }
+
+        if (throwable != null) {
+            if (isValidException(method, throwable)) {
+                throw throwable;
+            } else {
+                throw new EJBException().initCause(throwable);
+            }
+        }
+        return value;
     }
 
-    public void afterDelivery() throws ResourceException {
+    public void afterDelivery() throws ApplicationServerInternalException, UnavailableException {
         // verify current state
-        if (released) throw new IllegalStateException("Proxy has been released");
         switch (state) {
+            case RELEASED:
+                throw new IllegalStateException("Message endpoint factory has been released");
             case BEFORE_CALLED:
                 throw new IllegalStateException("Exactally one message must be delivered between beforeDelivery and afterDelivery");
             case NONE:
@@ -205,44 +203,60 @@
 
 
         // call afterDelivery on the container
+        boolean exceptionThrown = false;
         try {
             container.afterDelivery(instance);
-        } catch (ResourceException e) {
-            throw e;
-        } catch (Throwable throwable) {
-            throw new ResourceException(throwable);
+        } catch (SystemException se) {
+            exceptionThrown = true;
+
+            Throwable throwable = (se.getRootCause() != null) ? se.getRootCause() : se;
+            throw new ApplicationServerInternalException(throwable);
         } finally {
+            if (state == State.SYSTEM_EXCEPTION) {
+                recreateInstance(exceptionThrown);
+            }
             // we are now in the default NONE state
             state = State.NONE;
-            restoreAdapterClassLoader();
+        }
+    }
+
+    private void recreateInstance(boolean exceptionAlreadyThrown) throws UnavailableException {
+        try {
+            instance = instanceFactory.recreateInstance(instance);
+        } catch (UnavailableException e) {
+            // an error occured wile attempting to create the replacement instance
+            // this endpoint is now failed
+            state = State.RELEASED;
+
+            // if bean threw an exception, do not override that exception
+            if (!exceptionAlreadyThrown) {
+                throw e;
+            }
         }
     }
 
     public void release() {
-        if (released) return;
-        released = true;
+        if (state == State.RELEASED) return;
+        state = State.RELEASED;
 
         // notify the container
         try {
             container.release(instance);
         } finally {
-            restoreAdapterClassLoader();
+            instanceFactory.freeInstance(instance);
+            instance = null;
         }
     }
 
-    private void installAppClassLoader() {
-        Thread currentThread = Thread.currentThread();
-
-        adapterClassLoader = currentThread.getContextClassLoader();
-        if (adapterClassLoader != deployment.getClassLoader()) {
-            currentThread.setContextClassLoader(deployment.getClassLoader());
-        }
-    }
+    private boolean isValidException(Method method, Throwable throwable) {
+        if (throwable instanceof RuntimeException || throwable instanceof Error) return true;
 
-    private void restoreAdapterClassLoader() {
-        if (adapterClassLoader != deployment.getClassLoader()) {
-            Thread.currentThread().setContextClassLoader(adapterClassLoader);
+        Class<?>[] exceptionTypes = method.getExceptionTypes();
+        for (Class<?> exceptionType : exceptionTypes) {
+            if (exceptionType.isInstance(throwable)) {
+                return true;
+            }
         }
-        adapterClassLoader = null;
+        return false;
     }
 }

Modified: incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContainer.java
URL: http://svn.apache.org/viewvc/incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContainer.java?view=diff&rev=481138&r1=481137&r2=481138
==============================================================================
--- incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContainer.java (original)
+++ incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContainer.java Thu Nov 30 19:42:58 2006
@@ -20,17 +20,26 @@
 import org.apache.openejb.OpenEJBException;
 import org.apache.openejb.DeploymentInfo;
 import org.apache.openejb.Container;
+import org.apache.openejb.SystemException;
+import org.apache.openejb.ApplicationException;
+import org.apache.openejb.spi.SecurityService;
 import org.apache.openejb.core.ThreadContext;
 import org.apache.openejb.core.CoreDeploymentInfo;
 import org.apache.openejb.core.transaction.TransactionContext;
 import org.apache.openejb.core.transaction.TransactionPolicy;
 import org.apache.log4j.Logger;
+import org.apache.xbean.recipe.ObjectRecipe;
 
 import javax.transaction.TransactionManager;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAResource;
+import javax.resource.spi.ResourceAdapter;
+import javax.resource.spi.ActivationSpec;
+import javax.resource.ResourceException;
 import java.lang.reflect.Method;
-import java.rmi.RemoteException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Arrays;
 
 public class MdbContainer implements Container {
     private static final Logger logger = Logger.getLogger("OpenEJB");
@@ -38,20 +47,27 @@
 
     private final Object containerID;
     private final TransactionManager transactionManager;
+    private final SecurityService securityService;
+    private final ResourceAdapter resourceAdapter;
+    private final Class activationSpecClass;
 
-    private final Map<Object, DeploymentInfo> deploymentRegistry = new HashMap<Object, DeploymentInfo>();
+    private final Map<Object, DeploymentInfo> deployments = new HashMap<Object, DeploymentInfo>();
+    private final Map<Object, EndpointFactory> endpointFactories = new HashMap<Object, EndpointFactory>();
 
-    public MdbContainer(Object containerID, TransactionManager transactionManager) {
+    public MdbContainer(Object containerID, TransactionManager transactionManager, SecurityService securityService, ResourceAdapter resourceAdapter, Class activationSpecClass) {
         this.containerID = containerID;
         this.transactionManager = transactionManager;
+        this.securityService = securityService;
+        this.resourceAdapter = resourceAdapter;
+        this.activationSpecClass = activationSpecClass;
     }
 
     public synchronized DeploymentInfo [] deployments() {
-        return deploymentRegistry.values().toArray(new DeploymentInfo[deploymentRegistry.size()]);
+        return deployments.values().toArray(new DeploymentInfo[deployments.size()]);
     }
 
     public synchronized DeploymentInfo getDeploymentInfo(Object deploymentID) {
-        return deploymentRegistry.get(deploymentID);
+        return deployments.get(deploymentID);
     }
 
     public int getContainerType() {
@@ -62,49 +78,152 @@
         return containerID;
     }
 
-    public synchronized void deploy(Object deploymentID, DeploymentInfo info) throws OpenEJBException {
-        deploymentRegistry.put(deploymentID, info);
-        CoreDeploymentInfo di = (CoreDeploymentInfo) info;
-        di.setContainer(this);
+    public void deploy(Object deploymentId, DeploymentInfo deploymentInfo) throws OpenEJBException {
+        // create the activation spec
+        ActivationSpec activationSpec = createActivationSpec(deploymentInfo);
+
+        // create the message endpoint
+        MdbInstanceFactory instanceFactory = new MdbInstanceFactory(deploymentInfo, transactionManager, securityService, 0);
+        EndpointFactory endpointFactory = new EndpointFactory(activationSpec, this, deploymentInfo, instanceFactory);
+
+        // update the data structures
+        // this must be done before activating the endpoint since the ra may immedately begin delivering messages
+        synchronized (this) {
+            deploymentInfo.setContainer(this);
+            deployments.put(deploymentId, deploymentInfo);
+            endpointFactories.put(deploymentId, endpointFactory);
+        }
+
+        // activate the endpoint
+        try {
+            resourceAdapter.endpointActivation(endpointFactory, activationSpec);
+        } catch (ResourceException e) {
+            // activation failed... clean up
+            synchronized (this) {
+                deploymentInfo.setContainer(null);
+                deployments.remove(deploymentId);
+                endpointFactories.remove(deploymentId);
+            }
+
+            throw new OpenEJBException(e);
+        }
+
+    }
+
+    private ActivationSpec createActivationSpec(DeploymentInfo deploymentInfo)throws OpenEJBException {
+        try {
+            // initialize the object recipe
+            ObjectRecipe objectRecipe = new ObjectRecipe(activationSpecClass);
+            Map<String, String> activationProperties = deploymentInfo.getActivationProperties();
+            for (Map.Entry<String, String> entry : activationProperties.entrySet()) {
+                objectRecipe.setMethodProperty(entry.getKey(), entry.getValue());
+            }
+
+            // create the activationSpec
+            ActivationSpec activationSpec = (ActivationSpec) objectRecipe.create(deploymentInfo.getClassLoader());
+
+            // validate the activation spec
+            activationSpec.validate();
+
+            // set the resource adapter into the activation spec
+            activationSpec.setResourceAdapter(resourceAdapter);
+
+            return activationSpec;
+        } catch (Exception e) {
+            throw new OpenEJBException("Unable to create activation spec");
+        }
     }
 
-    public synchronized void undeploy(Object deploymentID) throws OpenEJBException {
-        CoreDeploymentInfo di = (CoreDeploymentInfo) deploymentRegistry.remove(deploymentID);
-        di.setContainer(null);
+    public void undeploy(Object deploymentId) throws OpenEJBException {
+        try {
+            EndpointFactory endpointFactory;
+            synchronized (this) {
+                endpointFactory = endpointFactories.get(deploymentId);
+            }
+            if (endpointFactory != null) {
+                resourceAdapter.endpointDeactivation(endpointFactory, endpointFactory.getActivationSpec());
+            }
+        } finally {
+            synchronized (this) {
+                endpointFactories.remove(deploymentId);
+                DeploymentInfo deploymentInfo = deployments.remove(deploymentId);
+                if (deploymentInfo != null) {
+                    deploymentInfo.setContainer(null);
+                }
+            }
+        }
     }
 
-    public void beforeDelivery(Object deployId, Object instance, Method method) throws Throwable {
+    public void beforeDelivery(Object deployId, Object instance, Method method, XAResource xaResource) throws SystemException {
         // get the target deployment (MDB)
         CoreDeploymentInfo deployInfo = (CoreDeploymentInfo) getDeploymentInfo(deployId);
+        if (deployInfo == null) throw new SystemException("Unknown deployment " + deployId);
 
-        // obtain the context objects
+        // intialize call context
         ThreadContext callContext = ThreadContext.getThreadContext();
+        callContext.setDeploymentInfo(deployInfo);
         MdbCallContext mdbCallContext = new MdbCallContext();
+        callContext.setUnspecified(mdbCallContext);
+        mdbCallContext.deliveryMethod = method;
 
         // create the tx data
         mdbCallContext.txPolicy = deployInfo.getTransactionPolicy(method);
         mdbCallContext.txContext = new TransactionContext(callContext, transactionManager);
 
+        // install the application classloader
+        installAppClassLoader(mdbCallContext, deployInfo.getClassLoader());
+
         // call the tx before method
-        mdbCallContext.txPolicy.beforeInvoke(instance, mdbCallContext.txContext);
+        try {
+            mdbCallContext.txPolicy.beforeInvoke(instance, mdbCallContext.txContext);
+            enlistResource(xaResource);
+        } catch (ApplicationException e) {
+            restoreAdapterClassLoader(mdbCallContext);
+
+            throw new SystemException("Should never get an Application exception", e);
+        } catch (SystemException e) {
+            restoreAdapterClassLoader(mdbCallContext);
+            throw e;
+        }
+    }
 
-        // save the tx data into the thread context
-        callContext.setDeploymentInfo(deployInfo);
-        callContext.setUnspecified(mdbCallContext);
+    private void enlistResource(XAResource xaResource) throws SystemException {
+        if (xaResource == null) return;
+
+        try {
+            Transaction transaction = transactionManager.getTransaction();
+            if (transaction != null) {
+                transaction.enlistResource(xaResource);
+            }
+        } catch (Exception e) {
+            throw new SystemException("Unable to enlist xa resource in the transaction", e);
+        }
     }
 
-    public Object invoke(Object instance, Method method, Object... args) throws Throwable {
+    public Object invoke(Object instance, Method method, Object... args) throws SystemException, ApplicationException {
         if (args == null) {
             args = NO_ARGS;
         }
 
+        // get the context data
+        ThreadContext callContext = ThreadContext.getThreadContext();
+        CoreDeploymentInfo deployInfo = callContext.getDeploymentInfo();
+        MdbCallContext mdbCallContext = (MdbCallContext) callContext.getUnspecified();
+
+        if (mdbCallContext == null) {
+            throw new IllegalStateException("beforeDelivery was not called");
+        }
+
+        // verify the delivery method passed to beforeDeliver is the same method that was invoked
+        if (!mdbCallContext.deliveryMethod.getName().equals(method.getName()) ||
+                !Arrays.deepEquals(mdbCallContext.deliveryMethod.getParameterTypes(), method.getParameterTypes())) {
+            throw new IllegalStateException("Delivery method specified in beforeDelivery is not the delivery method called");
+        }
+
+        // remember the return value or exception so it can be logged
         Object returnValue = null;
-        Throwable exception = null;
+        OpenEJBException openEjbException = null;
         try {
-            // get the context data
-            ThreadContext callContext = ThreadContext.getThreadContext();
-            CoreDeploymentInfo deployInfo = callContext.getDeploymentInfo();
-
             if (logger.isInfoEnabled()) {
                 logger.info("invoking method " + method.getName() + " on " + deployInfo.getDeploymentID());
             }
@@ -112,44 +231,29 @@
             // determine the target method on the bean instance class
             Method targetMethod = deployInfo.getMatchingBeanMethod(method);
 
-            // ivoke the target method
-            returnValue = _invoke(instance, targetMethod, args, (MdbCallContext) callContext.getUnspecified());
+            // invoke the target method
+            returnValue = _invoke(instance, targetMethod, args, mdbCallContext);
             return returnValue;
-        } catch (org.apache.openejb.ApplicationException ae) {
-            // Application exceptions must be reported dirctly to the client. They
-            // do not impact the viability of the proxy.
-            exception = (ae.getRootCause() != null) ? ae.getRootCause() : ae;
-            throw exception;
-        } catch (org.apache.openejb.SystemException se) {
-            // A system exception would be highly unusual and would indicate a sever
-            // problem with the container system.
-            exception = (se.getRootCause() != null) ? se.getRootCause() : se;
-            logger.error("The container received an unexpected exception: ", exception);
-            throw new RemoteException("Container has suffered a SystemException", exception);
-        } catch (org.apache.openejb.OpenEJBException oe) {
-            // This is a normal container exception thrown while processing the request
-            exception = (oe.getRootCause() != null) ? oe.getRootCause() : oe;
-            logger.warn("The container received an unexpected exception: ", exception);
-            throw new RemoteException("Unknown Container Exception", oe.getRootCause());
+        } catch (ApplicationException e) {
+            openEjbException = e;
+            throw e;
+        } catch (SystemException e) {
+            openEjbException = e;
+            throw e;
         } finally {
             // Log the invocation results
             if (logger.isDebugEnabled()) {
-                if (exception == null) {
+                if (openEjbException == null) {
                     logger.debug("finished invoking method " + method.getName() + ". Return value:" + returnValue);
                 } else {
-                    logger.debug("finished invoking method " + method.getName() + " with exception " + exception);
-                }
-            } else if (logger.isInfoEnabled()) {
-                if (exception == null) {
-                    logger.debug("finished invoking method " + method.getName());
-                } else {
+                    Throwable exception = (openEjbException.getRootCause() != null) ? openEjbException.getRootCause() : openEjbException;
                     logger.debug("finished invoking method " + method.getName() + " with exception " + exception);
                 }
             }
         }
     }
 
-    private Object _invoke(Object instance, Method runMethod, Object [] args, MdbCallContext mdbCallContext) throws OpenEJBException {
+    private Object _invoke(Object instance, Method runMethod, Object [] args, MdbCallContext mdbCallContext) throws SystemException, ApplicationException {
         try {
             Object returnValue = runMethod.invoke(instance, args);
             return returnValue;
@@ -176,14 +280,20 @@
     }
 
 
-    public void afterDelivery(Object instance) throws Throwable {
+    public void afterDelivery(Object instance) throws SystemException {
         // get the mdb call context
         ThreadContext callContext = ThreadContext.getThreadContext();
         MdbCallContext mdbCallContext = (MdbCallContext) callContext.getUnspecified();
         ThreadContext.setThreadContext(null);
 
         // invoke the tx after method
-        mdbCallContext.txPolicy.afterInvoke(instance, mdbCallContext.txContext);
+        try {
+            mdbCallContext.txPolicy.afterInvoke(instance, mdbCallContext.txContext);
+        } catch (ApplicationException e) {
+            throw new SystemException("Should never get an Application exception", e);
+        } finally {
+            restoreAdapterClassLoader(mdbCallContext);
+        }
     }
 
     public void release(Object instance) {
@@ -198,12 +308,30 @@
                 mdbCallContext.txPolicy.afterInvoke(instance, mdbCallContext.txContext);
             } catch (Exception e) {
                 logger.error("error while releasing message endpoint", e);
+            } finally {
+                restoreAdapterClassLoader(mdbCallContext);
             }
         }
     }
 
     private static class MdbCallContext {
+        private Method deliveryMethod;
+        private ClassLoader adapterClassLoader;
         private TransactionPolicy txPolicy;
         private TransactionContext txContext;
+    }
+
+    private void installAppClassLoader(MdbCallContext mdbCallContext, ClassLoader applicationClassLoader) {
+        Thread currentThread = Thread.currentThread();
+
+        mdbCallContext.adapterClassLoader = currentThread.getContextClassLoader();
+        if (mdbCallContext.adapterClassLoader != applicationClassLoader) {
+            currentThread.setContextClassLoader(applicationClassLoader);
+        }
+    }
+
+    private void restoreAdapterClassLoader(MdbCallContext mdbCallContext) {
+        Thread.currentThread().setContextClassLoader(mdbCallContext.adapterClassLoader);
+        mdbCallContext.adapterClassLoader = null;
     }
 }

Added: incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContext.java
URL: http://svn.apache.org/viewvc/incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContext.java?view=auto&rev=481138
==============================================================================
--- incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContext.java (added)
+++ incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContext.java Thu Nov 30 19:42:58 2006
@@ -0,0 +1,145 @@
+/**
+ *
+ * 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.mdb;
+
+import org.apache.openejb.spi.SecurityService;
+import org.apache.openejb.core.ThreadContext;
+import org.apache.openejb.core.Operations;
+import org.apache.openejb.core.CoreContext;
+import org.apache.openejb.core.stateless.StatelessEjbObjectHandler;
+import org.apache.openejb.core.ivm.EjbObjectProxyHandler;
+import org.apache.openejb.RpcContainer;
+import org.apache.openejb.InterfaceType;
+
+import javax.transaction.TransactionManager;
+import javax.xml.rpc.handler.MessageContext;
+import javax.ejb.EJBHome;
+import javax.ejb.EJBObject;
+import javax.ejb.EJBLocalObject;
+import javax.ejb.EJBLocalHome;
+
+public class MdbContext extends CoreContext implements javax.ejb.MessageDrivenContext {
+    public MdbContext(TransactionManager transactionManager, SecurityService securityService) {
+        super(transactionManager, securityService);
+    }
+
+    public void checkBeanState(byte methodCategory) throws IllegalStateException {
+        /*
+        SECURITY_METHOD:
+        USER_TRANSACTION_METHOD:
+        ROLLBACK_METHOD:
+        EJBOBJECT_METHOD:
+
+        The super class, CoreContext determines if Context.getUserTransaction( ) method
+        maybe called before invoking this.checkBeanState( ).  Only "bean managed" transaction
+        beans may access this method.
+
+        */
+        ThreadContext callContext = ThreadContext.getThreadContext();
+
+        switch (callContext.getCurrentOperation()) {
+            case Operations.OP_SET_CONTEXT:
+                /*
+                Allowed Operations:
+                    getEJBHome
+                Prohibited Operations:
+                    getCallerPrincipal
+                    getRollbackOnly,
+                    isCallerInRole
+                    setRollbackOnly
+                    getEJBObject
+                    getPrimaryKey
+                    getUserTransaction
+                */
+                if (methodCategory != EJBHOME_METHOD)
+                    throw new IllegalStateException("Invalid operation attempted");
+                break;
+            case Operations.OP_CREATE:
+            case Operations.OP_REMOVE:
+                /*
+                Allowed Operations:
+                    getEJBHome
+                    getEJBObject
+                    getPrimaryKey
+                    getUserTransaction
+                Prohibited Operations:
+                    getCallerPrincipal
+                    getRollbackOnly,
+                    isCallerInRole
+                    setRollbackOnly
+                */
+                if (methodCategory == EJBHOME_METHOD
+                        || methodCategory == EJBOBJECT_METHOD
+                        || methodCategory == USER_TRANSACTION_METHOD)
+                    break;
+                else
+                    throw new IllegalStateException("Invalid operation attempted");
+            case Operations.OP_BUSINESS:
+                /*
+                Allowed Operations:
+                    getEJBHome
+                    getEJBObject
+                    getPrimaryKey
+                    getUserTransaction
+                    getCallerPrincipal
+                    getRollbackOnly,
+                    isCallerInRole
+                    setRollbackOnly
+                Prohibited Operations:
+                */
+                break;
+        }
+
+    }
+
+    protected EjbObjectProxyHandler newEjbObjectHandler(RpcContainer container, Object pk, Object depID, InterfaceType interfaceType) {
+        return new StatelessEjbObjectHandler(container, pk, depID, interfaceType);
+    }
+
+    public MessageContext getMessageContext() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    public Object getBusinessObject(Class businessInterface) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    public Class getInvokedBusinessInterface() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    public Object getPrimaryKey() {
+        throw new UnsupportedOperationException("getPrimaryKey is not supported for a message driven bean");
+    }
+
+    public EJBHome getEJBHome() {
+        throw new UnsupportedOperationException("getEJBHome is not supported for a message driven bean");
+    }
+
+    public EJBObject getEJBObject() {
+        throw new UnsupportedOperationException("getEJBObject is not supported for a message driven bean");
+    }
+
+    public EJBLocalObject getEJBLocalObject() {
+        throw new UnsupportedOperationException("getEJBLocalObject is not supported for a message driven bean");
+    }
+
+    public EJBLocalHome getEJBLocalHome() {
+        throw new UnsupportedOperationException("getEJBLocalHome is not supported for a message driven bean");
+    }
+}

Added: incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbInstanceFactory.java
URL: http://svn.apache.org/viewvc/incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbInstanceFactory.java?view=auto&rev=481138
==============================================================================
--- incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbInstanceFactory.java (added)
+++ incubator/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbInstanceFactory.java Thu Nov 30 19:42:58 2006
@@ -0,0 +1,188 @@
+/**
+ *
+ * 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.mdb;
+
+import org.apache.log4j.Logger;
+import org.apache.openejb.DeploymentInfo;
+import org.apache.openejb.core.Operations;
+import org.apache.openejb.core.ThreadContext;
+import org.apache.openejb.spi.SecurityService;
+import org.apache.xbean.recipe.ObjectRecipe;
+import org.apache.xbean.recipe.StaticRecipe;
+
+import javax.resource.spi.UnavailableException;
+import javax.transaction.TransactionManager;
+import java.lang.reflect.Method;
+
+/**
+ * A MdbInstanceFactory creates instances of message driven beans for a single instance. This class differs from other
+ * instance managers in OpenEJB as it doesn't do pooling and it creates instances for only a single EJB deployment.
+ * </p>
+ * The MdbContainer assumes that the resouce adapter is pooling message endpoints so a second level of pooling in the
+ * container would be inefficient.  This is true of all known resouce adapters in opensource (ActiveMQ), so if this is
+ * a poor assumption for your resource adapter, contact the OpenEJB developers.
+ * </p>
+ * This class can optionally limit the number of bean instances and therefore the message endpoints available to the
+ * resource adapter.
+ */
+public class MdbInstanceFactory {
+    private static final Logger logger = Logger.getLogger("OpenEJB");
+
+    private final DeploymentInfo deploymentInfo;
+    private final TransactionManager transactionManager;
+    private final SecurityService securityService;
+    private final int instanceLimit;
+    private int instanceCount;
+
+    /**
+     * Creates a MdbInstanceFactory for a single specific deployment.
+     * @param deploymentInfo the deployment for which instances will be created
+     * @param transactionManager the transaction manager for this container system
+     * @param securityService the transaction manager for this container system
+     * @param instanceLimit the maximal number of instances or <= 0 if unlimited
+     */
+    public MdbInstanceFactory(DeploymentInfo deploymentInfo, TransactionManager transactionManager, SecurityService securityService, int instanceLimit) {
+        this.deploymentInfo = deploymentInfo;
+        this.transactionManager = transactionManager;
+        this.securityService = securityService;
+        this.instanceLimit = instanceLimit;
+    }
+
+    /**
+     * Gets the maximal number of instances that can exist at any time.
+     * @return the maximum number of instances or <= 0 if unlimitied
+     */
+    public int getInstanceLimit() {
+        return instanceLimit;
+    }
+
+    /**
+     * Gets the current number of created instances.
+     * @return the current number of instances created
+     */
+    public synchronized int getInstanceCount() {
+        return instanceCount;
+    }
+
+    /**
+     * Creates a new mdb instance preforming all necessary lifecycle callbacks
+     * @return a new message driven bean instance
+     * @throws UnavailableException if the instance limit has been exceeded or
+     *   if an exception occurs while creating the bean instance
+     */
+    public Object createInstance() throws UnavailableException {
+        synchronized (this) {
+            // check the instance limit
+            if (instanceLimit > 0 && instanceCount >= instanceLimit) {
+                throw new UnavailableException("Only " + instanceLimit + " instances can be created");
+            }
+            // increment the instance count
+            instanceCount++;
+        }
+
+        try {
+            Object bean = constructBean();
+            return bean;
+        } catch (UnavailableException e) {
+            // decrement the instance count
+            synchronized (this) {
+                instanceCount--;
+            }
+
+            throw e;
+        }
+    }
+
+    /**
+     * Frees an instance no longer needed by the resource adapter.  This method makes all the necessary lifecycle
+     * callbacks and decrements the instance count.  This method should not be used to disposed of beans that have
+     * thrown a system exception.  Instead the discardInstance method should be called.
+     * @param bean the bean instance to free
+     */
+    public void freeInstance(Object bean) {
+        if (bean == null) throw new NullPointerException("bean is null");
+
+        // decrement the instance count
+        synchronized (this) {
+            instanceCount--;
+        }
+
+        ThreadContext callContext = ThreadContext.getThreadContext();
+        byte originalOperation = callContext.getCurrentOperation();
+        try {
+            // call post destroy method
+            callContext.setCurrentOperation(Operations.OP_REMOVE);
+            Method preDestroy = callContext.getDeploymentInfo().getPreDestroy();
+            if (preDestroy != null){
+                preDestroy.invoke(bean);
+            }
+        } catch (Throwable re) {
+            MdbInstanceFactory.logger.error("The bean instance " + bean + " threw a system exception:" + re, re);
+        } finally {
+            callContext.setCurrentOperation(originalOperation);
+        }
+    }
+
+    /**
+     * Recreates a bean instance that has thrown a system exception.  As required by the EJB specification, lifecycle
+     * callbacks are not invoked.  To normally free a bean instance call the freeInstance method.
+     * @param bean the bean instance to discard
+     * @return the new replacement bean instance
+     */
+    public Object recreateInstance(Object bean) throws UnavailableException {
+        if (bean == null) throw new NullPointerException("bean is null");
+        Object newBean = constructBean();
+        return newBean;
+    }
+
+    private Object constructBean() throws UnavailableException {
+        Class beanClass = deploymentInfo.getBeanClass();
+        ObjectRecipe objectRecipe = new ObjectRecipe(beanClass);
+
+        ThreadContext callContext = ThreadContext.getThreadContext();
+        byte originalOperation = callContext.getCurrentOperation();
+        try {
+
+            // construct the bean instance
+            callContext.setCurrentOperation(Operations.OP_SET_CONTEXT);
+            MdbContext mdbContext = new MdbContext(transactionManager, securityService);
+            objectRecipe.setProperty("messageDrivenContext", new StaticRecipe(mdbContext));
+            Object bean = objectRecipe.create();
+
+            // call the post construct method
+            callContext.setCurrentOperation(Operations.OP_CREATE);
+            Method postConstruct = deploymentInfo.getPostConstruct();
+            if (postConstruct != null){
+                postConstruct.invoke(bean);
+            }
+
+
+            return bean;
+        } catch (Throwable e) {
+            if (e instanceof java.lang.reflect.InvocationTargetException) {
+                e = ((java.lang.reflect.InvocationTargetException) e).getTargetException();
+            }
+            String message = "The bean instance threw a system exception:" + e;
+            MdbInstanceFactory.logger.error(message, e);
+            throw new UnavailableException(message, e);
+        } finally {
+            callContext.setCurrentOperation(originalOperation);
+        }
+    }
+
+}