(no commit message)
[utils] / support / general / src / main / java / org / wamblee / concurrency / ReadWriteLockProxyFactory.java
1 /*
2  * Copyright 2005-2011 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.concurrency;
17
18 import java.io.Serializable;
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.HashMap;
24 import java.util.Map;
25 import java.util.concurrent.locks.ReentrantReadWriteLock;
26
27 import javax.naming.InitialContext;
28 import javax.naming.NamingException;
29
30 /**
31  * Proxy factory that provides locking using {@link ReentrantReadWriteLock} based
32  * on the {@link ReadLock} and {@link WriteLock} annotations. The annotations must be
33  * applied to the service implementation methods. Annotations on the interfaces are ignored.
34  * It uses fair read-write locking.
35  * <p>
36  * For example: 
37  * <pre>
38  *   class Service implements MyApi {
39  *       &#064;ReadLock
40  *       void doX() { ... }
41  *       &#064;WriteLock
42  *       void doY() { ... }
43  *       // no locking by default
44  *       void doZ() { ... } 
45  *   }
46  *   
47  *   // create service
48  *   Service svc = new Service();
49  *   
50  *   // create service guarded by read-write locking.
51  *   MyApi guardedSvc = new ReadWriteLockProxyFactory().getProxy(svc, MyApi.class);
52  * </pre>
53  * 
54  * @param T service interface to proxy. In case a service implements multiple interfaces,
55  *   it can be convenient to create a new interface that combines these interfaces so that
56  *   there is an interface type that represents all the implemented interfaces. 
57  *            
58  * @author Erik Brakkee
59  * 
60  */
61 public class ReadWriteLockProxyFactory<T> {
62
63     /**
64      * Invocation handler that does a lookup in JNDI and invokes the method on
65      * the object it found.
66      * 
67      * @author Erik Brakkee
68      */
69     private static class LockingInvocationHandler<T> implements
70         InvocationHandler, Serializable {
71
72         private static interface LockingSwitch {
73             Object readLock() throws Throwable;
74
75             Object writeLock() throws Throwable;
76
77             Object noLock() throws Throwable;
78         }
79
80         private static enum LockingType {
81             READ, WRITE, NONE;
82
83             public Object handleCase(LockingSwitch aSwitch) throws Throwable {
84                 switch (this) {
85                 case READ: {
86                     return aSwitch.readLock();
87                 }
88                 case WRITE: {
89                     return aSwitch.writeLock();
90                 }
91                 case NONE: {
92                     return aSwitch.noLock();
93                 }
94                 }
95                 throw new RuntimeException("Unexpected source location reached");
96             }
97
98         }
99
100         // Read-write locking for the service. 
101         private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(true);
102         private final ReentrantReadWriteLock.ReadLock rlock = rwlock.readLock();
103         private final ReentrantReadWriteLock.WriteLock wlock = rwlock
104             .writeLock();
105         
106         // Read-write locking for the cache of locking types. 
107         private final ReentrantReadWriteLock cacheRwlock = new ReentrantReadWriteLock(true);
108         private final ReentrantReadWriteLock.ReadLock cacheRlock = cacheRwlock.readLock();
109         private final ReentrantReadWriteLock.WriteLock cacheWlock = cacheRwlock
110             .writeLock();
111
112         /**
113          * Service which is being guarded by a lock.
114          */
115         private T service;
116
117         /**
118          * Cache mapping the method in the service implementation class to the locking type to be used. 
119          */
120         private Map<Method, LockingType> cache;
121
122         /**
123          * Constructs the invocation handler.
124          */
125         public LockingInvocationHandler(T aService) {
126             service = aService;
127             cache = new HashMap<Method, ReadWriteLockProxyFactory.LockingInvocationHandler.LockingType>();
128         }
129
130         @Override
131         public Object invoke(Object aProxy, final Method aMethod,
132             final Object[] aArgs) throws Throwable {
133
134             return getLockingType(aMethod).handleCase(new LockingSwitch() {
135                 @Override
136                 public Object readLock() throws Throwable {
137                     rlock.lock();
138                     try {
139                         return doInvoke(aMethod, aArgs);
140                     } finally {
141                         rlock.unlock();
142                     }
143                 }
144
145                 @Override
146                 public Object writeLock() throws Throwable {
147                     wlock.lock();
148                     try {
149                         return doInvoke(aMethod, aArgs);
150                     } finally {
151                         wlock.unlock();
152                     }
153                 }
154
155                 @Override
156                 public Object noLock() throws Throwable {
157                     return doInvoke(aMethod, aArgs);
158                 }
159             });
160         }
161
162         private LockingType getLockingType(Method aMethod)
163             throws NoSuchMethodException {
164             cacheRlock.lock();
165             try {
166                 LockingType type = cache.get(aMethod);
167                 if (type != null) {
168                     return type;
169                 }
170             } finally {
171                 cacheRlock.unlock();
172             }
173
174             // At the initial invocations, the write lock for the service is also 
175             // used for the cache. However, when all methods have been invoked already once,
176             // then the execution will never get here. 
177             cacheWlock.lock();
178             try {
179                 Method method = service.getClass().getMethod(aMethod.getName(),
180                     aMethod.getParameterTypes());
181                 LockingType type; 
182                 if (method.isAnnotationPresent(WriteLock.class)) {
183                     type = LockingType.WRITE;
184                 } else if (method.isAnnotationPresent(ReadLock.class)) {
185                     type = LockingType.READ;
186                 } else {
187                     type = LockingType.NONE;
188                 }
189                 cache.put(aMethod, type);
190                 return type; 
191             } finally {
192                 cacheWlock.unlock();
193             }
194         }
195
196         private Object doInvoke(Method aMethod, Object[] aArgs)
197             throws IllegalAccessException, Throwable {
198             try {
199                 return aMethod.invoke(service, aArgs);
200             } catch (InvocationTargetException e) {
201                 throw e.getCause();
202             }
203         }
204     }
205
206     /**
207      * Constructs the factory.
208      */
209     public ReadWriteLockProxyFactory() {
210         // Empty
211     }
212
213     /**
214      * Gets the proxy that delegates to the thread-specific instance set by
215      * {@link #set(Object)}
216      * 
217      * When at runtime the proxy cannot find lookup the object in JNDI, it
218      * throws {@link LookupException}.
219      * 
220      * @return Proxy.
221      */
222     public T getProxy(T aService, Class... aInterfaces) {
223         InvocationHandler handler = new LockingInvocationHandler<T>(aService);
224         Class proxyClass = Proxy.getProxyClass(aService.getClass()
225             .getClassLoader(), aInterfaces);
226         T proxy;
227         try {
228             proxy = (T) proxyClass.getConstructor(
229                 new Class[] { InvocationHandler.class }).newInstance(
230                 new Object[] { handler });
231             return proxy;
232         } catch (Exception e) {
233             throw new RuntimeException("Could not create proxy for " +
234                 aService.getClass().getName(), e);
235         }
236     }
237 }