cool stuff
/src_docs/quick_reference/index.html, v125

Simple Login App (Events Example)

The purpose of this document is to provide an overview of the Simple Login App and illustrate how event oriented programming can be successfully meshed with a Model 2 presentation paradigm.
  1. System Overview
  2. Event Hierarchy
  3. Application Topography
  4. Basic Event Handling
  5. Event Chaining
  6. Redirecting Event Flow
  7. Exceptional Events
  8. Passing State between Event Handlers
  9. Conclusion

System Overview

The best way to understand what an actual Barracuda application might look like is to actually run the Simple Login Example and then study both the javadocs and the source.

Before doing that, however, it may be helpful to consider the application structure. The Simple Login class diagram provides a high level overview of the system. This diagram basically illustrates that SampleApplicationGateway is our primary gateway (HttpServlet), and it contains one "sub-app", in this case the Simple Login App (note that while the diagram doesn't show it, SampleApplicationGateway also contains the another sub-app, the Event Model Configuration screen).

We should observe that when building an application we extend from three portions of the Barracuda event model:

  1. the ApplicationGateway - set up our particular HTTP gateway to service events
  2. the Barracuda event hierarchy - to define our event hierarchy
  3. the DefaultBaseEventListener - to define out event handlers

Event Hierarchy

Let's look first at the Event hierarchy, because this provides a logical representation that closely mirrors the physical organization of our system. In our case, our application should have two main components (Login and Main screens), along with a few ancillary error screens (Bad user/pwd). We can decompose this structure into a hierarchical chain of events:

Note the distinction between Request and Response events: 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. 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.

You should also note how we define this event hierarchy: in an events.xml file, the contents of which look something like this:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE build-events
PUBLIC "-//Enhydra Barracuda//DTD Barracuda Events 1.0//EN"
"http://barracudamvc.org../../dtds/BarracudaEventBuilder.dtd">

<build-events pkg="org.barracudamvc.examples.ex1.events">

  <control-events>
    <event name="SomeSpecialEvent"/>
  </control-events>
 
  <req-events>
    <event name="LocalRequestEvent">
      <event name="LoginScreenEvent">
        <event name="GetLoginScreen"/>
        <event name="AttemptLogin"/>
      </event>
      <event name="MainScreenEvent">
        <event name="GetMainScreen"/>
        <event name="GetMainScreenSlowly" template="LongRunning.template"
               on_cancel="new GetMainScreen()" eta="30" />
        <event name="ExpireSession"/>
      </event>
    </event>
  </req-events>

  <resp-events>
    <event name="LocalResponseEvent">
      <event name="RenderEvent">
        <event name="RenderLoginScreen"/>
        <event name="RenderMainScreen"/>
      </event>
      <event name="ErrorEvent">
        <event name="BadUsername"/>
        <event name="BadPassword"/>
        <event name="UnknownLoginError"/>
      </event>
    </event>
  </resp-events>

</build-events>

This file gets processed at compile time, and the corresponding Java event classes are generated behind the scenes.

Application Topography

Now that we have a basic understanding of the event hierarchy, let's take a look at the actual application structure. In a nutshell, the application consists of the following components.

We should also note that there are two subpackages: events and services. The events subpackage contains the events we described above. The services subpackages contains classes which provide services for the event handlers to use (in this case to validate a user name and to access the user's session).

Basic Event Handling

Now let's take a look at how we actually handle events in an event gateway like LoginScreen.

First of all, in order to handle an event we must first express our interest in it. We do this by registering with the event broker which is passed to us through the EventGateway interface. Registering is actually a two step process:

  1. We must create a factory to actually instantiate the event handler (this is done for scalability purposes...remember, this is a multithread app and the event model must be able to service hundreds of requests simultaneously). The code to define the factory can be inlined as an anonymous class to look something like this:
     

    private ListenerFactory getLoginFactory = new DefaultListenerFactory(){
        public BaseEventListener getInstance() {return new GetLoginScreenHandler();}
        public String getListenerID() {return (GetLoginScreenHandler.class).getName();}
    };


    This effecively binds the GetLoginScreenHandler() event listener to the getLoginFactory object
     

  2. We must also register the factory with the event broker, expressing our interest in a particular class of event. We do this in the registerLocalEventInterests() method like this:
     

    eb.addEventListener(getLoginFactory, GetLoginScreenEvent.class);


    This basically tells the event broker "When you get a GetLoginScreenEvent, notify the HandleGetLogin event listener.

The second step is to actually create the event handler. These can be implemented either as inner classes or standalone classes. The key is they must implement the BaseEventListener interface. In our case, we chose to extend DefaultBaseEventListener and implement the handler as an inner class that looks something like this:

class GetLoginScreenHandler extends DefaultBaseEventListener {
    public void handleReqEvent(HttpRequestEvent event, HttpServletRequest req, DispatchQueue queue)
        throws EventException, ServletException, IOException {
            //redirect to the RenderLogin view
            BaseEvent newEvent = new RenderLoginScreenEvent();
            newEvent.setSource(event);
            queue.addEvent(newEvent);
            event.setHandled(true);
        }
    }
}

Ok, this allows us to receive the event if we are notified, but what do we do next? In this case, we really don't have to do any special "Controller" processing, so we simply add a ResponseEvent to the queue (in this case RenderLoginScreen event) which will cause the actual Login Screen to get generated.

Event Chaining

Well, that was certainly simple enough, but it was also fairly trivial. What happens if we want to do something more sophisticated, like automatically log someone in? Actually, we already are, and as the sample application demonstrates we can do so without ever modifying the event listener than handles the GetLoginScreenEvent. The way we accomplish this is through event chaining.

As the diagram illustrates, all HttpRequestEvents implement a marker interface called Polymorphic. This basically tells the dispatcher that before dispatching an event (ie. GetLoginScreen), it should first dispatch it's parent events. For instance, consider the following event chain:

Polymorphic basically means "When you fire GetLoginScreenEvent, fire HttpRequestEvent,  LocalRequestEvent, and LoginScreenEvent events first because the GetLoginScreenEvent is an instance of those events". So how do we take advantage of this behavior?

Well, in this case, if you look in the LocalUtilities source file you will see that we install an event handler that listens for LocalRequestEvents. This means that before any event is dispatched in the system, this particular handler will always get notified, and it can apply all kinds of "global filtering" before the specific event being dispatched is ever delivered.

In our case here, we do several interesting things.

  1. First we can also check the client's session info and if they have valid user/pwd credentials.
     
  2. If they are trying fire an event which requires authentication (like GetMainScreenEvent) and they HAVEN'T yet been validated, then we again redirect flow to the login screen.
     
  3. If they come to the login screen an HAVE already been authenticated, we can just automatically route them on to the main screen.

As we can see, event chaining provides a powerful mechanism to control system behaviour by listening for events higher up in the event hierarchy. For instance, if we wanted to apply some kind of controller logic to all login request handlers, all we would have to do is listen for the LoginScreenEvent.

This approach is very flexible because as applications requirements evolve over time we can simply modify the event hierarchy (inserting or deleting events as needed) -- the individual handlers require little if any modification. This demonstrates one of the main benefits of a more granular event oriented programming approach. Of course it also emphasizes the importance of properly defining your event hierarchy.

Redirecting Event Flow

At this point it may be helpful to discuss various ways to redirect application through with events. There are 3 options:

  1. The first method is to just add a new event to the event queue. The event will be processed in due turn, and the approriate handlers will be invoked. This particular approach is particularly effective when the events being fired are atomical, that is they can be executed independently of any other events in the queue.
     

  2. Some events however, need to override whatever is currently in the queue and replace it with the new event. In the case of authentication for instance, if a client attempts to access the main page and they have not been authenticated we really want to consume the initial event and replace it with an event that redirects them to the Login screen.

    The easiest way to do this from within an event handler is to throw an InterruptDispatchException. This tells the dispatcher to stop processing, mark everything in the queue as handled, add the new event, and then continue dispatching events.
     

  3. A third option is to throw a RedirectException, which actually causes a response to be sent back to the browser asking it to redirect itself to the new event. This has the effect of causing the URL in the browser to actually change to the event we should be using. For instance, if I try and access a bad event, I should get redirected to the Login screen. By throwing a Redirect exception, my browser URL will actually be changed to point at the Login event now, instead of the bad event I initially entered.
     

    The one caveat to keep in mind with RedirectExceptions is that the event model will not dispatch events that do not implement HttpRequestEvent, so you can not use a redirect exception to fire a response event.

The sample application provides examples of each of these approaches.

Exceptional Events

Let's take a moment and consider how we generate responses. When the Application Gateway receives an incoming HTTP request, it MUST generate an HTTP response. Consequently, it creates a DispatchQueue and indicates that the queue requires a response. When we have dispatched all regular events, if there is no response event in the queue, the dispatcher automatically adds a default HTTP Response Event, effectively guaranteeing that a response will be sent back to the client.

Now, let's assume we have a response event in the queue. If we fire a BadUsernameEvent, there is a handler in place to generate the appropriate response. What happens, however, if we forgot to implement a response handler or haven't got that far in building our system yet?  As an example, observe that nothing in the sample application handles the UnknownLoginErrorEvent. If this event is somehow fired, how will a response be generated to send back to the browser?

If you look closely, you will notice that all HttpResponseEvents implement Exceptional. The Exceptional interface is used to indicate that an event must be handled. If it is not, the parent event will be generated and added to the event queue.   Here's how this plays out: if no one handles the UnknownLoginErrorEvent the parent ErrorEvent would be added to the queue. We do have an event handler for this, which sends a generic error message back to the client. If we did not have an event handler here, an HttpResponseEvent would be generated, and if that was not handled by our app the ApplicationGateway itself will generate a default response.

This type of event chaining is almost the opposite of that we observed with the Polymorphic interface. Instead of dispatching down the event hierarchy it dispatches up. Instead of dispatching before the event is handled it dispatches after, and only if the event is not handled.

There are a number of applications which this kind of behavior is useful. First, it allows us to create default event handlers which can service any number of child events without knowing what those specific events might be. For instance, if I have 100 error events in an app, I may want to just create one event handler that handles all of them. The Exceptional interface makes this very easy to do, and as I add new events to the hieararchy, they are automatically handled by the same default handler. I could also provide custom handlers as needed for specific events simply by installing additional listeners on the events in question.

It should be noted that an event should never implement BOTH Polymorphic and Exceptional. Fortunately, this is impossible to do since both of these interfaces intentionally implement the same method to prevent this from happening.

Passing State between Event Handlers

One of the other questions inevitably raised is "How do I pass state from one event handler to another?" There a couple different approaches, each of which has a number of options.

Doing it manually

  1. The events themselves are capable of carrying state information. However, this is probably the least desirable approach. Events are intentionally lightweight and should really not be used for passing additional information unless there is a compelling reason to do so.
     

  2. A better solution is to use the event context to pass state information. Like the BaseEvent interface, it also implements StateMap, which means we can set/get attributes as necessary. Since all event handlers share the same instance of the context, placing an object in the context's state makes it accessible to later handlers.
     

  3. A third solution is to use the client's HttpSession object. The advantage of this approach is that the state will span multiple requests (wheras the queue's state is limited to the duration of the event dispatch for a particular HTTP request).
     

  4. A fourth option is to use the state provided by the ServletContext structure. This state is shared between servlet threads, so you may need to take particular care when using this type of mechanism.
     

  5. A fifth option would be to persist the data in a database or in a flatfile. This is probably the most permanent means of sharing state, but is also one of the slower approaches because of disk io.

