Architect's CornerDynamic servlet to JSP™ page navigation

WELCOME TO THIS installment of the "Architect's Corner." I examine how to create a dynamic Servlet to JSP page navigation system.

Last year my colleague, Dan Malks, wrote an article exposing the power behind the integration of servlets and JSP technology.1 During about that same time I was using a pattern very similar to Dan's Service to Workers pattern—one in which the service is handled initially by a single servlet, which delegates the processing of the business functionality to a business delegate via a worker bean, and ultimately dispatches to a JSP page for the dynamic generation of the presentation. One of the responsibilities of the worker bean, or helper, is to act as an adapter for the data of the business services (see Figure 1).

Figure 1

Figure 1. Service to workers.

I was dealing with the common problem of actually integrating the servlet and JSP construct together into a common mechanism. There wasn't a problem with the integration of the two, but it quickly became apparent that if not designed properly, maintenance could become difficult. Using this design, the servlet may have the responsibility of determining which worker bean to use, in addition to determining which JSP page to invoke to fulfill the request. Simply stated, the maintenance of the navigational mechanism, such as which JSP page and worker bean to call, can quickly become a burden if left to the servlet alone to determine.

It is clear why we would marry the servlet/JSP technology; however, we can struggle with not only how to make it easy for a development staff to embrace and use this technology, but also to fully understand what this would actually buy for that staff. A mechanism is needed to seamlessly tie together the concept of navigation in such a way that the developer does not have to worry about how to manage the navigation as well as the integration within the back-end services via the worker bean. When a Web page request is posted to the servlet, the servlet should determine which services need to be activated to successfully fulfill the request. At the same time, the servlet should also be aware of which corresponding JSP page should facilitate the outgoing response. This process should of course be seamless for the user and transparent for the developer. If a developer understands the process of how a mechanism tied the components together, all he/she has to deal with is the JSP page and/or the worker bean. The responsibility of tying these concepts together would not be the developer's; rather, it would be the underlying mechanism's. This not only ensures the separation of the presentation logic from that of the business logic, but in large organizations will allow separate developers to focus on either the JSP page or the worker bean at the same time without having to work closely together.

From this design perspective, the servlet acts as the main focal point and can be considered the controller. However, this introduces another problem that I refer to as the "Navigational Problem." The servlet should be orchestrating the request without knowing the details of all the different players in the system. It should know that there are worker beans involved in the process, but it should not be aware of any particular worker beans. Likewise, it should be aware of the JSP pages but not aware of any particular one. This can be easier said than done, and often tends not to be the case. This concept is shown in Figure 1, where the servlet is forced to have knowledge of the individual worker beans and to keep track of the JSP pages at the same time. This type of design is limiting because any additional JSP pages that are added to the system must somehow register their presence with the servlet. The worker beans have a similar dilemma, and the servlet must contain the overhead of maintaining this relationship. A design of this type most likely would directly involve some type of code change any time new components were introduced into the system in the form of JSP pages or worker beans. It would be a much better design if the navigation services could be managed dynamically. In other words, add components (JSP pages and worker beans) to the system without using additional code to manage the navigation or integration of the new JSP page and/or worker bean.

The first and easy step toward the resolution of this problem is to abstract the concept of navigation from the servlet. This allows the servlet to be aware of the process without worrying about the details of each request. A navigational service will be responsible for determining which worker beans and JSP page handle each request. This makes the servlet responsible for overall process flow without having to deal with the particulars of who needs to handle each request. Looking at Figure 2, which depicts navigational abstraction, we see that the responsibilities of determining the sequence of worker beans and which JSP page to invoke belong to the navigation service.

Figure 2

Figure 2. Navigational abstraction.

This simple abstraction allows the developer to leverage the full benefits of a single servlet handling the incoming requests. It is now the responsibility of the navigation to determine what the servlet will invoke next. The servlet is only responsible for understanding the process that exists (Servlet -> Navigation -> Servlet -> Worker Bean -> Servlet -> JSP page). In this model, the navigation creates what is called a controller object. The controller object contains all the details of the worker beans and the JSP pages that are to be invoked. Listing 1 shows the responsibility of the servlet.

