/* * Copyright 2005-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.wamblee.concurrency; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.naming.InitialContext; import javax.naming.NamingException; /** * Proxy factory that provides locking using {@link ReentrantReadWriteLock} based * on the {@link ReadLock} and {@link WriteLock} annotations. The annotations must be * applied to the service implementation methods. Annotations on the interfaces are ignored. * It uses fair read-write locking. *

* For example: *

 *   class Service implements MyApi {
 *       @ReadLock
 *       void doX() { ... }
 *       @WriteLock
 *       void doY() { ... }
 *       // no locking by default
 *       void doZ() { ... } 
 *   }
 *   
 *   // create service
 *   Service svc = new Service();
 *   
 *   // create service guarded by read-write locking.
 *   MyApi guardedSvc = new ReadWriteLockProxyFactory().getProxy(svc, MyApi.class);
 * 
* * @param T service interface to proxy. In case a service implements multiple interfaces, * it can be convenient to create a new interface that combines these interfaces so that * there is an interface type that represents all the implemented interfaces. * * @author Erik Brakkee * */ public class ReadWriteLockProxyFactory { /** * Invocation handler that does a lookup in JNDI and invokes the method on * the object it found. * * @author Erik Brakkee */ private static class LockingInvocationHandler implements InvocationHandler, Serializable { private static interface LockingSwitch { Object readLock() throws Throwable; Object writeLock() throws Throwable; Object noLock() throws Throwable; } private static enum LockingType { READ, WRITE, NONE; public Object handleCase(LockingSwitch aSwitch) throws Throwable { switch (this) { case READ: { return aSwitch.readLock(); } case WRITE: { return aSwitch.writeLock(); } case NONE: { return aSwitch.noLock(); } } throw new RuntimeException("Unexpected source location reached"); } } // Read-write locking for the service. private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(true); private final ReentrantReadWriteLock.ReadLock rlock = rwlock.readLock(); private final ReentrantReadWriteLock.WriteLock wlock = rwlock .writeLock(); // Read-write locking for the cache of locking types. private final ReentrantReadWriteLock cacheRwlock = new ReentrantReadWriteLock(true); private final ReentrantReadWriteLock.ReadLock cacheRlock = cacheRwlock.readLock(); private final ReentrantReadWriteLock.WriteLock cacheWlock = cacheRwlock .writeLock(); /** * Service which is being guarded by a lock. */ private T service; /** * Cache mapping the method in the service implementation class to the locking type to be used. */ private Map cache; /** * Constructs the invocation handler. */ public LockingInvocationHandler(T aService) { service = aService; cache = new HashMap(); } @Override public Object invoke(Object aProxy, final Method aMethod, final Object[] aArgs) throws Throwable { return getLockingType(aMethod).handleCase(new LockingSwitch() { @Override public Object readLock() throws Throwable { rlock.lock(); try { return doInvoke(aMethod, aArgs); } finally { rlock.unlock(); } } @Override public Object writeLock() throws Throwable { wlock.lock(); try { return doInvoke(aMethod, aArgs); } finally { wlock.unlock(); } } @Override public Object noLock() throws Throwable { return doInvoke(aMethod, aArgs); } }); } private LockingType getLockingType(Method aMethod) throws NoSuchMethodException { cacheRlock.lock(); try { LockingType type = cache.get(aMethod); if (type != null) { return type; } } finally { cacheRlock.unlock(); } // At the initial invocations, the write lock for the service is also // used for the cache. However, when all methods have been invoked already once, // then the execution will never get here. cacheWlock.lock(); try { Method method = service.getClass().getMethod(aMethod.getName(), aMethod.getParameterTypes()); LockingType type; if (method.isAnnotationPresent(WriteLock.class)) { type = LockingType.WRITE; } else if (method.isAnnotationPresent(ReadLock.class)) { type = LockingType.READ; } else { type = LockingType.NONE; } cache.put(aMethod, type); return type; } finally { cacheWlock.unlock(); } } private Object doInvoke(Method aMethod, Object[] aArgs) throws IllegalAccessException, Throwable { try { return aMethod.invoke(service, aArgs); } catch (InvocationTargetException e) { throw e.getCause(); } } } /** * Constructs the factory. */ public ReadWriteLockProxyFactory() { // Empty } /** * Gets the proxy that delegates to the thread-specific instance set by * {@link #set(Object)} * * When at runtime the proxy cannot find lookup the object in JNDI, it * throws {@link LookupException}. * * @return Proxy. */ public T getProxy(T aService, Class... aInterfaces) { InvocationHandler handler = new LockingInvocationHandler(aService); Class proxyClass = Proxy.getProxyClass(aService.getClass() .getClassLoader(), aInterfaces); T proxy; try { proxy = (T) proxyClass.getConstructor( new Class[] { InvocationHandler.class }).newInstance( new Object[] { handler }); return proxy; } catch (Exception e) { throw new RuntimeException("Could not create proxy for " + aService.getClass().getName(), e); } } }