Now the thread-specific proxies are serializable.
[utils] / support / general / src / main / java / org / wamblee / general / ThreadSpecificProxyFactory.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.general;
17
18 import java.io.Serializable;
19 import java.lang.reflect.InvocationHandler;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Proxy;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.UUID;
26
27 /**
28  * Thread-specific proxy is used to create implementations of interfaces that
29  * delegate to a thread-specific implementation of the service.
30  * 
31  * It can be used for instance to create a contextual reference to an entity manager 
32  * that delegates to a thread-specific instance. 
33  * 
34  * The {@link #set(Object)} method sets the current service instance for the current thread. 
35  * The {@link #get()} method gets the current service instance for the current thread. 
36  * The {@link #getProxy()} method gets a proxy that will delegate at runtime to the thread-specific 
37  * instance. The result from this method can be passed at construction of an object that will be used
38  * by multiple threads. 
39  * 
40  * This class is mostly used by infrastructure code (utilities) and test tools.
41  * 
42  * @param T
43  *            Interface to proxy.
44  * @author Erik Brakkee
45  * 
46  */
47 public class ThreadSpecificProxyFactory<T> {
48     
49     /**
50      * We store a map of unique ids of invocation handlers to thread local storage of the 
51      * service. In this way, serialiability of the generated proxy is obtained (required by 
52      * framweorks such as wicket). Also, different factories will still be separate and never
53      * use the same threadlocal storage. 
54      */
55     private static Map<String,ThreadLocal> STORAGE = 
56         initializeThreadLocal();
57     
58     private static class ThreadSpecificInvocationHandler<T> implements InvocationHandler, Serializable {
59         
60         private String id; 
61         private Class clazz; 
62         
63         public ThreadSpecificInvocationHandler(String aId, Class aClass) { 
64             id = aId; 
65             clazz = aClass;
66         }
67
68         @Override
69         public Object invoke(Object aProxy, Method aMethod, Object[] aArgs)
70             throws Throwable {
71             ThreadLocal<T> local = STORAGE.get(id);
72             T actualSvc = local.get();
73             if ( aMethod.getName().equals("toString") && actualSvc == null) { 
74                 return "Thread-specific proxy for '" + clazz.getName() + "'";
75             }
76             try {
77                 return aMethod.invoke(actualSvc, aArgs);
78             } catch (InvocationTargetException e) {
79                 throw e.getCause();
80             }
81         }
82     }
83
84     private ThreadLocal<T> svc;
85     private Class clazz;
86     private T proxy; 
87
88     /**
89      * Constructs the factory.
90      * 
91      * @param aClass
92      *            Interface class of the service to proxy.
93      */
94     public ThreadSpecificProxyFactory(Class<T> aClass) {
95         if (!aClass.isInterface()) {
96             throw new IllegalArgumentException("Class " + aClass.getName() +
97                 " is not an interface");
98         }
99         svc = new ThreadLocal<T>();
100         clazz = aClass;
101         proxy = createProxy(); 
102     }
103
104     private static Map<String, ThreadLocal> initializeThreadLocal() {
105         Map<String,ThreadLocal> map = new HashMap<String,ThreadLocal>();
106         return map;
107     }
108
109     /**
110      * Sets the thread-specific service.
111      * 
112      * @param aService
113      *            Service, use null value to reset.
114      */
115     public void set(T aService) {
116         svc.set(aService);
117     }
118     
119     /**
120      * Gets the current thread-specific service.
121      * To get a contextual reference that can be used by any thread but delegates to a thread-specific
122      * instance, use {@link #getProxy()}.  
123      * @return Service. 
124      */
125     public T get() { 
126         return svc.get();
127     }
128
129     /**
130      * Gets the proxy that delegates to the thread-specific instance set by
131      * {@link #set(Object)}
132      * 
133      * @return Proxy.
134      */
135     public T getProxy() {
136         return proxy;
137     }
138
139     private T createProxy() {
140         String id = UUID.randomUUID().toString();
141         STORAGE.put(id, svc);
142         InvocationHandler handler = new ThreadSpecificInvocationHandler(id, clazz);
143         Class proxyClass = Proxy.getProxyClass(clazz.getClassLoader(),
144             new Class[] { clazz });
145         T proxyObj;
146         try {
147             proxyObj = (T) proxyClass.getConstructor(
148                 new Class[] { InvocationHandler.class }).newInstance(
149                 new Object[] { handler });
150             return proxyObj;
151         } catch (Exception e) {
152             throw new RuntimeException("Could not create proxy for " +
153                 clazz.getName(), e);
154         }
155     }
156 }