As can be seen in Listing 1, the servlet invokes the createController method on the navigation service and passes it the request object. The navigational service has to figure out what the client request actually needs. At this point, it may be satisfactory for the navigational logic to exist within the navigational service in determining the navigational flow. However, we can take this one step further: The navigational service can store information about navigation within a database.

You may be thinking that this type of strategy is inefficient because for each request you must access the database. To avoid this overhead, the navigation service has been designed with a caching mechanism that loads the navigational information into memory. It only loads the navigational information on start-up and when something in the database has been modified.

There are a few assumptions that must first be understood and in place if the navigational service is to work properly. Every incoming request is a two-step operation:

  • Step 1: The incoming data must be saved (request).
  • Step 2: Data must be accessed and created so that the outgoing screen (response) can be created.
Not every post will have data to save (step 1), but a screen will always have to be created to handle the outgoing response (step 2).

Each time a screen is posted to the servlet there are two hidden fields within the client's HTML: the ToScreen and FromScreen. The navigation service will get these two fields out of the request object to determine what needs to be done for steps 1 and 2. The navigation also uses a role that is stored in the session object and allows a finer grain of navigational control for users. The navigation has stored two tables in its memory: the first to accomplish step 1 and the second to accomplish step 2. The first table is known as the save table, and the second table is known as the load table (see Figure 3 and Table 1).

Figure 3

Figure 3. Navigational process.

Table 1. Save and Load.
SAVE
TOSCREEN FROMSCREEN ROLE WORKER BEAN   ERROR JSP ORDER
ListScreen LoginScreen User com.sun.exampleWB   ListError 1
ListScreen LoginScreen User com.sun.exampleWB   ListError 2
MainScreen ListScreen User com.sun.exampleWB   MainScreenError 1
MainScreen ListScreen Admin com.sun.exampleWB   MainScreenError 1%)
LOAD
TOSCREEN FROMSCREEN ROLE WORKER BEAN SUCCESS JSP ERROR JSP ORDER
ListScreen LoginScreen User com.sun.loadListWB List ListFailure 1
MainScreen ListScreen User   MainMenu MenuError 1
MainScreen ListScreen Admin com.sun.mainWB MainMenu MenuError 1

For any given request, the navigational service would look at the hidden field's (ToScreen and FromScreen) values embedded within the request object and then pull the role out of the session object. With these values, it would then use the tables in Table 1, which are each stored in memory as a com.objectspace.jgl.HashMap, and do a lookup based on the ToScreen, FromScreen, and Role. For step 1, this would give either a single bean or multiple worker beans. If multiple results are found, as would be the case using Table 1, assume during a request that the user role was a "User", the ToScreen was "ListScreen", and the FromScreen was "LoginScreen". The worker beans are placed in an array in the order in which they are to be invoked. Once the appropriate information has been gathered to accomplish step 1, the same type of data can be created to accomplish step 2. The only difference with step 2 is that some additional data is gathered, such as the success and error JSP page. As demonstrated from the table in Figure 4, multiple worker beans can be part of a single request.

Figure 4

Figure 4. Class diagram.

The controller object implements the GenericController interface (see Figure 4) which has a method called processRequest in which the request object is passed as the argument and returns the next JSP page, which can be a successful or error JSP page.

In Figure 5, the servlet invokes the process request on the GenericController. The GenericControllerImpl contains the save worker beans as well as the load worker beans. Once the servlet invokes the processRequest, the GenericControllerImpl in turn invokes processIncomingRequest on each of the save worker beans. The worker beans then each implement the worker bean interface, which has a processIncomingRequest method that takes the request as the argument and returns a Boolean response for the save worker beans. If the result from the process request is True (Boolean), the GenericControllerImpl will invoke the next save worker bean.

Figure 5

Figure 5. Invoking the process request.

Once all of processIncomingRequest has been invoked for all the save worker beans, the GenericControllerImpl moves on to the load worker beans. The GenericControllerImpl will then invoke processOutgoingRequest on each of the load worker beans. After a successful response on each of the load worker beans, the GenericControllerImpl returns the next JSP page that is stored within it to the ControllerServlet. The ControllerServlet forwards the request to the next JSP page, which in this case is the successful JSP page. The JSP page is invoked and the response is formed for the client (see Figure 5).

