Because of peculiarities such as relatively loose coupling between the user interface and application logic, Web applications present unique challenges to the programmer. Chief among these is ensuring that the application can respond intelligently to unexpected user activities, such as using the Back button in the client Web browser.
One way to work around such problems is to build Web applications as finite state machines. Let's take a look at a real, reusable Java class that will allow you to create robust Web applications modeled as finite state machines.
What’s a finite state machine?
What, exactly is a finite state machine, and how does it help you develop robust Web applications? For answers to these questions, and to see how to build a state table for a sample Web application, read “Set up Web applications as finite state machines.”
Creating the state table for an application is the time consuming part, but once you have the table, coding the application is rather straightforward. To make the table creation task even easier, I’ve created a generic class that implements the details of the finite state machine, which you will find in Listing A. This Dispatcher class was designed for use with Java servlets, but the same concepts can be used with other programming languages and Web applications solutions.
Dispatcher is a finite state machine that dispatches requests, identified as request ID’s, to methods according to a given application state mapping as determined by the state table. The class persists the machine's state between requests using an HTTP session, and you can easily extend this session management mechanism to persist application data, should you wish to do so.
Dispatcher in action
Using the Dispatcher class is pretty simple. First, create an instance of the class passing it a reference to your servlet object—the best place to do that is in the servlet's init method. Then, configure the Dispatcher instance with your Web application's state table, by calling the mapRequest method repeatedly, once for each entry in your state table. You’ll pass mapRequest the request ID, the state identifier, and the name of the method to call from your state table. Figure A provides a description of the entire Dispatcher API.
The method to call for a state and request ID combination is identified by name, as a string, and must be a member of the servlet class. It receives as a parameter an instance of the Dispatcher.Session class that contains the machine's state, the request ID, and the HTTP request and response. The method is responsible for processing the request and setting the machine's new state. Figure B provides the complete API for the Dispatcher.Session class.
The Dispatcher class saves the machine's state between requests using an instance of the Dispatcher.Session inner class. When the application receives its first request, Dispatcher creates an instance of Dispatcher.Session and saves it in an HttpSession object. When future requests are received, Dispatcher retrieves the Dispatcher.Session instance from the existing session.
Most of the action occurs inside the _dispatch method, which is listed by itself in Listing B. After extracting the machine’s previous state from the session, _dispatch attempts to match the request ID and state with a method name. Once the appropriate method is located, Dispatcher invokes it via reflection.
The class expects to find the request ID in a parameter called _reqId. Consequently, every HTTP request made to the servlet must include the parameter _reqId, either as part of the URL (in the case of a GET request), or as a hidden parameter (for a POST request). This way, Dispatcher can identify the request and retrieve the method to call from the state table. If an HTTP request doesn’t specify a _reqId parameter, the request identification will be null, which corresponds to the default event in the state table. Then you can access the initial page of the application using a familiar URL without passing any parameters.
The sample Favorites application
Once you have configured Dispatcher and coded all the methods you need to handle the requests in your state table, all you have to do is to call the method dispatch from your servlet's doX methods. Listing C shows the complete source code for a sample application. You may remember this app as the Favorites Web application I discussed in my previous article. To keep the example simple, the sample application saves the favorites in memory as global static objects. Of course, a real application would use a database or something similar to persist the data.
As you can see from the init method, the sample app uses pretty much the same state table I created in that previous article. There is one small difference: The initial state and default requests both correspond to null, which launches the application’s login page.
dispatcher.mapRequest( null, null, "showLoginPage" );
Also, you’ll notice that I map a request and state combination of asterisks (*) with the restart_Application method:
dispatcher.mapRequest( "*", "*", "restartApplication" );
Dispatcher lets you map asterisk (*) in place of any request ID or state to specify default actions in order to simplify configuring the state table. Since so many state and request combinations shouldn’t occur in normal use and represent errors (therefore mapping to the same “reset” action), this behavior greatly simplifies your application. You can specify a default action for a particular request (by specifying an asterisk [*] as the state), for a particular state (asterisk [*] as the request ID) and a global default action (asterisk [*] for both the request ID and state) to have a particular method called in the event that a matching request ID or state cannot be found. However, if an action is specified for a particular request ID and state combination, it takes precedence over any default action. The default action for the sample Favorites application is to display an error page and reset the application.
You can download the entire source for Dispatcher and the sample Favorites application to see how it all works. The downloadable source for Dispatcher contains a lot of comments that were removed from the listings that accompany this article. I’ve even included JavaDoc markup in the comments.
A little extra added value
As I alluded to earlier, you can subclass Dispatcher.Session to store application-specific data, so that you can get automatic session management for free. To do this, though, you’ll need to pass the subclassed class definition to the Dispatcher constructor as the sessionClass parameter.
By combining the finite state machine concept with automatic session management in a reusable component, you can build robust Web applications with greatly simplified code. There is room for improvements to Dispatcher, however, like testing for the existence of expected parameters and preconditions, and differentiating between GET and POST requests.