I wrote this in 2006. It has been a while. I wrote it in preparation to publish on java.net. Here is that finished article link from java.net. I guess these sites don'the longevity I thought they would have. There goes the saying "all is forever on the internet"!
Events and publish/subscribe patterns were all the rage when c++, UI, UI frameworks, and CORBA were the norm of the day just before the web onslaught on system design. With the emphasis of simplified thin UI web clients, Event designs took somewhat of a back seat. Nevertheless Events continue to rule in the enterprise messaging world. TIBCO, MQ, and EJB Message Beans are all examples of this manifestation.
Events actually play an equally 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 a software pattern called an "EventDistributor" where event responders can be decoupled in a type safe manner. Although the decoupling 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 interface.
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 a) when a request starts or ends b) when a session is established or terminated c) when a user logs in or logs off. Connection pool events can be used to monitor such events as a) when connection is created or closed b) when a connection is requested from the pool or returned to the pool.
An application server being container is a generator of lot of events whether they are defined as events or not. Let me start by considering the http pipline and the events it produces. Some of these events are
These events are independent of each other and work on a specific context. For example an http begin request will have the request and response objects as its context where as a Session Start will have the session object as its context. In that sense each event is like a function 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. Instead in Java you can group the above collection of events as a consolidated interface as follows:
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 userChange(String oldUser,
String newUser,
HttpSession session,
HttpServletRequest request,
HttpServletResponse response) throws AspireServletException;
} //eof-class
Each event is expressed as a function call in the interface. The parameters of the event became arguments to the function. 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 do that, I always paid. This is not as essential to languages where exceptions are primarily runtime exceptions and not checked exceptions.
The intent of the interface is that I can allow an implementation which can be supplied at run time. For example I can go with an implemenation that looks like
class MyEventResponse implements IHttpEvents
{
}
To generate a beginRequest event, I can then proceed to use the above impplementation as
..
IHttpEvents httpEvents = SomeUtility.getHttpEventImplementation();
httpEvents.beginRequest(request,response)
As I raise events from different parts of the program it became tedious to always get the interface and then call the method on the interface. It is lot more easier to do the following
..
SWIHttpEvents.beginRequest(request,response)
Where SWIHttpEvents is a static wrapper class to the IHttpEvents interface. Here is a complete definition for the above interface
public class SWIHttpEvents
{
public static IHttpEvents m_events = null;
static
{
try
{
m_events = (IHttpEvents)AppObjects.getObject(IHttpEvents.NAME,null);
}
catch(RequestExecutionException x)
{
m_events=null;
AppObjects.log("Warn: No http events class available. No events will be reported");
}
}
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 userChange(String oldUser,
String newUser,
HttpSession session,
HttpServletRequest request,
HttpServletResponse response) throws AspireServletException
{
if (m_events == null) return true;
return m_events.userChange(oldUser,newUser,session,request,response);
}
}//eof-class
For optimizing the calls to individual events the following code in the static holder class is going to cache the implementation object.
public static IHttpEvents m_events = null;
static
{
try
{
m_events = (IHttpEvents)AppObjects.getObject(IHttpEvents.NAME,null);
}
catch(RequestExecutionException x)
{
m_events=null;
AppObjects.log("Warn: No http events class available. No events will be reported");
}
}
The initialization is carried out using a static block. The event object is located through somekind of a factory or look up service. Depending on the container various approaches can be used. In Aspire a configuration file provides this name to class binding. Here is an actual example
request.HttpEvents.classname=com.ai.servlets.HttpEventDistributor
Like any other well behaved container Aspire ensures the implementation class is a singleton. If for some reason if this line is not available in the configuration file then a null is returned. In such an event the SWIHttpEvents is coded in such a way that the events are ignored and not throw any exception. The following method implementation is an example of that
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);
}
Now that a provision is made for implementing the events let me show you a trivial implementation for this 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 for the events as follows
request.HttpEvents.classname=com.ai.servlets.LogHttpEvents
There is an obvious drawback or rather limitation to the implementation. I can only supply one implementation. What if I want to accomplish two things for an event. That is like saying I have two subscribers to an event. This is where an event distributor comes into play. An event distributor acts like any other implementation of IHttpEvents but distributes the events on to a "chain or bus" of implementations. That is to say that the event distributor will delegate the events to any number of implementations. Here is what a configuration file will look like to accomodate this
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 returns false, it will terminate the chain. An exception will 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
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 = com.ai.common.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("Error: Could not obtain the requested event handler",x);
continue;
}
}
}
}
//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;
}
//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
}
If I am implementing the IHttpEvents I am forced to implement all the events whether I am interested in them or not. This can be unnecessarily tedious. To help with this 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. An 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(java.io.UnsupportedEncodingException x)
{
throw new AspireServletException("Error: Encoding error while setting http request char encoding",x);
}
}//eof-function
}//eof-class
So far I have documented how to respond to events primarily. Here is an example of how to raise an event using the SWIHttpEvents 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("INVALID_PASSWORD:userid or password is wrong");
}
//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.
The approach suggested here has parallels to lot of other component architectures such as dynamic proxies in Java, Remoting in C#, Delegates in C#, and even EJBs. Although the examples show a staright forward non-reflection based approach it is possible to redesign the principles using reflection where by 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.