If at any point in time one of the worker beans, whether it be a save or load, returns false to the controller object, the controller replaces the next JSP page with the error JSP page that is found in the table. If the worker beans invoked were successful, the next JSP page becomes the successful JSP page. Interestingly, the navigational service is a client to itself in that if the navigation detects that the table needs to be reloaded, it uses a worker bean that interacts with a stateless session bean to save and reload its properties from the database.

The navigation set up in this manner allows for dynamic navigation. A Web-based client can add, remove, and modify navigational components on the fly. Once a component has been added to the navigational service, the navigation service internally marks itself as dirty. The next client that comes in to use the navigational service automatically checks the dirty flag and checks if it is set to dirty. If the navigational service finds that the table is marked dirty, it automatically reloads the tables from the database. If additional screens (JSP pages) are added into the system without any additional worker beans, the system never has to be restarted. It is only when a worker bean has been added to the system that it needs to be restarted.

When a developer adds the new service to the system in the form of new worker beans and JSP pages, all the developer has to do is interface with the navigational service screens, which display the tables as seen in Table 1. They can then add their worker beans and JSP pages into the system dynamically without code modifications. This creates not only a dynamic system, but also a more flexible system. More importantly, the concept of navigation is determined by the underlying architecture and not by each developer (see Figure 6) as he/she implements requirements. With this mechanism in place, the developer does not have to do anything special to make the process work. Overall, it makes the development experience much easier and maintainable for the developer.

Figure 6

Figure 6. Navigation determined by the architecture.

Summary
This approach allows a system to increase dramatically in terms of maintainability and flexibility, because all of the navigation is maintained and stored in one central location instead of each screen being responsible for its own navigation. If any changes need to be made, the changes can be made in one central location. This approach also creates a good mechanism for enforcing the strict separation of the presentation tier from the business logic, which is a common problem found in many Web-based systems. A change can be made to a system without retesting all of the business logic. Another major disadvantage of integrating business logic and presentation logic is that other developers cannot reuse business logic that is stored within the presentation tier because their presentation requirements are usually different from the existing presentation. Developers have to duplicate business logic throughout the system unnecessarily, which ultimately leads to an increase in testing, development, and maintainability costs for the entire system. With this approach, no longer do JSP pages directly invoke worker beans to call the back-end services. Instead, the worker beans place the data that the JSP pages need access to in the session object. The JSP page accesses the session object and takes the data that it needs for presentation purposes out of the session object.

This approach allows business logic to be leveraged across the enterprise, especially if the Service to Workers pattern is utilized (see Figure 1). Note, the business logic does not exist within the worker beans either. Rather, the worker beans act as the gateway to all of the services that become available within the back-end system in which all of the business logic resides. The worker beans simply tie together the services that are needed for a given request and act as a gateway.

If you would like to know more about these types of architectures, have comments about those presented, or want to suggest a topic for a future column, please email your thoughts to [email protected]. We will do our best to address as many requests as possible.

Reference

  1. Malks, D., "JSP and Servlets: a powerful pair," Java Report, Vol. 4, No. 11, Nov. 1999, pp. 74–78, 80.

Trademarks
Sun, Sun Microsystems, the Sun logo, Java, JavaBeans, and Enterprise JavaBeans are trademarks or registered trademarks of Sun Microsystems Inc. in the United States and other countries. Sun Microsystems Inc. may have intellectual property rights relating to implementations of the technology described in this article, and no license of any kind is given here.

The information in this article (the "information") is provided "as is," for discussion purposes only. All express or implied conditions, representations and warranties, including any implied warranty of merchantability, fitness for a particular purpose, or noninfringement, are disclaimed, except to the extent that such disclaimers are held to be legally invalid. Neither Sun nor the author makes any representations, warranties, or guarantees as to the quality, suitability, truth, accuracy, or completeness of any of the information. Neither Sun nor the author shall be liable for any damages suffered as a result of using, modifying, contributing, copying, or distributing the information.

Errata
The "Stale Data Problem" sidebar on page 100 of the September 2000 Architect's Corner, "Persistence alternatives using an EJB(tm) component architecture" by Tim Seltzer and Himanshu Bhatt, contained two errors:

The domain model name in Example 1 should have read:


Loan->Borrower<-application>

The name in Example 2 should have read:


Order<->* OrderItems

These names were mistakenly formatted with @ symbols.

We apologize for any confusion this may have caused.