View Javadoc

1   package org.lsst.ccs.bus;
2   
3   //import org.apache.log4j.Logger;
4   
5   import org.lsst.ccs.utilities.beanutils.Optional;
6   import org.lsst.ccs.utilities.logging.Logger;
7   import org.lsst.ccs.utilities.tracers.Tracer;
8   
9   import java.io.IOException;
10  import java.util.IdentityHashMap;
11  import java.util.List;
12  import java.util.concurrent.CopyOnWriteArrayList;
13  
14  /**
15   * Calls a BusMessagingLayer and adds all that is necessary to handle messages
16   * following application layer concerns.
17   * <p>
18   * Each is bound to a subsystem.
19   * <p>
20   * This class is meant to be subclassed if there is a need to setup complex
21   * correlations between messages (or more generally for "application layer" concerns)
22   * </P>
23   * <B>beware</B> <TT>ThreadLocal</TT> objects may be changed in future releases: they may be
24   * moved to <I>Context</I> objects carried through thread creations.
25   */
26  public class BusApplicationLayer {
27      protected final String subsystemName;
28      protected final BusMessagingLayer busMessagingLayer;
29      protected static Logger log = Logger.getLogger("org.lsst.ccs.bus.layer");
30  
31      /* REMOVED : do not work
32      protected ThreadLocal<String> localCorrelID = new ThreadLocal<String>();
33      protected ThreadLocal<String> localOrigin = new ThreadLocal<String>();
34      protected ThreadLocal<Command> localCommand = new ThreadLocal<Command>();
35      */
36      String token = java.util.UUID.randomUUID().toString(); // for arbitrator
37      private final MembershipMultiplexor membershipMultiplexor = new MembershipMultiplexor();
38  
39  
40      /**
41       * creates an entry point to communication for the subsystem.
42       * One is not supposed to interact directly with the <TT>BusMessagingLayer</TT>
43       * afterwards except for administrative purposes (example: MembershipListeners).
44       *
45       * @param subsystemName     rules of naming apply
46       * @param busMessagingLayer transport layer
47       * @throws NullPointerException if busMessagingLayer is null
48       */
49      public BusApplicationLayer(String subsystemName, BusMessagingLayer busMessagingLayer) {
50          this.subsystemName = subsystemName;
51          this.busMessagingLayer = busMessagingLayer;
52      }
53  
54      public String getToken() {
55          return token;
56      }
57  
58      /**
59       * registers current subsystem to the Command bus
60       *
61       * @throws IOException
62       */
63      public void registerToCommand() throws IOException {
64          busMessagingLayer.register(subsystemName, Bus.COMMAND);
65      }
66  
67      /**
68       * registers current subsystem to the Status bus
69       *
70       * @throws IOException
71       */
72      public void registerToStatus() throws IOException {
73          busMessagingLayer.register(subsystemName, Bus.STATUS);
74      }
75  
76      /**
77       * registers current subsystem to the logging bus
78       *
79       * @throws IOException
80       */
81      public void registerToLog() throws IOException {
82          busMessagingLayer.register(subsystemName, Bus.LOG);
83      }
84  
85      /**
86       * utility method: parse the destination string in Commands.
87       * destination is a list of comma separated list of names(beware of spaces!).
88       * rules: if there is a "*" in the destination list then the message is broadcast;
89       * if there is a slash in a name only the first part of the name (before the slash)
90       * is used for transport destination (so for instance "sft/carrousel" is sent to "sft").
91       *
92       * @param destination
93       * @return an array of agent names or an empty array if broadcasting is requested
94       */
95      protected String[] parseDestination(String destination) {
96          String[] dests = destination.split(",");
97          for (int ix = 0; ix < dests.length; ix++) {
98              String dest = dests[ix];
99              if ("*".equals(dest)) {
100                 dests = new String[0];
101                 break;
102             }
103             if (dest.contains("/")) {
104                 dests[ix] = dest.substring(0, dest.indexOf("/"));
105             }
106         }
107         return dests;
108     }
109 
110     /**
111      * sends a command message to all destinations.
112      * If origin of message is not set then sets it using the current subsystem name.
113      * If a Correlation Id is not set then creates one.
114      *
115      * @param cmd
116      * @throws IOException
117      * @throws DestinationsException may be thrown if the transport layer is unable to find some of the
118      *                               destination and has no broadcast policy in this case.
119      * @see org.lsst.ccs.bus.BusMembershipListener#anormalEvent(Exception) for another way to signal
120      * destinations exceptions
121      */
122     public void sendCommand(Command cmd) throws IOException {
123         String[] destinations = parseDestination(cmd.getDestination());
124         cmd.setKey(token);
125         if (cmd.getOrigin() == null) {
126             cmd.setOrigin(subsystemName);
127         }
128         if (cmd.getCorrelId() == null) {
129             // let's generate one
130             cmd.setCorrelId(java.util.UUID.randomUUID().toString());
131         }
132         busMessagingLayer.sendMessage(subsystemName, Bus.COMMAND, cmd, destinations);
133     }
134 
135     /**
136      * broadcasts a status message.
137      * If origin is not set then sets it with the current subsystem name.
138      *
139      * @param status
140      * @throws IOException
141      */
142     public void sendStatus(Status status) throws IOException {
143         if (status.getOrigin() == null) {
144             status.setOrigin(subsystemName);
145         }
146         busMessagingLayer.sendMessage(subsystemName, Bus.STATUS, status);
147     }
148 
149     /**
150      * broadcasts a log message.
151      * If origin is not set then sets it with the current subsystem name.
152      *
153      * @param evt
154      * @throws IOException
155      */
156     public void sendLogEvent(LogEvent evt) throws IOException {
157         if (evt.getOrigin() == null) {
158             evt.setOrigin(subsystemName);
159         }
160         busMessagingLayer.sendMessage(subsystemName, Bus.LOG, evt);
161 
162     }
163 
164     /**
165      * sends a reply or an ack responding to a command.
166      * <B>Beware</B> : if <TT>originalCommand</TT> or <TT>CorrelID</TT>
167      * or <TT>destination</TT> are not set in the parameter this code will try to populate
168      * these fields with informations coming from <TT>ThreadLocal</TT> data so if
169      * the initial Thread that received the command spawn children (or more generally
170      * if replies or ack are generated in different Threads) then this facility will be defeated.
171      *
172      * @param cmd
173      * @throws IOException
174      * @throws DestinationsException may be thrown if the transport layer is unable to find some of the
175      *                               destination and has no broadcast policy in this case.
176      * @see org.lsst.ccs.bus.BusMembershipListener#anormalEvent(Exception) for another way to signal
177      * destinations exceptions
178      */
179     public void reply(CommandAckOrReply cmd) throws IOException {
180         /*  REMOVE
181         if(cmd.getOriginalCommand() == null) {
182            cmd.setOriginalCommand(localCommand.get()); 
183         }
184         */
185         if (cmd.getOrigin() == null) {
186             cmd.setOrigin(subsystemName);
187         }
188         /* REMOVE correlID is set by ctor of CommandAckOrReply
189         if(cmd.getCorrelId() == null) {
190            cmd.setCorrelId(localCorrelID.get());
191         }
192         */
193         String destination = cmd.getDestination();
194         /* REMOVE should be set by ctor of CommandAckOrReply
195         if(destination == null) {
196  		    destination= localOrigin.get() ;
197             cmd.setDestination(destination);
198         }
199         */
200         busMessagingLayer.sendMessage(subsystemName, Bus.COMMAND, cmd, destination);
201     }
202 
203     ////////////// Listeners
204 
205     /*
206     this will lead to a big bug because the same object can be Listening to different org.lsst.gruth.types of message
207     IdentityHashMap<Object, BusMessageForwarder> forwarderMap = new IdentityHashMap<Object, BusMessageForwarder>() ;
208     */
209     IdentityHashMap<CommandListener, BusMessageForwarder> commandForwarderMap = new IdentityHashMap<>();
210     IdentityHashMap<LogListener, BusMessageForwarder> logForwarderMap = new IdentityHashMap<>();
211     IdentityHashMap<StatusListens, BusMessageForwarder> statusForwarderMap = new IdentityHashMap<>();
212 
213     void addMembershipListener(BusMembershipListener l) {
214         // We implement this method for now using BusMessagingLayer's setMembershipListener
215         // This avoids having to duplicated the functionality in each implementation of BusMessagingLayer
216         membershipMultiplexor.addMembershipListener(l);
217     }
218 
219     void removeMembershipListener(BusMembershipListener l) {
220         membershipMultiplexor.removeMembershipListener(l);
221     }
222     /**
223      * Allows multiple listeners to be registered, despite BusMessagingLayer supporting only
224      * a single listener.
225      */
226     private class MembershipMultiplexor implements BusMembershipListener {
227         // We use a CopyOnWriteArrayList in case a listener registers or 
228         // unregisters while we are iterating to deliver messages 
229         private final List<BusMembershipListener> listenerList = new CopyOnWriteArrayList<>();
230         private boolean registered = false;
231         
232         private void addMembershipListener(BusMembershipListener l) {
233             synchronized (this) {
234                 if (!registered) {
235                    busMessagingLayer.setMembershipListener(this, Bus.COMMAND);
236                    registered = true;
237                 }
238             }
239             listenerList.add(l);
240         }
241 
242         private void removeMembershipListener(BusMembershipListener l) {
243             listenerList.remove(l);
244         }
245 
246         @Override
247         public void connecting(String agentName, String otherInfos) {
248             for (BusMembershipListener l : listenerList) {
249                 l.connecting(agentName, otherInfos);
250             }
251         }
252 
253         @Override
254         public void disconnecting(String agentName, String otherInfos) {
255             for (BusMembershipListener l : listenerList) {
256                 l.disconnecting(agentName, otherInfos);
257             }
258         }
259 
260         @Override
261         public void anormalEvent(Exception exc) {
262             for (BusMembershipListener l : listenerList) {
263                 l.anormalEvent(exc);
264             }
265         }       
266     }
267     
268 
269     /**
270      * instances of this class will forward commands coming from the transport layer
271      * to the registered <TT>CommandListener</TT>.
272      * When the incoming message is a command to be executed the <TT>TreadLocal</TT>
273      * data that links commands with their corresponding relpis or ack is populated
274      * (see <TT>reply</TT> method documentation).
275      */
276     protected class ForwarderToCommand implements BusMessageForwarder {
277         CommandListener listener;
278 
279         ForwarderToCommand(CommandListener listener) {
280             this.listener = listener;
281             commandForwarderMap.put(listener, this);
282         }
283 
284         @Override
285         public void update(BusMessage message) {
286             if (message instanceof CommandReply) {
287                 listener.onReply((CommandReply) message);
288             } else if (message instanceof CommandAck) {
289                 listener.onAck((CommandAck) message);
290             } else if (message instanceof Command) {
291                 Command cmd = (Command) message;
292                 /* REMOVE
293                 localCommand.set(cmd);
294                 localOrigin.set(cmd.getOrigin());
295                 localCorrelID.set(cmd.getCorrelId());
296         	   //System.out.println("origin : " + cmd.getOrigin() + " on " + Thread.currentThread());
297                 */
298                 try {
299                     assert Tracer.trace("executing: " + cmd);
300                     listener.onCommand(cmd);
301                 } catch (Throwable throwable) {
302                     log.error("on command :" + throwable);
303                 }
304             }
305 
306         }
307     }
308 
309     /**
310      * instances of this class will forward directly status messages to statuslistener
311      */
312     public class ForwarderToStatus implements BusMessageForwarder {
313         final StatusListens listener;
314         final boolean isEncodedListener;
315         final boolean isKeyValueListener;
316         final boolean isDataListener;
317         final boolean isCrystallizedListener;
318 
319         public ForwarderToStatus(StatusListens listener) {
320             this.listener = listener;
321             isEncodedListener = listener instanceof EncodedStatusListens;
322             isKeyValueListener = listener instanceof KeyValueStatusListener;
323             isDataListener = listener instanceof DataStatusListener;
324             isCrystallizedListener = listener instanceof SerializedDataStatusListener;
325             statusForwarderMap.put(listener, this);
326         }
327 
328         @Override
329         public void update(BusMessage message) {
330             if (message instanceof EncodedDataStatus && isEncodedListener) {
331                 EncodedDataStatus status = (EncodedDataStatus) message;
332                 String source = status.getOrigin();
333                 for (EncodedDataStatus dataStatus : status) {
334                     long timeStamp = dataStatus.getDataTimestamp();
335                     KVList list = dataStatus.getContent();
336                     for (KeyData keyData : list) {
337                         String key = keyData.getKey();
338                         if (isDataListener) {
339                             Optional<Object> optObj = keyData.getValue();
340                             if (optObj.isPresent()) {
341                                 ((DataStatusListener) listener).onDataArrival(source, timeStamp, key, optObj.get());
342 
343                             }
344 
345                         }
346                         if (isKeyValueListener) {
347                             int id = keyData.hashCode();
348                             List<KeyData> detailsList = keyData.getContentAsList();
349                             for (KeyData detaileddata : detailsList) {
350                                 String complexKey = detaileddata.getKey();
351                                 Optional<Object> optional = detaileddata.getValue();
352                                 if (optional.isPresent()) {
353                                     ((KeyValueStatusListener) listener).onKeyValueStatusDecomposition(source, timeStamp, complexKey, optional.get(), id);
354                                 }
355                             }
356                         }
357                         if (isCrystallizedListener) {
358                             Optional<byte[]> crysta = keyData.getCrystallizedData();
359                             if (crysta.isPresent()) {
360                                 ((SerializedDataStatusListener) listener).onSerializedDataArrival(source, timeStamp, key, crysta.get());
361                             }
362                         }
363                     }
364 
365                 }
366 
367 
368             } else {
369                 if (listener instanceof StatusListener) {
370                     ((StatusListener) listener).onStatus(message);
371                 }
372             }
373         }
374     }
375 
376     /**
377      * instances of this class will forward directly log messages to a log listener
378      */
379     protected class ForwarderToLog implements BusMessageForwarder {
380         LogListener listener;
381 
382         ForwarderToLog(LogListener listener) {
383             this.listener = listener;
384             logForwarderMap.put(listener, this);
385         }
386 
387         @Override
388         public void update(BusMessage message) {
389             if (message instanceof LogEvent) {
390                 listener.onLog((LogEvent) message);
391             }
392         }
393     }
394 
395     /**
396      * registers a CommandListener (in fact a ForwarderToCommand to the underlying transport)
397      *
398      * @param l
399      */
400     public void addCommandListener(CommandListener l) {
401         busMessagingLayer.addMessageListener(subsystemName, new ForwarderToCommand(l), Bus.COMMAND);
402     }
403 
404     /**
405      * removes a CommandListener: since this command is based on strict identity the listener should be exactly the same
406      * as the one registered.
407      *
408      * @param l
409      */
410     public void removeCommandListener(CommandListener l) {
411         BusMessageForwarder forwarder = commandForwarderMap.remove(l);
412         if (forwarder != null) {
413             busMessagingLayer.removeMessageListener(subsystemName, forwarder, Bus.COMMAND);
414         } else {
415             //TODO: log
416         }
417     }
418 
419     /**
420      * registers a StatusListener (in fact a ForwarderToStatus to the underlying transport)
421      *
422      * @param l
423      */
424     public void addStatusListener(StatusListens l) {
425         busMessagingLayer.addMessageListener(subsystemName, new ForwarderToStatus(l), Bus.STATUS);
426     }
427 
428     public void removeStatusListener(StatusListens l) {
429         BusMessageForwarder forwarder = statusForwarderMap.remove(l);
430         if (forwarder != null) {
431             busMessagingLayer.removeMessageListener(subsystemName, forwarder, Bus.STATUS);
432         } else {
433             //TODO: log
434         }
435 
436     }
437 
438     /**
439      * registers a LogListener (in fact a ForwarderToLog to the underlying transport)
440      *
441      * @param l
442      */
443     public void addLogListener(LogListener l) {
444         busMessagingLayer.addMessageListener(subsystemName, new ForwarderToLog(l), Bus.LOG);
445     }
446 
447     public void removeLogListener(LogListener l) {
448         BusMessageForwarder forwarder = logForwarderMap.remove(l);
449         if (forwarder != null) {
450             busMessagingLayer.removeMessageListener(subsystemName, forwarder, Bus.LOG);
451         } else {
452             //TODO: log
453         }
454 
455     }
456 
457     /**
458      * lists the name of agents currently connected to the Command Bus
459      * @return
460      */
461     public List<String> connectedToCommand() {
462         return busMessagingLayer.getConnectedNames(Bus.COMMAND) ;
463     }
464 
465     /**
466      * closes the underlying transport layer, stops the listening threads,
467      * after this call all other sending calls will fail.
468      */
469     public void close() {
470         busMessagingLayer.closeFor(subsystemName);
471     }
472 }