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