Server-Side Typed Event Distributors Blog

Version 2

    {cs.r.title}



              
                                          

    Contents
    Applications of Event Distributors
    Events Seen as Interfaces
    SWIHttpEvents: The Static Wrapper
    for IHttpEvents
    Initializing theSWIHttpEvents
    Looking at a Trivial Implementation
    for IHttpEvents
    Need for an Event Distributor
    Responding to only Selected Events
    How to Raise an Event?
    Event Distributor Class Diagram
    JavaBeans and Property Change Events
    Parallels to Remoting Proxies
    Resources

    Event frameworks such as publish/subscribe were much in vogue during the early- to mid-90s. Those were also the days when C++, UI frameworks, and CORBA were quite popular. This was just before the onslaught of web-based programming on system design, with an emphasis on simplicity. With the simplified thin UI of web clients, the elaborate event designs took somewhat of a back seat.

    Nevertheless, "events" continue to play a significant role in the enterprise messaging world. TIBCO, MQ, and EJB Message Beans are all examples of the prevalence of event-based, enterprise-level architectures. Events also play an important role for in-process software design, so that systems can be built with flexibility and configurability, especially in the form of libraries and frameworks.

    This article documents an idea called the "Event Distributor" model, where event responders can be decoupled in a type-safe manner. Although decoupling of events is not new, this article explores a typed, delegation-based approach without involving an explicit subscription to solve the problem. The approach also talks about how to take any interface and convert it to an event-based (or event-inspired) interface. This article, more importantly, documents the number of classes needed to implement such a pattern so that the solution can be served as a copy book.

    This article also examines how this kind of implementation for events relates to messaging systems such as JMS and MQ on one hand, and on the other, to the event-generation mechanism of JavaBeans.

    Applications of Event Distributors

    Before going into describing how Event Distributors can be implemented, I want to point out the practical uses of Event Distributors. Event Distributors have applications for such things as HTTP events and connection pool events in an application server. For example, HTTP events can be used to detect and respond to such occurrences as:

    • A request starting or ending.
    • A session being established or terminated.
    • A user logging in or logging out.

    In a similar fashion, "connection pool" events can be used to monitor such events as:

    • A connection being created or closed.
    • A connection being requested from the pool or returned to the pool.

    Events Seen as Interfaces

    An application server, being a container, is a generator of a number of events, whether they are seen as events or not. Let me start by considering a typical HTTP pipeline and the typical events such a pipeline produces:

    • Application start
    • Application stop
    • Begin HTTP request
    • End HTTP request
    • Session start
    • Session end
    • User login
    • User logoff

    These events are largely independent of each other, but work within a specific context. For example, an HTTP begin request will have the request and response objects as its context, whereas a session start will have the session object as its context. In that sense, each event is like a method call, waiting to be answered either synchronously or asynchronously.

    In windowing systems, these events are modeled as messages with a unique message ID and a set of parameters. Responders to these events will register based on an event ID and then retrieve the parameters for that event from the bag of parameters.

    The treatment of events is also similar in messaging systems such as MQ and TIBCO, and to an extent, mirrored in JMS. In these systems, messages typically leave process boundaries. Messages are also expected to carry payloads that are large and sometimes even streams of data. These systems explicitly depend on an intermediate layer called a "queue," where events are persisted and guaranteed if necessary. Especially in JMS and EJB 2 MDBs, each event is mapped to a specific bean in the EJB tier via anonMessage method on that bean. So if you were to haven number of events, then you would need n number of beans to respond to those events.

    In contrast, the events described in this article are largelyin-process events that work on in-process entities, which get passed around via reference and not by value. The problem space for in-process events is a lot simpler. Taking these facts into account, I suggest that sets of events can be grouped as a consolidated interface. If I were to take the above HTTP-related events and apply these principles, the resulting interface would look like this:

     
    public interface IHttpEvents { public boolean applicationStart() throws AspireServletException; public boolean applicationStop() throws AspireServletException; public boolean sessionStart( HttpSession session, HttpServletRequest request, HttpServletResponse response) throws AspireServletException; public boolean sessionStop() throws AspireServletException; public boolean beginRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException; public boolean endRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException; public boolean userLogin(String username, HttpSession session, HttpServletRequest request, HttpServletResponse response) throws AspireServletException; public boolean userLogoff(String username, HttpSession session, HttpServletRequest request, HttpServletResponse response) throws AspireServletException; } //eof-class 
    

    Each event is expressed as a method call in the interface. The parameters of each event have become arguments to the method representing the corresponding event. As Java uses checked exceptions, it is best to declare interfaces with an exception. One lesson I have learned over the years, while designing interfaces, is to always have a declared exception to the interface methods. When I didn't anticipate that, I always paid. This is not as essential to languages where exceptions are primarily runtime exceptions and not checked exceptions.

    SWIHttpEvents: The Static Wrapper for IHttpEvents

    The intent of an event interface is that I can allow an implementation which can be supplied at run time. For example, I can go with an implementation that looks like this:

     
    class MyEventResponse implements IHttpEvents { //All implemented methods } 
    

    Then, to generate a beginRequest event, I can use the following code:

     
    .. IHttpEvents httpEvents = SomeUtility.getHttpEventImplementation(); httpEvents.beginRequest(request,response) 
    

    As I raise events from different parts of the program, it is tedious to always get the interface and then call the method on the interface. It is lot easier to just do the following:

     
    .. SWIHttpEvents.beginRequest(request,response) 
    

    Where SWIHttpEvents is a static wrapper class to the IHttpEvents interface. A fairly complete definition for the static wrapper class SWIHttpEventsis provided below.

    The code below assumes that the container will provide a standard set of services such as a factory service, a configuration service, and a logging service. In the example below, these services are encapsulated in another static wrapper calledAppObjects, where each application scope object represents a service.

    These services are fairly common in modern containers such as Spring and Pico. Even if one doesn't have access to these containers, it is not that hard to simulate what is required here by reading a properties file and instantiating singletons to represent event handlers. TheAppObjects used here is taken from the container Aspire/J2EE.

    If you would like to copy and compile the following code for your use, you will need to replace all of the calls toAppObjects with an equivalent call that suits your environment.

     
    public class SWIHttpEvents { //********************************************** //* A place holder for the implementation //********************************************** public static IHttpEvents m_events = null; //********************************************** //* static initialization with the implementation //********************************************** static { try { //Call a factory service to instantiate //an event implementation for IHttpEvents m_events = (IHttpEvents) AppObjects.getObject(IHttpEvents.NAME,null); } catch(RequestExecutionException x) { //log the warning String msg = "Warn: No http events class available." + " No events will be reported"; //use your container to log the message AppObjects.log(msg); //null event handler means no //events will be fired. m_events=null; } } //********************************************** //* Individual delegated methods //********************************************** static public boolean applicationStart() throws AspireServletException { if (m_events == null) return true; return m_events.applicationStart(); } static public boolean applicationStop() throws AspireServletException { if (m_events == null) return true; return m_events.applicationStop(); } static public boolean sessionStart( HttpSession session, HttpServletRequest request, HttpServletResponse response) throws AspireServletException { if (m_events == null) return true; return m_events.sessionStart(session ,request,response); } static public boolean sessionStop() throws AspireServletException { if (m_events == null) return true; return m_events.sessionStop(); } static public boolean beginRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException { if (m_events == null) return true; return m_events.beginRequest( request,response); } static public boolean endRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException { if (m_events == null) return true; return m_events.endRequest( request,response); } static public boolean userLogin( String username, HttpSession session, HttpServletRequest request, HttpServletResponse response) throws AspireServletException { if (m_events == null) return true; return m_events.userLogin(username ,session,request,response); } static public boolean userLogoff( String username, HttpSession session, HttpServletRequest request, HttpServletResponse response) throws AspireServletException { if (m_events == null) return true; return m_events.userLogoff(username ,session,request,response); } }//eof-class 
    
     

    Initializing theSWIHttpEvents

    As SWIHttpEvents is a static wrapper (or a static proxy) to the actual implementation of IHttpEvents, it is initialized up front with a preferred copy of an implementation object. For optimizing calls to individual events, the following code fragment in the above static wrapper class caches the implementation object.

     
    public static IHttpEvents m_events = null; static { try { //Call a factory service to instantiate //an event implementation for // IHttpEvents m_events = (IHttpEvents) AppObjects.getObject( IHttpEvents.NAME,null); } catch(RequestExecutionException x) { //log the warning String msg = "Warn: No http events class available." + " No events will be reported"; //use your container to log the message AppObjects.log(msg); //null event handler means no //events will be fired. m_events=null; } } 
    

    Initialization in the above code is carried out using a static block. The event implementation object is located through a factory service. Depending on your container, various approaches can be used. The example above, as pointed out already, is taken from the open source container that I wrote, called Aspire. In Aspire,AppObjects is a collection of application-level objects. The AppObjects static class exposes some of the public members of these services as static methods for simplicity. Underneath, in Aspire, a configuration file provides this name to class binding. Here is an actual example:

     
    request.HttpEvents.classname=\ com.ai.servlets.HttpEventDistributor 
    

    Aspire uses this binding to return an implementation ofIHttpEvents in response to a request for creating an object with a symbolic name of HttpEvents. Aspire by default assumes the instance is a singleton, while the class designer has the ability to override that capability if needed. If the above line is not available in the configuration file, thennull is returned. In that case,SWIHttpEvents is coded in such a way that events are ignored and the code does not throw any exceptions. The following method implementation is an example of that policy:

     
    static public boolean userLogin( String username, HttpSession session, HttpServletRequest request, HttpServletResponse response) throws AspireServletException { //if there is no implementation do not //call the method. if (m_events == null) return true; //An implementation is available //delegate the method to the implementation return m_events.userLogin(username, session,request,response); } 
    

    Looking at a Trivial Implementation for IHttpEvents

    Now that a provision is made for implementing and calling the events let me show you a trivial implementation for the events interface:

     
    public class LogHttpEvents implements IHttpEvents { public boolean beginRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException { AppObjects.log("Info:request begin event"); return true; } public boolean endRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException { AppObjects.log("Info:request end event"); return true; } ... so on and so forth } 
    

    Now I can specify this class as my implementation of theIHttpEvents interface as follows:

     
    request.HttpEvents.classname=com.ai.servlets.LogHttpEvents 
    

    So far, I have shown how to take a disconnected set of events and declare them as a cohesive typed interface. I have then shown how a static wrapper to this interface simplifies the publishing of these events. But there is more work to be done to really have this interface simulate event behavior. I will now turn my attention to an implementation called EventDistributor that allows for multiple subscriptions.

    Need for an Event Distributor

    There is an obvious drawback, or rather a limitation, to the above single point implementation. The static wrapper assumes that I can supply only one implementation. What if I want to accomplish two tasks for an event? This is similar to having two subscribers for an event. This issue is addressed by an event distributor. An event distributor acts like any other implementation ofIHttpEvents but distributes the events on to a "chain" or "bus" of implementations. Thus, an event distributor will delegate the events to any number of implementations. Here is sample configuration file to accommodate this design:

     
    request.HttpEvents.classname=com.ai.servlets.HttpEventDistributor request.HttpEvents.eventchain=EventHandler1,EventHandler2,EventHandler3 request.EventHandler1.classname=com.mypackage.mysubpackage.EventHandler1 request.EventHandler2.classname=com.mypackage.mysubpackage.EventHandler2 request.EventHandler3.classname=com.mypackage.mysubpackage.EventHandler3 
    

    The implementation of the HttpEventDistributor is such that it will call each event handler for each method, and if the method returns true, it will continue to call event handlers down the chain. If the method returnsfalse, it will terminate the chain. An exception can also break the chain. All of this is just a strategy. Different sorts of event distributors can be written with different strategies. Here is the code for the event distributor for this particular strategy.

     
    public class HttpEventDistributor implements IHttpEvents, IInitializable { //Keep a list of IHttpEvent handlers private List m_eventHandlers=new ArrayList(); //Load up the handlers at initialization // time of this factory loaded class //********************************************** //* initialize method //********************************************** public Object initialize(String requestName) throws RequestExecutionException { //The event handlers are specified as a //comma separated string of symbolic event //handler names. String eventHandlers = AppObjects.getValue( requestName + ".eventchain",null); if (eventHandlers != null) { Vector v = Tokenizer.tokenize(eventHandlers,","); for (Enumeration e = v.elements(); e.hasMoreElements();) { String eventHandler = (String)e.nextElement(); try { //Use the factory interface again // to instantiate the event // handler object based on the // symbolic name. IHttpEvents ieh = (IHttpEvents)AppObjects .getObject(eventHandler,null); m_eventHandlers.add(ieh); } catch(RequestExecutionException x) { AppObjects.log("log error",x); continue; } } } } //********************************************** //* beginRequest implementation //********************************************** //Call each handler's method public boolean beginRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException { Iterator itr = m_eventHandlers.iterator(); while(itr.hasNext()) { IHttpEvents ihe = (IHttpEvents)itr.next(); boolean rtncode = ihe.beginRequest(request,response); if (rtncode == false) return false; } return true; } //********************************************** //* endRequest implementation //********************************************** //Another example public boolean endRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException { Iterator itr = m_eventHandlers.iterator(); while(itr.hasNext()) { IHttpEvents ihe = (IHttpEvents)itr.next(); boolean rtncode = ihe.endRequest(request,response); if (rtncode == false) return false; } return true; } .. so on and so forth } 
    

    Again, for the sake of space, I have used some of the facilities from Aspire in the above code. You should be able to substitute similar facilities from your container or write equivalent code.

    An interesting insight into the workings of an event distributor is that it breaks down the general notion that an interface is implemented by a single implementation. In this case, an interface is seen as a gateway to a chain of implementations. In this respect, an in-process event mechanism is lot closer to the concept of "delegates" as laid out in C# than to the MDBs in EJB2. I certainly do not mean to imply one version is better than the other, as I believe both approaches are valid in their respective domains.

    Responding to only Selected Events

    If I am implementing the IHttpEvents directly, I am forced to implement all of the events (or methods), whether I am interested in them or not. This can be unnecessarily tedious. Instead, I usually extend the LogHttpEvents, which does nothing but log each event, and then implement the only one or two methods that concern the event at hand.

    To draw a parallel, while editing this article, the editor pointed out that in the AWT/Swing/JavaBeans world, there's a concept of an "adapter" implementation, which no-ops all interface methods, so you subclass and override only those methods that interest you. That is what is exactly happening here, as well in the following example:

     
    public class HttpRequestCharacterEncodingHandler extends LogHttpEvents { public boolean beginRequest( HttpServletRequest request, HttpServletResponse response) throws AspireServletException { try { String enc = request.getCharacterEncoding(); if (enc == null) { String encoding = AppObjects.getValue( m_requestName + ".encoding", "UTF-8"); request.setCharacterEncoding(encoding); AppObjects.log( "Info:setting encoding to " + encoding); } return true; } catch(UnsupportedEncodingException x) { throw new AspireServletException( "report error",x); } }//eof-function }//eof-class 
    

    How to Raise an Event?

    So far I have documented primarily how to respond to events. Here is an example of how to raise an event using theSWIHttpEvents wrapper.

     
    private boolean login( String username, String password, HttpServletRequest request, HttpServletResponse response, HttpSession session) throws RequestExecutionException, AuthorizationException, AspireServletException { boolean validPassword = yourLogin(username,password); if (validPassword == false) { //Invalid password throw new AuthorizationException(..); } //Good password ServletCompatibility. putSessionValue(session ,AspireConstants.ASPIRE_USER_NAME_KEY ,username); 
    SWIHttpEvents.userLogin(username ,session ,request ,response);  ServletCompatibility .putSessionValue(session, AspireConstants.ASPIRE_LOGGEDIN_STATUS_KEY , "true"); return true; } 
    

    In this example, as part of the login process, a successful login event is raised using the highlighted code segment.

    Event Distributor Class Diagram

    The general pattern of the event distributor can be summarized pictorially using the class diagram in Figure 1.

      

    Figure 1
    Figure 1. Event Distributor class diagram (click for full-size version)

    In the diagram, client sections of the code (such as "client1" and "client2") raise events by obtaining a reference to anIHttpEvents and calling methods on that interface. Each method corresponds to an equivalent event. Instead of repeating this process, every time a client needs the event interface, this is handled by an intermediate static classcalled SWIHttpEvents.

    The event interface could have been implemented by any number of implementations, one of which is a concrete event distributor called HttpEventDistributor and the other a default implementation for the event interface.

    The default implementation acts as an abstract implementation for the derived event-specific implementations, whereby the derived implementation can pick and choose the events to implement. In the diagram, two such event handlers are being shown. It is also possible that a given event may be implemented by more than one implementation.

    This class diagram can serve as a guide for implementing new event interfaces. In particular, the naming conventions and the various classes listed here could serve as a copy book.

    JavaBeans and Property Change Events

    Earlier in this article, I compared the events discussed so far with the macro-level messaging systems such as JMS. On the other side of the spectrum, at the micro level, many Java programmers are quite familiar with the property change events in the JavaBeans architecture. In this architecture, one can register property change listeners. In this scenario, when the value of an attribute of a bean changes, a whole number of listeners can be invoked. This model is often used in UI toolkits and frameworks. The events I have noted in this article are not granular enough to be tied to a set of properties but exist independently, and also don't adhere to the concept of a "value change," where one could track the value before and after an event. I believe these are different semantics and require separate treatment.

    Parallels to Remoting Proxies

    The approach suggested here has parallels to other component architectures such as dynamic proxies in Java, remoting in C#, delegates in C#, and even EJBs. Although the examples show a straightforward, non-reflection-based approach, it is possible to redesign the principles using reflection, whereby a number of intermediate classes can be eliminated.

    It is even conceivable to design language constructs for interfaces where a natural delegation such as this is inherent in the language. For example, I can envision the following:

     
    public static class SWIHttpEvents defined_as_a_multiple_delegate_for IHttpEvents { //No other code necessary } 
    

    All the code above will be automatically enabled. To support this, perhaps configuration should become a natural part of the language as well. Also, bringing this higher level of abstraction to a language allows programmers to think of their interfaces as delegated events where appropriate.

    Resources

      
    http://today.java.net/im/a.gif