package rename for test libraries.
[utils] / test / enterprise / src / main / java / org / wamblee / test / persistence / TransactionProxyFactory.java
diff --git a/test/enterprise/src/main/java/org/wamblee/test/persistence/TransactionProxyFactory.java b/test/enterprise/src/main/java/org/wamblee/test/persistence/TransactionProxyFactory.java
new file mode 100644 (file)
index 0000000..3806dc2
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2005-2010 the original author or authors.
+ * 
+ * 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.wamblee.test.persistence;
+
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javax.persistence.EntityManager;
+
+import org.wamblee.test.persistence.JpaBuilder.JpaUnitOfWork;
+import org.wamblee.test.transactions.ThreadSpecificProxyFactory;
+
+/**
+ * This utility makes sure that each invocation on a certain interface is
+ * carried out within a JPA unit of work. Note that this is equivalent
+ * to the sementics of a requiresNew transaction attribute. 
+ * 
+ * Use {@link #getTransactionScopedEntityManager()} to get the transaction
+ * scoped entity manager to pass to services.
+ * 
+ * 
+ * For example: 
+ * <pre>
+ *     JpaBuilder builder = ...
+ *     TransactionProxyFactory<Service> factory = new TransactionProxyFactory<Service>(
+ *           builder, Service.class);
+ *     Service service = new JpaService(factory.getTransactionScopedEntityManager());
+ *     Service proxy = factory.getProxy(service);
+ *     proxy.executeMethod(...); 
+ * </pre>
+ * The above example executes the executeMethod() call on the service object within an active transaction.
+ * In the constructor of the service a transaction scoped entity manager is passed.  
+ * 
+ * @param T
+ *            Type of interface to proxy.
+ * 
+ * @author Erik Brakkee
+ */
+public class TransactionProxyFactory<T> {
+
+    /**
+     * Executes the call on the service within a new transaction.  
+     * 
+     * @author Erik Brakkee
+     *
+     * @param <T> Type of the service interface. 
+     */
+    private class UnitOfWorkInvocationHandler<T> implements InvocationHandler {
+
+        private T service;
+
+        public UnitOfWorkInvocationHandler(T aService) {
+            service = aService;
+        }
+
+        @Override
+        public Object invoke(Object aProxy, final Method aMethod,
+            final Object[] aArgs) throws Throwable {
+            return TransactionProxyFactory.this.jpaBuilder
+                .execute(new JpaUnitOfWork<Object>() {
+                    @Override
+                    public Object execute(EntityManager aEm) throws Exception {
+                        EntityManager oldEm = ENTITY_MANAGER.get(); 
+                        try {
+                            ENTITY_MANAGER.set(aEm);
+                            return aMethod.invoke(service, aArgs);
+                        } catch (InvocationTargetException e) {
+                            Throwable cause = e.getCause();
+                            if (cause instanceof Exception) {
+                                throw (Exception) cause;
+                            } else if (cause instanceof Error) {
+                                throw (Error) cause;
+                            }
+                            // last resort.
+                            throw new RuntimeException(e);
+                        } finally {
+                            ENTITY_MANAGER.set(oldEm);
+                        }
+                    }
+                });
+        }
+
+    }
+
+    private static final ThreadSpecificProxyFactory<EntityManager> ENTITY_MANAGER = new ThreadSpecificProxyFactory<EntityManager>(
+        EntityManager.class);
+
+    private JpaBuilder jpaBuilder;
+    private Class<T> clazz;
+
+    /**
+     * Constructs the transaction proxy.
+     * 
+     * @param aJpaBuilder
+     */
+    public TransactionProxyFactory(JpaBuilder aJpaBuilder, Class<T> aClass) {
+        jpaBuilder = aJpaBuilder;
+        clazz = aClass;
+    }
+
+    public EntityManager getTransactionScopedEntityManager() {
+        return ENTITY_MANAGER.getProxy();
+    }
+
+    public T getProxy(T aService) {
+        InvocationHandler handler = new UnitOfWorkInvocationHandler<T>(aService);
+        Class proxyClass = Proxy.getProxyClass(clazz.getClassLoader(),
+            new Class[] { clazz });
+        T proxy;
+        try {
+            proxy = (T) proxyClass.getConstructor(
+                new Class[] { InvocationHandler.class }).newInstance(
+                new Object[] { handler });
+            return proxy;
+        } catch (Exception e) {
+            throw new RuntimeException("Could not create proxy for " +
+                clazz.getName(), e);
+        }
+    }
+}