Jump the hurdles of Struts development

Tips for solving common difficulties in Struts

Unless you have been living under a rock or in a cave for the past few years, you have most likely heard of the Struts framework. Struts is the open source initiative sponsored by the Apache Software Foundation and was created to encourage the Model-View-Controller (MVC) design paradigm within a Web application's presentation layer. Struts implements the MVC pattern using the Service to Worker pattern. A well-designed architecture strives to be loosely coupled and highly cohesive. Struts provides the mechanism to achieve this design goal in the presentation layer of multitiered, enterprise Web applications.

One of the most daunting tasks facing enterprise application architects is the presentation layer's creation and maintenance. Users expect highly functional, robust, and elegant graphical user interfaces. Thus, the presentation layer's codebase winds up greatly outnumbering that of the application layer. In addition, the advent of different display platforms such as wireless phones and PDAs has made an already complex situation much more complex.

Various books and articles already cover Struts's inner workings and teach how to use the framework. This article elaborates on the issues facing Web application developers who use Struts and how to resolve them. Many of the following approaches can be abstracted and applied to different MVC frameworks such as the upcoming JavaServer Faces specification. Craig R. McClanahan, one of the original creators of Struts, leads that specification.

This discussion's topics cover those areas that present the most hurdles while building a J2EE (Java 2 Platform, Enterprise Edition) application using Struts with BEA WebLogic Server. We cover the following specific issues:

  • Creating/maintaining struts-config.xml
  • Form/session management
  • Relationships of Struts mappings and the user interface
  • Managing the Back button
  • User authentication
  • User interface control flow
  • Exception handling
  • Testing

Take two and call us in the morning

The Struts framework unarguably eases the development and maintenance of user interfaces for enterprise applications. However, after working with Struts on even a simple application, one quickly realizes the nightmare that is struts-config.xml. That file can quickly become unwieldy at best. When building an enterprise application, struts-config.xml can grow in excess of 500 action mappings, making it virtually unmanageable.

We recommend two tools to help manage this headache. First, document your user interface flow using Microsoft Visio and StrutsGUI from Alien-Factory. StrutsGUI is a Visio stencil that helps depict user interface flow diagrams using Struts nomenclature. There is a hidden gem within the Struts stencil item: by right-clicking this option, selecting Edit Title Properties, and then selecting the Tools option, you can generate a struts-config.xml file based on this diagram. For example, the simple application shown in Figure 1 generates the following struts-config.xml shown in the code below:

Figure 1. StrutsGUI model. Click on thumbnail to view full-size image.
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!-- Struts Config XML - Sample Struts App -->
<!-- ===================================== -->
<!-- AutoGenerated from : c:\dev\javaworld\app\sample.vsd -->
<!-- AutoGenerated on   : 02-18-2003 23:05:47 -->
<!-- AutoGenerated by   : Struts GUI v2.11   (c)2002 Alien-Factory -->
<!--                    : See 'http://www.alien-factory.co.uk' for details -->
<!-- GET YOUR STICKY FINGERS OFF! i.e. Do not edit. -->
<!DOCTYPE struts-config PUBLIC
      "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN"
      "http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd">
<struts-config>
  <!-- ====================== Form Bean Definitions =================== -->
  <form-beans>
  </form-beans>
  <!-- ================= Global Forward Definitions =================== -->
  <global-forwards>
  </global-forwards>
  <!-- ======================= Action Definitions ===================== -->
  <action-mappings>
    <action  path="/Login"
             type="com.agilquest.onboard.presentation.actions.LoginAction">
      <description>Authenticates and authorizes a user.</description>
    </action>
  </action-mappings>
  
</struts-config>

To make your user interface flow diagrams more complete, we recommend an additional step when using StrutsGUI. Within your StrutsGUI Visio document, you can easily link each JSP (JavaServer Pages) page to its actual screen shot in the application. Not only does creating this artifact aid in documenting the application, but more importantly, it becomes an excellent tool for training new developers about the user interface design.

