EXTCDI-265 implementation validation for @TransactionScoped

git-svn-id: https://svn.apache.org/repos/asf/myfaces/extensions/cdi/trunk@1240870 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/jee-modules/jpa-module/api/src/main/java/org/apache/myfaces/extensions/cdi/jpa/api/Transactional.java b/jee-modules/jpa-module/api/src/main/java/org/apache/myfaces/extensions/cdi/jpa/api/Transactional.java
index f146dbe..458ce13 100644
--- a/jee-modules/jpa-module/api/src/main/java/org/apache/myfaces/extensions/cdi/jpa/api/Transactional.java
+++ b/jee-modules/jpa-module/api/src/main/java/org/apache/myfaces/extensions/cdi/jpa/api/Transactional.java
@@ -34,6 +34,8 @@
  * The optional qualifier can be used to specify different entity managers.
  */
 
+//TODO instead of using #qualifier it should be possible to use a custom qualifier annotated with @TransactionQualifier
+
 @InterceptorBinding
 @Documented
 @Inherited
diff --git a/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/TransactionalInterceptorStrategy.java b/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/TransactionalInterceptorStrategy.java
index b8920c4..6bebb37 100644
--- a/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/TransactionalInterceptorStrategy.java
+++ b/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/TransactionalInterceptorStrategy.java
@@ -136,23 +136,33 @@
                 for (Map.Entry<String, EntityManager> emsEntry: emsEntries.entrySet())
                 {
                     EntityManager em = emsEntry.getValue();
-                    transaction = em.getTransaction();
-                    if (transaction != null && transaction.isActive())
+
+                    try
                     {
-                        try
+                        transaction = em.getTransaction();
+
+                        if (transaction != null && transaction.isActive())
                         {
-                            transaction.rollback();
-                        }
-                        catch (Exception eRollback)
-                        {
-                            if(LOGGER.isLoggable(Level.SEVERE))
+                            try
                             {
-                                LOGGER.log(Level.SEVERE,
-                                        "Got additional Exception while subsequently " +
-                                                "rolling back other SQL transactions", eRollback);
+                                transaction.rollback();
+                            }
+                            catch (Exception eRollback)
+                            {
+                                if(LOGGER.isLoggable(Level.SEVERE))
+                                {
+                                    LOGGER.log(Level.SEVERE,
+                                            "Got additional Exception while subsequently " +
+                                                    "rolling back other SQL transactions", eRollback);
+                                }
                             }
                         }
                     }
+                    catch (IllegalStateException e2)
+                    {
+                        //just happens if the setup is wrong -> we can't do a proper cleanup
+                        //but we have to continue to cleanup the scope
+                    }
                 }
 
                 // drop all EntityManagers from the ThreadLocal
@@ -234,6 +244,12 @@
                     TransactionBeanStorage.getStorage().endAllTransactionScopes();
                     TransactionBeanStorage.resetStorage();
                 }
