You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by ga...@apache.org on 2010/08/25 19:48:13 UTC

svn commit: r989259 - in /openejb/trunk/openejb3/container/openejb-core/src: main/java/org/apache/openejb/assembler/classic/ main/java/org/apache/openejb/config/ main/java/org/apache/openejb/core/ main/java/org/apache/openejb/core/stateful/ main/resour...

Author: gawor
Date: Wed Aug 25 17:48:12 2010
New Revision: 989259

URL: http://svn.apache.org/viewvc?rev=989259&view=rev
Log:
OPENEJB-1144: Discover and process @AccessTimeout annotations on methods. Support per-method @AccessTimeout and improve locking in stateful container.

Added:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodBuilder.java   (with props)
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodInfo.java   (with props)
Modified:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EjbJarBuilder.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanInfo.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/EjbJarInfoBuilder.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/MethodContext.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/StatefulContainer.java
    openejb/trunk/openejb3/container/openejb-core/src/main/resources/META-INF/org.apache.openejb/service-jar.xml
    openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulConcurrencyTest.java
    openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTransactionLockingTest.java

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodBuilder.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodBuilder.java?rev=989259&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodBuilder.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodBuilder.java Wed Aug 25 17:48:12 2010
@@ -0,0 +1,108 @@
+/**
+ * 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.assembler.classic;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.ejb.LockType;
+
+import org.apache.openejb.core.CoreDeploymentInfo;
+import org.apache.openejb.core.MethodContext;
+import org.apache.openejb.util.Classes;
+import org.apache.openejb.util.Duration;
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
+import org.apache.openejb.util.SetAccessible;
+
+public class ConcurrentMethodBuilder {
+
+    public static final Logger logger = Logger.getInstance(LogCategory.OPENEJB_STARTUP, ConcurrentMethodBuilder.class.getPackage().getName());
+
+    public void build(CoreDeploymentInfo deploymentInfo, EnterpriseBeanInfo beanInfo) {
+        Class<?> clazz = deploymentInfo.getBeanClass();
+
+        for (ConcurrentMethodInfo info : beanInfo.concurrentMethodInfos) {
+            Method method;
+            try {
+                method = getMethod(clazz, info.method.methodName, toClasses(info.method.methodParams, clazz.getClassLoader()));
+            } catch (NoSuchMethodException e) {
+                // method doesn't exist
+                logger.warning("Concurrent method does not exist: "+info.method.methodName, e);
+                continue;
+            } catch (ClassNotFoundException e) {
+                logger.warning("Concurrent method param cannot be loaded.", e);
+                continue;
+            }
+
+            if (info.method.className == null || method.getDeclaringClass().getName().equals(info.method.className)) {
+                MethodContext methodContext = deploymentInfo.getMethodContext(method);
+                
+                if (info.accessTimeout != null) {
+                    Duration accessTimeout = new Duration(info.accessTimeout.time, TimeUnit.valueOf(info.accessTimeout.unit));
+                    methodContext.setAccessTimeout(accessTimeout);
+                }
+                
+                if (info.lockType != null) {
+                    methodContext.setLockType(LockType.valueOf(info.lockType));
+                }
+            }
+        }
+    }
+
+    private Class<?>[] toClasses(List<String> params, ClassLoader classLoader) throws ClassNotFoundException {
+        if (params == null) {
+            return null;
+        }
+        Class<?>[] paramsArray = new Class[params.size()];
+        for (int j = 0; j < paramsArray.length; j++) {
+            String methodParam = params.get(j);
+            paramsArray[j] = Classes.forName(methodParam, classLoader);
+
+        }
+        return paramsArray;
+    }
+
+    /**
+     * Finds the nearest java.lang.reflect.Method with the given
+     * name and parameters.  Callbacks can be private so class.getMethod() cannot be used.  Searching
+     * starts by looking in the specified class, if the method is not found searching continues with
+     * the immediate parent and continues recurssively until the method is found or java.lang.Object
+     * is reached.  If the method is not found a NoSuchMethodException is thrown.
+     *
+     * @param clazz
+     * @param methodName
+     * @param parameterTypes
+     * @return
+     * @throws NoSuchMethodException if the method is not found in this class or any of its parent classes
+     */
+    private Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
+        NoSuchMethodException original = null;
+        while (clazz != null){
+            try {
+                Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
+                return SetAccessible.on(method);
+            } catch (NoSuchMethodException e) {
+                if (original == null) original = e;
+            }
+            clazz = clazz.getSuperclass();
+        }
+        throw original;
+    }
+
+}

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodBuilder.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodBuilder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodInfo.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodInfo.java?rev=989259&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodInfo.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodInfo.java Wed Aug 25 17:48:12 2010
@@ -0,0 +1,27 @@
+/**
+ * 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.assembler.classic;
+
+public class ConcurrentMethodInfo extends InfoObject {
+
+    public TimeoutInfo accessTimeout;
+    
+    public NamedMethodInfo method;
+    
+    public String lockType;
+    
+}

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodInfo.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodInfo.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/ConcurrentMethodInfo.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EjbJarBuilder.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EjbJarBuilder.java?rev=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EjbJarBuilder.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EjbJarBuilder.java Wed Aug 25 17:48:12 2010
@@ -58,6 +58,8 @@ public class EjbJarBuilder {
         InterceptorBindingBuilder interceptorBindingBuilder = new InterceptorBindingBuilder(context.getClassLoader(), ejbJar);
 
         MethodScheduleBuilder methodScheduleBuilder = new MethodScheduleBuilder();
+        
+        ConcurrentMethodBuilder concurrentMethodBuilder = new ConcurrentMethodBuilder();
 
         for (EnterpriseBeanInfo ejbInfo : ejbJar.enterpriseBeans) {
             try {
@@ -67,6 +69,8 @@ public class EjbJarBuilder {
                 interceptorBindingBuilder.build(deployment, ejbInfo);
 
                 methodScheduleBuilder.build(deployment, ejbInfo);
+                
+                concurrentMethodBuilder.build(deployment, ejbInfo);
 
                 deployments.put(ejbInfo.ejbDeploymentId, deployment);
 

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanInfo.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanInfo.java?rev=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanInfo.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/EnterpriseBeanInfo.java Wed Aug 25 17:48:12 2010
@@ -83,4 +83,5 @@ public abstract class EnterpriseBeanInfo
     public TimeoutInfo statefulTimeout;
     public TimeoutInfo accessTimeout;
     public List<MethodScheduleInfo> methodScheduleInfos = new ArrayList<MethodScheduleInfo>();
+    public final List<ConcurrentMethodInfo> concurrentMethodInfos = new ArrayList<ConcurrentMethodInfo>();
 }

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java?rev=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java Wed Aug 25 17:48:12 2010
@@ -65,6 +65,8 @@ import javax.ejb.Init;
 import javax.ejb.Local;
 import javax.ejb.LocalBean;
 import javax.ejb.LocalHome;
+import javax.ejb.Lock;
+import javax.ejb.LockType;
 import javax.ejb.MessageDriven;
 import javax.ejb.PostActivate;
 import javax.ejb.PrePassivate;
@@ -108,6 +110,7 @@ import org.apache.openejb.jee.AroundTime
 import org.apache.openejb.jee.AssemblyDescriptor;
 import org.apache.openejb.jee.ConcurrencyManagementType;
 import org.apache.openejb.jee.ConcurrentLockType;
+import org.apache.openejb.jee.ConcurrentMethod;
 import org.apache.openejb.jee.ContainerConcurrency;
 import org.apache.openejb.jee.ContainerTransaction;
 import org.apache.openejb.jee.EjbJar;
@@ -1098,7 +1101,7 @@ public class AnnotationDeployer implemen
 
                     // Merge AccessTimeout value from XML - XML value takes precedence
                     if(sessionBean.getAccessTimeout() == null) {
-                        final AccessTimeout annotation = clazz.getAnnotation(AccessTimeout.class);
+                        final AccessTimeout annotation = getInheritableAnnotation(clazz, AccessTimeout.class);
                         if(annotation != null) {
                             final Timeout timeout = new Timeout();
                             timeout.setTimeout(annotation.value());
@@ -1109,7 +1112,7 @@ public class AnnotationDeployer implemen
 
                     // Merge StatefulTimeout value from XML - XML value takes precedence
                     if(sessionBean.getStatefulTimeout() == null) {
-                        final StatefulTimeout annotation = clazz.getAnnotation(StatefulTimeout.class);
+                        final StatefulTimeout annotation = getInheritableAnnotation(clazz, StatefulTimeout.class);
                         if(annotation != null) {
                             final Timeout timeout = new Timeout();
                             timeout.setTimeout(annotation.value());
@@ -1117,6 +1120,12 @@ public class AnnotationDeployer implemen
                             sessionBean.setStatefulTimeout(timeout);
                         }
                     }
+                    
+                    /*
+                     * @AccessTimeout
+                     * @Lock 
+                     */
+                    processAccessTimeoutAndLock(sessionBean, inheritedClassFinder);
                 }
 
 