Another tool that helps manage Struts applications is the Struts Console created by James Holmes. In essence, it provides facilities that enable you to get to the same endpoint as StrutsGUI, but differs in approach and strengths. Both tools perform well, and either will enhance a Struts-based enterprise application's maintenance.

Now, where did I put that form?

ActionForm session management can be tricky. What should the ActionForm's life be? Should it be in request scope or session scope? One solution to this problem puts the ActionForm in session for the life of the functionality it is supposed to represent.

In such a case, how do you maintain these ActionForm objects generically? Who assumes the responsibility of cleaning up these ActionForm objects once they are no longer needed? A typical scenario is when a user traverses from one functionality to another through a menu. In that case, the old ActionForm objects should be removed from the session and new ActionForm objects created. A centralized Action class, called MenuAction, which deals only with the menu traversal, should be present. This Action class deletes the redundant ActionForm objects from the session. It then forwards the user to the new page where new ActionForm objects are created.

Given this, how do we show the different menu items to the user based on her role or permissions? This menu should also be internationalized, and it can change based on the user permissions; that is, if the permissions change, the menu must change accordingly. One such approach persists the user's permissions. When she logs in, a MenuFactory creates menus from these permissions. For additional safety, the MenuAction class can then authorize the user before permitting her to proceed to the selected functionality.

A rule of thumb in naming the ActionForm objects in struts-config.xml is to end the object name with Form, thereby simplifying the maintenance of these forms in session. For example: ReservationForm, SearchUserForm, BankAccountForm, UserProfileForm, and so on.

The code below further clarifies ActionForm(s) management by illustrating a generic menu traversal action with the Action mappings:

 public class MenuAction {
  public ActionForward perform(ActionMapping       _mapping,
                               ActionForm          _form,
                               HttpServletRequest  _request,
                               HttpServletResponse _response)
                                  throws IOException, ServletException {
    // Check end-user permissions whether allowed into the requested     
    // functionality 
    checkIfUserAllowedToProceed(_mapping, _form, _request, _response); 
    // Clean up the session object (this logic is in its own method)
    String formName = null; 
    HttpSession session = _request.getSession();
    Enumeration e = session.getAttributeNames();  
 
    while(e.hasMoreElements()) {
     
      formName = (String)e.nextElement();
      if (formName.endsWith("Form")){
        session.removeAttribute(formName);
      }    
    }
    // Now find out which functionality the end-user wants to go to
    String forwardStr = _request.getParameter("nextFunctionality");
    if (forwardStr != null && forwardStr.trim().length() > 0){
      return _mapping.findForward(forwardStr);
    }
    else {
      return _mapping.findForward("index");
    }
  }  
}

The following Action mapping is an example of how to implement an action based on a menu selection:

<!-- A generic menu action that forwards the user from one 
     functionality to another functionality (after checking permissions)
-->
<action path="/menuAction"
        type="x.y.z.MenuAction"
        input="/menu.jsp">      
  <forward name="create_reservation" path="/actionResv.do"/> 
  <forward name="index"              path="/menu.jsp"/> 
  <forward name="add_person"         path="/actionPerson.do"/> 
  <forward name="logout"             path="/actionLogout.do"/> 
</action>

The example and the mapping are self-explanatory.

Tell me again, how are we related?

Any JSP can have one to many entry points and one to many exit points, depending on the complexity of the page itself. Recognizing these relationships is paramount to understanding and maintaining the complexity of the user interface. We have defined the relationships between a JSP page and an Action class as:

  • 1:1 relationship
  • 1:N relationship
  • N:N relationship

1:1 relationship

In a 1:1 relationship, a user goes from one JSP page to another through an Action class; this facilitates tight coupling between a JSP page and an Action. The only extra overhead is one Action mapping in struts-config.xml. This single Action with only one Action mapping in struts-config.xml can be used to go from one page to another.

