(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 import java.util.logging.Level;
24
25 import javax.persistence.EntityManager;
26
27 import org.wamblee.general.ThreadSpecificProxyFactory;
28 import org.wamblee.test.persistence.JpaBuilder;
29 import org.wamblee.test.persistence.LoggingTransactionResultCallback;
30 import org.wamblee.test.persistence.JpaBuilder.JpaUnitOfWork;
31
32 /**
33  * This utility makes sure that each invocation on a certain interface is
34  * carried out within a JPA unit of work. Note that this is equivalent
35  * to the sementics of a requiresNew transaction attribute. 
36  * 
37  * Use {@link #getTransactionScopedEntityManager()} to get the transaction
38  * scoped entity manager to pass to services.
39  * 
40  * 
41  * For example: 
42  * <pre>
43  *     JpaBuilder builder = ...
44  *     TransactionProxyFactory<Service> factory = new TransactionProxyFactory<Service>(
45  *           builder, Service.class);
46  *     Service service = new JpaService(factory.getTransactionScopedEntityManager());
47  *     Service proxy = factory.getProxy(service);
48  *     proxy.executeMethod(...); 
49  * </pre>
50  * The above example executes the executeMethod() call on the service object within an active transaction.
51  * In the constructor of the service a transaction scoped entity manager is passed.  
52  * 
53  * @param T
54  *            Type of interface to proxy.
55  * 
56  * @author Erik Brakkee
57  */
58 public class TransactionProxyFactory<T> {
59
60     /**
61      * Executes the call on the service within a new transaction.  
62      * 
63      * @author Erik Brakkee
64      *
65      * @param <T> Type of the service interface. 
66      */
67     private class UnitOfWorkInvocationHandler<T> implements InvocationHandler {
68
69         private T service;
70
71         public UnitOfWorkInvocationHandler(T aService) {
72             service = aService;
73         }
74
75         @Override
76         public Object invoke(Object aProxy, final Method aMethod,
77             final Object[] aArgs) throws Throwable {
78             return TransactionProxyFactory.this.jpaBuilder
79                 .execute(new JpaUnitOfWork<Object>() {
80                     @Override
81                     public Object execute(EntityManager aEm) throws Exception {
82                         EntityManager oldEm = ENTITY_MANAGER.get(); 
83                         try {
84                             ENTITY_MANAGER.set(aEm);
85                             return aMethod.invoke(service, aArgs);
86                         } catch (InvocationTargetException e) {
87                             Throwable cause = e.getCause();
88                             if (cause instanceof Exception) {
89                                 throw (Exception) cause;
90                             } else if (cause instanceof Error) {
91                                 throw (Error) cause;
92                             }
93                             // last resort.
94                             throw new RuntimeException(e);
95                         } finally {
96                             ENTITY_MANAGER.set(oldEm);
97                         }
98                     }
99                 }, new LoggingTransactionResultCallback(Level.INFO));
100         }
101
102     }
103
104     private static final ThreadSpecificProxyFactory<EntityManager> ENTITY_MANAGER = new ThreadSpecificProxyFactory<EntityManager>(
105         EntityManager.class);
106
107     private JpaBuilder jpaBuilder;
108     private Class<T> clazz;
109
110     /**
111      * Constructs the transaction proxy.
112      * 
113      * @param aJpaBuilder
114      */
115     public TransactionProxyFactory(JpaBuilder aJpaBuilder, Class<T> aClass) {
116         jpaBuilder = aJpaBuilder;
117         clazz = aClass;
118     }
119
120     public EntityManager getTransactionScopedEntityManager() {
121         return ENTITY_MANAGER.getProxy();
122     }
123
124     public T getProxy(T aService) {
125         InvocationHandler handler = new UnitOfWorkInvocationHandler<T>(aService);
126         Class proxyClass = Proxy.getProxyClass(clazz.getClassLoader(),
127             new Class[] { clazz });
128         T proxy;
129         try {
130             proxy = (T) proxyClass.getConstructor(
131                 new Class[] { InvocationHandler.class }).newInstance(
132                 new Object[] { handler });
133             return proxy;
134         } catch (Exception e) {
135             throw new RuntimeException("Could not create proxy for " +
136                 clazz.getName(), e);
137         }
138     }
139 }