This purpose of this document is to provide a
topographical overview of the Simple Login App and illustrate how event oriented
programming can be successfully meshed with a Model 2 presentation paradigm.
- System Overview
- Event Hierarchy
- Application Topography
- Basic Event Handling
- Event Chaining
- Redirecting Event Flow
- Exceptional Events
- Passing State between Event Handlers
- 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:
- the ApplicationGateway - set up our particular HTTP gateway to service events
- the Barracuda event hierarchy - to define our event hierarchy
- 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:
- DefaultBaseEvent
- ControlEvent
- HttpRequestEvent
- LocalRequestEvent
- LoginScreenEvent
- GetLoginScreenEvent
- AttemptLoginEvent
- MainScreenEvent
- GetMainScreenEvent
- ExpireSessionEvent
- ViewEvent
- HttpResponseEvent
- LocalResponseEvent
- RenderEvent
- RenderLoginScreenEvent
- RenderMainScreenEvent
- ErrorEvent
- BadUsernameEvent
- BadPasswordEvent
- UnknownLoginErrorEvent
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.
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.
SampleApplicationGateway - When the gateway is created
by the servlet container it instantiates the LoginGateway and asks it to register with the
event broker for any events it the login app might be interested in. The primary function
of the SampleApplicationGateway is to act as the servlet gateway for incoming client
events. It converts HttpServletRequests into the appropriate events and dispatches them to
interested listeners.
GlobalUtilities - This particular class just provides a
simple event handler for any unhandled HttpResponseEvents, something of a catchall
mechanism. If we didn't provide this, the ApplicationGateway would just return error
responses for unhandled HttpResponseEvents.
LoginGateway - This is really the master controller for
the Simple Login application. It instantiates the three main event handlers (LoginScreen,
MainScreen, and Utilities) and gives each of them the opportunity to register with the
event broker.
NOTE: This class is no longer used; gateways are
now registered in WEB-INF/ex4.xml file. The entries that
accomplish this look something like this:
<event-gateway class="org.barracudamvc.examples.ex1.LocalUtilities" />
<event-gateway class="org.barracudamvc.examples.ex1.LoginScreen" />
<event-gateway class="org.barracudamvc.examples.ex1.MainScreen" />
LoginScreen - This particular class is responsible for
handling all events related to the Login portions of the system.
MainScreen - Like the login screen, this class handles
functionality that is related to the main screen. In a real app, the main screen might
actually be composed of many sub-screens, in which case we could break the functionality
out into separate classes to manage complexity.
LocalUtilities - This class is responsible for
functionality that applies to the whole subsystem, in this case, both the Login and the
Main screens.
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:
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
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 next 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.
- First we can also check the client's session info and if they have valid user/pwd
credentials.
- 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.
- 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:
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.
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.
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
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.
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.
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).
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.
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:
- getGlobalRepository()
- getSessionRepository()
- getLocalRepository()
- 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 somekind 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. |