From 5ddf0de8b6ecb45f2420c9b2e68f82cb7c52dfbb Mon Sep 17 00:00:00 2001 From: Erik Brakkee Date: Sat, 31 Jul 2010 22:43:03 +0000 Subject: [PATCH] --- .../SerializableInvocationHandler.java | 77 ++++++++++++ .../general/SerializableProxyFactory.java | 113 ++++++++++++++++++ .../general/ThreadSpecificProxyFactory.java | 5 + .../general/SerializableProxyFactoryTest.java | 94 +++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 support/general/src/main/java/org/wamblee/general/SerializableInvocationHandler.java create mode 100644 support/general/src/main/java/org/wamblee/general/SerializableProxyFactory.java create mode 100644 support/general/src/test/java/org/wamblee/general/SerializableProxyFactoryTest.java diff --git a/support/general/src/main/java/org/wamblee/general/SerializableInvocationHandler.java b/support/general/src/main/java/org/wamblee/general/SerializableInvocationHandler.java new file mode 100644 index 00000000..51704b06 --- /dev/null +++ b/support/general/src/main/java/org/wamblee/general/SerializableInvocationHandler.java @@ -0,0 +1,77 @@ +/* + * 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.general; + +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Serialiable invocation handler that delegate to a possibly non-serializable object. + * The trick is to store the object in a static map with a unique id and use the id to + * retrieve the object instead of storing the object. + * + * @author Erik Brakkee + * + * @param + */ +class SerializableInvocationHandler implements InvocationHandler, + Serializable { + + /** + * We store a map of unique ids of invocation handlers to thread local + * storage of the service. In this way, serialiability of the generated + * proxy is obtained (required by framweorks such as wicket). Also, + * different factories will still be separate and never use the same + * threadlocal storage. + */ + private static Map STORAGE = new ConcurrentHashMap(); + + private static AtomicInteger COUNTER = new AtomicInteger(); + + private int id; + private Class clazz; + + /** + * Constructs the handler. + * + * @param aSvc + * Thread local for the service. + * @param aClass + * Service interface class. + */ + public SerializableInvocationHandler(T aSvc, Class aClass) { + id = COUNTER.incrementAndGet(); + clazz = aClass; + STORAGE.put(id, aSvc); + } + + @Override + public Object invoke(Object aProxy, Method aMethod, Object[] aArgs) + throws Throwable { + + T local = (T)STORAGE.get(id); + try { + return aMethod.invoke(local, aArgs); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } +} \ No newline at end of file diff --git a/support/general/src/main/java/org/wamblee/general/SerializableProxyFactory.java b/support/general/src/main/java/org/wamblee/general/SerializableProxyFactory.java new file mode 100644 index 00000000..34b7e3a5 --- /dev/null +++ b/support/general/src/main/java/org/wamblee/general/SerializableProxyFactory.java @@ -0,0 +1,113 @@ +/* + * 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.general; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +/** + *

+ * Serializable proxy factory that allows to create serializable proxies to + * objects that are themselves not serializable. + *

+ * + *

+ * This class does not do any cleanup so it is recommended to only use this in + * test utilities. + *

+ * + * + * @author Erik Brakkee + * + * @param + */ +public class SerializableProxyFactory { + + private T svc; + private Class clazz; + private T proxy; + + /** + * Constructs the factory. + * + * @param aClass + * Interface class of the service to proxy. + */ + public SerializableProxyFactory(Class aClass) { + this(aClass, null); + } + + /** + * Constructs the factory with a callback to create thread-specific objects + * automatically. + * + * @param aClass + * Interface class of the service to proxy. + * @param aCallback + * Callback to create the object if it does not exist. When null, + * then no initialization is done. + */ + public SerializableProxyFactory(Class aClass, T aSvc) { + if (!aClass.isInterface()) { + throw new IllegalArgumentException("Class " + aClass.getName() + + " is not an interface"); + } + if (!aClass.isInstance(aSvc)) { + throw new IllegalArgumentException("Object " + aSvc + + " cannot be cast to " + aSvc.getClass()); + } + svc = aSvc; + clazz = aClass; + proxy = createProxy(); + + } + + /** + * Gets the underlying service. + * + * @return Service. + */ + public T get() { + return svc; + } + + /** + * Gets the proxy that delegates to the thread-specific instance set by + * {@link #set(Object)} + * + * @return Proxy. + */ + public T getProxy() { + return proxy; + } + + private T createProxy() { + InvocationHandler handler = new SerializableInvocationHandler(svc, + clazz); + Class proxyClass = Proxy.getProxyClass(clazz.getClassLoader(), + new Class[] { clazz }); + T proxyObj; + try { + proxyObj = (T) proxyClass.getConstructor( + new Class[] { InvocationHandler.class }).newInstance( + new Object[] { handler }); + return proxyObj; + } catch (Exception e) { + throw new RuntimeException("Could not create proxy for " + + clazz.getName(), e); + } + } +} diff --git a/support/general/src/main/java/org/wamblee/general/ThreadSpecificProxyFactory.java b/support/general/src/main/java/org/wamblee/general/ThreadSpecificProxyFactory.java index db51ba1c..ac1ec8fc 100644 --- a/support/general/src/main/java/org/wamblee/general/ThreadSpecificProxyFactory.java +++ b/support/general/src/main/java/org/wamblee/general/ThreadSpecificProxyFactory.java @@ -48,6 +48,11 @@ import java.lang.reflect.Proxy; * distributed context where it can be sent to another JVM. *

* + *

+ * This class currently does not do any cleanup. So it should not be used in production code + * but only in test utilities. + *

+ * * @param T * Interface to proxy. * @author Erik Brakkee diff --git a/support/general/src/test/java/org/wamblee/general/SerializableProxyFactoryTest.java b/support/general/src/test/java/org/wamblee/general/SerializableProxyFactoryTest.java new file mode 100644 index 00000000..5ff74768 --- /dev/null +++ b/support/general/src/test/java/org/wamblee/general/SerializableProxyFactoryTest.java @@ -0,0 +1,94 @@ +/* + * 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.general; + +import static org.mockito.Mockito.*; + +import org.apache.derby.impl.sql.execute.OnceResultSet; +import org.junit.Before; +import org.junit.Test; + +public class SerializableProxyFactoryTest { + + public static interface MyInterface { + void doSomething(); + } + + private MyInterface intf; + + @Before + public void setUp() { + intf = mock(MyInterface.class); + } + + @Test + public void testProxyWorks() { + SerializableProxyFactory factory = new SerializableProxyFactory( + MyInterface.class, intf); + MyInterface proxy = factory.getProxy(); + proxy.doSomething(); + verify(intf).doSomething(); + } + + @Test + public void testProxiesAreUnique() { + SerializableProxyFactory factory1 = new SerializableProxyFactory( + MyInterface.class, intf); + MyInterface proxy1 = factory1.getProxy(); + + MyInterface intf2 = mock(MyInterface.class); + SerializableProxyFactory factory2 = new SerializableProxyFactory( + MyInterface.class, intf2); + MyInterface proxy2 = factory2.getProxy(); + + proxy1.doSomething(); + verify(intf, times(1)).doSomething(); + verifyNoMoreInteractions(intf); + verifyNoMoreInteractions(intf2); + + reset(intf); + reset(intf2); + + proxy2.doSomething(); + verify(intf2, times(1)).doSomething(); + verifyNoMoreInteractions(intf); + verifyNoMoreInteractions(intf2); + } + + @Test + public void testStillWorksAfterSerialization() throws Exception { + SerializableProxyFactory factory = new SerializableProxyFactory( + MyInterface.class, intf); + MyInterface proxy = factory.getProxy(); + + MyInterface deserialized = ObjectSerializationUtils.deserialize( + ObjectSerializationUtils.serialize(proxy), MyInterface.class); + deserialized.doSomething(); + verify(intf).doSomething(); + } + + @Test(expected = IllegalArgumentException.class) + public void testWrongServiceType() { + SerializableProxyFactory factory = new SerializableProxyFactory( + MyInterface.class, "hello"); + } + + @Test(expected = IllegalArgumentException.class) + public void testNotAnInterface() { + SerializableProxyFactory factory = new SerializableProxyFactory( + String.class, "hello"); + } +} -- 2.31.1