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 2008/08/26 23:36:20 UTC

svn commit: r689243 - in /openejb/trunk/openejb3/container/openejb-core/src: main/java/org/apache/openejb/ main/java/org/apache/openejb/core/stateful/ test/java/org/apache/openejb/core/stateful/

Author: dain
Date: Tue Aug 26 14:36:19 2008
New Revision: 689243

URL: http://svn.apache.org/viewvc?rev=689243&view=rev
Log:
Added simple pluggable cache interface to StatefulContainer.
Merged StatefulInstanceManger and StatefulSynchronizationCorrdinator into StatefulContainer.

Added:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java
Removed:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/BeanEntry.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SessionSynchronizationCoordinator.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulInstanceManager.java
Modified:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/InjectionProcessor.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/PassivationStrategy.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimplePassivater.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java
    openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java
    openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/InjectionProcessor.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/InjectionProcessor.java?rev=689243&r1=689242&r2=689243&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/InjectionProcessor.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/InjectionProcessor.java Tue Aug 26 14:36:19 2008
@@ -21,12 +21,13 @@
 import org.apache.openejb.util.Logger;
 import org.apache.xbean.recipe.ObjectRecipe;
 import org.apache.xbean.recipe.Option;
-import org.apache.xbean.recipe.StaticRecipe;
 
 import javax.naming.Context;
 import javax.naming.NamingException;
 import java.util.List;
 import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.Map.Entry;
 import java.lang.reflect.Method;
 import java.lang.reflect.InvocationTargetException;
 
@@ -34,11 +35,20 @@
     private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, InjectionProcessor.class);
     private final Class<? extends T> beanClass;
     private final List<Injection> injections;
+    private final Map<String, Object> properties = new LinkedHashMap<String, Object>();
     private final List<Method> postConstructMethods;
     private final List<Method> preDestroyMethods;
     private final Context context;
     private T instance;
 
+    public InjectionProcessor(Class<? extends T> beanClass, List<Injection> injections, Context context) {
+        this.beanClass = beanClass;
+        this.injections = injections;
+        this.context = context;
+        postConstructMethods = null;
+        preDestroyMethods = null;
+    }
+
     public InjectionProcessor(Class<? extends T> beanClass, List<Injection> injections, List<Method> postConstructMethods, List<Method> preDestroyMethods, Context context) {
         this.beanClass = beanClass;
         this.injections = injections;
@@ -47,6 +57,10 @@
         this.context = context;
     }
 
