View Javadoc

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 }