tapestry-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kao...@apache.org
Subject [2/2] tapestry-5 git commit: TAP5-2553: Support pseudo nested JPA transactions etc.
Date Wed, 08 Jun 2016 17:06:10 GMT
TAP5-2553: Support pseudo nested JPA transactions etc.

- add all classes from
https://github.com/kaosko/tapestry-jpa-transactions
- rename packages from net.satago.tapestry5.. to org.apache.tapestry5...
- replace code contents of all advice and worker related classes
(CommitAfterMethodAdvice, CommitAfterWorker, JpaTransactionAdvisorImpl)
with the new implementation
- manually merge relevant code from TransactionUnitsModule to JpaModule
- add new dependencies
- remove remaining net.satago. classes
- I could only make the JpaIntegrationTestWithAnnotationsInServiceImplementation test succeeded by adding the @PersistenceContext
annotations to the app6.UserDAO interface, why is that?
- reformat code according to Tapestry guidelines


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/5047220c
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/5047220c
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/5047220c

Branch: refs/heads/master
Commit: 5047220cfe9a6f8a640fb10f0e3de266af51630f
Parents: f29b64e
Author: kaosko <kaosko@apache.org>
Authored: Mon Jun 6 23:48:46 2016 -0700
Committer: kaosko <kaosko@apache.org>
Committed: Wed Jun 8 09:59:50 2016 -0700

----------------------------------------------------------------------
 tapestry-jpa/build.gradle                       |   6 +-
 .../internal/jpa/CommitAfterMethodAdvice.java   |  78 +----
 .../internal/jpa/CommitAfterWorker.java         |  35 +-
 .../jpa/EntityTransactionManagerImpl.java       | 128 ++++++++
 .../internal/jpa/JpaInternalUtils.java          |  13 +-
 .../internal/jpa/JpaTransactionAdvisorImpl.java |  35 +-
 .../internal/jpa/NoopAnnotatedType.java         |  91 ++++++
 .../tapestry5/internal/jpa/NoopBeanManager.java | 320 +++++++++++++++++++
 .../internal/jpa/NoopCreationalContext.java     |  33 ++
 .../internal/jpa/NoopInjectionTarget.java       |  67 ++++
 ...ContextSpecificEntityTransactionManager.java | 213 ++++++++++++
 ...stryCDIBeanManagerForJPAEntityListeners.java |  89 ++++++
 .../tapestry5/jpa/EntityTransactionManager.java |  47 +++
 .../apache/tapestry5/jpa/modules/JpaModule.java |  65 +++-
 .../jpa/JpaTransactionAdvisorImplTest.java      |  65 ++--
 .../tapestry5/jpa/test/CommitCounter.java       |  56 ++++
 .../org/apache/tapestry5/jpa/test/JpaTest.java  | 215 +++++++++++++
 .../tapestry5/jpa/test/JpaTestModule.java       |  97 ++++++
 .../tapestry5/jpa/test/NestedService.java       |  24 ++
 .../tapestry5/jpa/test/NestedServiceImpl.java   |  36 +++
 .../tapestry5/jpa/test/TopLevelService.java     |  29 ++
 .../tapestry5/jpa/test/TopLevelServiceImpl.java |  92 ++++++
 .../tapestry5/jpa/test/entities/ThingOne.java   |  92 ++++++
 .../tapestry5/jpa/test/entities/ThingTwo.java   |  92 ++++++
 .../jpa/test/entities/VersionedThing.java       |  89 ++++++
 .../java/org/example/app6/services/UserDAO.java |  12 +
 26 files changed, 1988 insertions(+), 131 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/build.gradle
