From 099827f9fe35da8a3be1010a222e90db94f0b5c4 Mon Sep 17 00:00:00 2001 From: Erik Brakkee Date: Sat, 17 Jul 2010 20:25:40 +0000 Subject: [PATCH] --- .../DefaultUserTransactionFactory.java | 43 ++++++ .../SimpleTransactionManager.java | 80 +++++++++++ .../transactions/SimpleUserTransaction.java | 131 +++++++++++++++++ .../transactions/TransactionProxyFactory.java | 136 ++++++++++++++++++ .../transactions/TransactionResource.java | 47 ++++++ .../transactions/UserTransactionCallback.java | 20 +++ .../transactions/UserTransactionFactory.java | 31 ++++ 7 files changed, 488 insertions(+) create mode 100644 test/enterprise/src/main/java/org/wamblee/test/transactions/DefaultUserTransactionFactory.java create mode 100644 test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleTransactionManager.java create mode 100644 test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleUserTransaction.java create mode 100644 test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionProxyFactory.java create mode 100644 test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionResource.java create mode 100644 test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionCallback.java create mode 100644 test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionFactory.java 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 index 00000000..9d58546d --- /dev/null +++ b/test/enterprise/src/main/java/org/wamblee/test/transactions/DefaultUserTransactionFactory.java @@ -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 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 index 00000000..ee8eb77d --- /dev/null +++ b/test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleTransactionManager.java @@ -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 current; + private UserTransactionCallback callback; + private List resources; + + /** + * Constructs the transaction manager. + * + * @param aFactory + * Factory to create transactions with. + */ + public SimpleTransactionManager(UserTransactionFactory aFactory) { + factory = aFactory; + current = new ThreadLocal(); + callback = new UserTransactionCallback() { + + @Override + public void transactionFinished() { + current.set(null); + } + }; + resources = new ArrayList(); + } + + /** + * 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 index 00000000..1b9b59ac --- /dev/null +++ b/test/enterprise/src/main/java/org/wamblee/test/transactions/SimpleUserTransaction.java @@ -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 index 00000000..9aa5cef8 --- /dev/null +++ b/test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionProxyFactory.java @@ -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: + *
+ *     JpaBuilder builder = ...
+ *     TransactionProxyFactory factory = new TransactionProxyFactory(
+ *           builder, Service.class);
+ *     Service service = new JpaService(factory.getTransactionScopedEntityManager());
+ *     Service proxy = factory.getProxy(service);
+ *     proxy.executeMethod(...); 
+ * 
+ * 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 { + + /** + * Executes the call on the service within a new transaction. + * + * @author Erik Brakkee + * + * @param Type of the service interface. + */ + private class UnitOfWorkInvocationHandler 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() { + @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 ENTITY_MANAGER = new ThreadSpecificProxyFactory( + EntityManager.class); + + private JpaBuilder jpaBuilder; + private Class clazz; + + /** + * Constructs the transaction proxy. + * + * @param aJpaBuilder + */ + public TransactionProxyFactory(JpaBuilder aJpaBuilder, Class aClass) { + jpaBuilder = aJpaBuilder; + clazz = aClass; + } + + public EntityManager getTransactionScopedEntityManager() { + return ENTITY_MANAGER.getProxy(); + } + + public T getProxy(T aService) { + InvocationHandler handler = new UnitOfWorkInvocationHandler(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 index 00000000..25fc290f --- /dev/null +++ b/test/enterprise/src/main/java/org/wamblee/test/transactions/TransactionResource.java @@ -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 + */ +public interface TransactionResource { + + /** + * 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 index 00000000..09b656db --- /dev/null +++ b/test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionCallback.java @@ -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 index 00000000..c1a08592 --- /dev/null +++ b/test/enterprise/src/main/java/org/wamblee/test/transactions/UserTransactionFactory.java @@ -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 aResources); +} -- 2.31.1