+                else
+                {
+                    //just happens if the wrong qualifier is used in an application
+                    TransactionBeanStorage.getStorage().endAllTransactionScopes();
+                    TransactionBeanStorage.resetStorage();
+                }
             }
             else
             {
diff --git a/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionBeanEntry.java b/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionBeanEntry.java
index 7da453e..837601e 100644
--- a/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionBeanEntry.java
+++ b/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionBeanEntry.java
@@ -20,6 +20,10 @@
 
 import javax.enterprise.context.spi.Contextual;
 import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.Bean;
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.Set;
 
 /**
  * Holds the information we need store to manage
@@ -34,6 +38,7 @@
     public TransactionBeanEntry(Contextual<T> bean, T contextualInstance, CreationalContext<T> creationalContext)
     {
         this.bean = bean;
+
         this.contextualInstance = contextualInstance;
         this.creationalContext = creationalContext;
     }
@@ -52,4 +57,13 @@
     {
         return creationalContext;
     }
+
+    public Set<Annotation> getQualifiers()
+    {
+        if(bean instanceof Bean)
+        {
+            return ((Bean<?>)bean).getQualifiers();
+        }
+        return Collections.emptySet();
+    }
 }
diff --git a/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionBeanStorage.java b/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionBeanStorage.java
index 3372f82..2997982 100644
--- a/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionBeanStorage.java
+++ b/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionBeanStorage.java
@@ -184,6 +184,10 @@
         return oldTransactionContextKey;
     }
 
+    public String getActiveTransactionKey()
+    {
+        return activeTransactionKey;
+    }
 
     /**
      * This will destroy all stored transaction contexts.
diff --git a/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionContext.java b/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionContext.java
index 3240164..f6f4fbc 100644
--- a/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionContext.java
+++ b/jee-modules/jpa-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/jpa/impl/transaction/context/TransactionContext.java
@@ -48,6 +48,7 @@
         TransactionBeanEntry transactionBeanEntry = transactionBeanEntryMap.get(component);
         if (transactionBeanEntry != null)
         {
+            checkTransactionBeanEntry(transactionBeanEntry);
             return (T) transactionBeanEntry.getContextualInstance();
         }
 
@@ -72,6 +73,7 @@
         TransactionBeanEntry transactionBeanEntry = transactionBeanEntryMap.get(component);
         if (transactionBeanEntry != null)
         {
+            checkTransactionBeanEntry(transactionBeanEntry);
             return (T) transactionBeanEntry.getContextualInstance();
         }
 
@@ -80,9 +82,43 @@
         transactionBeanEntry = new TransactionBeanEntry(component, instance, creationalContext);
         transactionBeanEntryMap.put(component, transactionBeanEntry);
 
+        checkTransactionBeanEntry(transactionBeanEntry);
         return instance;
     }
 
+    private void checkTransactionBeanEntry(TransactionBeanEntry<?> transactionBeanEntry)
+    {
+        String activeTransactionKey = TransactionBeanStorage.getStorage().getActiveTransactionKey();
+
+        for(Annotation qualifier : transactionBeanEntry.getQualifiers())
+        {
+            if(qualifier.annotationType().getName().endsWith(activeTransactionKey))
+            {
+                return;
+            }
+        }
+
+        throw new IllegalStateException("Transaction qualifier of the intercepted bean or method and " +
+                "the injected entity-manager has to be the same. Active transaction qualifier: " +
+                activeTransactionKey + " qualifier/s of the entity-manager: " +
+                extractQualifiers(transactionBeanEntry));
+    }
+
+    private String extractQualifiers(TransactionBeanEntry<?> transactionBeanEntry)
+    {
+        StringBuilder result = new StringBuilder();
+        for(Annotation annotation : transactionBeanEntry.getQualifiers())
+        {
+            if(result.length() != 0)
+            {
+                result.append(";");
+            }
+
+            result.append(annotation.annotationType().getName());
+        }
+        return result.toString();
+    }
+
     private Map<Contextual, TransactionBeanEntry> getTransactionBeanEntryMap()
     {
         TransactionBeanStorage transactionBeanStorage = TransactionBeanStorage.getStorage();
diff --git a/jee-modules/jpa-module/impl/src/test/java/org/apache/myfaces/extensions/cdi/jpa/test/TestTransactionHelper.java b/jee-modules/jpa-module/impl/src/test/java/org/apache/myfaces/extensions/cdi/jpa/test/TestTransactionHelper.java
new file mode 100644
index 0000000..3fa9224
--- /dev/null
+++ b/jee-modules/jpa-module/impl/src/test/java/org/apache/myfaces/extensions/cdi/jpa/test/TestTransactionHelper.java
@@ -0,0 +1,46 @@
+/*

+ * 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.myfaces.extensions.cdi.jpa.test;

+

+import org.apache.myfaces.extensions.cdi.core.api.provider.BeanManagerProvider;

+import org.apache.myfaces.extensions.cdi.jpa.api.Transactional;

+

+import javax.enterprise.context.ApplicationScoped;

+import java.util.concurrent.Callable;

+

+/**

+ * see {@link org.apache.myfaces.extensions.cdi.jpa.api.TransactionHelper}

+ */

+@ApplicationScoped

+public class TestTransactionHelper

+{

+    public static TestTransactionHelper getInstance()

+    {

+        return BeanManagerProvider.getInstance().getContextualReference(TestTransactionHelper.class);

+    }

+

+    /**

+     * see {@link org.apache.myfaces.extensions.cdi.jpa.api.TransactionHelper}

+     */

+    @Transactional(qualifier = TransactionScopeAware.class)

+    public <T> T executeTransactional(Callable<T> callable) throws Exception

+    {

+        return callable.call();

+    }

+}

diff --git a/jee-modules/jpa-module/impl/src/test/java/org/apache/myfaces/extensions/cdi/jpa/test/TransactionInterceptorTest.java b/jee-modules/jpa-module/impl/src/test/java/org/apache/myfaces/extensions/cdi/jpa/test/TransactionInterceptorTest.java
index 0bf1e3a..e480c45 100644
--- a/jee-modules/jpa-module/impl/src/test/java/org/apache/myfaces/extensions/cdi/jpa/test/TransactionInterceptorTest.java
+++ b/jee-modules/jpa-module/impl/src/test/java/org/apache/myfaces/extensions/cdi/jpa/test/TransactionInterceptorTest.java
@@ -20,6 +20,7 @@
 
 
 import org.apache.myfaces.extensions.cdi.core.api.provider.BeanManagerProvider;
+import org.apache.myfaces.extensions.cdi.core.impl.util.DefaultLiteral;
 import org.apache.myfaces.extensions.cdi.core.test.util.ContainerTestBase;
 import org.apache.myfaces.extensions.cdi.jpa.api.TransactionHelper;
 import org.apache.myfaces.extensions.cdi.jpa.impl.transaction.context.TransactionBeanStorage;
@@ -91,7 +92,7 @@
             // this was expected, all is fine!
         }
 