Referencing one JSP page directly through another is poor practice; the user's permissions that go to the target JSP page cannot be checked (if applicable). This also leads to maintenance issues. To avoid these issues, always go from one JSP page to another through an Action class:

<!-- A generic action that forwards request from one JSP page to another JSP page -->
<action path="/forwardAction"
        type="x.y.z.One2OneAction"
        input="/test1.jsp">      
  <forward name="continue"  path="/test2.jsp"/> 
</action>

1:N relationship

A slightly more complex relationship is one where a JSP page has multiple exit points but a single point of entry, referred to as a 1:N relationship. In this case, always use a single Action class to branch to different targets. That will ensure that Action can check for different situations or permissions before forwarding the user to the target.

The only extra overhead is one Action mapping in struts-config.xml. This will also facilitate a 1:1 mapping between a JSP page and an Action mapping.

The Action mapping below notes a mapping that has a single point of entry yet multiple forwards, signifying many exit points:

<!-- A generic action that forwards request from one JSP page to different 
     branches depending on the selected hyperlink, by the end-user
-->
<action path="/branchAction"
         type="x.y.z.One2NAction"
         input="/test1.jsp">      
  <forward name="target1"   path="/test2.jps"/> 
  <forward name="target2"   path="/test3.jsp"/> 
  <forward name="target3"   path="/someAction.do"/> 
</action>

N:N relationship

The most complex piece of relationship, commonly referred to as an N:N relationship, is when a JSP page or an Action class has multiple entry points and multiple exit points. The N:N relationship is an interesting and complex piece that occurs frequently in enterprise applications. The N:N relationship is primarily used when different JSP pages access a common JSP page or a common Action class. Suppose the user has come to a JSP page that is a hub (i.e., the JSP page can be reached from different JSP pages) and somehow wants to go back or cancel the flow; the developer faces the dilemma of how to send the user to the correct page.

Another scenario is an Action class that commits to the database (through different functionalities or JSP pages), and an error occurs. Sending the user back to where he originated or to forward appropriately, based on where the user came from, requires careful thought. The struts-config.xml mapping will not prove helpful since the input field is a fixed JSP page or Action class. A flexible architecture should be created wherein a developer can change the flow logic easily without mucking around in the struts-config.xml. This is where the N:N relationship kicks in. By implementing an interface with the flexibility to send the user either to the target or the destination, the values can change easily.

The Action mapping below notes a mapping that has multiple points of entry and multiple forwards, signifying many exit points:

 public class N2NAction {
  public ActionForward perform(ActionMapping      _mapping,
                               ActionForm         _form,
                               HttpServletRequest _request,
                               HttpServletResponse _response)
                                  throws IOException, ServletException {
    N2NInterface if = (N2NInterface)_form;
    //Execute some business functionality here
    try{
      //Business logic successful?
               
    }
    catch(Exception e){
      //Indicates failure
      return _mapping.findForward(if.getSource()); 
    }
    //Indicates success
    return _mapping.findForward(if.getDestination());            
  } 
 
}
<!-- A generic action that forwards request from one JSP page to another JSP page -->
<action  path="/sourceAndDestinationAction"
         type="x.y.z.N2NAction"
         input="/test1.JSP">      
  <forward name="source1"          path="/source1.JSP"/> 
  <forward name="source2"          path="/source2.JSP"/> 
  <forward name="source3"          path="/someAction.do"/> 
  <forward name="destination1"     path="/destination1.JSP"/> 
  <forward name="destination2"     path="/destination1.JSP"/> 
  <forward name="destination3"     path="/destination2.JSP"/>
</action>
   A hyerplink can be something like
   <a href="sourceAndDestinationAction.do?source=source1&destination=destination1">click me</a>
   <a href="sourceAndDestinationAction.do?source=source2&destination=destination2">click me too</a>

All ActionForms by default must support all three relationships (usually through interfaces). Using generic Action classes, you can navigate easily to anywhere in the user interface flow.

What a pain in the Back!

A good approach to designing the presentation layer is to design by functionality. For example, while making a reservation in a reservation system, packaging all the related action classes into a package such as

