(no commit message)
authorErik Brakkee <erik@brakkee.org>
Wed, 23 Mar 2011 19:49:12 +0000 (19:49 +0000)
committerErik Brakkee <erik@brakkee.org>
Wed, 23 Mar 2011 19:49:12 +0000 (19:49 +0000)
support/general/src/main/java/org/wamblee/concurrency/ReadLock.java [new file with mode: 0644]
support/general/src/main/java/org/wamblee/concurrency/ReadWriteLockProxyFactory.java [new file with mode: 0644]
support/general/src/main/java/org/wamblee/concurrency/WriteLock.java [new file with mode: 0644]
support/general/src/main/java/org/wamblee/concurrency/package-info.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 (file)
index 0000000..a7e6b8b
--- /dev/null
@@ -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 (file)
index 0000000..1ed7228
--- /dev/null
@@ -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.
+ * <p>
+ * For example: 
+ * <pre>
+ *   class Service implements MyApi {
+ *       &#064;ReadLock
+ *       void doX() { ... }
+ *       &#064;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);
+        }
+    }
+}
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 (file)
index 0000000..d548022
--- /dev/null
@@ -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.
+}
index 68507296cd9122eaa4f3bda37d68a3814d84024d..0906368912c2017b833f20dd62d29342a2fac566 100644 (file)
@@ -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