Using the Scoped Object Repositories

The basic idea of an org.barracudamvc.core.util.data.ObjectRepository is that it provides a place to store information. It implements StateMap, so in many ways its a lot like ViewContext, EventContext, etc.

If you look inside this class, you will see a few useful static methods:

Here is a brief explanation of the types of object repositories:

Global Repository (Threadsafe):

The first one returns a reference to a "Global" object repository. Its called global in that its static across the JVM.

Because multiple threads might conceivably try and access it at the same time, the map that backs this structure is threadsafe. You would typically use this for storing global type objects: data sources, constants, etc. Note that any objects which are place in the global object repository need to be threadsafe themselves, since multiple threads might need to use them at the same time.

Session Repository (Not Threadsafe since only accessed in the context of a given thread):

The second one is a wrapper around the HttpSession object. If you place something in this repository you are placing it in the user's session (without actually tying yourself to the Servlet's Session API).

This is particularly useful when you need to write test cases. For example, let's say you have a piece of code that looks for some value (ie. UserName) in the Session. Well, if you want to write a test case for such a scenario, its kind of a pain because you have to go and set up a SessionWrapper, etc. If on the other hand, you simply refer to getSessionRepository(), then when you're running in a servlet environment it will be backed by the Servlet's session, and when running in a plain-jane testing environment, it'll be backed by a regular old statemap.

