1 package org.lsst.ccs.utilities.dispatch;
2
3 import java.util.LinkedHashMap;
4 import java.util.concurrent.Callable;
5 import java.util.concurrent.Future;
6 import java.util.concurrent.FutureTask;
7 import java.util.concurrent.atomic.AtomicLong;
8
9 /**
10 * Typical use of this pattern is to have two threads:
11 * <UL>
12 * <LI> one is going to write commands to a stream. Before writing the command data
13 * the calling thread registers a code that may handle the corresponding asynchronous reply.
14 * This is done by invoking one of the <TT>register</TT> methods.
15 * So there must be a token that must be sent with the command and must be delivered
16 * with the correponding response.
17 * <LI> The other Thread is reading responses on a stream.
18 * Each time it receives a response it gathers: the token needed for correlation, and the data
19 * needed to execute the response code. The correlation token and the data is passed
20 * to the <TT>callBack</TT> method of the InvocationDispenser</LI>
21 * </UL>
22 * examples:
23 * <PRE>
24 * public synchronized void replytoA(Object elem1, elem2) {
25 * //do something
26 * }
27 * public void sendCommandA(Object someData) {
28 * String token = this.myInvocationDispenser.register( new Invoker() {
29 * public void invoke(Object... args) {
30 * //check args ?
31 * replytoA(args[0], args[1]) ;
32 * }
33 *
34 * } ) ;
35 * ACommand toBeSent = buildCommand(token, someData) ;
36 * sendCommand(toBeSent)
37 * }
38 * </PRE>
39 *
40 * The listening Thread :
41 * <PRE>
42 * public void run() {
43 * while(doItAgain) {
44 * // don't forget exception handling!
45 * CommandData receivedData = readReply() ;
46 * String correlationString = receivedData.getToken() ;
47 * Object[] responseData = receivedData.getData() ;
48 * myInvocationDispenser.callBack(correlationString, responseData) ;
49 * }
50 * }
51 * </PRE>
52 * @author bamade
53 */
54 // Date: 30/05/12
55
56 public class InvocationDispenser {
57 public static class TokenLostException extends Exception {
58 public final String token ;
59
60 public TokenLostException(String token) {
61 this.token = token;
62 }
63
64 public String toString() {
65 return super.toString() + " " + token ;
66 }
67 }
68 class DelegateInvoker implements Invoker{
69 Invoker delegate ;
70
71 Object res ;
72
73 Object getRes() {
74 return res ;
75 }
76
77 FutureTask task = new FutureTask(new Callable() {
78 @Override
79 public Object call() throws Exception {
80 return getRes();
81 }
82 });
83
84 public DelegateInvoker(Invoker delegate) {
85 this.delegate = delegate;
86 }
87
88
89 @Override
90 public Object invoke(Object... args) throws Exception{
91 res = delegate.invoke(args);
92 task.run() ;
93 return res ;
94 }
95
96 public FutureTask getTask() {
97 return task ;
98 }
99 }
100 //TODO: naive seed!!! use UUID ?
101 private static AtomicLong seed = new AtomicLong(System.nanoTime()) ;
102
103 // TODO :get rid of "old" registrations that are did not receive a response
104 private LinkedHashMap<String, Invoker> map = new LinkedHashMap<String, Invoker>() ;
105
106 /**
107 * generates a unique token (for this ClassLoader context!)
108 * @return
109 */
110 public String generateToken() {
111 long longKey = seed.getAndIncrement() ;
112 String key = Long.toString(longKey,Character.MAX_RADIX);
113 return key ;
114 }
115 /**
116 * registers an Invoker and returns a token to be used to run the invocation code
117 * @param invoker should not be null.
118 * @return
119 */
120 public synchronized String register(Invoker invoker) {
121 if(invoker == null) {
122 throw new IllegalArgumentException("null Invoker") ;
123 }
124 String key = generateToken() ;
125 map.put(key, invoker) ;
126 return key ;
127 }
128
129 /**
130 * registers an invoker and the token is given by the calling code.
131 * @param token
132 * @param invoker
133 * @return
134 */
135 public synchronized String register(String token ,Invoker invoker) {
136 if(invoker == null) {
137 throw new IllegalArgumentException("null Invoker") ;
138 }
139 map.put(token, invoker) ;
140 return token ;
141 }
142
143 /**
144 * Gets a <TT>Future</TT> object from this task registration.
145 * This can be used by methods that are "nearly synchronous" : that is
146 * it is a synchronous call but the process that reads the response may be polluted
147 * by other incoming events.
148 * <P>
149 * Exemple of call may be :
150 * <PRE>
151 * public String pleaseCall(String arg) {
152 * String token = myInvocationDispenser.generateToken() ;
153 * Future future = registerFuture(token, new Invoker() {
154 * public Object invoke(Object... args) {
155 * return args[0] ;
156 * }
157 * }
158 * ACommand toBeSent = buildCommand(token, arg) ;
159 * sendCommand(toBeSent)
160 * return (String) future.get() ;
161 * }
162 * </PRE>
163 * @param token
164 * @param invoker
165 * @return
166 */
167 public synchronized Future registerFuture(String token, Invoker invoker) {
168 if(invoker == null) {
169 throw new IllegalArgumentException("null Invoker") ;
170 }
171 DelegateInvoker delegateInvoker = new DelegateInvoker(invoker) ;
172 map.put(token, delegateInvoker) ;
173 return delegateInvoker.getTask() ;
174 }
175
176 /**
177 * Removes a response task
178 * @param token
179 */
180 public synchronized void dumpTask(String token) {
181 map.remove(token) ;
182 }
183
184
185 /**
186 * tells if the token is known.
187 * <B>beware</B> : in a multihreaded environment there may be race condition
188 * where this method returns true but where an Exception is fired upon call of
189 * callback Method.
190 * @param token
191 * @return
192 */
193 public synchronized boolean isTokenUsed(String token) {
194 if(token == null) return false ;
195 return map.containsKey(token) ;
196 }
197
198 /**
199 * calls the registered invoker which corresponds to the token.
200 * The token is no longer valid.
201 * @param token
202 * @param args
203 * @return
204 * @throws Exception
205 */
206 public Object callBack(String token, Object... args) throws Exception{
207 Invoker invoker ;
208 synchronized (this) {
209 invoker = map.remove(token) ;
210 if(invoker == null) {
211 throw new TokenLostException(token) ;
212 }
213 }
214 return invoker.invoke(args) ;
215 }
216
217 /**
218 * calls the registered invoker which corresponds to the token.
219 * The token is still valid.
220 * That is to be used when there is a registration of a code
221 * that is to treat purely incoming events (without a corresponding send).
222 * The invoker should be able to run many times.
223 * @param token
224 * @param args
225 * @return
226 * @throws Exception
227 */
228 public Object call(String token, Object... args) throws Exception{
229 Invoker invoker ;
230 synchronized (this) {
231 invoker = map.get(token) ;
232 if(invoker == null) {
233 throw new TokenLostException(token) ;
234 }
235 }
236 return invoker.invoke(args) ;
237
238 }
239 }