--- /dev/null
+/*
+ * 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.
+ * <p>
+ * For example:
+ * <pre>
+ * 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);
+ * </pre>
+ *
+ * @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<T> {
+
+ /**
+ * Invocation handler that does a lookup in JNDI and invokes the method on
+ * the object it found.
+ *
+ * @author Erik Brakkee
+ */
+ private static class LockingInvocationHandler<T> 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 {
+ //System.out.println("locking type:" + this);
+ 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();
+ 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<Method, LockingType> cache;
+
+ /**
+ * Constructs the invocation handler.
+ */
+ public LockingInvocationHandler(T aService) {
+ service = aService;
+ cache = new HashMap<Method, ReadWriteLockProxyFactory.LockingInvocationHandler.LockingType>();
+ }
+
+ @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<T>(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);
+ }
+ }
+}