ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Programming Jakarta Struts

Jakarta Struts: Seven Lessons from the Trenches

by Chuck Cavaness, author of Programming Jakarta Struts
10/30/2002

Editor's note: After his Internet company decided to adopt the Struts framework, Chuck Cavaness spent months trying to figure out how to use it in order to build a company application. If you're a Java programmer charged with developing Web applications with servlets and JSPs, you'll find a lot of insight and valuable information in the lessons Chuck had to learn the hard way. He describes some of them here.

Introduction

The Jakarta Struts framework has only been around for a short time, but the impact it has made for Web developers is significant. The framework is based on widely accepted design patterns and is very extensible. Although the learning curve for Struts is manageable, there are still best practices when using the framework. These lessons become apparent during development of any medium- to large-size development project. However, a much faster way to get up to speed is to leverage lessons learned by others in the Struts community. Several of those lessons are offered here and are designed to increase your productivity and efficiency when building applications using Struts.

1. Extend When You Must

There are several characteristics of a good framework. One is that it must meet the needs of its intended audience. The Struts framework does this by providing a general infrastructure for building Web applications. This allows developers to focus more on solving the business problem. Another characteristic is that a good framework must provide extension points in the appropriate places to allow applications to extend the framework to better suit their needs.

It would be great if the Struts framework worked for every application in every situation. Realistically, no framework can make that claim. Some applications have requirements that can't be foreseen by the developers of a framework, and therefore the next best thing is to provide enough extension points where developers can twist and mold the framework to better fit their specific requirements.

Related Reading

Programming Jakarta Struts
By Chuck Cavaness

There are many places where the Struts framework can be stretched and customized to allow for customized behavior. Almost every configuration class in the Struts framework can be substituted for a customized version. This is accomplished by simple editing of the Struts configuration file. Other components such as the ActionServlet and RequestProcessor can also be replaced with a specialized version. Even the new features of Struts 1.1 are designed with extension in mind. The declarative Exception Handling mechanism for example, allows for custom exception handlers to be used to better respond to application failures.

The ability to pull and shape a framework to better suit your domain can have a dramatic impact on the outcome of a development project. First and foremost, since you are relying on an existing, mature, and stable framework like Struts, the number of defects found during testing should be reduced. You also can reduce the development time and number of required resources, since resources won't have to be spent on developing infrastructure code.

However, with this great power comes great responsibility. You must be careful not to overextend the application needlessly. There are many existing features spread throughout the framework classes, both in the core package and in the various utility packages that Struts relies on. Don't fall into the trap of extending the framework blindly without looking at other ways of doing the same thing with existing functionality. Make sure that when you decide to extend the framework that the functionality doesn't already exist somewhere else. Not doing so will result in redundant behavior and a bigger mess to clean up later.

2. Use Declarative Exception Handling

Specifying runtime behavior outside of the source code rather than hardcoding it within the application is almost always preferred; the world of J2EE is filled with examples of this. From the security and transactional behavior of Enterprise JavaBeans to the relationships between JMS messages and destinations, many of the runtime aspects can be declared outside of the application.

The Struts architects have taken this approach from the beginning by utilizing the Struts configuration file to specify runtime aspects of an application. That approach continues with the new 1.1 features, including the new exception-handling capabilities. In earlier versions of the framework, developers were left to their own devices when handling error situations that occurred in a Struts application. With the latest version, that's no longer the case. The framework includes a class called ExceptionHandler that by default is responsible for processing any exceptions that occur during action execution. As the previous tip on extending the framework mentioned, this is one of the many extension points that is available within the framework.

The default Struts exception handler class creates an ActionError object and stores it in the appropriate scope object. This allows the JSP pages to use the errors to inform the user of a problem. If this behavior does not fulfill your requirements, you are free to plug in one of your own ExceptionHandler classes.

Customizing the Exception Handling

