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 {
86 return aSwitch.readLock();
89 return aSwitch.writeLock();
92 return aSwitch.noLock();
95 throw new RuntimeException("Unexpected source location reached");
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
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
113 * Service which is being guarded by a lock.
118 * Cache mapping the method in the service implementation class to the locking type to be used.
120 private Map<Method, LockingType> cache;
123 * Constructs the invocation handler.
125 public LockingInvocationHandler(T aService) {
127 cache = new HashMap<Method, ReadWriteLockProxyFactory.LockingInvocationHandler.LockingType>();
131 public Object invoke(Object aProxy, final Method aMethod,
132 final Object[] aArgs) throws Throwable {
134 return getLockingType(aMethod).handleCase(new LockingSwitch() {
136 public Object readLock() throws Throwable {
139 return doInvoke(aMethod, aArgs);
146 public Object writeLock() throws Throwable {
149 return doInvoke(aMethod, aArgs);
156 public Object noLock() throws Throwable {
157 return doInvoke(aMethod, aArgs);
162 private LockingType getLockingType(Method aMethod)
163 throws NoSuchMethodException {
166 LockingType type = cache.get(aMethod);
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.
179 Method method = service.getClass().getMethod(aMethod.getName(),
180 aMethod.getParameterTypes());
182 if (method.isAnnotationPresent(WriteLock.class)) {
183 type = LockingType.WRITE;
184 } else if (method.isAnnotationPresent(ReadLock.class)) {
185 type = LockingType.READ;
187 type = LockingType.NONE;
189 cache.put(aMethod, type);
196 private Object doInvoke(Method aMethod, Object[] aArgs)
197 throws IllegalAccessException, Throwable {
199 return aMethod.invoke(service, aArgs);
200 } catch (InvocationTargetException e) {
207 * Constructs the factory.
209 public ReadWriteLockProxyFactory() {
214 * Gets the proxy that delegates to the thread-specific instance set by
215 * {@link #set(Object)}
217 * When at runtime the proxy cannot find lookup the object in JNDI, it
218 * throws {@link LookupException}.
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);
228 proxy = (T) proxyClass.getConstructor(
229 new Class[] { InvocationHandler.class }).newInstance(
230 new Object[] { handler });
232 } catch (Exception e) {
233 throw new RuntimeException("Could not create proxy for " +
234 aService.getClass().getName(), e);