1ed722880386500b40fba8e42633313268f7b2ff
[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                 //System.out.println("locking type:" + this);
85                 switch (this) {
86                 case READ: {
87                     return aSwitch.readLock();
88                 }
89                 case WRITE: {
90                     return aSwitch.writeLock();
91                 }
92                 case NONE: {
93                     return aSwitch.noLock();
94                 }
95                 }
96                 throw new RuntimeException("Unexpected source location reached");
97             }
98
99         }
100
101         // Read-write locking for the service. 
102         private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(true);
103         private final ReentrantReadWriteLock.ReadLock rlock = rwlock.readLock();
104         private final ReentrantReadWriteLock.WriteLock wlock = rwlock
105             .writeLock();
106         
107         // Read-write locking for the cache of locking types. 
108         private final ReentrantReadWriteLock cacheRwlock = new ReentrantReadWriteLock();
109         private final ReentrantReadWriteLock.ReadLock cacheRlock = cacheRwlock.readLock();
110         private final ReentrantReadWriteLock.WriteLock cacheWlock = cacheRwlock
111             .writeLock();
112
113         /**
114          * Service which is being guarded by a lock.
115          */
116         private T service;
117
118         /**
119          * Cache mapping the method in the service implementation class to the locking type to be used. 
120          */
121         private Map<Method, LockingType> cache;
122
123         /**
124          * Constructs the invocation handler.
125          */
126         public LockingInvocationHandler(T aService) {
127             service = aService;
128             cache = new HashMap<Method, ReadWriteLockProxyFactory.LockingInvocationHandler.LockingType>();
129         }
130
131         @Override
132         public Object invoke(Object aProxy, final Method aMethod,
133             final Object[] aArgs) throws Throwable {
134
135             return getLockingType(aMethod).handleCase(new LockingSwitch() {
136                 @Override
137                 public Object readLock() throws Throwable {
138                     rlock.lock();
139                     try {
140                         return doInvoke(aMethod, aArgs);
141                     } finally {
142                         rlock.unlock();
143                     }
144                 }
145
146                 @Override
147                 public Object writeLock() throws Throwable {
148                     wlock.lock();
149                     try {
150                         return doInvoke(aMethod, aArgs);
151                     } finally {
152                         wlock.unlock();
153                     }
154                 }
155
156                 @Override
157                 public Object noLock() throws Throwable {
158                     return doInvoke(aMethod, aArgs);
159                 }
160             });
161         }
162
163         private LockingType getLockingType(Method aMethod)
164             throws NoSuchMethodException {
165             cacheRlock.lock();
166             try {
167                 LockingType type = cache.get(aMethod);
168                 if (type != null) {
169                     return type;
170                 }
171             } finally {
172                 cacheRlock.unlock();
173             }
174
175             // At the initial invocations, the write lock for the service is also 
176             // used for the cache. However, when all methods have been invoked already once,
177             // then the execution will never get here. 
178             cacheWlock.lock();
179             try {
180                 Method method = service.getClass().getMethod(aMethod.getName(),
181                     aMethod.getParameterTypes());
182                 LockingType type; 
183                 if (method.isAnnotationPresent(WriteLock.class)) {
184                     type = LockingType.WRITE;
185                 } else if (method.isAnnotationPresent(ReadLock.class)) {
186                     type = LockingType.READ;
187                 } else {
188                     type = LockingType.NONE;
189                 }
190                 cache.put(aMethod, type);
191                 return type; 
192             } finally {
193                 cacheWlock.unlock();
194             }
195         }
196
197         private Object doInvoke(Method aMethod, Object[] aArgs)
198             throws IllegalAccessException, Throwable {
199             try {
200                 return aMethod.invoke(service, aArgs);
201             } catch (InvocationTargetException e) {
202                 throw e.getCause();
203             }
204         }
205     }
206
207     /**
208      * Constructs the factory.
209      */
210     public ReadWriteLockProxyFactory() {
211         // Empty
212     }
213
214     /**
215      * Gets the proxy that delegates to the thread-specific instance set by
216      * {@link #set(Object)}
217      * 
218      * When at runtime the proxy cannot find lookup the object in JNDI, it
219      * throws {@link LookupException}.
220      * 
221      * @return Proxy.
222      */
223     public T getProxy(T aService, Class... aInterfaces) {
224         InvocationHandler handler = new LockingInvocationHandler<T>(aService);
225         Class proxyClass = Proxy.getProxyClass(aService.getClass()
226             .getClassLoader(), aInterfaces);
227         T proxy;
228         try {
229             proxy = (T) proxyClass.getConstructor(
230                 new Class[] { InvocationHandler.class }).newInstance(
231                 new Object[] { handler });
232             return proxy;
233         } catch (Exception e) {
234             throw new RuntimeException("Could not create proxy for " +
235                 aService.getClass().getName(), e);
236         }
237     }
238 }