The Event Model interfaces each play a specific
role in the event delivery process. We'll describe the basic responsibilities of each
entity here.
- ApplicationGateway
- EventGateway
- EventPool
- BaseEvent
- EventBroker
- DispatcherFactory
- EventDispatcher
- DispatchQueue
- EventContext
- ListenerFactory
- BaseEventListener
- ApplicationAssembler
ApplicationGateway - An
ApplicationGateway is a servlet that acts as the primary interface between the URL request
that represents a WebEvent, and the EventBroker that dispatches the event. The gateway has
three primary tasks:
Define implementation specifics - By this, we mean that the Application
Gateway is the class that defines which implementations we are going to use within a
running instance of the event model. For example, the gateway is responsible for
instantiating the EventBroker, the EventPool, and the Dispatcher Factory. This makes it
easy to plug in new functionality--all the developer has to do is override the specific
method that instanstantiates these objects.
Provide lifecycle services for EventGateways - An Application Gateway is
also responsible for causing all known event handlers to be registered/deregistered with
the event broker. This actually happens when the servlet itself is init'ed or destroyed.
More on EventGateways below.
Convert HTTP Requests to Events and Dispatch to the system - In
short, the Application Gateway tells the servlet container (via the web.xml file) that it
is capable of handling all URLs ending in a particular event extension (ie.
*.event). When a URL is received, the event broker servlet converts the URL string
into the appropriate event name, which it can either instantiate directly or retrieve from
the EventPool. The gateway dispatches the event through the EventBroker, and if there is
an error or no one handles the event, it generates the default error response.
EventGateway - An EventGateway
simply represents an interface to all known event handlers within a system. Gateways are
heirarchical in nature: they may contain other EventGateways. In other words, an
EventGateway knows about local event listeners plus child event gateways. Invoking
register on a gateway should cause it to register all known enties that are interested in
receiving events from the EventBroker, PLUS invoke register for any child gateways it
contains. Invoking deregister has the opposite effect.
Any class that has BaseEventListeners should implement EventGateway
(usually by extending DefaultEventGateway). The registerLocalEventInterests() and
deregisterLocalEventInterests() are the methods that should register/dergister local event
listeners.
EventPool - An EventPool is a
optional mechanism to offset the penalty of instantiating real event objects from a URL
string. In a nutshell, it allows you to checkout events, use them, and then check them
back in when you are done with them. Events that are not checked back in eventually time
out, and are returned to the active pool by a background thread that runs on a period
basis (configurable). By default, the ApplicationGateway is the only class that actually
uses the EventPool. Custom event listeners that wish to post additional events would
probably just instantiate the event objects directly (since they wouldn't need to use
reflection in the first place).
BaseEvent - All events in the
system extend from BaseEvent. Events are used to notify listeners that something happened.
In general, events are very lightweight and should carry as little state
information as possible (although they are capable of it). It is much better to pass state
information between event handlers by storing it in the event queue. An event has one
basic state: handled or unhandled. It may be addressed to specific listeners, or just to
any parties interested in this class of event. All events are nestable, and may contain a
reference to the object which created them. In addition, these events are designed to be
reusable -- they may be reset to their default state.
There are two kinds of BaseEvents: ControlEvents and ViewEvents.* Control
events indicate something happened; view events indicate that not only did something
happen, but a response is required (usually to generate a view). For an HTTP interface to
the event model, we extend from these event classes to create an HttpRequestEvent and an
HttpResponseEvent. If we ever need to interface with another type of gateway (ie. SMTP,
SNMP, etc) we would create new event hierarchies at the same level.
The request event structure represents the "API" we expose to
the HTTP gateway; the response event structure represents what gets sent back to the
client in "response" to various requests. HTTP clients can invoke any event that
extends from HttpRequestEvent. Response events are generated by the listeners which
process these requests. There are obvious parallels here to both the HTTP request-response
structure AND the Model 2 Controller/View roles: request events loosely correspond with
"controller" while response events represent the "views".
It should also be noted that HttpRequestEvents are Polymorphic. This means
that when a polymorphic event is dispatched it's parent events should be fired first,
because the event "is an instance" of the parent events. Likewise,
HttpResponseEvents are Exceptional, which means that they must be handled. When an
exceptional event is dispatched if it is not handled it's parent event will be added to
the queue. Exceptional is essentially of the opposite of Polymorphic.
*Note: previously ControlEvents and ViewEvents were each called
RequestEvents and ResponseEvents respectively. Just keep this in mind in case you
encounter the older terminology.
EventBroker - The EventBroker
is responsible for two tasks:
- keeping track of which listeners are interested in which events
providing an interface to dispatch events to interested listeners
There are two ways for listeners to register with the EventBroker. They
may simply register their event listener ID, so that any events addressed specifically to
them (ie. from client side components) may be delivered, OR, they may express an interest
in a particular type of event, in which case they will be notified anytime that event is
generated (provided it is not already addressed to specific listeners).
When an event is dispatched, the EventBroker uses an EventDispatcher to
actually do the dirty work. Consequently, a broker can be modified to use different
dispatch policies simply by binding it to a different event dispatcher. The dispatcher
being used receives the event to be dispatched, as well as a reference to the broker,
which it may need to query in order to find listeners for a specific event.
DispatcherFactory - A
DispatcherFactory is simply responsible for providing an instance of EventDispatcher. It
should either return a new EventDispatcher for each request, or it should use a static
synchronized copy to ensure threadsafety.
EventDispatcher - The
EventDispatcher implements the a given event dispatching policy. The default
implementation works something like this.
The dispatcher receives a queue of events, which it will faithfully
attempt to deliver. While the event interfaces themselves are not tied to any
particular dispatching pattern, the DefaultEventDispatcher makes specific use of a Model 2
pattern to deliver events. The process is really a 2 phased dispatch, and looks something
like this:
In Phase 1, all request events are dispatched. Listeners can ignore the
event, handle the event, and even post new events to the dispatch queue. This phase
corresponds loosely with the "Controller" part of Model 2.
When all request events are delivered, the dispatcher checks to see if
the queue requires a response (this would be true if the event originated in an HTTP
gateway). If so, the dispatcher looks to see if we have any response events. Typically,
one of the controllers in Phase 1 would have added a response event; if not, a default
HttpResponseEvent is generated and added automatically.
In Phase 2, all response events are dispatched. Listeners can ignore the
event, handle the event, and even post new events back into the dispatch queue. It should
be noted however, that if the queue requires a response, at least one of the listeners
must generate one, or an UnhandledEventException will be thrown. In addition, if the
listeners add Request events back into the queue during the response phase, these events
will be ignored because they are only handled in the first phase.
This second phase corresponds loosely with with the "View" part
of Model 2.
In terms of actually dispatching an event, when the dispatcher removes an
event from the queue, it first looks to see if the event is addressed to specific
listeners. If not, then it queries the event broker for a list of interested listeners.
Once it has this information, it notifies all listeners until someone handles the event
(it also notifies those request notifyAlways()).
If the event implements Polymorphic, all it's parent events are dispatched
first since the event "is an instance" of all its parents. This gives developers
the ability to install listeners on a whole hierarchy of events, which has some very
powerful implications. If the event implements Exceptional, this tells the
dispatcher that the event must be handled. If it is not, it is treated like an Exception,
and it's immediate parent event is added to the queue. This has some very useful
implications for error handling.
Note that events may either be Polymorphic or Exceptional (or neither),
but they may never implement both. Fortunately, the interfaces preclude this by
intentionally defining the same method.
DispatchQueue - The
DispatchQueue is a very simple interface: there are only a few methods that are exposed
publically: addEvent and listEvents are the most important. This allows listeners to add
events to the queue, without modifying it in any other way. The event queue is intelligent
enough to collapse multiple events (just like Swing does) so that if three listeners each
generate a "RedrawView" event, there will only be one instance of the event in
the queue.
It should be noted that the DefaultDispatchQueue internally stores events
in different locations depending on their type (ResponseEvents vs. every other kind of
event). This allows the dispatcher to use a 2 Phase dispatching mechanism to dispatch
non-Response events first and Response events second. The queue, however, could still be
used for single phased dispatch: the application gateway would simply define the queue as
not requiring a response and then it would not add any response events to the queue.
As a final note, we should point out that the dispatch queue implements
the Stated interface, which makes it useful for passing state between handlers. Keep in
mind however, that a new queue is created for every HTTP request, so state information
will not carry over between subsequent requests.
EventContext - When the
EventDispatcher notifies event listeners, it packages all the information relating to the
event in an EventContext object. The context contains of the actual event thatwas fired, a
reference to the event queue, as well as references to the ServletRequest,
ServletResponse, and ServletConfig objects (whether or not these objects are accessible
depends on the type of the event). The event context also implements StateMap, meaning it
can carry state information. If you need to pass information between event handlers, this
is not the preferred mechanism for doing so.
ListenerFactory - Much like
the DispatcherFactory, a ListenerFactory is responsible for provide an instance of
BaseEventListener. It should either provide a new instance each time, or use a static
synchronized copy to ensure threadsafety.
BaseEventListener - A
BaseEventListener is an interface that allows an implementor to handle all BaseEvents.
Typically, a listener will express interest in an event by registering with an
EventBroker. The EventBroker is responsible for delivering events to the interested
parties. When the listener handles an event, it receives several pieces of information,
including a reference to the event itself, as well as a handle to the EventQueue. If the
listener wishes to fire additional events, this is typically done by adding them to the
EventQueue.
Also notice that a listener can tell the broker what kind of delivery
policy it prefers. By default, only unhandled events are delivered to listeners. However,
listeners can get all applicable events (handled or not) by overriding the notifyAlways()
method. This makes it possible to implement logging mechanism, etc.
Finally, we should note that an event listener must be capable of
providing a listener ID. This value is used by both client components and the event broker
to specify direct delivery of events to a specific listener(s).
ApplicationAssembler -
With the latest Barracuda release, there is a new interface in the event package called
ApplicationAssembler. This interface is invoked by the ApplicationGateway if the web.xml
file contains 2 key parameters: ApplicationAssembler (the name of the assembler class) and
AssemblyDescriptor (the name of the XML file to be used by the assembler). This makes it
possible for you to create custom assemblers which dynamically create your Barracuda
system from an XML file, rather than requiring you to extend ApplicationGateway, etc.
There is currently a very basic implementation of ApplicationAssembler available in
org.enhydra.barracuda.experimental.assembler.DefaultApplicationAssembler.
This covers all the basic roles in the current event model implementation. |