----------------------------------------------------------------------
diff --git a/tapestry-jpa/build.gradle b/tapestry-jpa/build.gradle
index 4fdf9fd..275a084 100644
--- a/tapestry-jpa/build.gradle
+++ b/tapestry-jpa/build.gradle
@@ -3,13 +3,15 @@ description = "Provides support for simple CRUD applications built on top of Tap
 dependencies {
   compile project(':tapestry-core')
   compile "org.apache.geronimo.specs:geronimo-jpa_2.0_spec:1.1"
+  compile 'javax.enterprise:cdi-api:1.2'
+
 
   testCompile project(':tapestry-test')
+  testCompile 'org.eclipse.persistence:eclipselink:2.6.2'
 
   testRuntime "com.h2database:h2:1.2.145"
-  testCompile "org.eclipse.persistence:org.eclipse.persistence.jpa:2.2.0"
   testRuntime "org.apache.tomcat:dbcp:6.0.32"
-
+  testRuntime 'com.h2database:h2:1.3.175'
 }
 
 repositories {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterMethodAdvice.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterMethodAdvice.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterMethodAdvice.java
index a0f58f7..d734444 100644
--- a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterMethodAdvice.java
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterMethodAdvice.java
@@ -14,85 +14,33 @@
 
 package org.apache.tapestry5.internal.jpa;
 
-import javax.persistence.EntityManager;
-import javax.persistence.EntityTransaction;
-import javax.persistence.PersistenceContext;
-
-import org.apache.tapestry5.jpa.EntityManagerManager;
-import org.apache.tapestry5.jpa.annotations.CommitAfter;
+import org.apache.tapestry5.ioc.Invokable;
+import org.apache.tapestry5.jpa.EntityTransactionManager;
 import org.apache.tapestry5.plastic.MethodAdvice;
 import org.apache.tapestry5.plastic.MethodInvocation;
 
 public class CommitAfterMethodAdvice implements MethodAdvice
 {
-    private final EntityManagerManager manager;
+    private EntityTransactionManager manager;
+    private String context;
 
-    public CommitAfterMethodAdvice(final EntityManagerManager manager)
+    public CommitAfterMethodAdvice(EntityTransactionManager manager, String context)
     {
         this.manager = manager;
+        this.context = context;
     }
 
     @Override
     public void advise(final MethodInvocation invocation)
     {
-    	
-    	if (invocation.hasAnnotation(CommitAfter.class))
-    	{
-    	
-			final PersistenceContext annotation = invocation.getAnnotation(PersistenceContext.class);
-	        final EntityTransaction transaction = getTransaction(annotation);
-	
-	        if (transaction != null && !transaction.isActive())
-	        {
-	            transaction.begin();
-	        }
-	
-	        try
-	        {
-	            invocation.proceed();
-	        } catch (final RuntimeException e)
-	        {
-	            if (transaction != null && transaction.isActive())
-	            {
-	                rollbackTransaction(transaction);
-	            }
-	
-	            throw e;
-	        }
-	
-	        // Success or checked exception:
-	
-	        if (transaction != null && transaction.isActive())
-	        {
-	            transaction.commit();
-	        }
-	        
-    	}
-    	else
-    	{
-    		invocation.proceed();
-    	}
-
-    }
-
-    private void rollbackTransaction(EntityTransaction transaction)
-    {
-        try
+        manager.invokeInTransaction(context, new Invokable<MethodInvocation>()
         {
-            transaction.rollback();
-        } catch (Exception e)
-        { // Ignore
-        }
-    }
-
-    private EntityTransaction getTransaction(PersistenceContext annotation)
-    {
-        EntityManager em = JpaInternalUtils.getEntityManager(manager, annotation);
+            @Override
+            public MethodInvocation invoke()
+            {
+                return invocation.proceed();
+            }
+        });
 
-        if (em == null)
-            return null;
-
-        return em.getTransaction();
     }
-
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterWorker.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterWorker.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterWorker.java
index ac84dc3..30046e8 100644
--- a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterWorker.java
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/CommitAfterWorker.java
@@ -14,7 +14,14 @@
 
 package org.apache.tapestry5.internal.jpa;
 
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
 import org.apache.tapestry5.jpa.EntityManagerManager;
+import org.apache.tapestry5.jpa.EntityTransactionManager;
 import org.apache.tapestry5.jpa.annotations.CommitAfter;
 import org.apache.tapestry5.model.MutableComponentModel;
 import org.apache.tapestry5.plastic.MethodAdvice;
@@ -23,33 +30,29 @@ import org.apache.tapestry5.plastic.PlasticMethod;
 import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
 import org.apache.tapestry5.services.transform.TransformationSupport;
 
-import javax.persistence.PersistenceContext;
-
 public class CommitAfterWorker implements ComponentClassTransformWorker2
 {
+    private final Map<String, MethodAdvice> methodAdvices;
 
-    private final MethodAdvice shared;
-
-    private final EntityManagerManager manager;
-
-    public CommitAfterWorker(EntityManagerManager manager)
+    public CommitAfterWorker(EntityManagerManager manager,
+            EntityTransactionManager transactionManager)
     {
-        this.manager = manager;
-
-        shared = new CommitAfterMethodAdvice(manager);
+        methodAdvices = new HashMap<>(manager.getEntityManagers().size());
+        for (Map.Entry<String, EntityManager> entry : manager.getEntityManagers().entrySet())
+            methodAdvices.put(entry.getKey(),
+                    new CommitAfterMethodAdvice(transactionManager, entry.getKey()));
+        methodAdvices.put(null, new CommitAfterMethodAdvice(transactionManager, null));
     }
 
     @Override
-    public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
+    public void transform(PlasticClass plasticClass, TransformationSupport support,
+            MutableComponentModel model)
     {
-        for (final PlasticMethod method : plasticClass
-                .getMethodsWithAnnotation(CommitAfter.class))
+        for (final PlasticMethod method : plasticClass.getMethodsWithAnnotation(CommitAfter.class))
         {
             PersistenceContext annotation = method.getAnnotation(PersistenceContext.class);
 
-            MethodAdvice advice = annotation == null ? shared : new CommitAfterMethodAdvice(manager);
-
-            method.addAdvice(advice);
+            method.addAdvice(methodAdvices.get(annotation == null ? null : annotation.unitName()));
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/EntityTransactionManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/EntityTransactionManagerImpl.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/EntityTransactionManagerImpl.java
new file mode 100644
index 0000000..c0c639d
--- /dev/null
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/EntityTransactionManagerImpl.java
@@ -0,0 +1,128 @@
+/**
+ * Licensed 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.tapestry5.internal.jpa;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+
+import org.apache.tapestry5.ioc.Invokable;
+import org.apache.tapestry5.ioc.ScopeConstants;
+import org.apache.tapestry5.ioc.annotations.Scope;
+import org.apache.tapestry5.jpa.EntityManagerManager;
+import org.apache.tapestry5.jpa.EntityTransactionManager;
+import org.slf4j.Logger;
+
+@Scope(ScopeConstants.PERTHREAD)
+public class EntityTransactionManagerImpl implements EntityTransactionManager
+{
+
+    private final Logger logger;
+    private final EntityManagerManager entityManagerManager;
+
+    private final Map<String, PersistenceContextSpecificEntityTransactionManager> transactionManagerMap;
+
+    public EntityTransactionManagerImpl(Logger logger, EntityManagerManager entityManagerManager)
+    {
+        this.logger = logger;
+        this.entityManagerManager = entityManagerManager;
+        transactionManagerMap = new HashMap<>(entityManagerManager.getEntityManagers().size());
+    }
+
+    private EntityManager getEntityManager(String unitName)
+    {
+        // EntityManager em = JpaInternalUtils.getEntityManager(entityManagerManager, unitName);
+        // FIXME we should simply incorporate the logic in JpaInternalUtils.getEntityManager to
+        // EntityManagerManager.getEntityManager(unitName)
+        if (unitName != null)
+            return entityManagerManager.getEntityManager(unitName);
+        else
+        {
+            Map<String, EntityManager> entityManagers = entityManagerManager.getEntityManagers();
+            if (entityManagers.size() == 1)
+                return entityManagers.values().iterator().next();
+            else
+                throw new RuntimeException(
+                        "Unable to locate a single EntityManager. "
+                                + "You must provide the persistence unit name as defined in the persistence.xml using the @PersistenceContext annotation.");
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see net.satago.tapestry5.jpa.EntityTransactionManager#runInTransaction(java.lang.String,
+     * java.lang.Runnable)
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void runInTransaction(final String unitName, final Runnable runnable)
+    {
+        getPersistenceContextSpecificEntityTransactionManager(unitName).invokeInTransaction(
+                new VoidInvokable(runnable));
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see net.satago.tapestry5.jpa.EntityTransactionManager#invokeInTransaction(java.lang.String,
+     * org.apache.tapestry5.ioc.Invokable)
+     */
+    @Override
+    public <T> T invokeInTransaction(String unitName, Invokable<T> invokable)
+    {
+        return getPersistenceContextSpecificEntityTransactionManager(unitName).invokeInTransaction(
+                invokable);
+    }
+
+    private PersistenceContextSpecificEntityTransactionManager getPersistenceContextSpecificEntityTransactionManager(
+            String unitName)
+    {
+        if (!transactionManagerMap.containsKey(unitName))
+        {
+            PersistenceContextSpecificEntityTransactionManager transactionManager = new PersistenceContextSpecificEntityTransactionManager(
+                    logger, getEntityManager(unitName));
+            transactionManagerMap.put(unitName, transactionManager);
+            return transactionManager;
+        }
+        else
+            return transactionManagerMap.get(unitName);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see
+     * net.satago.tapestry5.jpa.EntityTransactionManager#invokeBeforeCommit(org.apache.tapestry5
+     * .ioc.Invokable, java.lang.String)
+     */
+    @Override
+    public void invokeBeforeCommit(String unitName, Invokable<Boolean> invokable)
+    {
+        getPersistenceContextSpecificEntityTransactionManager(unitName).addBeforeCommitInvokable(
+                invokable);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see
+     * net.satago.tapestry5.jpa.EntityTransactionManager#invokeAfterCommit(org.apache.tapestry5.
+     * ioc.Invokable, java.lang.String)
+     */
+    @Override
+    public void invokeAfterCommit(String unitName, Invokable<Boolean> invokable)
+    {
+        getPersistenceContextSpecificEntityTransactionManager(unitName).addAfterCommitInvokable(
+                invokable);
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaInternalUtils.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaInternalUtils.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaInternalUtils.java
index adec0a2..d612e74 100644
--- a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaInternalUtils.java
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaInternalUtils.java
@@ -14,17 +14,18 @@
 
 package org.apache.tapestry5.internal.jpa;
 
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.jpa.EntityManagerManager;
-import org.apache.tapestry5.jpa.JpaConstants;
+import java.util.Map;
+import java.util.Set;
 
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
 import javax.persistence.PersistenceContext;
 import javax.persistence.metamodel.EntityType;
 import javax.persistence.metamodel.Metamodel;
-import java.util.Map;
-import java.util.Set;
+
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.jpa.EntityManagerManager;
+import org.apache.tapestry5.jpa.JpaConstants;
 
 public class JpaInternalUtils
 {
@@ -60,7 +61,7 @@ public class JpaInternalUtils
 
             for (final EntityType<?> entityType : entities)
             {
-                if (entityType.getJavaType() == entity.getClass())
+                if (entityType.getJavaType().equals(entity.getClass()))
                 {
                     if (em.contains(entity))
                     {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImpl.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImpl.java
index a74b93b..1803722 100644
--- a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImpl.java
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImpl.java
@@ -14,23 +14,48 @@
 
 package org.apache.tapestry5.internal.jpa;
 
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
 import org.apache.tapestry5.ioc.MethodAdviceReceiver;
 import org.apache.tapestry5.jpa.EntityManagerManager;
+import org.apache.tapestry5.jpa.EntityTransactionManager;
 import org.apache.tapestry5.jpa.JpaTransactionAdvisor;
+import org.apache.tapestry5.jpa.annotations.CommitAfter;
+import org.apache.tapestry5.plastic.MethodAdvice;
 
 public class JpaTransactionAdvisorImpl implements JpaTransactionAdvisor
 {
-    private final EntityManagerManager manager;
+    private final Map<String, MethodAdvice> methodAdvices;
 
-    public JpaTransactionAdvisorImpl(EntityManagerManager manager)
+    public JpaTransactionAdvisorImpl(EntityManagerManager manager,
+            EntityTransactionManager transactionManager)
     {
-        this.manager = manager;
+        methodAdvices = new HashMap<>(manager.getEntityManagers().size());
+        for (Map.Entry<String, EntityManager> entry : manager.getEntityManagers().entrySet())
+            methodAdvices.put(entry.getKey(),
+                    new CommitAfterMethodAdvice(transactionManager, entry.getKey()));
+        methodAdvices.put(null, new CommitAfterMethodAdvice(transactionManager, null));
     }
 
     @Override
-    public void addTransactionCommitAdvice(final MethodAdviceReceiver receiver)
+    public void addTransactionCommitAdvice(MethodAdviceReceiver receiver)
     {
-    	receiver.adviseAllMethods(new CommitAfterMethodAdvice(manager));
+        for (final Method m : receiver.getInterface().getMethods())
+        {
+            if (m.getAnnotation(CommitAfter.class) != null)
+            {
+                PersistenceContext annotation = receiver.getMethodAnnotation(m,
+                        PersistenceContext.class);
+
+                receiver.adviseMethod(m,
+                        methodAdvices.get(annotation == null ? null : annotation.unitName()));
+            }
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopAnnotatedType.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopAnnotatedType.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopAnnotatedType.java
new file mode 100644
index 0000000..2d7e028
--- /dev/null
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopAnnotatedType.java
@@ -0,0 +1,91 @@
+/**
+ * Licensed 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.tapestry5.internal.jpa;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Set;
+
+import javax.enterprise.inject.spi.AnnotatedConstructor;
+import javax.enterprise.inject.spi.AnnotatedField;
+import javax.enterprise.inject.spi.AnnotatedMethod;
+import javax.enterprise.inject.spi.AnnotatedType;
+
+public class NoopAnnotatedType<X> implements AnnotatedType<X>
+{
+
+    @Override
+    public Type getBaseType()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Set<Type> getTypeClosure()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T extends Annotation> T getAnnotation(Class<T> annotationType)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Set<Annotation> getAnnotations()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean isAnnotationPresent(Class<? extends Annotation> annotationType)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public Class<X> getJavaClass()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Set<AnnotatedConstructor<X>> getConstructors()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Set<AnnotatedMethod<? super X>> getMethods()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Set<AnnotatedField<? super X>> getFields()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopBeanManager.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopBeanManager.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopBeanManager.java
new file mode 100644
index 0000000..0ad7743
--- /dev/null
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopBeanManager.java
@@ -0,0 +1,320 @@
+/**
+ * Licensed 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.tapestry5.internal.jpa;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+import javax.el.ELResolver;
+import javax.el.ExpressionFactory;
+import javax.enterprise.context.spi.Context;
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.AnnotatedField;
+import javax.enterprise.inject.spi.AnnotatedMember;
+import javax.enterprise.inject.spi.AnnotatedMethod;
+import javax.enterprise.inject.spi.AnnotatedParameter;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanAttributes;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.Decorator;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.inject.spi.InjectionTargetFactory;
+import javax.enterprise.inject.spi.InterceptionType;
+import javax.enterprise.inject.spi.Interceptor;
+import javax.enterprise.inject.spi.ObserverMethod;
+import javax.enterprise.inject.spi.ProducerFactory;
+
+public class NoopBeanManager implements BeanManager
+{
+
+    @Override
+    public Object getReference(Bean<?> bean, Type beanType, CreationalContext<?> ctx)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Object getInjectableReference(InjectionPoint ij, CreationalContext<?> ctx)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T> CreationalContext<T> createCreationalContext(Contextual<T> contextual)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Set<Bean<?>> getBeans(Type beanType, Annotation... qualifiers)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Set<Bean<?>> getBeans(String name)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Bean<?> getPassivationCapableBean(String id)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <X> Bean<? extends X> resolve(Set<Bean<? extends X>> beans)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void validate(InjectionPoint injectionPoint)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void fireEvent(Object event, Annotation... qualifiers)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public <T> Set<ObserverMethod<? super T>> resolveObserverMethods(T event, Annotation... qualifiers)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public List<Decorator<?>> resolveDecorators(Set<Type> types, Annotation... qualifiers)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public List<Interceptor<?>> resolveInterceptors(InterceptionType type, Annotation... interceptorBindings)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean isScope(Class<? extends Annotation> annotationType)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public boolean isNormalScope(Class<? extends Annotation> annotationType)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public boolean isPassivatingScope(Class<? extends Annotation> annotationType)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public boolean isQualifier(Class<? extends Annotation> annotationType)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public boolean isInterceptorBinding(Class<? extends Annotation> annotationType)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public boolean isStereotype(Class<? extends Annotation> annotationType)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public Set<Annotation> getInterceptorBindingDefinition(Class<? extends Annotation> bindingType)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Set<Annotation> getStereotypeDefinition(Class<? extends Annotation> stereotype)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean areQualifiersEquivalent(Annotation qualifier1, Annotation qualifier2)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public boolean areInterceptorBindingsEquivalent(Annotation interceptorBinding1, Annotation interceptorBinding2)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public int getQualifierHashCode(Annotation qualifier)
+    {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public int getInterceptorBindingHashCode(Annotation interceptorBinding)
+    {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public Context getContext(Class<? extends Annotation> scopeType)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public ELResolver getELResolver()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public ExpressionFactory wrapExpressionFactory(ExpressionFactory expressionFactory)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T> AnnotatedType<T> createAnnotatedType(Class<T> type)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T> InjectionTarget<T> createInjectionTarget(AnnotatedType<T> type)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T> InjectionTargetFactory<T> getInjectionTargetFactory(AnnotatedType<T> annotatedType)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <X> ProducerFactory<X> getProducerFactory(AnnotatedField<? super X> field, Bean<X> declaringBean)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <X> ProducerFactory<X> getProducerFactory(AnnotatedMethod<? super X> method, Bean<X> declaringBean)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T> BeanAttributes<T> createBeanAttributes(AnnotatedType<T> type)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public BeanAttributes<?> createBeanAttributes(AnnotatedMember<?> type)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T> Bean<T> createBean(BeanAttributes<T> attributes, Class<T> beanClass, InjectionTargetFactory<T> injectionTargetFactory)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T, X> Bean<T> createBean(BeanAttributes<T> attributes, Class<X> beanClass, ProducerFactory<X> producerFactory)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public InjectionPoint createInjectionPoint(AnnotatedField<?> field)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public InjectionPoint createInjectionPoint(AnnotatedParameter<?> parameter)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public <T extends Extension> T getExtension(Class<T> extensionClass)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopCreationalContext.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopCreationalContext.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopCreationalContext.java
new file mode 100644
index 0000000..154df2f
--- /dev/null
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopCreationalContext.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed 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.tapestry5.internal.jpa;
+
+import javax.enterprise.context.spi.CreationalContext;
+
+public class NoopCreationalContext<T> implements CreationalContext<T>
+{
+    @Override
+    public void push(T incompleteInstance)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void release()
+    {
+        // TODO Auto-generated method stub
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopInjectionTarget.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopInjectionTarget.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopInjectionTarget.java
new file mode 100644
index 0000000..d30c1c8
--- /dev/null
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/NoopInjectionTarget.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed 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.tapestry5.internal.jpa;
+
+import java.util.Set;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+
+public class NoopInjectionTarget<T> implements InjectionTarget<T>
+{
+
+    @Override
+    public T produce(CreationalContext<T> ctx)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void dispose(T instance)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public Set<InjectionPoint> getInjectionPoints()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void inject(T instance, CreationalContext<T> ctx)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void postConstruct(T instance)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void preDestroy(T instance)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/PersistenceContextSpecificEntityTransactionManager.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/PersistenceContextSpecificEntityTransactionManager.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/PersistenceContextSpecificEntityTransactionManager.java
new file mode 100644
index 0000000..ed53f2b
--- /dev/null
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/PersistenceContextSpecificEntityTransactionManager.java
@@ -0,0 +1,213 @@
+/**
+ * Licensed 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.tapestry5.internal.jpa;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+
+import org.apache.tapestry5.ioc.Invokable;
+import org.apache.tapestry5.jpa.EntityTransactionManager.VoidInvokable;
+import org.slf4j.Logger;
+
+public class PersistenceContextSpecificEntityTransactionManager
+{
+
+    private final Logger logger;
+    private final EntityManager entityManager;
+
+    private boolean transactionBeingCommitted;
+
+    private Deque<Invokable<?>> invokableUnitsForSequentialTransactions = new ArrayDeque<Invokable<?>>();
+    private Deque<Invokable<?>> invokableUnits = new ArrayDeque<Invokable<?>>();
+
+    private List<Invokable<Boolean>> beforeCommitInvokables = new ArrayList<Invokable<Boolean>>();
+    private List<Invokable<Boolean>> afterCommitInvokables = new ArrayList<Invokable<Boolean>>();
+
+    public PersistenceContextSpecificEntityTransactionManager(Logger logger,
+            EntityManager entityManager)
+    {
+        this.logger = logger;
+        this.entityManager = entityManager;
+    }
+
+    private EntityTransaction getTransaction()
+    {
+        EntityTransaction transaction = entityManager.getTransaction();
+        if (!transaction.isActive())
+            transaction.begin();
+        return transaction;
+    }
+
+    public void addBeforeCommitInvokable(Invokable<Boolean> invokable)
+    {
+        beforeCommitInvokables.add(invokable);
+    }
+
+    public void addAfterCommitInvokable(Invokable<Boolean> invokable)
+    {
+        afterCommitInvokables.add(invokable);
+    }
+
+    public <T> T invokeInTransaction(Invokable<T> invokable)
+    {
+        if (transactionBeingCommitted)
+        {
+            // happens for example if you try to run a transaction in @PostCommit hook. We can only
+            // allow VoidInvokables
+            // to be executed later
+            if (invokable instanceof VoidInvokable)
+            {
+                invokableUnitsForSequentialTransactions.push(invokable);
+                return null;
+            }
+            else
+            {
+                rollbackTransaction(getTransaction());
+                throw new RuntimeException(
+                        "Current transaction is already being committed. Transactions started @PostCommit are not allowed to return a value");
+            }
+        }
+
+        final boolean topLevel = invokableUnits.isEmpty();
+        invokableUnits.push(invokable);
+        if (!topLevel)
+        {
+            if (logger.isWarnEnabled())
+            {
+                logger.warn("Nested transaction detected, current depth = " + invokableUnits.size());
+            }
+        }
+
+        final EntityTransaction transaction = getTransaction();
+        try
+        {
+            T result = invokable.invoke();
+
+            if (topLevel && invokableUnits.peek().equals(invokable))
+            {
+                // Success or checked exception:
+
+                if (transaction.isActive())
+                {
+                    invokeBeforeCommit(transaction);
+                }
+
+                // FIXME check if we are still on top
+
+                if (transaction.isActive())
+                {
+                    transactionBeingCommitted = true;
+                    transaction.commit();
+                    transactionBeingCommitted = false;
+                    invokableUnits.clear();
+                    invokeAfterCommit();
+                    if (invokableUnitsForSequentialTransactions.size() > 0)
+                        invokeInTransaction(invokableUnitsForSequentialTransactions.pop());
+                }
+            }
+
+            return result;
+        }
+        catch (final RuntimeException e)
+        {
+            if (transaction != null && transaction.isActive())
+            {
+                rollbackTransaction(transaction);
+            }
+
+            throw e;
+        }
+        finally
+        {
+            invokableUnits.remove(invokable);
+        }
+    }
+
+    private void invokeBeforeCommit(final EntityTransaction transaction)
+    {
+        for (Iterator<Invokable<Boolean>> i = beforeCommitInvokables.iterator(); i.hasNext();)
+        {
+            Invokable<Boolean> invokable = i.next();
+            i.remove();
+            Boolean beforeCommitSucceeded = tryInvoke(transaction, invokable);
+
+            // Success or checked exception:
+            if (beforeCommitSucceeded != null && !beforeCommitSucceeded.booleanValue())
+            {
+                rollbackTransaction(transaction);
+
+                // Don't invoke further callbacks
+                break;
+            }
+        }
+    }
+
+    private void invokeAfterCommit()
+    {
+
+        for (Iterator<Invokable<Boolean>> i = afterCommitInvokables.iterator(); i.hasNext();)
+        {
+            Invokable<Boolean> invokable = i.next();
+            i.remove();
+            Boolean afterCommitSucceeded = invokable.invoke();
+
+            // Success or checked exception:
+            if (afterCommitSucceeded != null && !afterCommitSucceeded.booleanValue())
+            {
+                if (invokableUnitsForSequentialTransactions.size() > 0) { throw new RuntimeException(
+                        "After commit hook returned false but there are still uncommitted Invokables scheduled for the next transaction"); }
+                return;
+            }
+        }
+    }
+
+    private static <T> T tryInvoke(final EntityTransaction transaction, Invokable<T> invokable)
+            throws RuntimeException
+    {
+        T result;
+
+        try
+        {
+            result = invokable.invoke();
+        }
+        catch (final RuntimeException e)
+        {
+            if (transaction != null && transaction.isActive())
+            {
+                rollbackTransaction(transaction);
+            }
+
+            throw e;
+        }
+
+        return result;
+    }
+
+    private static void rollbackTransaction(EntityTransaction transaction)
+    {
+        try
+        {
+            transaction.rollback();
+        }
+        catch (Exception e)
+        { // Ignore
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/TapestryCDIBeanManagerForJPAEntityListeners.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/TapestryCDIBeanManagerForJPAEntityListeners.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/TapestryCDIBeanManagerForJPAEntityListeners.java
new file mode 100644
index 0000000..af1f21d
--- /dev/null
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/internal/jpa/TapestryCDIBeanManagerForJPAEntityListeners.java
@@ -0,0 +1,89 @@
+/**
+ * Licensed 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.tapestry5.internal.jpa;
+
+import java.lang.reflect.Method;
+
+import javax.annotation.PreDestroy;
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.InjectionTarget;
+
+import org.apache.tapestry5.ioc.ObjectLocator;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TapestryCDIBeanManagerForJPAEntityListeners extends NoopBeanManager
+{
+    private static final Logger logger = LoggerFactory
+            .getLogger(TapestryCDIBeanManagerForJPAEntityListeners.class);
+
+    @Inject
+    private ObjectLocator objectLocator;
+
+    @Override
+    public <T> AnnotatedType<T> createAnnotatedType(final Class<T> type)
+    {
+        return new NoopAnnotatedType<T>()
+        {
+            @Override
+            public Class<T> getJavaClass()
+            {
+                return type;
+            }
+        };
+    }
+
+    @Override
+    public <T> InjectionTarget<T> createInjectionTarget(final AnnotatedType<T> type)
+    {
+        return new NoopInjectionTarget<T>()
+        {
+            @Override
+            public T produce(CreationalContext<T> ctx)
+            {
+                return objectLocator.autobuild(type.getJavaClass());
+            }
+
+            @Override
+            public void preDestroy(T instance)
+            {
+                try
+                {
+                    for (Method method : type.getJavaClass().getMethods())
+                    {
+                        if (method.getAnnotation(PreDestroy.class) != null)
+                        {
+                            method.invoke(instance);
+                        }
+                    }
+                }
+                catch (Exception e)
+                {
+                    logger.error(
+                            "Error invoking @PreDestroy callback on instance of class "
+                                    + type.getJavaClass(), e);
+                }
+            }
+        };
+    }
+
+    @Override
+    public <T> CreationalContext<T> createCreationalContext(Contextual<T> contextual)
+    {
+        return new NoopCreationalContext<T>();
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/EntityTransactionManager.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/EntityTransactionManager.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/EntityTransactionManager.java
new file mode 100644
index 0000000..e8b2ae1
--- /dev/null
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/EntityTransactionManager.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed 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.tapestry5.jpa;
+
+import org.apache.tapestry5.ioc.Invokable;
+
+public interface EntityTransactionManager
+{
+
+    void runInTransaction(String unitName, Runnable runnable);
+
+    <T> T invokeInTransaction(String unitName, Invokable<T> invokable);
+
+    void invokeBeforeCommit(String unitName, Invokable<Boolean> invokable);
+
+    void invokeAfterCommit(String unitName, Invokable<Boolean> invokable);
+
+    @SuppressWarnings("rawtypes")
+    public static class VoidInvokable implements Invokable
+    {
+        private final Runnable runnable;
+
+        public VoidInvokable(Runnable runnable)
+        {
+            this.runnable = runnable;
+        }
+
+        @Override
+        public Object invoke()
+        {
+            runnable.run();
+            return null;
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/modules/JpaModule.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/modules/JpaModule.java b/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/modules/JpaModule.java
index a9c824a..723b38a 100644
--- a/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/modules/JpaModule.java
+++ b/tapestry-jpa/src/main/java/org/apache/tapestry5/jpa/modules/JpaModule.java
@@ -14,23 +14,62 @@
 
 package org.apache.tapestry5.jpa.modules;
 
-import org.apache.tapestry5.ValueEncoder;
-import org.apache.tapestry5.internal.InternalConstants;
-import org.apache.tapestry5.internal.jpa.*;
-import org.apache.tapestry5.internal.services.PersistentFieldManager;
-import org.apache.tapestry5.ioc.*;
-import org.apache.tapestry5.ioc.annotations.*;
-import org.apache.tapestry5.ioc.services.*;
-import org.apache.tapestry5.jpa.*;
-import org.apache.tapestry5.services.*;
-import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
-import org.slf4j.Logger;
+import java.util.Collection;
 
 import javax.persistence.EntityManagerFactory;
 import javax.persistence.metamodel.EntityType;
 import javax.persistence.metamodel.Metamodel;
 import javax.persistence.spi.PersistenceUnitInfo;
-import java.util.Collection;
+
+import org.apache.tapestry5.ValueEncoder;
+import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.internal.jpa.CommitAfterWorker;
+import org.apache.tapestry5.internal.jpa.EntityApplicationStatePersistenceStrategy;
+import org.apache.tapestry5.internal.jpa.EntityManagerManagerImpl;
+import org.apache.tapestry5.internal.jpa.EntityManagerObjectProvider;
+import org.apache.tapestry5.internal.jpa.EntityManagerSourceImpl;
+import org.apache.tapestry5.internal.jpa.EntityPersistentFieldStrategy;
+import org.apache.tapestry5.internal.jpa.EntityTransactionManagerImpl;
+import org.apache.tapestry5.internal.jpa.JpaTransactionAdvisorImpl;
+import org.apache.tapestry5.internal.jpa.JpaValueEncoder;
+import org.apache.tapestry5.internal.jpa.PackageNamePersistenceUnitConfigurer;
+import org.apache.tapestry5.internal.jpa.PersistenceContextWorker;
+import org.apache.tapestry5.internal.services.PersistentFieldManager;
+import org.apache.tapestry5.ioc.Configuration;
+import org.apache.tapestry5.ioc.LoggerSource;
+import org.apache.tapestry5.ioc.MappedConfiguration;
+import org.apache.tapestry5.ioc.ObjectProvider;
+import org.apache.tapestry5.ioc.OrderedConfiguration;
+import org.apache.tapestry5.ioc.ScopeConstants;
+import org.apache.tapestry5.ioc.ServiceBinder;
+import org.apache.tapestry5.ioc.annotations.Contribute;
+import org.apache.tapestry5.ioc.annotations.Primary;
+import org.apache.tapestry5.ioc.annotations.Scope;
+import org.apache.tapestry5.ioc.annotations.Startup;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.services.FactoryDefaults;
+import org.apache.tapestry5.ioc.services.MasterObjectProvider;
+import org.apache.tapestry5.ioc.services.PerthreadManager;
+import org.apache.tapestry5.ioc.services.PropertyAccess;
+import org.apache.tapestry5.ioc.services.SymbolProvider;
+import org.apache.tapestry5.ioc.services.TypeCoercer;
+import org.apache.tapestry5.jpa.EntityManagerManager;
+import org.apache.tapestry5.jpa.EntityManagerSource;
+import org.apache.tapestry5.jpa.EntityTransactionManager;
+import org.apache.tapestry5.jpa.JpaEntityPackageManager;
+import org.apache.tapestry5.jpa.JpaPersistenceConstants;
+import org.apache.tapestry5.jpa.JpaSymbols;
+import org.apache.tapestry5.jpa.JpaTransactionAdvisor;
+import org.apache.tapestry5.jpa.PersistenceUnitConfigurer;
+import org.apache.tapestry5.services.ApplicationStateContribution;
+import org.apache.tapestry5.services.ApplicationStateManager;
+import org.apache.tapestry5.services.ApplicationStatePersistenceStrategy;
+import org.apache.tapestry5.services.ApplicationStatePersistenceStrategySource;
+import org.apache.tapestry5.services.PersistentFieldStrategy;
+import org.apache.tapestry5.services.ValueEncoderFactory;
+import org.apache.tapestry5.services.ValueEncoderSource;
+import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
+import org.slf4j.Logger;
 
 /**
  * Defines core services for JPA support.
@@ -44,6 +83,8 @@ public class JpaModule
         binder.bind(JpaTransactionAdvisor.class, JpaTransactionAdvisorImpl.class);
         binder.bind(PersistenceUnitConfigurer.class, PackageNamePersistenceUnitConfigurer.class).withSimpleId();
         binder.bind(EntityManagerSource.class, EntityManagerSourceImpl.class);
+        binder.bind(EntityTransactionManager.class, EntityTransactionManagerImpl.class);
+
     }
 
     public static JpaEntityPackageManager buildJpaEntityPackageManager(final Collection<String> packageNames)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java
index 78173c8..6d3eb40 100644
--- a/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java
+++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java
@@ -14,6 +14,13 @@
 
 package org.apache.tapestry5.internal.jpa;
 
+import java.sql.SQLException;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+import javax.persistence.PersistenceContext;
+
 import org.apache.tapestry5.ioc.IOCUtilities;
 import org.apache.tapestry5.ioc.Registry;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
@@ -22,6 +29,7 @@ import org.apache.tapestry5.ioc.services.AspectInterceptorBuilder;
 import org.apache.tapestry5.ioc.test.IOCTestCase;
 import org.apache.tapestry5.ioc.test.TestBase;
 import org.apache.tapestry5.jpa.EntityManagerManager;
+import org.apache.tapestry5.jpa.EntityTransactionManager;
 import org.apache.tapestry5.jpa.JpaTransactionAdvisor;
 import org.apache.tapestry5.jpa.annotations.CommitAfter;
 import org.testng.Assert;
@@ -29,12 +37,6 @@ import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-import javax.persistence.EntityManager;
-import javax.persistence.EntityTransaction;
-import javax.persistence.PersistenceContext;
-import java.sql.SQLException;
-import java.util.Map;
-
 public class JpaTransactionAdvisorImplTest extends IOCTestCase
 {
     private static final String UNIT_NAME = "FooUnit";
@@ -65,7 +67,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final VoidService delegate = newMock(VoidService.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
 
         final AspectInterceptorBuilder<VoidService> builder = aspectDecorator.createBuilder(
                 VoidService.class, delegate, "foo.Bar");
@@ -86,7 +89,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final VoidService delegate = newMock(VoidService.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         Map<String, EntityManager> managers = CollectionFactory.newMap();
         managers.put("A", newMock(EntityManager.class));
         managers.put("B", newMock(EntityManager.class));
@@ -119,7 +123,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final VoidService delegate = newMock(VoidService.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
         EntityManager em = newMock(EntityManager.class);
         Map<String, EntityManager> managers = CollectionFactory.newMap();
@@ -147,7 +152,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final VoidService delegate = newMock(VoidService.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         Map<String, EntityManager> managers = CollectionFactory.newMap();
         managers.put("A", newMock(EntityManager.class));
         managers.put("B", newMock(EntityManager.class));
@@ -180,7 +186,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final VoidService delegate = newMock(VoidService.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
         EntityManager em = newMock(EntityManager.class);
         Map<String, EntityManager> managers = CollectionFactory.newMap();
@@ -208,7 +215,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final VoidService delegate = newMock(VoidService.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityManager entityManager = newMock(EntityManager.class);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
 
@@ -235,7 +243,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final VoidService delegate = newMock(VoidService.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityManager entityManager = newMock(EntityManager.class);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
 
@@ -262,7 +271,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final VoidService delegate = newMock(VoidService.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityManager entityManager = newMock(EntityManager.class);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
 
@@ -287,7 +297,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final Performer delegate = newMock(Performer.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityManager entityManager = newMock(EntityManager.class);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
         final RuntimeException re = new RuntimeException("Unexpected.");
@@ -322,7 +333,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final Performer delegate = newMock(Performer.class);
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityManager entityManager = newMock(EntityManager.class);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
         final SQLException se = new SQLException("Checked.");
@@ -362,7 +374,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final ReturnTypeService delegate = newTestService();
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityManager entityManager = newMock(EntityManager.class);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
 
@@ -387,7 +400,8 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
     {
         final ReturnTypeService delegate = newTestService();
         final EntityManagerManager manager = newMock(EntityManagerManager.class);
-        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager);
+        final EntityTransactionManager transactionManager = newMock(EntityTransactionManager.class);
+        final JpaTransactionAdvisor advisor = newJpaTransactionAdvisor(manager, transactionManager);
         final EntityManager entityManager = newMock(EntityManager.class);
         final EntityTransaction transaction = newMock(EntityTransaction.class);
 
@@ -448,9 +462,10 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
         transaction.rollback();
     }
 
-    private JpaTransactionAdvisor newJpaTransactionAdvisor(final EntityManagerManager manager)
+    private JpaTransactionAdvisor newJpaTransactionAdvisor(final EntityManagerManager manager,
+            EntityTransactionManager transactionManager)
     {
-        return new JpaTransactionAdvisorImpl(manager);
+        return new JpaTransactionAdvisorImpl(manager, transactionManager);
     }
 
     private ReturnTypeService newTestService()
@@ -518,20 +533,20 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase
         @PersistenceContext(unitName = UNIT_NAME)
         void perform() throws SQLException;
     }
-    
+
     public interface Service
     {
     	void perform();
     }
-    
+
     public class ServiceImpl implements Service {
     	@Override
     	@CommitAfter
     	@PersistenceContext(unitName = UNIT_NAME)
-    	public void perform() 
+        public void perform()
     	{
-    		
+
     	}
     }
-    
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/CommitCounter.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/CommitCounter.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/CommitCounter.java
new file mode 100644
index 0000000..caeb28f
--- /dev/null
+++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/CommitCounter.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed 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.tapestry5.jpa.test;
+
+import java.util.Date;
+
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.PostPersist;
+import javax.persistence.PostUpdate;
+
+import org.apache.tapestry5.jpa.EntityTransactionManager;
+import org.apache.tapestry5.jpa.test.entities.VersionedThing;
+
+public class CommitCounter
+{
+
+    @Inject
+    private EntityManager entityManager;
+
+    @Inject
+    private EntityTransactionManager transactionManager;
+
+    @PostPersist
+    @PostUpdate
+    private void updateVersion(Object entity)
+    {
+        transactionManager.runInTransaction(null, new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                VersionedThing versionedThing = entityManager.find(VersionedThing.class, 1);
+                if (versionedThing == null)
+                    versionedThing = new VersionedThing();
+                versionedThing.setId(1);
+                versionedThing.setLastTouched(new Date());
+
+                entityManager.merge(versionedThing);
+            }
+        });
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTest.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTest.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTest.java
new file mode 100644
index 0000000..cbdf019
--- /dev/null
+++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTest.java
@@ -0,0 +1,215 @@
+/**
+ * Licensed 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.tapestry5.jpa.test;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertTrue;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+import javax.persistence.RollbackException;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+
+import org.apache.tapestry5.internal.jpa.JpaInternalUtils;
+import org.apache.tapestry5.internal.jpa.PersistedEntity;
+import org.apache.tapestry5.internal.test.PageTesterContext;
+import org.apache.tapestry5.ioc.Registry;
+import org.apache.tapestry5.ioc.RegistryBuilder;
+import org.apache.tapestry5.jpa.EntityManagerManager;
+import org.apache.tapestry5.jpa.modules.JpaModule;
+import org.apache.tapestry5.jpa.test.entities.ThingOne;
+import org.apache.tapestry5.jpa.test.entities.ThingTwo;
+import org.apache.tapestry5.jpa.test.entities.VersionedThing;
+import org.apache.tapestry5.modules.TapestryModule;
+import org.apache.tapestry5.services.ApplicationGlobals;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class JpaTest
+{
+
+    private static Registry registry;
+    private EntityManagerManager entityManagerManager;
+    private TopLevelService topLevelService;
+
+    // @BeforeSuite
+    public final void setupRegistry()
+    {
+        RegistryBuilder builder = new RegistryBuilder();
+        builder.add(TapestryModule.class);
+        builder.add(JpaModule.class);
+        builder.add(JpaTestModule.class);
+
+        registry = builder.build();
+        // set PageTesterContext, otherwise T5 tries to load classpath assets
+        ApplicationGlobals globals = registry.getObject(ApplicationGlobals.class, null);
+        globals.storeContext(new PageTesterContext(""));
+        registry.performRegistryStartup();
+
+        entityManagerManager = registry.getService(EntityManagerManager.class);
+        topLevelService = registry.getService(TopLevelService.class);
+
+    }
+
+    private EntityManager getEntityManager()
+    {
+        return entityManagerManager.getEntityManagers().values().iterator().next();
+    }
+
+    // @AfterSuite
+    public final void shutdownRegistry()
+    {
+        registry.cleanupThread();
+        registry.shutdown();
+        registry = null;
+    }
+
+    @BeforeMethod
+    public final void beginTransaction()
+    {
+        setupRegistry();
+        EntityTransaction tx = getEntityManager().getTransaction();
+        if (!tx.isActive())
+            tx.begin();
+    }
+
+    @AfterMethod
+    public void rollbackLastTransactionAndClean() throws SQLException
+    {
+        EntityTransaction transaction = getEntityManager().getTransaction();
+        if (transaction.isActive())
+            transaction.rollback();
+        clearDatabase();
+        getEntityManager().clear();
+        shutdownRegistry();
+    }
+
+    // based on http://www.objectpartners.com/2010/11/09/unit-testing-your-persistence-tier-code/
+    private void clearDatabase() throws SQLException
+    {
+        EntityManager em = getEntityManager();
+        em.clear();
+        EntityTransaction transaction = em.getTransaction();
+        if (!transaction.isActive())
+            transaction.begin();
+        Connection c = em.unwrap(Connection.class);
+        Statement s = c.createStatement();
+        s.execute("SET REFERENTIAL_INTEGRITY FALSE");
+        Set<String> tables = new HashSet<String>();
+        ResultSet rs = s.executeQuery("select table_name " + "from INFORMATION_SCHEMA.tables "
+                + "where table_type='TABLE' and table_schema='PUBLIC'");
+        while (rs.next())
+        {
+            // if we don't skip over the sequence table, we'll start getting
+            // "The sequence table information is not complete"
+            // exceptions
+            if (!rs.getString(1).startsWith("DUAL_") && !rs.getString(1).equals("SEQUENCE"))
+            {
+                tables.add(rs.getString(1));
+            }
+        }
+        rs.close();
+        for (String table : tables)
+        {
+            s.executeUpdate("DELETE FROM " + table);
+        }
+        transaction.commit();
+        s.execute("SET REFERENTIAL_INTEGRITY TRUE");
+        s.close();
+    }
+
+    private <T> List<T> getInstances(final Class<T> type)
+    {
+        EntityManager em = getEntityManager();
+        CriteriaBuilder qb = em.getCriteriaBuilder();
+        CriteriaQuery<T> query = qb.createQuery(type);
+        query.select(query.from(type));
+        return em.createQuery(query).getResultList();
+    }
+
+    @Test
+    public void commitBothInNestedTransaction()
+    {
+        topLevelService.createThingOneAndTwo("one", "two");
+        assertEquals(1, getInstances(ThingOne.class).size());
+        assertEquals(1, getInstances(ThingTwo.class).size());
+        assertTrue(getEntityManager().find(VersionedThing.class, 1).getVersion() > 0);
+    }
+
+    @Test(expectedExceptions = RollbackException.class)
+    public void rollbackNestedFails()
+    {
+        topLevelService.createThingOneAndTwo("one", null);
+    }
+
+    @Test(expectedExceptions = RollbackException.class)
+    public void rollbackTopFails()
+    {
+        topLevelService.createThingOneAndTwo(null, "two");
+    }
+
+    @Test
+    public void sequentialCommitUsingInvokeAfterCommit()
+    {
+        topLevelService.createThingOneThenTwo("one", "two");
+        assertEquals(1, getInstances(ThingOne.class).size());
+        assertEquals(1, getInstances(ThingTwo.class).size());
+        assertTrue(getEntityManager().find(VersionedThing.class, 1).getVersion() > 1);
+    }
+
+    @Test
+    public void sequentialCommitUsingInvokeAfterCommitAndCommitAfterAnnotation()
+    {
+        topLevelService.createThingOneThenTwoWithNestedCommitAfter("one", "two");
+        assertEquals(1, getInstances(ThingOne.class).size());
+        assertEquals(1, getInstances(ThingTwo.class).size());
+        assertTrue(getEntityManager().find(VersionedThing.class, 1).getVersion() > 1);
+    }
+
+    @Test
+    public void sequentialRollbackAndAbortUsingInvokeAfterCommit()
+    {
+        try
+        {
+            topLevelService.createThingOneThenTwo(null, "two");
+            Assert.fail();
+        }
+        catch (RollbackException e)
+        {
+        }
+        assertEquals(0, getInstances(ThingOne.class).size());
+        assertEquals(0, getInstances(ThingTwo.class).size());
+    }
+
+    @Test
+    public void trySomething()
+    {
+        ThingOne thingOne = new ThingOne();
+        thingOne.setId(1);
+        PersistedEntity entity = JpaInternalUtils.convertApplicationValueToPersisted(
+                entityManagerManager, thingOne);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTestModule.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTestModule.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTestModule.java
new file mode 100644
index 0000000..d70e4b8
--- /dev/null
+++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTestModule.java
@@ -0,0 +1,97 @@
+/**
+ * Licensed 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.tapestry5.jpa.test;
+
+import javax.persistence.spi.PersistenceUnitTransactionType;
+
+import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.internal.jpa.TapestryCDIBeanManagerForJPAEntityListeners;
+import org.apache.tapestry5.ioc.Configuration;
+import org.apache.tapestry5.ioc.MappedConfiguration;
+import org.apache.tapestry5.ioc.MethodAdviceReceiver;
+import org.apache.tapestry5.ioc.ObjectLocator;
+import org.apache.tapestry5.ioc.ServiceBinder;
+import org.apache.tapestry5.ioc.annotations.Contribute;
+import org.apache.tapestry5.ioc.annotations.Match;
+import org.apache.tapestry5.ioc.services.ApplicationDefaults;
+import org.apache.tapestry5.ioc.services.SymbolProvider;
+import org.apache.tapestry5.jpa.EntityManagerSource;
+import org.apache.tapestry5.jpa.JpaEntityPackageManager;
+import org.apache.tapestry5.jpa.JpaTransactionAdvisor;
+import org.apache.tapestry5.jpa.PersistenceUnitConfigurer;
+import org.apache.tapestry5.jpa.TapestryPersistenceUnitInfo;
+import org.apache.tapestry5.jpa.test.entities.ThingOne;
+import org.apache.tapestry5.jpa.test.entities.ThingTwo;
+import org.apache.tapestry5.jpa.test.entities.VersionedThing;
+
+public class JpaTestModule
+{
+
+    public static void bind(final ServiceBinder binder)
+    {
+        binder.bind(TopLevelService.class);
+        binder.bind(NestedService.class);
+    }
+
+    @Contribute(SymbolProvider.class)
+    @ApplicationDefaults
+    public static void defaultsSymbols(MappedConfiguration<String, Object> configuration)
+    {
+        configuration.add(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM, JpaTestModule.class
+                .getPackage().getName());
+        // configuration.add(InternalSymbols.APP_PACKAGE_PATH, "org/tynamo/model/jpa");
+    }
+
+    @Contribute(JpaEntityPackageManager.class)
+    public static void addPackages(Configuration<String> configuration)
+    {
+        configuration.add(JpaTestModule.class.getPackage().getName());
+    }
+
+    @Contribute(EntityManagerSource.class)
+    public static void configurePersistenceUnit(
+            MappedConfiguration<String, PersistenceUnitConfigurer> cfg, ObjectLocator objectLocator)
+    {
+        PersistenceUnitConfigurer configurer = new PersistenceUnitConfigurer()
+        {
+            @Override
+            public void configure(TapestryPersistenceUnitInfo unitInfo)
+            {
+                unitInfo.transactionType(PersistenceUnitTransactionType.RESOURCE_LOCAL)
+                        .persistenceProviderClassName(
+                                "org.eclipse.persistence.jpa.PersistenceProvider")
+                        .excludeUnlistedClasses(true)
+                        .addProperty("javax.persistence.jdbc.user", "sa")
+                        .addProperty("javax.persistence.jdbc.password", "sa")
+                        .addProperty("javax.persistence.jdbc.driver", "org.h2.Driver")
+                        .addProperty("javax.persistence.jdbc.url", "jdbc:h2:mem:jpatest")
+                        .addProperty("eclipselink.ddl-generation", "create-or-extend-tables")
+                        .addProperty("eclipselink.logging.level", "FINE")
+                        .addManagedClass(ThingOne.class).addManagedClass(ThingTwo.class)
+                        .addManagedClass(VersionedThing.class);
+                unitInfo.getProperties().put("javax.persistence.bean.manager",
+                        objectLocator.autobuild(TapestryCDIBeanManagerForJPAEntityListeners.class));
+            }
+        };
+        // cfg.add("jpatest", configurer);
+        cfg.add("TestUnit", configurer);
+    }
+
+    @Match(
+    { "*Service" })
+    public static void adviseTransactionally(JpaTransactionAdvisor advisor,
+            MethodAdviceReceiver receiver)
+    {
+        advisor.addTransactionCommitAdvice(receiver);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/NestedService.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/NestedService.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/NestedService.java
new file mode 100644
index 0000000..d427a86
--- /dev/null
+++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/NestedService.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed 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.tapestry5.jpa.test;
+
+import org.apache.tapestry5.jpa.annotations.CommitAfter;
+
+public interface NestedService
+{
+
+    @CommitAfter
+    public void createThingTwo(String nameTwo);
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/NestedServiceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/NestedServiceImpl.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/NestedServiceImpl.java
new file mode 100644
index 0000000..a507ac9
--- /dev/null
+++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/NestedServiceImpl.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed 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.tapestry5.jpa.test;
+
+import javax.persistence.EntityManager;
+
+import org.apache.tapestry5.jpa.test.entities.ThingTwo;
+
+public class NestedServiceImpl implements NestedService
+{
+    private final EntityManager em;
+
+    public NestedServiceImpl(EntityManager em)
+    {
+        this.em = em;
+    }
+
+    public void createThingTwo(String nameTwo)
+    {
+        ThingTwo thingTwo = new ThingTwo();
+        thingTwo.setName(nameTwo);
+        em.persist(thingTwo);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/TopLevelService.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/TopLevelService.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/TopLevelService.java
new file mode 100644
index 0000000..849b84d
--- /dev/null
+++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/TopLevelService.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed 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.tapestry5.jpa.test;
+
+import org.apache.tapestry5.jpa.annotations.CommitAfter;
+
+public interface TopLevelService
+{
+
+    @CommitAfter
+    void createThingOneAndTwo(String nameOne, String nameTwo);
+
+    @CommitAfter
+    void createThingOneThenTwo(final String nameOne, final String nameTwo);
+
+    void createThingOneThenTwoWithNestedCommitAfter(String nameOne, String nameTwo);
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5047220c/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/TopLevelServiceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/TopLevelServiceImpl.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/TopLevelServiceImpl.java
new file mode 100644
index 0000000..2bec13d
--- /dev/null
+++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/TopLevelServiceImpl.java
@@ -0,0 +1,92 @@
+/**
+ * Licensed 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.tapestry5.jpa.test;
+
+import javax.persistence.EntityManager;
+
+import org.apache.tapestry5.ioc.Invokable;
+import org.apache.tapestry5.jpa.EntityTransactionManager;
+import org.apache.tapestry5.jpa.annotations.CommitAfter;
+import org.apache.tapestry5.jpa.test.entities.ThingOne;
+
+public class TopLevelServiceImpl implements TopLevelService
+{
+
+    private final EntityManager em;
+    private final NestedService nestedService;
+    private final EntityTransactionManager entityTransactionManager;
+
+    public TopLevelServiceImpl(EntityManager em, NestedService nestedService,
+            EntityTransactionManager transactionalUnits)
+    {
+        this.em = em;
+        this.nestedService = nestedService;
+        this.entityTransactionManager = transactionalUnits;
+    }
+
+    @Override
+    @CommitAfter
+    public void createThingOneAndTwo(String nameOne, String nameTwo)
+    {
+        ThingOne thingOne = new ThingOne();
+        thingOne.setName(nameOne);
+        em.persist(thingOne);
+        nestedService.createThingTwo(nameTwo);
+    }
+
+    @Override
+    @CommitAfter
+    public void createThingOneThenTwo(final String nameOne, final String nameTwo)
+    {
+        entityTransactionManager.invokeAfterCommit(null, new Invokable<Boolean>()
+        {
+            @Override
+            public Boolean invoke()
+            {
+                nestedService.createThingTwo(nameTwo);
+                return true;
+            }
+        });
+        ThingOne thingOne = new ThingOne();
+        thingOne.setName(nameOne);
+        em.persist(thingOne);
+    }
+
+    @Override
+    @CommitAfter
+    public void createThingOneThenTwoWithNestedCommitAfter(final String nameOne,
+            final String nameTwo)
+    {
+        entityTransactionManager.runInTransaction(null, new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                entityTransactionManager.invokeAfterCommit(null, new Invokable<Boolean>()
+                {
+                    @Override
+                    public Boolean invoke()
+                    {
+                        nestedService.createThingTwo(nameTwo);
+                        return true;
+                    }
+                });
+                ThingOne thingOne = new ThingOne();
+                thingOne.setName(nameOne);
+                em.persist(thingOne);
+            }
+        });
+
+    }
+}


Mime
View raw message