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:
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/Barracuda/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.
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).
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 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.
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.
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.
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.
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.
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.
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.
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.
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.