org.wamblee.concurrency.ReadWriteLockProxyFactory Maven / Gradle / Ivy
The newest version!
/*
* 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);
}
}
}