Event bus

Note

With the release of OMERO 5.3.0, the OMERO.insight desktop client has entered maintenance mode, meaning it will only be updated if a major bug is discovered. Instead, the OME team will be focusing on developing the web clients. As a result, coding against this client is no longer recommended.

Interactions among agents are event-driven. Agents communicate by using a shared event bus provided by the container. The event bus is an event propagation mechanism loosely based on the ` Publisher-Subscriber <https://en.wikipedia.org/wiki/Publish/subscribe>`_ pattern and can be regarded as a time-ordered event queue - if event A is posted on the bus before event B, then event A is also delivered before event B.

Events are fired by creating an instance of a subclass of AgentEvent and by posting it on the event bus. Agents interested in receiving notification of AgentEvent occurrences implement the AgentEventListener interface and register with the event bus. This interface has a callback method, eventFired, that the event bus invokes in order to dispatch an event. A listener typically registers interest only for some given events - by specifying a list of AgentEvent subclasses when registering with the event bus. The event bus will then take care of event de-multiplexing - an event is eventually dispatched to a listener only if the listener registered for that kind of event.

Structure

Event bus

OMERO.insight event bus

EventBus
  • Defines how client classes access the event bus service.

  • A client object (subscriber) makes/cancels a subscription by calling register()/remove().

  • A client object (publisher) fires an event by calling postEvent().

AgentEvent
  • Ancestor of all classes that represent events.

  • Source field is meant to be a reference to the publisher that fired the event.

  • An event is “published” by adding its class to the events package within the agents package.

AgentEventListener
  • Represents a subscriber to the event bus.

  • Defines a callback method, eventFired, that the event bus invokes in order to dispatch an event.

EventBusListener
  • Concrete implementation of the event bus.

  • Maintains a de-multiplex table to keep track of what events have to be dispatched to which subscribers.

In action

  • When a subscriber invokes the register or remove method, the de-multiplex table is updated accordingly and then the event bus returns to idle.

  • When a publisher invokes postEvent(), the event bus enters into its dispatching loop and delivers the event to all subscribers in the event notification list.

  • Time-ordered event queue - if event A is posted on the bus before event B, then event A is also delivered before event B.

  • Dispatching loop runs within same thread that runs the agents (Swing dispatching thread).

Event dispatching

OMERO.insight event dispatching

Event

Structure

We devise two common categories of events:

  • Events that serve as a notification of state change. Usually events posted by agent to notify other agents of a change in its internal state.

  • Events that represent invocation requests and completion of asynchronous operations between agents and some services.

In the first category fall those events that an agent posts on the event bus to notify other agents of a change in its internal state. Events in the second category are meant to support asynchronous communication between agents and internal engine. The AgentEvent class, which represents the generic event type, is sub-classed in order to create a hierarchy that represents the above categories. Thus, on one hand we have an abstract StateChangeEvent class from which agents derive concrete classes to represent state change notifications. On the other hand, the RequestEvent and ResponseEvent abstract classes are sub-classed by the container in order to define, respectively, how to request the asynchronous execution of an operation and how to represent its completion. We use the Asynchronous Completion Token pattern to dispatch processing actions in response to the completion of asynchronous operations.

Events

OMERO.insight events

StateChangeEvent
  • Ancestor of all classes that represent state change notifications.

  • Its state field can be used to carry all state-change information.

RequestEvent
  • Abstractly represents a request to execute an asynchronous operation.

  • A concrete subclass encapsulates the actual request.

  • Knows how and which processing action to dispatch upon completion of the asynchronous operation.

CompletionHandler
  • Represents a processing action.

  • Allows for all processing action to be treated uniformly.

ResponseEvent
  • Abstractly represents the completion of an asynchronous operation.

  • A concrete subclass encapsulates the result of the operation, if any.

  • Knows the RequestEvent object that originated it.

  • Knows how to activate the de-multiplexing of a completion event to the processing action.

In action

Follow a concrete example:

//Somewhere in the Data Manager
//Request to View an image

EventRequest req = new ViewImage((ImageData) image, null)
//Request the execution of the view call.
eventBus.postEvent(req);


//Somewhere in the Viewer Agent
public void eventFired(AgentEvent e)
{
    if (e instanceof ViewImage) handleViewImage((ViewImage) e);
}

A concrete RequestEvent encapsulates a request to execute an asynchronous operation. Asynchrony involves a separation in space and time between invocation and processing of the result of an operation: we request the execution of the operation at some point in time within a given call stack (say in methodX we make a new request and we post it on the event bus). Then, at a later point in time and within another call stack (eventFired method), we receive a notification that the execution has completed and we have to handle this completion event - which mainly boils down to doing something with the result, if any, of the operation. Recall that the ResponseEvent class is used for representing a completion event and a concrete subclass carries the result of the operation, if any. After the operation has completed, a concrete ResponseEvent is put on the event bus so that the object which initially made the request (often an agent, but, in this context, we will refer to it as the initiator, which is obviously required to implement the AgentEventListener interface and register with the event bus) can be notified that execution has completed and possibly handle the result. Thus, at some point in time the initiator’s eventFired method is called passing in the response object.

Now the initiator has to find out which processing action has to be dispatched to handle the response. Moreover, the processing action often needs to know about the original invocation context - unfortunately, we cannot relinquish the original call stack (methodX is gone). The solution is to require that a response be linked to the original request and that the initiator link a request to a completion handler (which encapsulates the processing action) before posting it on the event bus (this explains the fancy arrangement of the RequestEvent, ResponseEvent and CompletionHandler).

This way de-multiplexing matters are made very easy for the initiator. Upon reception of a completion event notification, all what the initiator has to do is to ask the response object to start the de-multiplexing process - by calling the complete method. This method calls handleCompletion() on the original request, passing in the response object. In turn, handleCompletion() calls the handle method on its completion handler, passing in both the request and the response. The right processing action has been dispatched to handle the response. Also, notice that the completion handler is linked to the request in the original invocation context, which makes it possible to provide the handler with all the needed information from the invocation context. Moreover, both the original request and the corresponding response are made available to the completion handler. This is enough to provide the completion handler with a suitable execution context - all the needed information from the original call stack is now available to the processing action.