+    public void setProperty(String name, Object value) {
+        properties.put(name, value);
+    }
+
     public T createInstance() throws OpenEJBException {
         if (instance == null) {
             construct();
@@ -65,10 +79,15 @@
         objectRecipe.allow(Option.FIELD_INJECTION);
         objectRecipe.allow(Option.PRIVATE_PROPERTIES);
         objectRecipe.allow(Option.IGNORE_MISSING_PROPERTIES);
+        objectRecipe.allow(Option.NAMED_PARAMETERS);
 
         fillInjectionProperties(objectRecipe);
 
-        Object object = null;
+        for (Entry<String, Object> entry : properties.entrySet()) {
+            objectRecipe.setProperty(entry.getKey(), entry.getValue());
+        }
+
+        Object object;
         try {
             object = objectRecipe.create(beanClass.getClassLoader());
         } catch (Exception e) {
@@ -117,19 +136,29 @@
     private void fillInjectionProperties(ObjectRecipe objectRecipe) {
         if (injections == null) return;
         
+        boolean usePrefix = true;
+        try {
+            beanClass.getConstructor();
+        } catch (NoSuchMethodException e) {
+            // Using constructor injection
+            // xbean can't handle the prefix yet
+            usePrefix = false;
+        }
+
         for (Injection injection : injections) {
             if (!injection.getTarget().isAssignableFrom(beanClass)) continue;
             try {
                 String jndiName = injection.getJndiName();
-                Object object = context.lookup("java:comp/env/" + jndiName);
-                if (object instanceof String) {
-                    String string = (String) object;
-                    // Pass it in raw so it could be potentially converted to
-                    // another data type by an xbean-reflect property editor
-                    objectRecipe.setProperty(injection.getTarget().getName() + "/" + injection.getName(), string);
+                Object value = context.lookup("java:comp/env/" + jndiName);
+
+                String prefix;
+                if (usePrefix) {
+                    prefix = injection.getTarget().getName() + "/";
                 } else {
-                    objectRecipe.setProperty(injection.getTarget().getName() + "/" + injection.getName(), new StaticRecipe(object));
+                    prefix = "";
                 }
+
+                objectRecipe.setProperty(prefix + injection.getName(), value);
             } catch (NamingException e) {
                 logger.warning("Injection data not found in JNDI context: jndiName='" + injection.getJndiName() + "', target=" + injection.getTarget() + "/" + injection.getName());
             }

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java?rev=689243&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java Tue Aug 26 14:36:19 2008
@@ -0,0 +1,91 @@
+/**
+ *
+ * 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;
+
+public interface Cache<K, V> {
+    /**
+     * Add a new entry to the cache.  The entry is marked checked-out and can
+     * not be accessed again until checked-in.
+     *
+     * @IllegalStateException if an value is already associated with the key
+     */
+    void add(K key, V value);
+
+    /**
+     * Marks the entry checked-out, so this entry can not be accessed until
+     * checked-in.
+     *
+     * @throws IllegalStateException if the entry is already checked out.
+     * @throws Exception if an entry is loaded and the afterLoad method threw an
+     * exception
+     */
+    V checkOut(K key) throws Exception;
+
+    /**
+     * Marks the entry available, so it can be accessed again.
+     *
+     * @throws IllegalStateException if the entry is not checked out.
+     */
+    void checkIn(K key);
+
+    /**
+     * Removes the entry from the cache.
+     */
+    V remove(K key);
+
+    /**
+     * Removes all of th entries that match the specified filter.
+     */
+    void removeAll(CacheFilter<V> filter);
+
+    /**
+     * Callback listener for cache events.
+     */
+    public interface CacheListener<V> {
+        /**
+         * Called after an entry is loaded from a store.
+         *
+         * @throws Exception if there is a problem with the instance
+         */
+        void afterLoad(V value) throws Exception;
+
+        /**
+         * Called before an entry is written to a store.
+         *
+         * @throws Exception if there is a problem with the instance
+         */
+        void beforeStore(V value) throws Exception;
+
+        /**
+         * Called when an instance has been removed from the cache due to a
+         * time-out.
+         */
+        void timedOut(V value);
+    }
+
+    /**
+     * CacheFileter is used to select values to remove during a removeAll
+     * invocation.
+     */
+    public interface CacheFilter<V> {
+        /**
+         * True if the filter matches the value.
+         */
+        boolean matches(V v);
+    }
+}

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java?rev=689243&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java Tue Aug 26 14:36:19 2008
@@ -0,0 +1,160 @@
+/**
+ *
+ * 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 java.io.Serializable;
+import java.io.ObjectStreamException;
+import java.util.Map;
+import java.util.HashMap;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityManager;
+
+import org.apache.openejb.core.CoreDeploymentInfo;
+import org.apache.openejb.core.transaction.BeanTransactionPolicy.SuspendedTransaction;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.spi.ContainerSystem;
+import org.apache.openejb.util.Index;
+import org.apache.openejb.util.PojoSerialization;
+
+public class Instance implements Serializable {
+    private static final long serialVersionUID = 2862563626506556542L;
+    public final CoreDeploymentInfo deploymentInfo;
+    public final Object primaryKey;
+    public final Object bean;
+    public final Map<String, Object> interceptors;
+
+    private boolean inUse;
+    private SuspendedTransaction beanTransaction;
+    private boolean callSessionSynchronization;
+
+    // todo if we keyed by an entity manager factory id we would not have to make this transient and rebuild the index below
+    // This would require that we crete an id and that we track it
+    // alternatively, we could use ImmutableArtifact with some read/write replace magic
+    private Map<EntityManagerFactory, EntityManager> entityManagers;
+    private final EntityManager[] entityManagerArray;
+
+    public Instance(CoreDeploymentInfo deploymentInfo, Object primaryKey, Object bean, Map<String, Object> interceptors, Map<EntityManagerFactory, EntityManager> entityManagers) {
+        this.deploymentInfo = deploymentInfo;
+        this.primaryKey = primaryKey;
+        this.bean = bean;
+        this.interceptors = interceptors;
+        this.entityManagers = entityManagers;
+        this.entityManagerArray = null;
+    }
+
+    public Instance(Object deploymentId, Object primaryKey, Object bean, Map<String, Object> interceptors, EntityManager[] entityManagerArray) {
+        this.deploymentInfo = (CoreDeploymentInfo) SystemInstance.get().getComponent(ContainerSystem.class).getDeploymentInfo(deploymentId);
+        if (deploymentInfo == null) {
+            throw new IllegalArgumentException("Unknown deployment " + deploymentId);
+        }
+        this.primaryKey = primaryKey;
+        this.bean = bean;
+        this.interceptors = interceptors;
+        this.entityManagerArray = entityManagerArray;
+    }
+
+    public synchronized boolean isInUse() {
+        return inUse;
+    }
+
+    public synchronized void setInUse(boolean inUse) {
+        this.inUse = inUse;
+    }
+
+    public synchronized SuspendedTransaction getBeanTransaction() {
+        return beanTransaction;
+    }
+
+    public synchronized void setBeanTransaction(SuspendedTransaction beanTransaction) {
+        this.beanTransaction = beanTransaction;
+    }
+
+    public synchronized boolean isCallSessionSynchronization() {
+        return callSessionSynchronization;
+    }
+
+    public synchronized void setCallSessionSynchronization() {
+        this.callSessionSynchronization = true;
+    }
+
+    public synchronized Map<EntityManagerFactory, EntityManager> getEntityManagers(Index<EntityManagerFactory, Map> factories) {
+        if (entityManagers == null && entityManagerArray != null) {
+            entityManagers = new HashMap<EntityManagerFactory, EntityManager>();
+            for (int i = 0; i < entityManagerArray.length; i++) {
+                EntityManagerFactory entityManagerFactory = factories.getKey(i);
+                EntityManager entityManager = entityManagerArray[i];
+                entityManagers.put(entityManagerFactory, entityManager);
+            }
+        }
+        return entityManagers;
+    }
+
+    protected Object writeReplace() throws ObjectStreamException {
+        if (inUse) {
+            throw new IllegalStateException("Bean is still in use");
+        }
+        if (beanTransaction != null) {
+            throw new IllegalStateException("Bean is associated with a bean-managed transaction");
+        }
+        return new Serialization(this);
+    }
+
+    private static class Serialization implements Serializable {
+        private static final long serialVersionUID = 6002078080752564395L;
+        public final Object deploymentId;
+        public final Object primaryKey;
+        public final Object bean;
+        public final Map<String, Object> interceptors;
+        public final EntityManager[] entityManagerArray;
+
+        public Serialization(Instance i) {
+            deploymentId = i.deploymentInfo.getDeploymentID();
+            primaryKey = i.primaryKey;
+            if (i.bean instanceof Serializable) {
+                bean = i.bean;
+            } else {
+                bean = new PojoSerialization(i.bean);
+            }
+
+            interceptors = new HashMap(i.interceptors.size());
+            for (Map.Entry<String, Object> e : i.interceptors.entrySet()) {
+                if (e.getValue() == i.bean) {
+                    // need to use the same wrapped reference or well get two copies.
+                    interceptors.put(e.getKey(), bean);
+                } else if (!(e.getValue() instanceof Serializable)) {
+                    interceptors.put(e.getKey(), new PojoSerialization(e.getValue()));
+                }
+            }
+
+            if (i.entityManagerArray != null) {
+                entityManagerArray = i.entityManagerArray;
+            } else if (i.entityManagers != null) {
+                entityManagerArray = i.entityManagers.values().toArray(new EntityManager[i.entityManagers.values().size()]);
+            } else {
+                entityManagerArray = null;
+            }
+        }
+
+        protected Object readResolve() throws ObjectStreamException {
+            // Anything wrapped with PojoSerialization will have been automatically
+            // unwrapped via it's own readResolve so passing in the raw bean
+            // and interceptors variables is totally fine.
+            return new Instance(deploymentId, primaryKey, bean, interceptors, entityManagerArray);
+        }
+    }
+}

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/PassivationStrategy.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/PassivationStrategy.java?rev=689243&r1=689242&r2=689243&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/PassivationStrategy.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/PassivationStrategy.java Tue Aug 26 14:36:19 2008
@@ -16,17 +16,15 @@
  */
 package org.apache.openejb.core.stateful;
 
-import java.util.Hashtable;
+import java.util.Map;
 import java.util.Properties;
 
-public interface PassivationStrategy {
-
-    public void init(Properties props) throws org.apache.openejb.SystemException;
+import org.apache.openejb.SystemException;
 
-    public void passivate(Hashtable stateTable)
-            throws org.apache.openejb.SystemException;
+public interface PassivationStrategy {
+    public void init(Properties props) throws SystemException;
 
-    public Object activate(Object primaryKey)
-            throws org.apache.openejb.SystemException;
+    public void passivate(Map stateTable) throws SystemException;
 
+    public Object activate(Object primaryKey) throws SystemException;
 }

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java?rev=689243&r1=689242&r2=689243&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java Tue Aug 26 14:36:19 2008
@@ -23,6 +23,8 @@
 import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.Properties;
+import java.util.Map;
+import java.util.Iterator;
 
 // optimization: replace HashTable with HashMap (vc no debug hashmap)
 
@@ -46,16 +48,16 @@
     public void init(Properties props) throws org.apache.openejb.SystemException {
     }
 
-    public synchronized void passivate(Hashtable stateTable)
+    public synchronized void passivate(Map stateTable)
             throws org.apache.openejb.SystemException {
         try {
             fileID++;
 
             RandomAccessFile ras = new RandomAccessFile(System.getProperty("java.io.tmpdir", File.separator + "tmp") + File.separator + "passivation" + fileID + ".ser", "rw");
-            Enumeration enumeration = stateTable.keys();
+            Iterator iterator = stateTable.keySet().iterator();
             Pointer lastPointer = null;
-            while (enumeration.hasMoreElements()) {
-                Object id = enumeration.nextElement();
+            while (iterator.hasNext()) {
+                Object id = iterator.next();
                 Object obj = stateTable.get(id);
                 byte [] bytes = Serializer.serialize(obj);
                 long filepointer = ras.getFilePointer();

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java?rev=689243&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java Tue Aug 26 14:36:19 2008
@@ -0,0 +1,459 @@
+/**
+ *
+ * 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 java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
+
+public class SimpleCache<K, V> implements Cache<K, V> {
+    public static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, "org.apache.openejb.util.resources");
+
+    /**
+     * Map of all known values by key
+     */
+    private final ConcurrentHashMap<K, Entry> cache = new ConcurrentHashMap<K, Entry>();
+
+    /**
+     * All values not in use in least resently used order
+     */
+    private final Queue<Entry> lru = new LinkedBlockingQueue<Entry>();
+
+    /**
+     * Notified when values are loaded, stored, or timedOut
+     */
+    private final CacheListener<V> listener;
+
+    /**
+     * Used to load and store values
+     */
+    private final PassivationStrategy passivator;
+
+    /**
+     * Maximum number of values that should be in the LRU
+     */
+    private final int capacity;
+
+    /**
+     * When the LRU is exceeded, this is the is the number of beans stored.
+     * This helps to avoid passivating a bean at a time.
+     */
+    private final int bulkPassivate;
+
+    /**
+     * A bean may be destroyed if it isn't used in this length of time (in
+     * milliseconds).
+     */
+    private final long timeOut;
+
+    public SimpleCache(CacheListener<V> listener, PassivationStrategy passivator, int capacity, int bulkPassivate, long timeOut) {
+        this.listener = listener;
+        this.passivator = passivator;
+        this.capacity = capacity;
+        this.bulkPassivate = bulkPassivate;
+        this.timeOut = timeOut;
+    }
+
+    public void add(K key, V value) {
+        // find the existing entry
+        Entry entry = cache.get(key);
+        if (entry != null) {
+            entry.lock.lock();
+            try {
+                if (entry.getState() != EntryState.REMOVED) {
+                    throw new IllegalStateException("An entry for the key " + key + " already exists");
+                }
+                // Entry has been removed between get and lock, simply remove the garbage entry
+                cache.remove(key);
+                lru.remove(entry);
+            } finally {
+                entry.lock.unlock();
+            }
+        }
+
+        entry = new Entry(key, value, EntryState.CHECKED_OUT);
+        cache.put(key, entry);
+    }
+
+    public V checkOut(K key) throws Exception {
+        // attempt (up to 10 times) to obtain the entry from the cache
+        for (int i = 0; i < 10; i++) {
+            // find the entry
+            Entry entry = cache.get(key);
+            if (entry == null) {
+                entry = loadEntry(key);
+                if (entry == null) {
+                    return null;
+                }
+            }
+
+            entry.lock.lock();
+            try {
+                // verfiy state
+                switch (entry.getState()) {
+                    case AVAILABLE:
+                        break;
+                    case CHECKED_OUT:
+                        throw new IllegalStateException("The entry " + key + " is already checked-out");
+                    case PASSIVATED:
+                        // Entry was passivated between get and lock, we need to load the Entry again
+                        // If the cache somehow got corrupted by an entry containing in state PASSIVATED, this remove
+                        // call will remove the corruption
+                        cache.remove(key, entry);
+                        continue;
+                    case REMOVED:
+                        // Entry has been removed between get and lock (most likely by undeploying the EJB), simply drop the instance
+                        return null;
+                }
+
+                // mark entry as in-use
+                entry.setState(EntryState.CHECKED_OUT);
+
+                // entry is removed from the lru while in use
+                lru.remove(entry);
+
+                return entry.getValue();
+            } finally {
+                entry.lock.unlock();
+            }
+        }
+
+        // something is really messed up with this entry, try to cleanup before throwing an exception
+        Entry entry = cache.remove(key);
+        if (entry != null) {
+            lru.remove(entry);
+        }
+        throw new RuntimeException("Cache is corrupted: the entry " + key + " in the Map 'cache' is in state PASSIVATED");
+    }
+
+    public void checkIn(K key) {
+        // find the entry
+        Entry entry = cache.get(key);
+        if (entry == null) {
+            return;
+        }
+
+        entry.lock.lock();
+        try {
+            // verfiy state
+            switch (entry.getState()) {
+                case AVAILABLE:
+                    throw new IllegalStateException("The entry " + key + " is not checked-out");
+                case PASSIVATED:
+                    // An entry in-use should not be passivated so we can only assume
+                    // that the caller never checked out the bean in the first place
+                    throw new IllegalStateException("The entry " + key + " is not checked-out");
+                case REMOVED:
+                    // Entry has been removed between get and lock (most likely by undeploying the EJB), simply drop the instance
+                    return;
+            }
+
+            // mark entry as available
+            entry.setState(EntryState.AVAILABLE);
+
+            // add entry to lru
+            lru.add(entry);
+            entry.resetTimeOut();
+        } finally {
+            entry.lock.unlock();
+        }
+
+        processLRU();
+    }
+
+    public V remove(K key) {
+        // find the entry
+        Entry entry = cache.get(key);
+        if (entry == null) {
+            return null;
+        }
+
+        entry.lock.lock();
+        try {
+            // remove the entry from the cache and lru
+            cache.remove(key);
+            lru.remove(entry);
+
+            // There is no need to check the state because users of the cache
+            // are responsible for maintaining references to beans in use
+
+            // mark the entry as removed
+            entry.setState(EntryState.REMOVED);
+
+            return entry.getValue();
+        } finally {
+            entry.lock.unlock();
+        }
+    }
+
+    public void removeAll(CacheFilter<V> filter) {
+        for (Iterator<Entry> iterator = cache.values().iterator(); iterator.hasNext();) {
+            Entry entry = iterator.next();
+
+            entry.lock.lock();
+            try {
+                if (filter.matches(entry.getValue())) {
+                    // remove the entry from the cache and lru
+                    iterator.remove();
+                    lru.remove(entry);
+
+                    // There is no need to check the state because users of the cache
+                    // are responsible for maintaining references to beans in use
+
+                    // mark the entry as removed
+                    entry.setState(EntryState.REMOVED);
+                }
+            } finally {
+                entry.lock.unlock();
+            }
+        }
+    }
+
+    public void processLRU() {
+        // check for timed out entries
+        Iterator<Entry> iterator = lru.iterator();
+        while (iterator.hasNext()) {
+            Entry entry = iterator.next();
+            entry.lock.lock();
+            try {
+                switch (entry.getState()) {
+                    case AVAILABLE:
+                        break;
+                    case CHECKED_OUT:
+                        // bean is in use so cannot be passivated
+                        continue;
+                    case PASSIVATED:
+                        // Entry was passivated between get and lock
+                        iterator.remove();
+                        continue;
+                    case REMOVED:
+                        // Entry was remmoved between get and lock
+                        iterator.remove();
+                        continue;
+                }
+
+
+                if (entry.isTimedOut()) {
+                    iterator.remove();
+                    cache.remove(entry.getKey());
+                    entry.setState(EntryState.REMOVED);
+
+                    // notify listener that the entry has been removed
+                    try {
+                        listener.timedOut(entry.getValue());
+                    } catch (Exception e) {
+                        logger.error("An unexpected exception occured from timedOut callback", e);
+                    }
+                } else {
+                    // entries are in order of last updates, so if this bean isn't timed out
+                    // no further entries will be timed out
+                    break;
+                }
+            } finally {
+                entry.lock.unlock();
+            }
+        }
+
+        // if there are to many beans in the lru, shink is by on bulkPassivate size
+        // bulkPassivate size is just an estimate, as locked or timed out beans are skipped
+        if (lru.size() >= capacity) {
+            Map<K, V> valuesToStore = new LinkedHashMap<K, V>();
+            List<Entry> entries = new ArrayList<Entry>();
+
+            for (int i = 0; i < bulkPassivate; i++) {
+                Entry entry = lru.poll();
+                if (entry == null) {
+                    // lru is empty
+                    break;
+                }
+
+                if (!entry.lock.tryLock()) {
+                    // If two threads are running in this method, you could get a deadlock
+                    // due to lock acquisition order since this section gathers a group of
+                    // locks. Simply skip beans we can not obtain a lock on
+                    continue;
+                }
+                try {
+                    switch (entry.getState()) {
+                        case AVAILABLE:
+                            break;
+                        case CHECKED_OUT:
+                            // bean is in use so cannot be passivated
+                            continue;
+                        case PASSIVATED:
+                            // Entry was passivated between get and lock
+                            lru.remove(entry);
+                            continue;
+                        case REMOVED:
+                            // Entry was remmoved between get and lock
+                            lru.remove(entry);
+                            continue;
+                    }
+
+                    // remove it from the cache
+                    cache.remove(entry.getKey());
+
+                    // there is a race condition where the item could get added back into the lru
+                    lru.remove(entry);
+
+                    // if the entry is actually timed out we just destroy it; othewise it is written to disk
+                    if (entry.isTimedOut()) {
+                        entry.setState(EntryState.REMOVED);
+                        try {
+                            listener.timedOut(entry.getValue());
+                        } catch (Exception e) {
+                            logger.error("An unexpected exception occured from timedOut callback", e);
+                        }
+                    } else {
+                        // entry will be passivated, so we need to obtain an additional lock until the passivation is complete
+                        entry.lock.lock();
+                        entries.add(entry);
+
+                        entry.setState(EntryState.PASSIVATED);
+                        valuesToStore.put(entry.getKey(), entry.getValue());
+                    }
+                } finally {
+                    entry.lock.unlock();
+                }
+            }
+
+            if (!valuesToStore.isEmpty()) {
+                try {
+                    storeEntries(valuesToStore);
+                } finally {
+                    for (Entry entry : entries) {
+                        // release the extra passivation lock
+                        entry.lock.unlock();
+                    }
+                }
+            }
+        }
+
+    }
+
+    private Entry loadEntry(K key) throws Exception {
+        V value = null;
+        try {
+            value = (V) passivator.activate(key);
+        } catch (Exception e) {
+            logger.error("An unexpected exception occured while reading entries from disk", e);
+        }
+
+        if (value == null) {
+            return null;
+        }
+
+        listener.afterLoad(value);
+        Entry entry = new Entry(key, value, EntryState.AVAILABLE);
+        cache.put(key, entry);
+        return entry;
+    }
+
+    private void storeEntries(Map<K, V> entriesToStore) {
+        for (Iterator<java.util.Map.Entry<K, V>> iterator = entriesToStore.entrySet().iterator(); iterator.hasNext();) {
+            java.util.Map.Entry<K, V> entry = iterator.next();
+
+            try {
+                listener.beforeStore(entry.getValue());
+            } catch (Exception e) {
+                iterator.remove();
+                logger.error("An unexpected exception occured from beforeStore callback", e);
+            }
+
+        }
+
+        try {
+            passivator.passivate(entriesToStore);
+        } catch (Exception e) {
+            logger.error("An unexpected exception occured while writting the entries to disk", e);
+        }
+    }
+
+    private enum EntryState {
+        AVAILABLE, CHECKED_OUT, PASSIVATED, REMOVED
+    }
+
+    private class Entry {
+        private final K key;
+        private final V value;
+        private final ReentrantLock lock = new ReentrantLock();
+        private EntryState state;
+        private long lastAccess;
+
+        private Entry(K key, V value, EntryState state) {
+            this.key = key;
+            this.value = value;
+            this.state = state;
+            lastAccess = System.currentTimeMillis();
+        }
+
+        private K getKey() {
+            assertLockHeld();
+            return key;
+        }
+
+        private V getValue() {
+            assertLockHeld();
+            return value;
+        }
+
+        private EntryState getState() {
+            assertLockHeld();
+            return state;
+        }
+
+        private void setState(EntryState state) {
+            assertLockHeld();
+            this.state = state;
+        }
+
+        private boolean isTimedOut() {
+            assertLockHeld();
+
+            if (timeOut == 0) {
+                return false;
+            }
+            long now = System.currentTimeMillis();
+            return (now - lastAccess) > timeOut;
+        }
+
+        private void resetTimeOut() {
+            assertLockHeld();
+
+            if (timeOut > 0) {
+                lastAccess = System.currentTimeMillis();
+            }
+        }
+
+        private void assertLockHeld() {
+            if (!lock.isHeldByCurrentThread()) {
+                throw new IllegalStateException("Entry must be locked");
+            }
+        }
+    }
+
+}

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimplePassivater.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimplePassivater.java?rev=689243&r1=689242&r2=689243&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimplePassivater.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimplePassivater.java Tue Aug 26 14:36:19 2008
@@ -16,30 +16,30 @@
  */
 package org.apache.openejb.core.stateful;
 
-import org.apache.openejb.SystemException;
-import org.apache.openejb.util.LogCategory;
-import org.apache.openejb.util.Logger;
-import org.apache.openejb.core.EnvProps;
-import org.apache.openejb.loader.SystemInstance;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-import java.util.Enumeration;
-import java.util.Hashtable;
+import java.util.Map;
 import java.util.Properties;
 
+import org.apache.openejb.SystemException;
+import org.apache.openejb.core.EnvProps;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
+
+
 public class SimplePassivater implements PassivationStrategy {
-    private File sessionDirectory;
     private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, "org.apache.openejb.util.resources");
+    private File sessionDirectory;
 
     public SimplePassivater() throws SystemException {
         init(null);
     }
 
-    public void init(Properties props) throws org.apache.openejb.SystemException {
+    public void init(Properties props) throws SystemException {
         if (props == null) {
             props = new Properties();
         }
@@ -47,7 +47,6 @@
         String dir = props.getProperty(EnvProps.IM_PASSIVATOR_PATH_PREFIX);
 
         try {
-
             if (dir != null) {
                 sessionDirectory = SystemInstance.get().getBase().getDirectory(dir);
             } else {
@@ -55,14 +54,12 @@
             }
             logger.info("Using directory " + sessionDirectory + " for stateful session passivation");
         } catch (java.io.IOException e) {
-            throw new org.apache.openejb.SystemException(getClass().getName() + ".init(): can't use directory prefix " + dir + ":" + e, e);
+            throw new SystemException(getClass().getName() + ".init(): can't use directory prefix " + dir + ":" + e, e);
         }
     }
 
-    public void passivate(Object primaryKey, Object state)
-            throws org.apache.openejb.SystemException {
+    public void passivate(Object primaryKey, Object state) throws SystemException {
         try {
-
             String filename = primaryKey.toString().replace(':', '=');
 
             File sessionFile = new File(sessionDirectory, filename);
@@ -73,30 +70,23 @@
             oos.writeObject(state);// passivate just the bean instance
             oos.close();
             sessionFile.deleteOnExit();
-        }
-        catch (java.io.NotSerializableException nse) {
+        } catch (java.io.NotSerializableException nse) {
             logger.error("Passivation failed ", nse);
-            throw (SystemException) new SystemException("The type " + nse.getMessage() + " in the bean class " + ((BeanEntry) state).bean.getClass().getName() + " is not serializable as mandated by the EJB specification.").initCause(nse);
-        }
-        catch (Exception t) {
+            throw (SystemException) new SystemException("The type " + nse.getMessage() + " is not serializable as mandated by the EJB specification.").initCause(nse);
+        } catch (Exception t) {
             logger.error("Passivation failed ", t);
-            throw new org.apache.openejb.SystemException(t);
+            throw new SystemException(t);
         }
-
     }
 
-    public void passivate(Hashtable hash) throws org.apache.openejb.SystemException {
-        Enumeration enumeration = hash.keys();
-        while (enumeration.hasMoreElements()) {
-            Object id = enumeration.nextElement();
+    public void passivate(Map hash) throws SystemException {
+        for (Object id : hash.keySet()) {
             passivate(id, hash.get(id));
         }
     }
 
-    public Object activate(Object primaryKey) throws org.apache.openejb.SystemException {
-
+    public Object activate(Object primaryKey) throws SystemException {
         try {
-
             String filename = primaryKey.toString().replace(':', '=');
 
             File sessionFile = new File(sessionDirectory, filename);
@@ -113,13 +103,10 @@
                 logger.info("Activation failed: file not found " + sessionFile);
                 return null;
             }
-
         } catch (Exception t) {
             logger.info("Activation failed ", t);
 
-            throw new org.apache.openejb.SystemException(t);
+            throw new SystemException(t);
         }
-
     }
-
 }
\ No newline at end of file

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java?rev=689243&r1=689242&r2=689243&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java Tue Aug 26 14:36:19 2008
@@ -17,43 +17,56 @@
 package org.apache.openejb.core.stateful;
 
 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 javax.ejb.EJBAccessException;
 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.ejb.SessionSynchronization;
+import javax.naming.Context;
+import javax.naming.NamingException;
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
 
 import org.apache.openejb.ApplicationException;
 import org.apache.openejb.ContainerType;
 import org.apache.openejb.DeploymentInfo;
+import org.apache.openejb.InjectionProcessor;
 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.core.CoreDeploymentInfo;
 import org.apache.openejb.core.ExceptionType;
+import static org.apache.openejb.core.ExceptionType.APPLICATION_ROLLBACK;
+import static org.apache.openejb.core.ExceptionType.SYSTEM;
 import org.apache.openejb.core.Operation;
 import org.apache.openejb.core.ThreadContext;
-import org.apache.openejb.core.stateful.StatefulInstanceManager.Instance;
-import static org.apache.openejb.core.ExceptionType.SYSTEM;
-import static org.apache.openejb.core.ExceptionType.APPLICATION_ROLLBACK;
 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 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;
-import static org.apache.openejb.core.transaction.EjbTransactionUtil.createTransactionPolicy;
-import org.apache.openejb.core.transaction.BeanTransactionPolicy.SuspendedTransaction;
-import org.apache.openejb.core.transaction.BeanTransactionPolicy;
+import org.apache.openejb.core.transaction.EjbUserTransaction;
 import org.apache.openejb.core.transaction.TransactionPolicy;
-import org.apache.openejb.core.transaction.EjbTransactionUtil;
+import org.apache.openejb.core.transaction.TransactionPolicy.TransactionSynchronization;
 import org.apache.openejb.loader.SystemInstance;
 import org.apache.openejb.persistence.EntityManagerAlreadyRegisteredException;
 import org.apache.openejb.persistence.JtaEntityManagerRegistry;
@@ -61,16 +74,14 @@
 import org.apache.openejb.util.Index;
 import org.apache.openejb.util.LogCategory;
 import org.apache.openejb.util.Logger;
+import org.apache.xbean.recipe.ConstructionException;
 
-/**
- * @org.apache.xbean.XBean element="statefulContainer"
- */
 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;
-    protected final StatefulInstanceManager instanceManager;
+
     // todo this should be part of the constructor
     protected final JtaEntityManagerRegistry entityManagerRegistry = SystemInstance.get().getComponent(JtaEntityManagerRegistry.class);
 
@@ -79,37 +90,35 @@
      */
     protected final Map<Object, DeploymentInfo> deploymentsById = new HashMap<Object, DeploymentInfo>();
 
+    protected final Cache<Object, Instance> cache;
+    private final ConcurrentHashMap<Object, Instance> checkedOutInstances = new ConcurrentHashMap<Object, Instance>();
+
 
     public StatefulContainer(Object id,
             SecurityService securityService,
-            Class passivator,
+            Class<? extends PassivationStrategy> passivatorClass,
             int timeOut,
             int poolSize,
             int bulkPassivate) throws OpenEJBException {
         this.containerID = id;
         this.securityService = securityService;
 
-        instanceManager = newStatefulInstanceManager(
-                securityService,
-            passivator,
-            timeOut,
-            poolSize,
-            bulkPassivate);
+        cache = createCache(passivatorClass, timeOut, poolSize, bulkPassivate);
     }
 
-    protected StatefulInstanceManager newStatefulInstanceManager(
-            SecurityService securityService,
-            Class passivator,
-            int timeOut,
-            int poolSize,
-            int bulkPassivate) throws OpenEJBException {
-        return new StatefulInstanceManager(
-                securityService,
-            entityManagerRegistry,
-            passivator,
-            timeOut,
-            poolSize,
-            bulkPassivate);
+    protected Cache<Object, Instance> createCache(Class<? extends PassivationStrategy> passivatorClass, int timeOut, int poolSize, int bulkPassivate) throws OpenEJBException {
+        PassivationStrategy passivator;
+        if (passivatorClass != null) {
+            try {
+                passivator = passivatorClass.newInstance();
+            } catch (Exception e) {
+                throw new OpenEJBException("Could not create the passivator " + passivatorClass.getName(), e);
+            }
+        } else {
+            passivator = new SimplePassivater();
+        }
+
+        return new SimpleCache<Object, Instance>(new StatefulCacheListener(), passivator, poolSize, bulkPassivate, timeOut * 60 * 1000);
     }
 
     private Map<Method, MethodType> getLifecycleMethodsOfInterface(CoreDeploymentInfo deploymentInfo) {
@@ -212,10 +221,6 @@
         return containerID;
     }
 
-    public StatefulInstanceManager getInstanceManager() {
-        return instanceManager;
-    }
-
     public synchronized DeploymentInfo[] deployments() {
         return deploymentsById.values().toArray(new DeploymentInfo[deploymentsById.size()]);
     }
@@ -232,11 +237,21 @@
         undeploy((CoreDeploymentInfo) info);
     }
 
-    private synchronized void undeploy(CoreDeploymentInfo deploymentInfo) throws OpenEJBException {
+    private synchronized void undeploy(final CoreDeploymentInfo deploymentInfo) throws OpenEJBException {
         deploymentsById.remove(deploymentInfo.getDeploymentID());
         deploymentInfo.setContainer(null);
         deploymentInfo.setContainerData(null);
-        instanceManager.undeploy(deploymentInfo);
+
+        cache.removeAll(new CacheFilter<Instance>() {
+            public boolean matches(Instance instance) {
+                return deploymentInfo == instance.deploymentInfo;
+            }
+        });
+
+        StatefulContainerData data = (StatefulContainerData) deploymentInfo.getContainerData();
+        if (data != null) {
+            deploymentInfo.setContainerData(null);
+        }
     }
 
     private synchronized void deploy(CoreDeploymentInfo deploymentInfo) throws OpenEJBException {
@@ -244,7 +259,7 @@
 
         deploymentsById.put(deploymentInfo.getDeploymentID(), deploymentInfo);
         deploymentInfo.setContainer(this);
-        instanceManager.deploy(deploymentInfo, new Index<Method, MethodType>(methods));
+        deploymentInfo.setContainerData(new StatefulContainerData(new Index<Method, MethodType>(methods)));
     }
 
     /**
@@ -256,10 +271,12 @@
 
     public Object invoke(Object deployID, Class callInterface, Method callMethod, Object [] args, Object primKey) throws OpenEJBException {
         CoreDeploymentInfo deployInfo = (CoreDeploymentInfo) this.getDeploymentInfo(deployID);
-        if (deployInfo == null)
+        if (deployInfo == null) {
             throw new OpenEJBException("Deployment does not exist in this container. Deployment(id='" + deployID + "'), Container(id='" + containerID + "')");
+        }
 
-        MethodType methodType = instanceManager.getMethodIndex(deployInfo).get(callMethod);
+        StatefulContainerData data = (StatefulContainerData) deployInfo.getContainerData();
+        MethodType methodType = data.getMethodIndex().get(callMethod);
         methodType = (methodType != null) ? methodType : MethodType.BUSINESS;
 
         switch (methodType) {
@@ -301,14 +318,10 @@
             Instance instance = null;
             try {
                 // Create new instance
-                instance = (Instance) instanceManager.newInstance(primaryKey, deploymentInfo.getBeanClass());
-
-                // Register the entity managers with the instance
-                instanceManager.setEntityManagers(createContext, entityManagers);
-                registerEntityManagers(createContext);
+                instance = newInstance(primaryKey, deploymentInfo.getBeanClass(), entityManagers);
 
                 // Register for synchronization callbacks
-                SessionSynchronizationCoordinator.registerSessionSynchronization(instance, createContext);
+                registerSessionSynchronization(instance, createContext);
               
                 // Invoke create for legacy beans
                 if (!callMethod.getDeclaringClass().equals(DeploymentInfo.BusinessLocalHome.class) &&
@@ -347,6 +360,8 @@
     }
 
     protected Object removeEJBObject(CoreDeploymentInfo deploymentInfo, Object primKey, Class callInterface, Method callMethod, Object[] args) throws OpenEJBException {
+        if (primKey == null) throw new NullPointerException("primKey is null");
+
         ThreadContext callContext = new ThreadContext(deploymentInfo, primKey);
         ThreadContext oldCallContext = ThreadContext.enter(callContext);
         try {
@@ -355,8 +370,11 @@
 
             // If a bean managed transaction is active, the bean can not be removed
             InterfaceType interfaceType = deploymentInfo.getInterfaceType(callInterface);
-            if (interfaceType.isComponent() && instanceManager.getBeanTransaction(callContext) != null) {
-                throw new ApplicationException(new RemoveException("A stateful EJB enrolled in a transaction can not be removed"));
+            if (interfaceType.isComponent()) {
+                Instance instance = checkedOutInstances.get(primKey);
+                if (instance != null && instance.getBeanTransaction() != null) {
+                    throw new ApplicationException(new RemoveException("A stateful EJB enrolled in a bean-managed transaction can not be removed"));
+                }
             }
 
             // Start transaction
@@ -368,24 +386,25 @@
             Method runMethod = null;
             try {
                 // Obtain instance
-                instance = (Instance) instanceManager.obtainInstance(primKey, callContext);
+                instance = obtainInstance(primKey, callContext);
                 if (instance == null) throw new ApplicationException(new javax.ejb.NoSuchEJBException());
 
                 // Resume previous Bean transaction if there was one
                 if (txPolicy instanceof BeanTransactionPolicy){
                     // Resume previous Bean transaction if there was one
-                    SuspendedTransaction suspendedTransaction = instanceManager.getBeanTransaction(callContext);
+                    SuspendedTransaction suspendedTransaction = instance.getBeanTransaction();
                     if (suspendedTransaction != null) {
+                        instance.setBeanTransaction(null);
                         BeanTransactionPolicy beanTxEnv = (BeanTransactionPolicy) txPolicy;
                         beanTxEnv.resumeUserTransaction(suspendedTransaction);
                     }
                 }
 
                 // Register the entity managers
-                registerEntityManagers(callContext);
+                registerEntityManagers(instance, callContext);
 
                 // Register for synchronization callbacks
-                SessionSynchronizationCoordinator.registerSessionSynchronization(instance, callContext);
+                registerSessionSynchronization(instance, callContext);
 
                 // Setup for remove invocation
                 callContext.setCurrentOperation(Operation.REMOVE);
@@ -433,7 +452,7 @@
                     }
 
                     // todo destroy extended persistence contexts
-                    instanceManager.freeInstance(callContext);
+                    discardInstance(callContext);
                 }
 
                 // Commit transaction
@@ -460,22 +479,22 @@
             Instance instance = null;
             try {
                 // Obtain instance
-                instance = (Instance) instanceManager.obtainInstance(primKey, callContext);
+                instance = obtainInstance(primKey, callContext);
 
                 // Resume previous Bean transaction if there was one
                 if (txPolicy instanceof BeanTransactionPolicy){
-                    SuspendedTransaction suspendedTransaction = instanceManager.getBeanTransaction(callContext);
+                    SuspendedTransaction suspendedTransaction = instance.getBeanTransaction();
                     if (suspendedTransaction != null) {
+                        instance.setBeanTransaction(null);
                         BeanTransactionPolicy beanTxEnv = (BeanTransactionPolicy) txPolicy;
                         beanTxEnv.resumeUserTransaction(suspendedTransaction);
                     }
                 }
 
                 // Register the entity managers
-                registerEntityManagers(callContext);
-
+                registerEntityManagers(instance, callContext);
                 // Register for synchronization callbacks
-                SessionSynchronizationCoordinator.registerSessionSynchronization(instance, callContext);
+                registerSessionSynchronization(instance, callContext);
 
                 // Setup for business invocation
                 callContext.setCurrentOperation(Operation.BUSINESS);
@@ -502,6 +521,155 @@
         }
     }
 
+    private Instance newInstance(Object primaryKey, Class beanClass, Map<EntityManagerFactory, EntityManager> entityManagers) throws OpenEJBException {
+        Instance instance = null;
+
+        ThreadContext threadContext = ThreadContext.getThreadContext();
+        Operation currentOperation = threadContext.getCurrentOperation();
+        try {
+            ThreadContext callContext = ThreadContext.getThreadContext();
+            CoreDeploymentInfo deploymentInfo = callContext.getDeploymentInfo();
+            Context ctx = deploymentInfo.getJndiEnc();
+
+            // Get or create the session context
+            SessionContext sessionContext;
+            synchronized (this) {
+                try {
+                    sessionContext = (SessionContext) ctx.lookup("java:comp/EJBContext");
+                } catch (NamingException e1) {
+                    StatefulUserTransaction userTransaction = new StatefulUserTransaction(new EjbUserTransaction(), entityManagerRegistry);
+                    sessionContext = new StatefulContext(securityService, userTransaction);
+                    ctx.bind("java:comp/EJBContext", sessionContext);
+                }
+            }
+
+            // Create bean instance
+            InjectionProcessor injectionProcessor = new InjectionProcessor(beanClass, deploymentInfo.getInjections(), null, null, ctx);
+            try {
+                if (SessionBean.class.isAssignableFrom(beanClass) || beanClass.getMethod("setSessionContext", SessionContext.class) != null) {
+                    callContext.setCurrentOperation(Operation.INJECTION);
+                    injectionProcessor.setProperty("sessionContext", sessionContext);
+                }
+            } catch (NoSuchMethodException ignored) {
+                // bean doesn't have a setSessionContext method, so we don't need to inject one
+            }
+            Object bean = injectionProcessor.createInstance();
+
+            // Create interceptors
+            HashMap<String, Object> interceptorInstances = new HashMap<String, Object>();
+            for (InterceptorData interceptorData : deploymentInfo.getAllInterceptors()) {
+                if (interceptorData.getInterceptorClass().equals(beanClass)) {
+                    continue;
+                }
+
+                Class clazz = interceptorData.getInterceptorClass();
+                InjectionProcessor interceptorInjector = new InjectionProcessor(clazz, deploymentInfo.getInjections(), ctx);
+                try {
+                    Object interceptorInstance = interceptorInjector.createInstance();
+                    interceptorInstances.put(clazz.getName(), interceptorInstance);
+                } catch (ConstructionException e) {
+                    throw new Exception("Failed to create interceptor: " + clazz.getName(), e);
+                }
+            }
+            interceptorInstances.put(beanClass.getName(), bean);
+
+            // Invoke post construct method
+            callContext.setCurrentOperation(Operation.POST_CONSTRUCT);
+            List<InterceptorData> callbackInterceptors = deploymentInfo.getCallbackInterceptors();
+            InterceptorStack interceptorStack = new InterceptorStack(bean, null, Operation.POST_CONSTRUCT, callbackInterceptors, interceptorInstances);
+            interceptorStack.invoke();
+
+            // Wrap-up everthing into a object
+            instance = new Instance(deploymentInfo, primaryKey, bean, interceptorInstances, entityManagers);
+
+        } catch (Throwable callbackException) {
+            discardInstance(threadContext);
+            handleSystemException(threadContext.getTransactionPolicy(), callbackException, threadContext);
+        } finally {
+            threadContext.setCurrentOperation(currentOperation);
+        }
+
+        // add to cache
+        cache.add(primaryKey, instance);
+
+        // instance starts checked-out
+        checkedOutInstances.put(primaryKey, instance);
+
+        return instance;
+    }
+
+    private Instance obtainInstance(Object primaryKey, ThreadContext callContext) throws OpenEJBException {
+        if (primaryKey == null) {
+            throw new SystemException(new NullPointerException("Cannot obtain an instance of the stateful session bean with a null session id"));
+        }
+
+        // Find the instance
+        Instance 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);
+        }
+
+
+        synchronized (instance) {
+            // Is the instance alreayd in use?
+            if (instance.isInUse()) {
+                // the bean is already being invoked; the only reentrant/concurrent operations allowed are Session synchronization callbacks
+                Operation currentOperation = callContext.getCurrentOperation();
+                if (currentOperation != Operation.AFTER_COMPLETION && currentOperation != Operation.BEFORE_COMPLETION) {
+                    throw new ApplicationException(new RemoteException("Concurrent calls not allowed"));
+                }
+            }
+
+            // Mark the instance in use so we can detect reentrant calls
+            instance.setInUse(true);
+
+            return instance;
+        }
+    }
+
+    private void releaseInstance(Instance instance) {
+        // Don't pool if the bean has been undeployed
+        if (instance.deploymentInfo.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);
+
+        // 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;
+        }
+
+        checkedOutInstances.remove(primaryKey);
+        cache.remove(primaryKey);
+    }
+
     private void checkAuthorization(CoreDeploymentInfo deployInfo, Method callMethod, Class callInterface) throws ApplicationException {
         boolean authorized = securityService.isCallerAuthorized(callMethod, deployInfo.getInterfaceType(callInterface));
         if (!authorized) {
@@ -516,7 +684,7 @@
 
         ExceptionType type = callContext.getDeploymentInfo().getExceptionType(e);
         if (type == SYSTEM) {
-            instanceManager.freeInstance(callContext);
+            discardInstance(callContext);
             handleSystemException(txPolicy, e, callContext);
         } else {
             handleApplicationException(txPolicy, e, type == APPLICATION_ROLLBACK);
@@ -525,8 +693,7 @@
 
     private void afterInvoke(ThreadContext callContext, TransactionPolicy txPolicy, Instance instance) throws OpenEJBException {
         try {
-            unregisterEntityManagers(callContext);
-
+            unregisterEntityManagers(instance, callContext);
             if (instance != null && txPolicy instanceof BeanTransactionPolicy) {
                 // suspend the currently running transaction if any
                 SuspendedTransaction suspendedTransaction = null;
@@ -536,11 +703,13 @@
                 } catch (SystemException e) {
                     handleSystemException(txPolicy, e, callContext);
                 } finally {
-                    instanceManager.setBeanTransaction(callContext, suspendedTransaction);
+                    instance.setBeanTransaction(suspendedTransaction);
                 }
             }
         } finally {
-            instanceManager.checkInInstance(callContext);
+            if (instance != null) {
+                instance.setInUse(false);
+            }
             EjbTransactionUtil.afterInvoke(txPolicy, callContext);
         }
     }
@@ -570,7 +739,7 @@
         return entityManagers;
     }
 
-    private void registerEntityManagers(ThreadContext callContext) throws OpenEJBException {
+    private void registerEntityManagers(Instance instance, ThreadContext callContext) throws OpenEJBException {
         if (entityManagerRegistry == null) return;
 
         CoreDeploymentInfo deploymentInfo = callContext.getDeploymentInfo();
@@ -580,27 +749,262 @@
         if (factories == null) return;
 
         // get the managers for the factories
-        Object primaryKey = callContext.getPrimaryKey();
-        Map<EntityManagerFactory, EntityManager> entityManagers = instanceManager.getEntityManagers(callContext, factories);
+        Map<EntityManagerFactory, EntityManager> entityManagers = instance.getEntityManagers(factories);
         if (entityManagers == null) return;
 
         // register them
         try {
-            entityManagerRegistry.addEntityManagers((String) deploymentInfo.getDeploymentID(), primaryKey, entityManagers);
+            entityManagerRegistry.addEntityManagers((String) deploymentInfo.getDeploymentID(), instance.primaryKey, entityManagers);
         } catch (EntityManagerAlreadyRegisteredException e) {
             throw new EJBException(e);
         }
     }
 
-    private void unregisterEntityManagers(ThreadContext callContext) {
+    private void unregisterEntityManagers(Instance instance, ThreadContext callContext) {
         if (entityManagerRegistry == null) return;
+        if (instance == null) return;
 
         CoreDeploymentInfo deploymentInfo = callContext.getDeploymentInfo();
 
-        // get the managers for the factories
-        Object primaryKey = callContext.getPrimaryKey();
-
         // register them
-        entityManagerRegistry.removeEntityManagers((String) deploymentInfo.getDeploymentID(), primaryKey);
+        entityManagerRegistry.removeEntityManagers((String) deploymentInfo.getDeploymentID(), instance.primaryKey);
+    }
+
+
+    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 sessionSynchronization = callContext.getCurrentOperation() != Operation.CREATE &&
+                callContext.getDeploymentInfo().isBeanManagedTransaction() &&
+                instance.bean instanceof SessionSynchronization &&
+                txPolicy.isTransactionActive();
+
+        coordinator.registerSessionSynchronization(instance, callContext.getDeploymentInfo(), callContext.getPrimaryKey(), sessionSynchronization);
+    }
+
+    /**
+     * 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, Instance> registry = new HashMap<Object, Instance>();
+        private final TransactionPolicy txPolicy;
+
+        private SessionSynchronizationCoordinator(TransactionPolicy txPolicy) {
+            this.txPolicy = txPolicy;
+        }
+
+        private void registerSessionSynchronization(Instance instance, CoreDeploymentInfo deploymentInfo, Object primaryKey, boolean sessionSynchronization) {
+            // register
+            if (!registry.containsKey(primaryKey)) {
+                registry.put(primaryKey, instance);
+            }
+
+            // check if afterBegin has already been invoked or if this is not a session synchronization bean
+            if (instance.isCallSessionSynchronization() || !sessionSynchronization) {
+                return;
+            }
+            instance.setCallSessionSynchronization();
+
+            // Invoke afterBegin
+            ThreadContext callContext = new ThreadContext(instance.deploymentInfo, instance.primaryKey, Operation.AFTER_BEGIN);
+            ThreadContext oldCallContext = ThreadContext.enter(callContext);
+            try {
+
+                Method afterBegin = SessionSynchronization.class.getMethod("afterBegin");
+
+                List<InterceptorData> interceptors = deploymentInfo.getMethodInterceptors(afterBegin);
+                InterceptorStack interceptorStack = new InterceptorStack(instance.bean, afterBegin, 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 (Instance instance : registry.values()) {
+                // don't call beforeCompletion when transaction is marked rollback only
+                if (txPolicy.isRollbackOnly()) return;
+
+                // only call beforeCompletion on beans with session synchronization
+                if (!instance.isCallSessionSynchronization()) continue;
+
+                // Invoke beforeCompletion
+                ThreadContext callContext = new ThreadContext(instance.deploymentInfo, instance.primaryKey, Operation.BEFORE_COMPLETION);
+                callContext.setCurrentAllowedStates(StatefulContext.getStates());
+                ThreadContext oldCallContext = ThreadContext.enter(callContext);
+                try {
+                    instance.setInUse(true);
+
+                    Method beforeCompletion = SessionSynchronization.class.getMethod("beforeCompletion");
+
+                    List<InterceptorData> interceptors = instance.deploymentInfo.getMethodInterceptors(beforeCompletion);
+                    InterceptorStack interceptorStack = new InterceptorStack(instance.bean, beforeCompletion, 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();
+
+                    // [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 (Instance instance : registry.values()) {
+
+                ThreadContext callContext = new ThreadContext(instance.deploymentInfo, instance.primaryKey, Operation.AFTER_COMPLETION);
+                callContext.setCurrentAllowedStates(StatefulContext.getStates());
+                ThreadContext oldCallContext = ThreadContext.enter(callContext);
+                try {
+                    instance.setInUse(true);
+                    if (instance.isCallSessionSynchronization()) {
+                        Method afterCompletion = SessionSynchronization.class.getMethod("afterCompletion", boolean.class);
+
+                        List<InterceptorData> interceptors = instance.deploymentInfo.getMethodInterceptors(afterCompletion);
+                        InterceptorStack interceptorStack = new InterceptorStack(instance.bean, afterCompletion, Operation.AFTER_COMPLETION, interceptors, instance.interceptors);
+                        interceptorStack.invoke(status == Status.COMMITTED);
+                    }
+
+                    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 {
+            CoreDeploymentInfo deploymentInfo = instance.deploymentInfo;
+
+            ThreadContext threadContext = new ThreadContext(instance.deploymentInfo, instance.primaryKey, Operation.ACTIVATE);
+            ThreadContext oldContext = ThreadContext.enter(threadContext);
+            try {
+                Method remove = instance.bean instanceof SessionBean ? SessionBean.class.getMethod("ejbActivate") : null;
+
+                List<InterceptorData> callbackInterceptors = deploymentInfo.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) {
+            CoreDeploymentInfo deploymentInfo = instance.deploymentInfo;
+
+            ThreadContext threadContext = new ThreadContext(deploymentInfo, instance.primaryKey, Operation.PASSIVATE);
+            ThreadContext oldContext = ThreadContext.enter(threadContext);
+            try {
+                Method passivate = instance.bean instanceof SessionBean ? SessionBean.class.getMethod("ejbPassivate") : null;
+
+                List<InterceptorData> callbackInterceptors = deploymentInfo.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) {
+            CoreDeploymentInfo deploymentInfo = instance.deploymentInfo;
+
+            ThreadContext threadContext = new ThreadContext(deploymentInfo, instance.primaryKey, Operation.PRE_DESTROY);
+            threadContext.setCurrentAllowedStates(StatefulContext.getStates());
+            ThreadContext oldContext = ThreadContext.enter(threadContext);
+            try {
+                Method remove = instance.bean instanceof SessionBean ? SessionBean.class.getMethod("ejbRemove") : null;
+
+                List<InterceptorData> callbackInterceptors = deploymentInfo.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 StatefulContainerData {
+        private final Index<Method, MethodType> methodIndex;
+
+        private StatefulContainerData(Index<Method, MethodType> methodIndex) {
+            this.methodIndex = methodIndex;
+        }
+
+        public Index<Method, MethodType> getMethodIndex() {
+            return methodIndex;
+        }
+
     }
 }

Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java?rev=689243&r1=689242&r2=689243&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java Tue Aug 26 14:36:19 2008
@@ -97,7 +97,12 @@
     public static List<Call> calls = new ArrayList<Call>();
 
     public static enum Call {
-        Constructor, PostConstruct, EjbCreate, EjbPassivate1, EjbActivate1, BusinessMethod, EjbPassivate2, EjbActivate2, EjbPassivate3, EjbActivate3, EjbRemove
+        // construction
+        Constructor, PostConstruct, EjbCreate, EjbPassivate1,
+        // business method
+        EjbActivate1, BusinessMethod, EjbPassivate2,
+        // remove
+        EjbActivate2, EjbRemove
     }
 
     public static class TargetBean implements Serializable {

Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java?rev=689243&r1=689242&r2=689243&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java Tue Aug 26 14:36:19 2008
@@ -98,7 +98,12 @@
     public static List<Call> calls = new ArrayList<Call>();
 
     public static enum Call {
-        Constructor, SetSessionContext, EjbCreate, EjbPassivate1, EjbActivate1, BusinessMethod, EjbPassivate2, EjbActivate2, EjbPassivate3, EjbActivate3, EjbRemove
+        // construction
+        Constructor, SetSessionContext, EjbCreate, EjbPassivate1,
+        // business method
+        EjbActivate1, BusinessMethod, EjbPassivate2,
+        // remove
+        EjbActivate2, EjbRemove
     }
 
     public static class TargetBean implements SessionBean {



Re: SessionSynchronization (svn commit: r689243)

Posted by Jacek Laskowski <ja...@laskowski.net.pl>.
On Tue, Dec 16, 2008 at 6:18 AM, David Blevins <da...@visi.com> wrote:

> I hear that writing SessionSynchronization itests is a great way to exercise
> guilt :)

I've been missing it ;-) Welcome back Dave!

Jacek

-- 
Jacek Laskowski
Notatnik Projektanta Java EE - http://www.JacekLaskowski.pl

SessionSynchronization (svn commit: r689243)

Posted by David Blevins <da...@visi.com>.
On Aug 26, 2008, at 2:36 PM, dain@apache.org wrote:

> Author: dain
> Date: Tue Aug 26 14:36:19 2008
> New Revision: 689243
>
> URL: http://svn.apache.org/viewvc?rev=689243&view=rev
> Log:
> Added simple pluggable cache interface to StatefulContainer.
> Merged StatefulInstanceManger and StatefulSynchronizationCorrdinator  
> into StatefulContainer.


Looks like this changed broke SessionSynchronization.  There was one  
bug that prevented the callbacks from getting called at all (actually  
they would get called if you marked your bean as bean-managed tx).   
Clearing that up there was another bug that prevented it from getting  
called more than once on a bean instance (should be once per  
transaction) due to some bad state on the Instance that was never  
reset.  I've got it partially working again but the TCK still is not  
happy.  Still hammering on it.

I hear that writing SessionSynchronization itests is a great way to  
exercise guilt :)


-David