(no commit message)
[utils] / test / enterprise / src / main / java / org / wamblee / test / transactions / TransactionProxyFactory.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.test.transactions;
17
18
19 import java.lang.reflect.InvocationHandler;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Proxy;
23
24 import javax.persistence.EntityManager;
25
26 import org.wamblee.general.ThreadSpecificProxyFactory;
27 import org.wamblee.test.persistence.JpaBuilder;
28 import org.wamblee.test.persistence.JpaBuilder.JpaUnitOfWork;
29
30 /**
31  * This utility makes sure that each invocation on a certain interface is
32  * carried out within a JPA unit of work. Note that this is equivalent
33  * to the sementics of a requiresNew transaction attribute. 
34  * 
35  * Use {@link #getTransactionScopedEntityManager()} to get the transaction
36  * scoped entity manager to pass to services.
37  * 
38  * 
39  * For example: 
40  * <pre>
41  *     JpaBuilder builder = ...
42  *     TransactionProxyFactory<Service> factory = new TransactionProxyFactory<Service>(
43  *           builder, Service.class);
44  *     Service service = new JpaService(factory.getTransactionScopedEntityManager());
45  *     Service proxy = factory.getProxy(service);
46  *     proxy.executeMethod(...); 
47  * </pre>
48  * The above example executes the executeMethod() call on the service object within an active transaction.
49  * In the constructor of the service a transaction scoped entity manager is passed.  
50  * 
51  * @param T
52  *            Type of interface to proxy.
53  * 
54  * @author Erik Brakkee
55  */
56 public class TransactionProxyFactory<T> {
57
58     /**
59      * Executes the call on the service within a new transaction.  
60      * 
61      * @author Erik Brakkee
62      *
63      * @param <T> Type of the service interface. 
64      */
65     private class UnitOfWorkInvocationHandler<T> implements InvocationHandler {
66
67         private T service;
68
69         public UnitOfWorkInvocationHandler(T aService) {
70             service = aService;
71         }
72
73         @Override
74         public Object invoke(Object aProxy, final Method aMethod,
75             final Object[] aArgs) throws Throwable {
76             return TransactionProxyFactory.this.jpaBuilder
77                 .execute(new JpaUnitOfWork<Object>() {
78                     @Override
79                     public Object execute(EntityManager aEm) throws Exception {
80                         EntityManager oldEm = ENTITY_MANAGER.get(); 
81                         try {
82                             ENTITY_MANAGER.set(aEm);
83                             return aMethod.invoke(service, aArgs);
84                         } catch (InvocationTargetException e) {
85                             Throwable cause = e.getCause();
86                             if (cause instanceof Exception) {
87                                 throw (Exception) cause;
88                             } else if (cause instanceof Error) {
89                                 throw (Error) cause;
90                             }
91                             // last resort.
92                             throw new RuntimeException(e);
93                         } finally {
94                             ENTITY_MANAGER.set(oldEm);
95                         }
96                     }
97                 });
98         }
99
100     }
101
102     private static final ThreadSpecificProxyFactory<EntityManager> ENTITY_MANAGER = new ThreadSpecificProxyFactory<EntityManager>(
103         EntityManager.class);
104
105     private JpaBuilder jpaBuilder;
106     private Class<T> clazz;
107
108     /**
109      * Constructs the transaction proxy.
110      * 
111      * @param aJpaBuilder
112      */
113     public TransactionProxyFactory(JpaBuilder aJpaBuilder, Class<T> aClass) {
114         jpaBuilder = aJpaBuilder;
115         clazz = aClass;
116     }
117
118     public EntityManager getTransactionScopedEntityManager() {
119         return ENTITY_MANAGER.getProxy();
120     }
121
122     public T getProxy(T aService) {
123         InvocationHandler handler = new UnitOfWorkInvocationHandler<T>(aService);
124         Class proxyClass = Proxy.getProxyClass(clazz.getClassLoader(),
125             new Class[] { clazz });
126         T proxy;
127         try {
128             proxy = (T) proxyClass.getConstructor(
129                 new Class[] { InvocationHandler.class }).newInstance(
130                 new Object[] { handler });
131             return proxy;
132         } catch (Exception e) {
133             throw new RuntimeException("Could not create proxy for " +
134                 clazz.getName(), e);
135         }
136     }
137 }