-        Integer retVal = TransactionHelper.getInstance().executeTransactional( new Callable<Integer>() {
+        Integer retVal = TestTransactionHelper.getInstance().executeTransactional( new Callable<Integer>() {
 
             public Integer call() throws Exception
             {
@@ -116,6 +117,49 @@
         Assert.assertNull(TransactionBeanStorage.getStorage());
     }
 
+    @Test
+    public void testTransactionHelperWithOtherQualifierThanCurrentTransaction() throws Exception
+    {
+        try
+        {
+            TransactionHelper.getInstance().executeTransactional( new Callable<Integer>() {
+
+                public Integer call() throws Exception
+                {
+                    resolveEntityManager();
+
+                    return Integer.valueOf(3);
+                }
+            });
+        }
+        catch (IllegalStateException e)
+        {
+            //expected: TransactionHelper has different transaction qualifier than the injected/resolved entity-manager
+        }
+
+        Integer retVal = TransactionHelper.getInstance().executeTransactional( new Callable<Integer>() {
+
+            public Integer call() throws Exception
+            {
+                resolveDefaultEntityManager();
+
+                return Integer.valueOf(3);
+            }
+        });
+        Assert.assertEquals(retVal, Integer.valueOf(3));
+
+        Assert.assertNull(TransactionBeanStorage.getStorage());
+    }
+
+    private void resolveDefaultEntityManager()
+    {
+        EntityManager em = BeanManagerProvider.getInstance().
+                getContextualReference(EntityManager.class, new DefaultLiteral());
+        Assert.assertNotNull(em);
+        EntityTransaction et = em.getTransaction();
+        Assert.assertNotNull(et);
+    }
+
     private void resolveEntityManager()
     {
         EntityManager em = BeanManagerProvider.getInstance().