@@ -2253,6 +2262,64 @@ public class AnnotationDeployer implemen
             }
         }
 
+        private ConcurrentMethod findConcurrentMethod(Map<NamedMethod, ConcurrentMethod> methodMap, Method method) {
+            // step 1: lookup by method name and parameters
+            NamedMethod lookup = new NamedMethod(method);
+            ConcurrentMethod concurrentMethod = methodMap.get(lookup);
+            if (concurrentMethod == null) {
+                // step 2: lookup by method name only
+                lookup.setMethodParams(null);
+                concurrentMethod = methodMap.get(lookup);
+            }
+            return concurrentMethod;
+        }
+        
+        private void processAccessTimeoutAndLock(SessionBean bean, ClassFinder classFinder) {
+            Map<NamedMethod, ConcurrentMethod> methodMap = new HashMap<NamedMethod, ConcurrentMethod>();
+            for (ConcurrentMethod concurrentMethod : bean.getConcurrentMethod()) {
+                methodMap.put(concurrentMethod.getMethod(), concurrentMethod);
+            }
+            
+            for (Method method : classFinder.findAnnotatedMethods(AccessTimeout.class)) {
+                ConcurrentMethod concurrentMethod = findConcurrentMethod(methodMap, method);
+                if (concurrentMethod == null) {
+                    // create new and add to the list
+                    concurrentMethod = new ConcurrentMethod();
+                    concurrentMethod.setMethod(new NamedMethod(method));
+                    bean.getConcurrentMethod().add(concurrentMethod);
+                }
+                
+                AccessTimeout annotation = method.getAnnotation(AccessTimeout.class);
+                
+                if (concurrentMethod.getAccessTimeout() == null) {
+                    Timeout timeout = new Timeout();
+                    timeout.setTimeout(annotation.value());
+                    timeout.setUnit(annotation.unit());
+                    concurrentMethod.setAccessTimeout(timeout);                    
+                }
+            }
+            
+            for (Method method : classFinder.findAnnotatedMethods(Lock.class)) {
+                ConcurrentMethod concurrentMethod = findConcurrentMethod(methodMap, method);
+                if (concurrentMethod == null) {
+                    // create new and add to the list
+                    concurrentMethod = new ConcurrentMethod();
+                    concurrentMethod.setMethod(new NamedMethod(method));
+                    bean.getConcurrentMethod().add(concurrentMethod);
+                }
+                
+                Lock annotation = method.getAnnotation(Lock.class);
+                                               
+                if (concurrentMethod.getLock() == null) {
+                    if (LockType.READ.equals(annotation.value())) {
+                        concurrentMethod.setLock(ConcurrentLockType.READ);
+                    } else if (LockType.WRITE.equals(annotation.value())) {
+                        concurrentMethod.setLock(ConcurrentLockType.WRITE);
+                    }
+                }
+            }
+        }
+        
         private void processCallbacks(Lifecycle bean, ClassFinder classFinder) {
             /*
              * @PostConstruct

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/EjbJarInfoBuilder.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/EjbJarInfoBuilder.java?rev=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/EjbJarInfoBuilder.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/EjbJarInfoBuilder.java Wed Aug 25 17:48:12 2010
@@ -25,10 +25,14 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+
+import javax.ejb.LockType;
+
 import org.apache.openejb.OpenEJBException;
 import org.apache.openejb.assembler.classic.ApplicationExceptionInfo;
 import org.apache.openejb.assembler.classic.CallbackInfo;
 import org.apache.openejb.assembler.classic.CmrFieldInfo;
+import org.apache.openejb.assembler.classic.ConcurrentMethodInfo;
 import org.apache.openejb.assembler.classic.EjbJarInfo;
 import org.apache.openejb.assembler.classic.EnterpriseBeanInfo;
 import org.apache.openejb.assembler.classic.EntityBeanInfo;
@@ -60,6 +64,8 @@ import org.apache.openejb.jee.CallbackMe
 import org.apache.openejb.jee.CmpField;
 import org.apache.openejb.jee.CmpVersion;
 import org.apache.openejb.jee.ConcurrencyManagementType;
+import org.apache.openejb.jee.ConcurrentLockType;
+import org.apache.openejb.jee.ConcurrentMethod;
 import org.apache.openejb.jee.ContainerConcurrency;
 import org.apache.openejb.jee.ContainerTransaction;
 import org.apache.openejb.jee.EjbRelation;
@@ -525,6 +531,8 @@ public class EjbJarInfoBuilder {
                 remove.retainIfException = removeMethod.getRetainIfException();
                 stateful.removeMethods.add(remove);
             }
+            
+            copyConcurrentMethods(s.getConcurrentMethod(), bean.concurrentMethodInfos);
 
         } else if (s.getSessionType() == SessionType.MANAGED) {
             bean = new ManagedBeanInfo();
@@ -550,6 +558,8 @@ public class EjbJarInfoBuilder {
             copySchedules(s.getTimer(), bean.methodScheduleInfos);
             // See JndiEncInfoBuilder.buildDependsOnRefs for processing of DependsOn
             // bean.dependsOn.addAll(s.getDependsOn());
+            
+            copyConcurrentMethods(s.getConcurrentMethod(), bean.concurrentMethodInfos);
         } else {
             bean = new StatelessBeanInfo();
             copySchedules(s.getTimer(), bean.methodScheduleInfos);
@@ -595,23 +605,21 @@ public class EjbJarInfoBuilder {
         bean.serviceEndpoint = s.getServiceEndpoint();
         bean.properties.putAll(d.getProperties());
 
-        final Timeout statefulTimeout = s.getStatefulTimeout();
-        if(statefulTimeout != null) {
-        	bean.statefulTimeout = new TimeoutInfo();
-            bean.statefulTimeout.time = statefulTimeout.getTimeout();
-            bean.statefulTimeout.unit = statefulTimeout.getUnit().toString();
-        }
-
-        final Timeout accessTimeout = s.getAccessTimeout();
-        if(accessTimeout != null) {
-            bean.accessTimeout = new TimeoutInfo();
-            bean.accessTimeout.time = accessTimeout.getTimeout();
-            bean.accessTimeout.unit = accessTimeout.getUnit().toString();
-        }
+        bean.statefulTimeout = toInfo(s.getStatefulTimeout());
+        bean.accessTimeout = toInfo(s.getAccessTimeout());
 
         return bean;
     }
 
+    private TimeoutInfo toInfo(Timeout timeout) {
+        if (timeout == null) return null;
+        
+        TimeoutInfo accessTimeout = new TimeoutInfo();
+        accessTimeout.time = timeout.getTimeout();
+        accessTimeout.unit = timeout.getUnit().toString();
+        return accessTimeout;
+    }
+    
     private EnterpriseBeanInfo initMessageBean(MessageDrivenBean mdb, Map m) throws OpenEJBException {
         MessageDrivenBeanInfo bean = new MessageDrivenBeanInfo();
 
@@ -781,4 +789,16 @@ public class EjbJarInfoBuilder {
         }
         return bean;
     }
+    
+    private void copyConcurrentMethods(List<ConcurrentMethod> from, List<ConcurrentMethodInfo> to) {
+        for (ConcurrentMethod method : from) {
+            ConcurrentMethodInfo methodInfo = new ConcurrentMethodInfo();
+            methodInfo.accessTimeout = toInfo(method.getAccessTimeout());
+            methodInfo.method = toInfo(method.getMethod());
+            if (method.getLock() != null) {
+                methodInfo.lockType = method.getLock().value().toUpperCase();
+            }
+            to.add(methodInfo);
+        }
+    }
 }

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/MethodContext.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/MethodContext.java?rev=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/MethodContext.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/MethodContext.java Wed Aug 25 17:48:12 2010
@@ -48,7 +48,7 @@ public class MethodContext {
     }
 
     public Duration getAccessTimeout() {
-        return accessTimeout != null? accessTimeout: beanContext.getAccessTimeout();
+        return accessTimeout;
     }
 
     public CoreDeploymentInfo getBeanContext() {

Modified: 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=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java Wed Aug 25 17:48:12 2010
@@ -46,7 +46,7 @@ public class Instance implements Seriali
     private boolean inUse;
     private SuspendedTransaction beanTransaction;
     private Stack<Transaction> transaction = new Stack<Transaction>();
-    private final Lock lock = new ReentrantLock();
+    private final ReentrantLock lock = new ReentrantLock();
 
     // 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
@@ -112,9 +112,14 @@ public class Instance implements Seriali
         } else if (transaction != null){
             this.transaction.push(transaction);
         }
-
     }
 
+    public synchronized void releaseLock() {
+        if (lock.isHeldByCurrentThread()) {
+            lock.unlock();
+        }
+    }
+    
     public synchronized Map<EntityManagerFactory, EntityManager> getEntityManagers(Index<EntityManagerFactory, Map> factories) {
         if (entityManagers == null && entityManagerArray != null) {
             entityManagers = new HashMap<EntityManagerFactory, EntityManager>();

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=989259&r1=989258&r2=989259&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 Wed Aug 25 17:48:12 2010
@@ -61,6 +61,8 @@ import org.apache.openejb.core.CoreDeplo
 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.MethodContext;
 import org.apache.openejb.core.Operation;
 import org.apache.openejb.core.ThreadContext;
 import org.apache.openejb.core.InstanceContext;
@@ -107,7 +109,7 @@ public class StatefulContainer implement
     private final SessionContext sessionContext;
 
     public StatefulContainer(Object id, SecurityService securityService, Cache<Object, Instance> cache) {
-        this(id, securityService, cache, new Duration(0, TimeUnit.MILLISECONDS));
+        this(id, securityService, cache, new Duration(-1, TimeUnit.MILLISECONDS));
     }
 
     public StatefulContainer(Object id, SecurityService securityService, Cache<Object, Instance> cache, Duration accessTimeout) {
@@ -468,7 +470,7 @@ public class StatefulContainer implement
             Method runMethod = null;
             try {
                 // Obtain instance
-                instance = obtainInstance(primKey, callContext);
+                instance = obtainInstance(primKey, callContext, callMethod);
 
                 // Resume previous Bean transaction if there was one
                 if (txPolicy instanceof BeanTransactionPolicy){
@@ -568,7 +570,7 @@ public class StatefulContainer implement
             Instance instance = null;
             try {
                 // Obtain instance
-                instance = obtainInstance(primKey, callContext);
+                instance = obtainInstance(primKey, callContext, callMethod);
 
                 // Resume previous Bean transaction if there was one
                 if (txPolicy instanceof BeanTransactionPolicy){
@@ -610,7 +612,7 @@ public class StatefulContainer implement
         }
     }
 
-    private Instance obtainInstance(Object primaryKey, ThreadContext callContext) throws OpenEJBException {
+    private Instance obtainInstance(Object primaryKey, ThreadContext callContext, Method callMethod) throws OpenEJBException {
         if (primaryKey == null) {
             throw new SystemException(new NullPointerException("Cannot obtain an instance of the stateful session bean with a null session id"));
         }
@@ -618,37 +620,42 @@ public class StatefulContainer implement
         Transaction currentTransaction = getTransaction(callContext);
 
         // 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?
+        Instance instance;
+        synchronized (primaryKey) {
+            instance = checkedOutInstances.get(primaryKey);
             if (instance == null) {
-                throw new InvalidateReferenceException(new NoSuchObjectException("Not Found"));
-            }
-
-            // remember instance until it is returned to the cache
-            checkedOutInstances.put(primaryKey, instance);
-        }
+                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"));
+                }
 
-        Duration accessTimeout = instance.deploymentInfo.getAccessTimeout();
-        if (accessTimeout == null) accessTimeout = this.accessTimeout;
+                
+                // remember instance until it is returned to the cache                
+                checkedOutInstances.put(primaryKey, instance);
+            }
+        }
+        
+        Duration accessTimeout = getAccessTimeout(instance.deploymentInfo, callMethod);
 
         final Lock currLock = instance.getLock();
-    	final boolean lockAcquired;
-    	if(accessTimeout == null) {
-    		// returns immediately true if the lock is available 
+        final boolean lockAcquired;
+        if (accessTimeout == null || accessTimeout.getTime() < 0) {
+            // wait indefinitely for a lock
+            currLock.lock();
+            lockAcquired = true;
+        } else if (accessTimeout.getTime() == 0) {
+            // concurrent calls are not allowed, lock only once
     		lockAcquired = currLock.tryLock();
     	} else {
-    		// AccessTimeout annotation found. 
-    		// Trying to get the lock within the specified period. 
+    		// try to get a lock within the specified period. 
     		try {
 				lockAcquired = currLock.tryLock(accessTimeout.getTime(), accessTimeout.getUnit());
 			} catch (InterruptedException e) {
@@ -657,14 +664,14 @@ public class StatefulContainer implement
     	}
         // Did we acquire the lock to the current execution?
         if (!lockAcquired) {
-        	 throw new ApplicationException(new ConcurrentAccessTimeoutException("Unable to get lock."));
+            throw new ApplicationException(new ConcurrentAccessTimeoutException("Unable to get lock."));
         }
         
-        if (instance.getTransaction() != null){
+        if (instance.getTransaction() != null) {
             if (!instance.getTransaction().equals(currentTransaction) && !instance.getLock().tryLock()) {
                 throw new ApplicationException(new RemoteException("Instance is in a transaction and cannot be invoked outside that transaction.  See EJB 3.0 Section 4.4.4"));
             }
-        } else {
+        } else { 
             instance.setTransaction(currentTransaction);
         }
 
@@ -673,6 +680,19 @@ public class StatefulContainer implement
         return instance;
     }
 
+    private Duration getAccessTimeout(CoreDeploymentInfo deploymentInfo, Method callMethod) {
+        Duration accessTimeout = null;
+        callMethod = deploymentInfo.getMatchingBeanMethod(callMethod);
+        MethodContext methodContext = deploymentInfo.getMethodContext(callMethod);
+        if (methodContext != null) {
+            accessTimeout = methodContext.getAccessTimeout();
+        }
+        if (accessTimeout == null) {
+            accessTimeout = deploymentInfo.getAccessTimeout();
+        }
+        return (accessTimeout == null) ? this.accessTimeout : accessTimeout;
+    }
+    
     private Transaction getTransaction(ThreadContext callContext) {
         TransactionPolicy policy = callContext.getTransactionPolicy();
 
@@ -698,11 +718,13 @@ public class StatefulContainer implement
         instance.setInUse(false);
 
         if (instance.getTransaction() == null) {
-            // return to cache
-            cache.checkIn(instance.primaryKey);
+            synchronized (instance.primaryKey) {
+                // return to cache
+                cache.checkIn(instance.primaryKey);
 
-            // no longer checked out
-            checkedOutInstances.remove(instance.primaryKey);
+                // no longer checked out
+                checkedOutInstances.remove(instance.primaryKey);
+            }
         }
     }
 
@@ -757,6 +779,9 @@ public class StatefulContainer implement
                 instance.setInUse(false);
             }
             EjbTransactionUtil.afterInvoke(txPolicy, callContext);
+            if (instance != null) {
+                instance.releaseLock();
+            }
         }
     }
 

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/resources/META-INF/org.apache.openejb/service-jar.xml
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/resources/META-INF/org.apache.openejb/service-jar.xml?rev=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/resources/META-INF/org.apache.openejb/service-jar.xml (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/resources/META-INF/org.apache.openejb/service-jar.xml Wed Aug 25 17:48:12 2010
@@ -353,7 +353,7 @@
     # seconds, minutes, hours, days.  Or any combination such as
     # "1 hour and 27 minutes and 10 seconds"
 
-    AccessTimeout = 0 milliseconds
+    AccessTimeout = -1 milliseconds
 
     # The cache is responsible for managing stateful bean
     # instances.  The cache can page instances to disk as memory

Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulConcurrencyTest.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulConcurrencyTest.java?rev=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulConcurrencyTest.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulConcurrencyTest.java Wed Aug 25 17:48:12 2010
@@ -16,7 +16,16 @@
  */
 package org.apache.openejb.core.stateful;
 
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import javax.ejb.ConcurrentAccessTimeoutException;
+import javax.ejb.Local;
+import javax.ejb.Stateful;
+import javax.naming.InitialContext;
+
 import junit.framework.TestCase;
+
 import org.apache.openejb.assembler.classic.Assembler;
 import org.apache.openejb.assembler.classic.ProxyFactoryInfo;
 import org.apache.openejb.assembler.classic.SecurityServiceInfo;
@@ -24,16 +33,9 @@ import org.apache.openejb.assembler.clas
 import org.apache.openejb.assembler.classic.TransactionServiceInfo;
 import org.apache.openejb.client.LocalInitialContextFactory;
 import org.apache.openejb.config.ConfigurationFactory;
-import org.apache.openejb.jee.Timeout;
 import org.apache.openejb.jee.EjbJar;
 import org.apache.openejb.jee.StatefulBean;
-
-import javax.ejb.ConcurrentAccessTimeoutException;
-import javax.ejb.Local;
-import javax.ejb.Stateful;
-import javax.naming.InitialContext;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.TimeUnit;
+import org.apache.openejb.jee.Timeout;
 
 public class StatefulConcurrencyTest extends TestCase {
 
@@ -51,108 +53,104 @@ public class StatefulConcurrencyTest ext
         assembler.createSecurityService(config.configureService(SecurityServiceInfo.class));
 
         final StatefulSessionContainerInfo statefulContainerInfo = config.configureService(StatefulSessionContainerInfo.class);
-        statefulContainerInfo.properties.setProperty("BulkPassivate", "1");
         assembler.createContainer(statefulContainerInfo);
 
         final EjbJar ejbJar = new EjbJar();
-        final StatefulBean bean = new StatefulBean(MyLocalBeanImpl.class);
-
-        final Timeout timeout = new Timeout();
-        timeout.setTimeout(1000);
-        timeout.setUnit(TimeUnit.MILLISECONDS);
-        bean.setAccessTimeout(timeout);
+                
+        StatefulBean bean1 = new StatefulBean(MyLocalBeanImpl.class);
+        Timeout timeout1 = new Timeout();
+        timeout1.setTimeout(1000);
+        timeout1.setUnit(TimeUnit.MILLISECONDS);
+        bean1.setAccessTimeout(timeout1);
+        
+        StatefulBean bean2 = new StatefulBean("BeanNegative", MyLocalBeanImpl.class);
+        Timeout timeout2 = new Timeout();
+        timeout2.setTimeout(-1);
+        timeout2.setUnit(TimeUnit.MILLISECONDS);
+        bean2.setAccessTimeout(timeout2);
 
-        ejbJar.addEnterpriseBean(bean);
+        ejbJar.addEnterpriseBean(bean1);
+        ejbJar.addEnterpriseBean(bean2);
 
         assembler.createApplication(config.configureApplication(ejbJar));
     }
 
     public void testConcurrentMethodCall() throws Exception {
+        MyLocalBeanImpl.semaphore = new Semaphore(0);
+        
         InitialContext ctx = new InitialContext();
         MyLocalBean bean = (MyLocalBean) ctx.lookup("MyLocalBeanImplLocal");
         MyLocalBean bean2 = (MyLocalBean) ctx.lookup("MyLocalBeanImplLocal");
+        
+        CallRentrantThread call = new CallRentrantThread(bean, 3000);
+        (new Thread(call)).start();
+        
+        // ensure the call on thread came in
+        assertTrue(MyLocalBeanImpl.semaphore.tryAcquire(1, 30, TimeUnit.SECONDS));
+        
+        try {
+            bean2.callRentrant(bean, 0);
+            fail("Expected exception");
+        } catch (Exception e) {
+            if (e.getCause() instanceof ConcurrentAccessTimeoutException) {
+                // that's what we want
+            } else {
+                throw e;
+            }
+        }
+    }
+  
+    public void testNegativeAccessTimeout() throws Exception {
+        MyLocalBeanImpl.semaphore = new Semaphore(0);
+        
+        InitialContext ctx = new InitialContext();
+        MyLocalBean bean = (MyLocalBean) ctx.lookup("BeanNegativeLocal");
+        
+        CallRentrantThread call = new CallRentrantThread(bean, 3000);
+        (new Thread(call)).start();
+        
+        // ensure the call on thread came in
+        assertTrue(MyLocalBeanImpl.semaphore.tryAcquire(1, 30, TimeUnit.SECONDS));
 
-        boolean error = bean.method1(bean, 2000);
-        assertTrue(error);
-
-        error = bean2.method1(bean, 500);
-        assertFalse(error);
+        bean.callRentrant(bean, 0);
     }
 
     @Local
     public static interface MyLocalBean {
-        boolean method1(MyLocalBean bean, long sleep);
-
-        void method2(CyclicBarrier barrier);
+        void callRentrant(MyLocalBean myself, long sleep);
+        void sleep(long sleep);
     }
 
     @Stateful
     public static class MyLocalBeanImpl implements MyLocalBean {
 
+        public static Semaphore semaphore;
+        
+        public void callRentrant(MyLocalBean myself, long sleep) {
+            semaphore.release();
+            myself.sleep(sleep);
+        }
 
-        public boolean method1(MyLocalBean bean, long sleep) {
-            System.out.println("Method 1 invoked! Thread: " + Thread.currentThread().getName());
-
-            CyclicBarrier barrier = new CyclicBarrier(1);
-            MyRunningMethod method = new MyRunningMethod(barrier, bean);
-            Thread thread = new Thread(method);
-            thread.setName("MyRunningMethodThread");
-            thread.start();
+        public void sleep(long sleep) {
             try {
                 Thread.sleep(sleep);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
-
-            try {
-                barrier.await();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-
-            return method.isError();
-        }
-
-        public void method2(CyclicBarrier barrier) {
-            System.out.println("Method 2 invoked! Thread: "
-                    + Thread.currentThread().getName());
-            try {
-                Thread.sleep(1000);
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-            try {
-                barrier.await();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
         }
     }
-
-    public static class MyRunningMethod implements Runnable {
+    
+    public class CallRentrantThread implements Runnable {
         private final MyLocalBean bean;
-        private final CyclicBarrier barrier;
-
-        private boolean error;
-
-        public MyRunningMethod(CyclicBarrier barrier, MyLocalBean bean) {
-            super();
+        private final long sleep;
+        
+        public CallRentrantThread(MyLocalBean bean, long sleep) {
             this.bean = bean;
-            this.barrier = barrier;
-        }
-
-        public boolean isError() {
-            return error;
+            this.sleep = sleep;
         }
 
         public void run() {
-            try {
-                bean.method2(barrier);
-                error = false;
-            } catch (ConcurrentAccessTimeoutException e) {
-                error = true;
-            }
+            bean.callRentrant(bean, sleep);
         }
     }
-
 }
\ No newline at end of file

Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTransactionLockingTest.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTransactionLockingTest.java?rev=989259&r1=989258&r2=989259&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTransactionLockingTest.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTransactionLockingTest.java Wed Aug 25 17:48:12 2010
@@ -59,9 +59,12 @@ public class StatefulTransactionLockingT
 
         assembler.createTransactionManager(config.configureService(TransactionServiceInfo.class));
         assembler.createSecurityService(config.configureService(SecurityServiceInfo.class));
+        
+        StatefulSessionContainerInfo statefulContainerInfo = config.configureService(StatefulSessionContainerInfo.class);
+        statefulContainerInfo.properties.setProperty("AccessTimeout", "0 milliseconds");
 
         // containers
-        assembler.createContainer(config.configureService(StatefulSessionContainerInfo.class));
+        assembler.createContainer(statefulContainerInfo);
 
         // Setup the descriptor information