(no commit message)
authorErik Brakkee <erik@brakkee.org>
Sat, 17 Jul 2010 20:25:40 +0000 (20:25 +0000)
committerErik Brakkee <erik@brakkee.org>
Sat, 17 Jul 2010 20:25:40 +0000 (20:25 +0000)
test/enterprise/src/main/java/org/wamblee/test/transactions/DefaultUserTransactionFactory.java [new file with mode: 0644]
test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleTransactionManager.java [new file with mode: 0644]
test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleUserTransaction.java [new file with mode: 0644]
test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionProxyFactory.java [new file with mode: 0644]
test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionResource.java [new file with mode: 0644]
test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionCallback.java [new file with mode: 0644]
test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionFactory.java [new file with mode: 0644]

diff --git a/test/enterprise/src/main/java/org/wamblee/test/transactions/DefaultUserTransactionFactory.java b/test/enterprise/src/main/java/org/wamblee/test/transactions/DefaultUserTransactionFactory.java
new file mode 100644 (file)
index 0000000..9d58546
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.transactions;
+
+import java.util.List;
+
+import javax.transaction.UserTransaction;
+
+/**
+ * Transaction factory implementation that creates {@link SimpleUserTransaction} objects. 
+ * 
+ * @author Erik Brakkee
+ *
+ */
+public class DefaultUserTransactionFactory implements UserTransactionFactory {
+
+    /**
+     * Constructs the factory. 
+     */
+    public DefaultUserTransactionFactory() { 
+        // Empty. 
+    }
+    
+    @Override
+    public UserTransaction create(UserTransactionCallback aCallback,
+        List<TransactionResource> aResources) {
+        return new SimpleUserTransaction(aCallback, aResources.toArray(new TransactionResource[0]));
+    }
+
+}
diff --git a/test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleTransactionManager.java b/test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleTransactionManager.java
new file mode 100644 (file)
index 0000000..ee8eb77
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.transactions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.transaction.UserTransaction;
+
+/**
+ * Simple transaction manager provides a simple mechanism to manage transaction
+ * in test code through the {@link UserTransaction} object.
+ * 
+ * @author Erik Brakkee
+ * 
+ */
+public class SimpleTransactionManager {
+
+    private UserTransactionFactory factory;
+    private ThreadLocal<UserTransaction> current;
+    private UserTransactionCallback callback;
+    private List<TransactionResource> resources;
+
+    /**
+     * Constructs the transaction manager.
+     * 
+     * @param aFactory
+     *            Factory to create transactions with.
+     */
+    public SimpleTransactionManager(UserTransactionFactory aFactory) {
+        factory = aFactory;
+        current = new ThreadLocal<UserTransaction>();
+        callback = new UserTransactionCallback() {
+
+            @Override
+            public void transactionFinished() {
+                current.set(null);
+            }
+        };
+        resources = new ArrayList<TransactionResource>();
+    }
+
+    /**
+     * Adds a resource to manage. Adding resources is no longer allowed after
+     * the first transaction has started.
+     * 
+     * @param aResource
+     *            Resource.
+     */
+    public void addResource(TransactionResource aResource) {
+        resources.add(aResource);
+    }
+
+    /**
+     * Gets a transaction associated with the current thread.
+     * 
+     * @return User transaction.
+     */
+    public UserTransaction getTransaction() {
+        UserTransaction transaction = current.get();
+        if (transaction == null) {
+            transaction = factory.create(callback, resources);
+            current.set(transaction);
+        }
+        return transaction;
+    }
+}
diff --git a/test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleUserTransaction.java b/test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleUserTransaction.java
new file mode 100644 (file)
index 0000000..1b9b59a
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * 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.transactions;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.UserTransaction;
+
+/**
+ * Represents a user transaction spanning a number of resources.
+ */
+public class SimpleUserTransaction implements UserTransaction {
+
+    private UserTransactionCallback callback;
+    private TransactionResource[] resources;
+    private Object[] txStates;
+    private int status;
+
+    /**
+     * Construcst the transaction.
+     * 
+     * @param aCallback
+     *            Callback to use.
+     * @param aResources
+     *            Resources to use.
+     */
+    public SimpleUserTransaction(UserTransactionCallback aCallback,
+        TransactionResource<?>... aResources) {
+        callback = aCallback;
+        resources = aResources;
+        txStates = new Object[resources.length];
+        status = Status.STATUS_NO_TRANSACTION;
+    }
+
+    @Override
+    public void begin() throws NotSupportedException, SystemException {
+        if (status == Status.STATUS_ACTIVE) {
+            throw new NotSupportedException(
+                "Nested transactions not supported!");
+        }
+        for (int i = 0; i < resources.length; i++) {
+            txStates[i] = resources[i].begin();
+        }
+        status = Status.STATUS_ACTIVE;
+    }
+
+    @Override
+    public void rollback() throws IllegalStateException, SecurityException,
+        SystemException {
+        if ( status == Status.STATUS_NO_TRANSACTION) { 
+            throw new IllegalStateException("Rollback while not in a transaction");
+        }
+        try {
+            for (int i = 0; i < resources.length; i++) {
+                resources[i].rollback(txStates[i]);
+            }
+        } finally {
+            status = Status.STATUS_NO_TRANSACTION;
+            callback.transactionFinished();
+        }
+    }
+
+    @Override
+    public void commit() throws RollbackException, HeuristicMixedException,
+        HeuristicRollbackException, SecurityException, IllegalStateException,
+        SystemException {
+        if (status == Status.STATUS_MARKED_ROLLBACK) {
+            rollback();
+            throw new RollbackException();
+        }
+        if (status != Status.STATUS_ACTIVE) {
+            throw new IllegalStateException("Commit while not in a transaction");
+        }
+        try {
+            boolean committing = true;
+            for (int i = 0; i < resources.length; i++) {
+                try {
+                    if (committing) {
+                        resources[i].commit(txStates[i]);
+                    } else {
+                        resources[i].rollback(txStates[i]);
+                    }
+                } catch (Exception e) {
+                    committing = false; 
+                }
+            }
+            
+            if (!committing) { 
+                throw new HeuristicMixedException("Commit failed");
+            }
+        } finally {
+            status = Status.STATUS_NO_TRANSACTION;
+            callback.transactionFinished();
+        }
+    }
+
+    @Override
+    public int getStatus() throws SystemException {
+        return status;
+    }
+
+    @Override
+    public void setRollbackOnly() throws IllegalStateException, SystemException {
+        if ( status == Status.STATUS_NO_TRANSACTION) { 
+            throw new IllegalStateException("setRollbackOnly() while not in a transaction");
+        }
+        status = Status.STATUS_MARKED_ROLLBACK;
+    }
+
+    @Override
+    public void setTransactionTimeout(int aSeconds) throws SystemException {
+        // Ignored.
+    }
+}
diff --git a/test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionProxyFactory.java b/test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionProxyFactory.java
new file mode 100644 (file)
index 0000000..9aa5cef
--- /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.transactions;
+
+
+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;
+import org.wamblee.test.persistence.JpaBuilder.JpaUnitOfWork;
+
+/**
+ * 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);
+        }
+    }
+}
diff --git a/test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionResource.java b/test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionResource.java
new file mode 100644 (file)
index 0000000..25fc290
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.transactions;
+
+
+/**
+ * Interfaces to be implemented by resource that want to participate in transactions
+ * managed through {@link SimpleTransactionManager}.
+ * 
+ * @author Erik Brakkee
+ *
+ * @param <T>
+ */
+public interface TransactionResource<T> {
+
+    /**
+     * Begins a transaction. 
+     * @return Object that manages the transaction for the resource.
+     */
+    T begin(); 
+    
+    /**
+     * Rolls back a transaction.
+     * @param aT Object that manages the transaction for the resource. 
+     */
+    void rollback(T aT); 
+    
+    /**
+     * Commits the transaction. 
+     * @param aT Object that manages the transaction for the resource. 
+     */
+    void commit(T aT);
+    
+}
diff --git a/test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionCallback.java b/test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionCallback.java
new file mode 100644 (file)
index 0000000..09b656d
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * 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.transactions;
+
+public interface UserTransactionCallback { 
+    void transactionFinished(); 
+}
\ No newline at end of file
diff --git a/test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionFactory.java b/test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionFactory.java
new file mode 100644 (file)
index 0000000..c1a0859
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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.transactions;
+
+import java.util.List;
+
+import javax.transaction.UserTransaction;
+
+/**
+ * Factory used to create transactions. 
+ * 
+ * @author Erik Brakkee
+ *
+ */
+public interface UserTransactionFactory {
+
+    UserTransaction create(UserTransactionCallback aCallback, List<TransactionResource> aResources); 
+}