2 * Copyright 2005-2011 the original author or authors.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.wamblee.concurrency;
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;
25 import java.util.concurrent.locks.ReentrantReadWriteLock;
27 import javax.naming.InitialContext;
28 import javax.naming.NamingException;
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.
38 * class Service implements MyApi {
43 * // no locking by default
48 * Service svc = new Service();
50 * // create service guarded by read-write locking.
51 * MyApi guardedSvc = new ReadWriteLockProxyFactory().getProxy(svc, MyApi.class);
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.
58 * @author Erik Brakkee
61 public class ReadWriteLockProxyFactory<T> {
64 * Invocation handler that does a lookup in JNDI and invokes the method on
65 * the object it found.
67 * @author Erik Brakkee
69 private static class LockingInvocationHandler<T> implements
70 InvocationHandler, Serializable {
72 private static interface LockingSwitch {
73 Object readLock() throws Throwable;
75 Object writeLock() throws Throwable;
77 Object noLock() throws Throwable;
80 private static enum LockingType {
83 public Object handleCase(LockingSwitch aSwitch) throws Throwable {
84 //System.out.println("locking type:" + this);
87 return aSwitch.readLock();
90 return aSwitch.writeLock();
93 return aSwitch.noLock();
96 throw new RuntimeException("Unexpected source location reached");
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
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
114 * Service which is being guarded by a lock.
119 * Cache mapping the method in the service implementation class to the locking type to be used.
121 private Map<Method, LockingType> cache;
124 * Constructs the invocation handler.
126 public LockingInvocationHandler(T aService) {
128 cache = new HashMap<Method, ReadWriteLockProxyFactory.LockingInvocationHandler.LockingType>();
132 public Object invoke(Object aProxy, final Method aMethod,
133 final Object[] aArgs) throws Throwable {
135 return getLockingType(aMethod).handleCase(new LockingSwitch() {
137 public Object readLock() throws Throwable {
140 return doInvoke(aMethod, aArgs);
147 public Object writeLock() throws Throwable {
150 return doInvoke(aMethod, aArgs);
157 public Object noLock() throws Throwable {
158 return doInvoke(aMethod, aArgs);
163 private LockingType getLockingType(Method aMethod)
164 throws NoSuchMethodException {
167 LockingType type = cache.get(aMethod);
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.
180 Method method = service.getClass().getMethod(aMethod.getName(),
181 aMethod.getParameterTypes());
183 if (method.isAnnotationPresent(WriteLock.class)) {
184 type = LockingType.WRITE;
185 } else if (method.isAnnotationPresent(ReadLock.class)) {
186 type = LockingType.READ;
188 type = LockingType.NONE;
190 cache.put(aMethod, type);
197 private Object doInvoke(Method aMethod, Object[] aArgs)
198 throws IllegalAccessException, Throwable {
200 return aMethod.invoke(service, aArgs);
201 } catch (InvocationTargetException e) {
208 * Constructs the factory.
210 public ReadWriteLockProxyFactory() {
215 * Gets the proxy that delegates to the thread-specific instance set by
216 * {@link #set(Object)}
218 * When at runtime the proxy cannot find lookup the object in JNDI, it
219 * throws {@link LookupException}.
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);
229 proxy = (T) proxyClass.getConstructor(
230 new Class[] { InvocationHandler.class }).newInstance(
231 new Object[] { handler });
233 } catch (Exception e) {
234 throw new RuntimeException("Could not create proxy for " +
235 aService.getClass().getName(), e);