com.companyname.productname.presentation.
   reservation.mak

is a better approach. Thoughtful packaging also applies to the ActionForm classes. The forms should be in session for the functionality's life. This ensures that the data required for that complete functionality is in the form object itself. The user can navigate to any page of the functionality and find the correct data displayed on that page. The user can then update the values before finally persisting the data.

Thus, an interesting dilemma results: what happens when the user commits the data, uses the Back button in the browser, makes some changes, and submits again? For example, after creating the reservation in the database, the user goes back and again tries to submit the same reservation data. The presentation layer must catch this mistake before the application layer has a chance to complain. One way of dealing with this situation is to create a token before submission, check for the token's validity after submission, and immediately change the token value—so the user can't use the Back button to submit the same data again.

One problem with this approach is managing the tokens: for example, if the user attempts to persist the data and it fails, yet the token value already changed. If so, the user can't modify the data and resubmit without resetting the token. The corresponding Action class will not allow the submission. Exactly when should the token be reset? A user might have traversed through six pages, then received an error while committing his changes, and was directed to any of the six pages.

To manage this problem, create the form and store a default token value in session, as soon as the user requests a particular functionality. Before persisting, the user can use the Back button multiple times and make any changes. Once the user submits these changes, reset the token only after the persistence succeeds. If a commit fails, the user can use the Back button or go to any JSP page where the failure originated, modify the data, and resubmit. Once a successful commit occurs, the token value can change. The ActionForm objects themselves can hold this token value (which can be set programmatically). Alternately, after a first-time submission, the Submit button can be disabled, not allowing the user to submit again until something happens. We suggest each ActionForm handle its own token, an application will most likely use more than one different ActionForm object to deal with a particular functionality.

For another approach to duplicate form submission, read Romain Guay's "Java Tip 136: Protect Web Application Control Flow" (JavaWorld, March 2003).

Who are you and what are you doing here?

An enterprise application must be designed to simultaneously support multiple authentication schemes. This is especially true of software from ISVs (independent software vendors). For example, assume the authentication requirements of an application are single sign-on, user-password challenge, barcode authentication, or fingerprint scan authentication, with the future possibility of voice authentication. JAAS (Java Authentication and Authorization Service) is a pluggable authentication mechanism that proves useful for this purpose. By default, Struts 1.1 supports JAAS. JAAS can also be used in Struts 1.0 with beautiful results. A good inheritance design for authentication will facilitate using different authentication action classes, each with its own unique Action mapping. This kind of design approach will facilitate simultaneous support of different authentications. If new authentication mechanisms need to be added, simply create a new action mapping in struts-config.xml with an Action class supporting that authentication.

Where do we go from here?

Flow controllers are designed to restrict and guide the user through the application and are practical in ensuring that the user follows a particular flow. Basing the flow controllers on the system's roles proves useful. Whenever a user tries to access the functionality in the system, the flow controller ensures that the user has the rights to go into a particular flow. Also, in case of an undesired exception, the user can be guided to the proper page. The best place to put a flow controller is in the superclass. Only after the flow controller is satisfied can the user be allowed to perform a particular action.

Figure 2 represents a simple inheritance model that can be used to take advantage of a flow controller combined with user authentication.

Figure 2. Simple authentication class model

To err is human...if only we were cyborgs

The Struts framework provides an error handling infrastructure via the ActionError and ActionErrors classes. An ActionError is exactly that—an error that has occurred in an Action or ActionForm class, or has been thrown from the application layer. The ActionError class is typically constructed either by using a simple key or by using a key/value pair. If your application has internationalization concerns, the key can be used as a lookup into an internationalized message resource database. If these concerns do not exist, then the replacement values may be used to display an error message. Up to four placeholder values may be placed in an object array, each containing a separate part of the whole error message. These values are constructed similarly to the MessageFormatter class.

