/* * 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 java.util.logging.Level; import javax.persistence.EntityManager; import org.wamblee.general.ThreadSpecificProxyFactory; import org.wamblee.test.persistence.JpaBuilder; import org.wamblee.test.persistence.LoggingTransactionResultCallback; 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); } } }, new LoggingTransactionResultCallback(Level.INFO)); } } 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); } } }