From 137d09cffb180142d1f371e44ff7072cc0d82dbd Mon Sep 17 00:00:00 2001 From: Erik Brakkee Date: Wed, 23 Mar 2011 19:49:12 +0000 Subject: [PATCH] --- .../org/wamblee/concurrency/ReadLock.java | 33 +++ .../ReadWriteLockProxyFactory.java | 238 ++++++++++++++++++ .../org/wamblee/concurrency/WriteLock.java | 33 +++ .../org/wamblee/concurrency/package-info.java | 2 - 4 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 support/general/src/main/java/org/wamblee/concurrency/ReadLock.java create mode 100644 support/general/src/main/java/org/wamblee/concurrency/ReadWriteLockProxyFactory.java create mode 100644 support/general/src/main/java/org/wamblee/concurrency/WriteLock.java diff --git a/support/general/src/main/java/org/wamblee/concurrency/ReadLock.java b/support/general/src/main/java/org/wamblee/concurrency/ReadLock.java new file mode 100644 index 00000000..a7e6b8b9 --- /dev/null +++ b/support/general/src/main/java/org/wamblee/concurrency/ReadLock.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005-2010 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.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to apply to method to indicated they should be protected by a + * read-lock, see {@link ReadWriteLockProxyFactory}. + * + * @author Erik Brakkee + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface ReadLock { + // Empty. +} diff --git a/support/general/src/main/java/org/wamblee/concurrency/ReadWriteLockProxyFactory.java b/support/general/src/main/java/org/wamblee/concurrency/ReadWriteLockProxyFactory.java new file mode 100644 index 00000000..1ed72288 --- /dev/null +++ b/support/general/src/main/java/org/wamblee/concurrency/ReadWriteLockProxyFactory.java @@ -0,0 +1,238 @@ +/* + * 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 { + //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 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); + } + } +} diff --git a/support/general/src/main/java/org/wamblee/concurrency/WriteLock.java b/support/general/src/main/java/org/wamblee/concurrency/WriteLock.java new file mode 100644 index 00000000..d548022c --- /dev/null +++ b/support/general/src/main/java/org/wamblee/concurrency/WriteLock.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005-2010 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.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to apply to method to indicated they should be protected by a + * write-lock, see {@link ReadWriteLockProxyFactory}. + * + * @author Erik Brakkee + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface WriteLock { + // Empty. +} diff --git a/support/general/src/main/java/org/wamblee/concurrency/package-info.java b/support/general/src/main/java/org/wamblee/concurrency/package-info.java index 68507296..09063689 100644 --- a/support/general/src/main/java/org/wamblee/concurrency/package-info.java +++ b/support/general/src/main/java/org/wamblee/concurrency/package-info.java @@ -15,7 +15,5 @@ */ /** * This package provides utilities for dealing with concurrency. - * - * Currently, pessimistic locks are provided but other types can be added as well. */ package org.wamblee.concurrency; \ No newline at end of file -- 2.31.1