To install your customized exception handler, the first step is to create a class that extends org.apache.struts.action.ExceptionHandler. There are two methods that you can override, execute() and storeException(). In most cases, however, you will just need to override the execute() method. The signature for the execute() method in the ExceptionHandler class is shown here:

public ActionForward execute( Exception ex, 
                              ExceptionConfig exConfig,
                              ActionMapping mapping,
                              ActionForm formInstance,
                              HttpServletRequest request,
                              HttpServletResponse response
  ) throws ServletException;

The method takes in several arguments, including the original exception, and returns an ActionForward object, which directs the controller as to where the request should be forwarded after the exception is dealt with.

You can perform whatever behavior you like, but generally you should inspect the exception that was thrown and do something based on the type of exception. Again, the default behavior is to create an error message and forward to the resource specified in the configuration file. An example of why you would want or need to customize this functionality is to support nested exceptions. Suppose the exception contained nested exceptions, and those exceptions also may contain other exceptions. You would need to override the execute() method to create error messages for each of these.

Once you have created your specialized ExceptionHandler class, you will need to inform the Struts framework to use your version instead of the default Struts exception handler. To do this, you will just need to declare your class in the Struts configuration file; this is the declarative part.

You can configure your ExceptionHandler to be used by certain Action mappings or by all Actions. For specific Action mappings, you include an <exception> element inside of the <action> element. To utilize the ExceptionHandler for all Action mappings, you can specify it inside the <global-sections> element. For example, suppose we want to use a customized exception handler called CustomizedExceptionHandler for all Action mappings. The <global-exceptions> element might look like this:

<global-exceptions>
  <exception
    handler="com.cavaness.storefront.CustomizedExceptionHandler"
    key="global.error.message"
    path="/error.jsp"
    scope="request"
    type="java.lang.Exception"/>
</global-exceptions>

There are various attributes that you can specify for the <exception> element. The most important of these, for this discussion, is the handler attribute. This attribute is the fully- qualified class name of the ExceptionHandler subclass. If this attribute is not specified, the framework will default to the one provided by the Struts framework. The other attributes are also important, but this one is of the utmost importance if you are trying to override the default behavior.

One final thing should be pointed out. You can have different exception handlers for different exceptions. In the example above, the CustomizedExceptionHandler was configured to process any exceptions that were children of java.lang.Exception. However, you can create multiple exception handlers, each one worrying about different exception trees. The following XML fragment shows how this can be configured:

<global-exceptions>
  <exception
    handler="com.cavaness.storefront.CustomizedExceptionHandler"
    key="global.error.message"
    path="/error.jsp"
    scope="request"
    type="java.lang.Exception"/>

  <exception
    handler="com.cavaness.storefront.SecurityExceptionHandler"
    key="security.error.message"
    path="/login.jsp"
    scope="request"
    type="com.cavaness.storefront.SecurityException"/>
</global-exceptions>

In this case, when an exception is thrown, the framework will attempt to find an ExceptionHandler configured for the exact match. If there's no exact match, the framework will proceed up the superclass chain of the exception until a match is found. With this approach, you can have a hierarchical relationship of handlers, and all of it declarative.

3. Use Application Modules

A new feature of Struts 1.1 is the concept of application modules. Application modules allow a single Struts application to be split into multiple modules, each with its own Struts configuration file, JSP pages, Actions, and so on. This new feature solves one of the biggest complaints that development groups of more than a few people have had for some time: that is, to better support parallel development by allowing for multiple configuration files instead of just one.

Note: earlier in the beta cycle, this feature was being referred to as a "sub-applications," but was changed recently to reflect their more logical responsibility.

A single Struts configuration file easily becomes a resource contention when there are more than a few developers working on a single project. Application modules also allow a Struts application to be broken up based on functional requirements, which proves to be closer to the physical implementation, in many cases. For example, suppose you have a typical storefront application. You might separate the components into modules like catalog, customer, customer service, order, and so on. Each one of these modules may be separated into a distinct directory. This enables the resources for that component to be more easily found, and also helps aid development and deployment. Figure 1 shows a directory structure for an example storefront application.

Figure 1. A directory structure for a typical storefront application.


Note: the Struts framework supports a "default" application module if you don't need to break up your project into multiple modules. This allows applications built with version 1.0 to be portable, because the application automatically becomes the default application module.

To enable multiple application modules, there are several steps that you must perform:

Creating Separate Struts Configuration Files

Each Struts application module must have its own configuration file. This allows you to configure separate Actions, ActionForms, exception handling, and much more, independent of other application modules.

Continuing with the storefront example, we might create three configuration files: one called struts-config-catalog.xml, which contains the configuration information for catalogs, items, and other inventory-related functionality; another file called struts- config-order.xml, which contains settings for orders and order tracking; and finally, a third configuration file called struts-config.xml, which contains common functionality that belongs to the default application module.

Configuring the Web Deployment Descriptor

With earlier versions of Struts, you specified the path to the Struts configuration file in the Web.xml file. Fortunately, that's still the same (as it helps to support backwards compatibility), but for multiple application modules, new configuration settings have to be added to the Web deployment descriptor.

For the default application (and earlier versions of Struts), the Struts framework loads the Action mappings and other application settings by looking for an <init-param> element in the web-xml file with a name of config. The following XML fragment shows an example of a typical <init-param> element:

<init-param>
   <param-name>config>/param-name>
   <param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>

Note: the Struts framework will default to /WEB/struts-config.xml if there's no "config" <init-param> element present.

To support multiple application modules, you need to add additional <init-param> elements. The difference between the default <init-param> element and the additional ones for application modules is that the non-default elements must be named config/xxx, where x is a string that represents a unique name for an application module. For example, the <init-param> elements for the storefront application might look like this:

<init-param>
   <param-name>config</param-name>
   <param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
   <param-name>config/catalog</param-name>
   <param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
   <param-name>config/order</param-name>
   <param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>

The first <init-param> element is for the default application model. The second and third elements represent additional non-default application modules and are named catalog and order, respectively.

When the framework loads an application, it loads the configuration file for the default application module. It then searches for any addition initialization parameters that contain the string config/xxx. Each additional configuration file is parsed and loaded into memory. Once this step is complete, you are free to refer to the multiple application modules by name, where the name of the application module is the string after config/ in the <init-param> element.

Moving Between Application Modules

Once you have created the configuration file for each application module, you then have the ability to invoke Actions in the various modules. To do this, you need to utilize the SwitchAction provided with the framework. The framework will automatically add the application module name to the URL, just as it adds the application name to the URL. The application module feature is a great new addition to the framework and will help with parallel development. There's no requirement to use this feature if your team is small and you don't have a need to modularize your components. The framework works just the same if you only have a single default application module.

4. Protect JSPs Behind WEB-INF

To better protect your JavaServer Pages from unauthorized access or viewing, it's a good idea to store your pages in a directory that is below the Web application's WEB-INF directory.

It's very typical for JSP developers to keep their pages in subdirectories under the Web application root. For example, Figure 2 shows a typical directory structure for a storefront application. The JSPs relating to catalog are placed in the catalog subdirectory under the storefront directory. The same might be true for the customer JSPs, order JSPs, and so on.

Figure 2. JSPs are placed into separate directories based on functionality.


The problem with this approach is that these pages are a little more susceptible to a user being able to view the source of the JSPs, or at least call the JSP directly. In some cases, this might not be a huge issue, but under certain circumstances it can be a security risk. It could also present a problem if users are allowed to circumvent the Struts controller by invoking a JSP directly.

To reduce this risk, you can move the pages into a directory underneath WEB-INF. Based on the Servlet specification, WEB-INF is not part of the public document tree of the Web application. Therefore, no resource within the WEB-INF directory (nor those beneath it) may be served directly to a client. We can still use JSPs stored underneath the WEB-INF to render views for a client; however, a client may not request one of the JSPs directly. This helps to protect your site from unwanted access and at the same time, allows you to render views using JSPs.

Using the previous example, the directory would like the one in Figure 3, after the JSPs are moved into a directory called JSP underneath the WEB-INF directory.

Figure 3. The JSPs are more secure underneath the WEB-INF directory.


Once the JSPs are located underneath the WEB-INF directory, you must use "WEB-INF" as part of the URL when referencing the pages. For example, suppose we had an Action mapping in our Struts configuration file for a logoff action. The paths to JSPs must include WEB-INF at the beginning.

<action
  path="/logoff"
  type="org.apache.struts.webapp.example.LogoffAction">
  <forward name="success" path="/WEB-INF/jsp/index.jsp"/>
</action>

The only trick with using this approach, which is good practice with Struts in any case, is that you should always front your JSPs with a Struts action. Even if the Action is a very basic JSP, you should always call an Action that in turn invokes the JSP.

A final comment when storing your JSPs underneath WEB-INF: not all containers support this feature. Earlier versions of WebLogic did not interpret the Servlet specification the same as others, and it was not possible to do this. This is reported to be changed with newer versions of WebLogic. Just be sure to check your specific container.

5. Use the Prebuilt Actions

The Struts framework comes with several prebuilt Action classes that can save a tremendous amount of development time. The most beneficial of these are the org.apache.struts.actions.ForwardAction and the org.apache.struts.actions.DispatchAction.

Using the ForwardAction

There are probably many cases in your application where you want an Action to just forward to a JSP. In fact, the previous tip stated that it's good practice to always front a JSP with an Action. If you have the case where you don't need to perform any logic in the Action but would like to follow the convention of going through an Action to access a JSP, the ForwardAction can save you from creating many empty Action classes. The benefit of the ForwardAction is that you don't have to create an Action class of your own. All you have to do is to declaratively configure an Action mapping in your Struts configuration file.

For example, suppose that you had a JSP called index.jsp and instead of calling this page directly, you would rather have the application go through an Action class. What you can do is set up an Action mapping like this:

<action
   path="/home"
   parameter="/index.jsp"
   type="org.apache.struts.actions.ForwardAction"   
   scope="request"      
   validate="false">  
</action>

When the home action is invoked, the ForwardAction performs a forward to the index.jsp page.

While we are discussing forwarding without having to provide an Action class, it should also be mentioned that you could also use the forward attribute of the <action> element to achieve the same behavior. The <action> element would look like this:

<action
   path="/home"
   forward="/index.jsp">
</action>

Both approaches save you time and help to reduce the number of files needed by an application.

Using the DispatchAction

The DispatchAction is another Action that is included with the framework that saves a tremendous amount of development time. Instead of providing a single execute() method per Action class (and thus tying one Action to one business method), the DispatchAction allows you to encode several business-related methods within a single Action class. By doing this, the number of Action classes can be reduced and the related business methods can be grouped together, which can make maintenance somewhat easier.

To use the DispatchAction functionality, you need to create a class that extends the abstract DispatchAction. For each business method that you want to provide, you need to provide a method with a particular method signature. If for example, you had a method that you wanted to call to add items to the user's shopping cart, we could create a class called ShoppingCartDispatchAction that had the following method:

public ActionForward addItem( ActionMapping mapping, 
                              ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
  throws Exception;

This class would probably also have a deleteItem() method, a clearCart() method, and others. So instead of creating different Action classes for each of these, we can combine them into a single Action class.

To invoke one of the methods in the ShoppingCartDispatchAction, just include a parameter in the URL that matches one of the method names. So, to invoke the addItem() method, the URL would look something like:

http://myhost/storefront/action/cart?method=addItem

The parameter named method identifies the method to invoke within the ShoppingCartDispatchAction. The name of the parameter can be configured to be just about anything you want. The one used here, "method," is just an example. You configure it within the Struts configuration file.

6. Use Dynamic Forms

In the Struts framework, ActionForm objects are used to capture HTML form data sent along with the request and also to provide data that can be used to help render the dynamic views. They are essentially JavaBean classes that extend from the Struts ActionForm class and have the option of overriding a couple of default methods.

This feature is a big time saver, as it helps to facilitate automatic presentation-layer validation. The only downside to ActionForms has been the need to create multiple ActionForm classes to hold the data for the various HTML forms. For example, if you have one page for the details of a user registration and another page for user preferences, you typically would need two separate ActionForm classes. One of the nicest features of Struts 1.1 is the notion of dynamic ActionForms.

Dynamic ActionForms, which are implemented by the DynaActionForm class and various subclasses, allow you to configure an ActionForm completely through the Struts configuration file; there's no longer a real need to create actual concrete ActionForm classes in your application. In the Struts configuration file, you can configure a DynaActionForm for your application by adding a <form-bean> element and setting the type to be the fully-qualified class name of the DynaActionForm or one of its subclasses. The following example creates a dynamic ActionForm called logonForm that contains two instance variables: username and password.

<form-bean
  name="logonForm"
  type="org.apache.struts.action.DynaActionForm">
  <form-property name="username" type="java.lang.String"/>
  <form-property name="password" type="java.lang.String"/>
</form-bean>

This dynamic ActionForm can be used within Action classes and JSPs in the same way that a regular ActionForm is used, with one small difference. With normal ActionForm objects, we would provide get and set methods to get and set the data. In the example above, we would have to provide a getUsername() and setUsername() method and also a set for the password variable.

Because we are using a DynaActionForm (which stores the variables in a Map), we need to use the get(name) and set(name) method on the DynaActionForm class, where the argument is the name of the instance variable we are trying to access. For example, if we wanted to access the username value from a DynaActionForm, the code would look like this:

String username = (String)form.get("username");

Since the values are stored in a Map, we must cast the Object returned from the get() method call.

There are several useful subclasses of the DynaActionForm. The most important of these is arguably the DynaValidatorForm. This dynamic ActionForm is used with the Validator to provide automatic validation using the common Validator package. This is another nice feature that allows you to specify validation rules outside of the application source. Combining these two features is a very attractive incentive for developers.

7. Use Visual Tools

Since the release of 1.0, there have been several Visual tools created to help create, modify and maintain the Struts configuration file. The configuration file itself is based on XML and for any medium to large-sized application, can grow to be unwieldy. To better manage this file once it has grown beyond what is considered easy-on-the-eyes, you might try using one of the GUI tools developed for this purpose. There are several commercial and open source tools available. Table 1 lists several of the tools available and URLs where you can find out more information.

Table 1. Struts GUI tools

Name Cost URL
Adalon Commerical http://www.synthis.com/products/adalon
Easy Struts Open Source http://easystruts.sourceforge.net/
Struts Console Free http://www.jamesholmes.com/struts/console
JForms Commerical http://www.solanasoft.com/
Camino Commerical http://www.scioworks.com/scioworks_camino.html
Struts Builder Open Source http://sourceforge.net/projects/rivernorth/
StrutsGUI Free http://www.alien-factory.co.uk/struts/struts-index.html

Resources

For a more complete list of Struts GUIs (both free and commercial), visit the Struts resource page.

Chuck Cavaness is a graduate from Georgia Tech with degrees in computer engineering and computer science. He has built Java-based enterprise systems in the healthcare, banking, and B2B sectors. He is also the author of two O'Reilly books, Programming Jakarta Struts and Jakarta Struts Pocket Reference.


O'Reilly & Associates will soon release (November 2002) Programming Jakarta Struts.

Programming Jakarta Struts

Related Reading

Programming Jakarta Struts
By Chuck Cavaness

Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.