SO...for testing purposes you simply grab the session repository, put in any objects that are needed by the code being tested, and then run your test. Pretty slick. One other thing: the wrapper avoids actually creating an HttpSession unless absolutely necessary (ie. you actually put something in it), which should help avoid performance impedance.

Local Repository (Not Threadsafe since only accessed in the context of a given thread):

The third one is similar, except that its scoped to the length of the req-resp cycle; this provides a convenient place to store data without having to actually stick it in the user's session.

You can also create your own "custom-scoped" object repositories...just pass in a NameSpace or a String as the key, and it will return a repository for you. Note that if you do this, you have to remember to clean up the object repository when you're done with it.

SO...here's what this means for you the developer. Right now, you write code to handle events. And inside of this code, you might invoke a biz logic layer, which in turn might call down into a JDBC layer. In many cases, you may need to make resources available to these lower layers (like current user). On the one hand, you can modify the method signatures all the way down the line to include a reference to the object in question; this is klunky, however, and often results in middle layers that are unnecessarily coupled with upper/lower layers. A better solution is to have the upper layer place the object in some kind of centrally accessible structure (ie. and object repository), make the necessary biz logic layer call, and when the db layer gets invoked, it can retrieve the object from the repository and go about its business.

The downside of this, of course, is that the compiler won't catch your mistakes for you (ie. if you forget to put an object into the repository when its needed by something else later on). Still, in my experience, this is a pretty useful approach.

SO...here's what was modified to make this work: Basically, I changed ApplicationGateway and ComponentGateway so that whenever a request comes in it sets up the session repository space (by passing it a reference to req). Then, when the request is done, both session and local spaces are removed, automatically cleaning everything up for you. Pretty slick.

Conclusion

Hopefully this provides a good introduction to the Simple Login Application. If you have additional questions that are not addressed in this overview, or suggestions for improvement please let us know.

 


Last Modified: 2006-01-02 15:59:13 -0500 (Mon, 02 Jan 2006)