The ActionErrors class, which extends the ActionMessage class, is a collection of ActionError classes with a single public method: add(java.lang.String property, ActionError error). The second parameter in this method signature is straightforward: the actual error (ActionError) that has occurred. The property parameter is used for field-level validation by associating an error message to a specific field. Since the error message is associated with a specific field, you can easily locate the message in the vicinity of the field in question or wherever it makes the most sense. For example, if you had field-level validation on a user's login identifier and an error occurred in the class LoginAction, you would use the following code snippet:

ActionErrors errors = new ActionErrors();
errors.add("logiinID", new ActionError("loginID.invalid"));
saveErrors(_request, errors);
return (new ActionForward(_mapping.getInput()));

The string "loginID.invalid" is the key for an internationalized string value in a message resource database. To display the error message, in your login.jsp, use the following HTML:

<font color="red"><html:errors property="loginID"/></font>

With this specific understanding of the Struts framework facility for exceptions and exception chains, a more general approach is required for managing errors throughout enterprise application architectures. Some of the most important tenets of an enterprise application exception framework are:

  • Develop an exception class hierarchy
  • Decouple user exceptions from system exceptions
  • Give exceptions a context, type, and severity level
  • Decouple exception handling and application logging
  • Create a facility for exception chaining
  • Externalize exception strings for internationalization purposes

Many articles, and complete books for that matter, cover exception handling in Java. Brian Goetz, in his JavaWorld series "Exceptional Practices" provides very sound advice on this topic.

Testing, that's what users are for!

Although viewed by many developers as the least glamorous aspect of software development, testing an enterprise application is vital to its overall success. Many agile methods, such as extreme programming (XP), place testing at the forefront of enterprise application development. This emphasis is actually quite refreshing. For brevity's sake, we focus on unit and integration testing strategies for Struts classes.

Two primary strategies are used in server-side testing: mock object (MO) and in-container (IC). Mock object testing basically extends "stubbed" behavior and is self-explanatory. In essence, a developer is responsible for mocking up classes that define the interface to be consumed by the classes being tested. Using this approach, the developer mimics the container. Mock object testing is also referred to as endotesting because of the act of placing tests within a class in a controlled environment.

In-container testing is a strategy where the actual container that will be used in a production environment is also used throughout the unit testing process. These test cases must be incorporated into, at minimum, a nightly build process using Ant.

An effective way to unit test Java classes is by using JUnit. This is an excellent tool for testing your normal JavaBeans. Another great open source tool is Cactus. Cactus, an extension of the JUnit framework, tests server-side code at the unit level. Cactus supports the in-container approach to server-side testing. Both of these testing tools are invaluable and should be cornerstones of any enterprise application development team seeking quality.

The generally accepted strategy for testing Struts is to test the presentation layer classes through the application layer. Whether you select the mock object or the in-container approach depends on your group's needs and comfort level. To automate Struts testing, an extension to the JUnit testing approach called StrutsTestCase was created and supports both MO and IC. Testing Action and ActionForm classes using StrutsTestCase is one piece of an overall testing strategy that should include the regression testing tools discussed here. Other tools such as Apache JMeter provide stress/load testing.

Simplify Struts development

In summary, we discussed how you can simplify the design and maintenance of your struts-config.xml file. We defined error handling and testing strategies. We simplified common problems such as dealing with Web browsers' Back buttons, session-based form management, and user authentication. Finally, we clearly documented managing the user interface flow of control.

Our experience has shown the development of quality enterprise applications to be difficult. By incorporating our suggestions into your development plans, you can minimize design, development, and maintenance costs when creating enterprise applications using the Struts framework.

Michael Coen is a software architect at AgilQuest Corporation. While at AgilQuest, Mike has been instrumental in the development of the J2EE-based enterprise software application, OnBoard. Mike has been developing software professionally for more than 11 years. Amarnath Nanduri is a senior software developer at AgilQuest Corporation. Amar has been designing and developing enterprise software applications for the last six years with special emphasis in user interface design and development. Amar has been using the Struts framework for the past two years.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies