Coffea Works

Struts Best Practices

How To Develop Low-Maintenance, Quality Code

by Brijesh Deb

Struts is a popular Web presentation framework that has garnered considerable community support. This article aims to highlight some best practices that can be applied in medium to large projects to aid efficient development and low-maintenance quality code. In the course of this article we shall explore ways to optimize the design and development of Struts-based applications.

For an introduction to Struts, and to learn how to use Struts to build a properly designed Model 2 Web application, read Neal Ford's article at JAX Magazine.

Introduction
Struts, a Jakarta project hosted by Apache, is a framework for building Web applications based on the MVC2 design pattern. The framework, built upon standard technologies like Java Servlets, JavaBeans, ResourceBundles, and XML, aims to provide an extensible Web application architecture that the development team can build on. The use of Struts enforces modularity and separation of concern, increases code manageablity, extensibility, and consistency.

The information flow of an application based on the Struts framework is shown in the following figure:


Fig. 1: Information Flow in a Struts application

As shown in the figure, all user requests are dispatched through the Controller, which controls the flow of the application thereon. The Controller creates Action classes, which are built by the developer to perform the work of the application. These Action classes extend the Struts Action class, and the Action instances create Model Beans that perform domain specific activities. Depending on the user request it calls different actions on the Model. The Model represents the application data and business rules. Although Struts doesn't provide any model object, at times, ActionForms are referred to as models. The Controller gets the user inputs from the View. Though Struts in itself doesn't provide any View component, it provides a number of JSP custom tags that facilitates UI development in JSP.

Let's do a quick rundown of the Struts components that are relevant from the context of this article:
  • Actions: Actions are multi-threaded like servlets. They communicate with the Model, invoke business logic, and return the Model objects to the View. All application Actions have to extend Struts' org.apache.struts.action.Action. Every Action has to provide application specific implementation by overriding the execute() method.
  • ActionForm: ActionForm provides data transfer to and from HTML forms. They also provide field validations. Every application ActionForm has to extend Struts' org.apache.struts.action.ActionForm and provide application specific implementation of validate() method.
  • ActionErrors: ActionErrors are a collection of ActionError instances that are used to support exception handling. They encapsulate error messages that are displayed in the Presentation layer through .
  • Struts-config.xml: The struts-config.xml file is the deployment descriptor for Struts-based applications. It has to confirm to the Document Type Definition (DTD) provided at http://struts.apache.org/dtds/struts-config_1_2.dtd.

Best Practices
We shall use an Online Shopping Application example to highlight the best practices that can be followed in the Struts-based implementation of a typical Web-based J2EE application. Such an application typically includes features such as a search catalogue, browse item details, shopping cart updation, purchase order submission, customer profile maintenance, and user registration. In the following sections we shall see how to follow certain techniques to fine tune the features of such an application.

Use intermediate Action class for common operations
At times, common operations need to be included in all the actions. An example of such a requirement in our sample Online Shopping application would be to perform user authorization before processing all user requests. However, including common operations in the execute() method of all Action class results in code redundancy. Instead, add an intermediate Action class and make all other Action classes extend that to centralize the handling of common operations and reduce code redundancy.

The steps are as follows:

Create an intermediate Action class, say BaseApplicationAction, by extending org.apache.struts.action.ActionAdd an abstract method, say executeSpecificTask(), that has the following signature:

public abstract ActionForward executeSpecificTask
(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception

Add all the common methods of the application; for example, performAuthorization()Call all the common methods (like performAuthorization) inside the execute() method, and follow it with executeSpecificTask()All other Action classes should extend BaseApplicationAction, not org.apache.struts.action.ActionThe extended Action classes provide the specific implementations for the executeSpecificTask() method

The aforementioned steps have been implemented in the following code snippet:

Listing 1: Intermediate Action Class

Public class BaseApplicationAction extends Action
{
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
try
{
//Call all common methods
performAuthorization();
return executeSpecificTask(mapping,form,request,response);
}
catch(Exception ex){//exception}
}
// Authorization is an operation common through all the application actions
private void performAuthorization ()
{
//Code for user authorization
}

//Provide implementation of this method in sub-classes
public abstract ActionForward executeSpecificTask
(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception
}

Use DispatchAction to group related actions into a single class
Often we need to perform a group of related actions on the same domain object. For example, in the Online Shopping application customers should have a provision to add, delete, or modify an item in the shopping cart. This can be implemented using the execute() method of Action classes, wherein there will be one Action class for each business method. Struts provides a better option by clubbing these distinct, yet related, actions in a single DispatchAction class. (http://struts.apache.org/api/org/apache/struts/actions/DispatchAction.html). This reduces code redundancy thereby easing the maintenance.

The steps are as follows:

The Action class extends org.apache.struts.action.DispatchAction instead of org.apache.struts.action.ActionAdd separate methods having same signature as execute(), as shown in the following code bit:

Listing 2: Using DispatchAction

public class ItemAction extends DispatchAction
{
public ActionForward addItem(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
try
{
// Code for item add
}
catch(Exception ex){//exception}
}

public ActionForward deleteItem(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
try
{
// Code for item deletion
}
catch(Exception ex){//exception}
}

public ActionForward updateItem(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
try
{
// Code for item update
}
catch(Exception ex){//exception}
}
}

Make the following entry in struts-config.xml:

<action
path="/item"
type=" com.infosys.j2ee.sample.web.actions.ItemAction"
name="itemForm"
scope="request"
validate="true"
parameter="actionType"/>

The method can be invoked using a URL, like this: ItemAction.do?actionType=addItem

Handle duplicate form submission
The problem of duplicate form submission arises when a user clicks the Submit button more than once before the response is sent back or when a client accesses a view by returning to a previously bookmarked page. This may result in inconsistent transactions and must be avoided. In our sample application, a similar problem will arise if the customer clicks the submit button more than once while submitting the purchase order.

In Struts this problem can be handled by using the saveToken() and isTokenValid() methods of Action class. saveToken() method creates a token (a unique string) and saves that in the user's current session, while isTokenValid() checks if the token stored in the user's current session is the same as that was passed as the request parameter.

To do this the JSP has to be loaded through an Action. Before loading the JSP call saveToken() to save the token in the user session. When the form is submitted, check the token against that in the session by calling isTokenValid(), as shown in the following code snippet:

Listing 3: Using saveToken() and isTokenValid()

public class PurchaseOrderAction extends DispatchAction
{
public ActionForward load(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
try
{ //save the token
saveToken(request)

// rest of the code for loading the form
}
catch(Exception ex){//exception}
}

public ActionForward submitOrder(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
try
{
// check the token. Proceed only if token is valid
if(isTokenValid(request,true)) {
//implement order submit functionality here
} else {
return mapping.findForward("failure");
}
}
catch(Exception ex){//exception}
}
}

Use Application Modules for parallel development
In large projects with multiple modules, sharing the same Struts configuration file among different developers becomes an issue. Struts provides application modules to divide single monolithic applications into multiple modules each having its own Actions, ActionForms, configuration files, etc. This helps in parallel development in large development teams. In our sample application we can use multiple configuration files for different modules (customer maintenance, order processing, etc.) to facilitate effective parallel development.

The steps are as follows:

Create separate configuration files for each module; for example, struts-config-customermaintenance.xml, struts-config-orderprocessing.xml, and struts-config.xml (default configuration file).Make entries in the Web deployment descriptor, web.xml. The default entry for a single configuration file is:

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

Replace the default entry with the following:

<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>config/module1</param-name>
<param-value>/WEB-INF/struts-config-customermaintenance.xml</param-value>
</init-param>
<init-param>
<param-name>config/module2</param-name>
<param-value>/WEB-INF/struts-config- orderprocessing.xml</param-value>
</init-param>

Use org.apache.struts.actions.SwitchAction to switch from one module to another. Refer http://struts.apache.org/userGuide/configuration.html#dd_config_modules for more details.

Use single ActionForm for similar forms
For similar forms, use a single ActionForm that includes all possible fields instead of having several ActionForms. This is generally applicable to different forms required to implement the same use case. It leads to easy maintenance, though all the fields will be not be used for all the Actions. For example, in the Online Shopping application we can use a single ActionForm (CustomerProfileForm) for different forms related to customer profile management (like DisplayCustomerProfile.jsp, EditCustomerProfile.jsp, etc.).

Use global-forwards to avoid redundant forwards
The element allows configuring forwards that are used by multiple actions in a module. This helps to avoid mentioning for all the actions. For instance, the Online Shopping application should display the login page in case of session timeout. Instead of including for all the actions, add a single entry in the struts-config.xml to throw the login.jsp to the user in case of session timeout encountered in any Action, like this:

<global-forwards>
<!-- session timeout or invalid session, displays login page -->
<forward name="sessionError" path="/jsp/login.jsp" />
</global-forwards>

Use Struts exception handler for generic exceptions
Follow these steps to handle generic exceptions:

Create a new exception extending org.apache.struts.action.ExceptionHandler

Override the execute method.

Make an entry in struts-config.xml like so:

<global-exceptions>
<exception
key="global.error.Message"
type="java.lang.Exception"
handler="com.infy.app.exceptionHandler.CustomisedException Handler/>
path="/jsp/error.jsp"
</global-exceptions>

Remove ActionForm from session
If ActionForm is set to session scope, it should be removed from session whenever it's utility is over. In continuation with our sample application, in case of the multi-screen customer registration, if the RegistrationForm is set to session scope, it should be removed form the session once the user clicks Cancel.

Use Business Delegate
Action should not implement complex business functionalities, rather delegate these to the Model. Use Business Delegate to talk to the Business tier and the Data Tier.

Use ForwardAction for simple JSPs
Always render a JSP through an Action. Use ForwardAction in case there is no logic required in the Action other than throwing a JSP. The benefit of using ForwardAction is that no separate Action class has to be created; only an Action mapping needs to be added in the configuration file.

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

Avoid using instance/static variable in Action class
Instance and static variables should not be used in an Action class to store information related to the state of a particular request. The same instance of an Action class can be shared among multiple simultaneous requests through multi-threading. So any instance/static variable may be updated unexpectedly by any thread. This bug is difficult to simulate, which gives us all the more reason to prevent it in the first place. Instance/static variable may however be used to share global resources across requests for the same action.

ActionForms are not Model
ActionForm represents HTML form(s) and it is used by Struts to transfer data between View and Controller. They should not be treated as part of the Model. Therefore, do not include any business functionality in the reset() or validate() method of ActionForms as this would lead to tight coupling of application business functionality with the presentation tier (implemented through the Struts framework).

Use html:messages instead of html:errors
For displaying error messages to the end user, use html:messages instead of html:errors. html:messages allows to get the markup language out of the resource bundle.

Use Tools
There are several Open Source tools available to ease development of Struts-based applications. A few of them are listed in the following table:

Name Details URL
Easy Struts Easy Struts provides plug-ins for the Eclipse 2.0, Borland JBuilder 5, and Borland JBuilder 6 development environments to enable you to develop Web applications based on the MVC design pattern provided by the Jakarta Struts framework. http://easystruts.sourceforge.net/
Struts Console The Struts Console is a free, standalone Java Swing application for developing and managing Struts-based applications. With the Struts Console you can visually edit JSP Tag Library, Struts, Tiles and Validator configuration files. IDE supported include Eclipse, NetBeans, IBM Websphere, JBuilder. http://www.jamesholmes.com/struts/console/
Struts Builder A Java Swing-based development environment to assist in the rapid creation of Struts-based Web applications. http://sourceforge.net/projects/rivernorth/

Most of the popular IDEs, like Websphere Studio Application Developer (WSAD) and Eclipse, now provide support for Struts.

Use StrutsTestCase for unit testing
StrutsTestCase is an extension of the standard JUnit TestCase class that provides facilities for testing code based on the Struts framework. Because StrutsTestCase uses the ActionServlet controller to test the code, you can test not only the implementation of your Action objects, but also the mappings, form beans, and forwards declarations. Further, since StrutsTestCase provides validation methods, it is quick and easy to write unit test cases.

StrutsTestCase provides both container testing and simulated container testing to actually run the Struts ActionServlet, allowing you to test Struts code with or without a running servlet engine. Refer http://strutstestcase.sourceforge.net/ for details.

Conclusion
Best practices are proven solutions to recurring problems and following them will lead to the development of easily maintainable, quality code while reducing development effort and time. Hopefully, this article has set you on the path to introduce best practices into your Struts-based development work.
Brijesh Deb has a Bachelor degree in Electrical Engineering. He works as a J2EE Technical Architect with the Software Engineering and Technology Labs of Infosys Technologies Limited, at Bangalore (India).

References
Struts homepage: http://struts.apache.org/
An Introduction to Struts: http://www.jaxmag.com/itr/online_artikel/psecom,id,616,nodeid,147.html


back

top

print

recommend