URL of the article:

Struts
Web Frameworks - Part II
by Neal Ford
Building reusable code is all the rage today - after all, if we can build it once and reuse it over and over, we can build less and less over time. The whole short history of software development is an exploration of the layers of abstraction used to make building applications easier. Once you have a collection of pre-built generic parts, you have the beginnings of a framework. A framework is a set of related classes and other supporting elements that make application development easier by supplying pre-built parts. They provide the infrastructure for application development.

In the first part of this series, we saw how the judicious use of design patterns can help consolidate common code into reusable assets--this is the first step in constructing your own framework. If you extrapolate this behavior to multiple developers and multiple projects, you have a generic framework. A generic framework is one that is built from parts common to most applications. For example, many Web applications need a database connection pooling facility. This is a perfect candidate for a framework component. Fortunately, you don't have to build each framework for Web development from scratch.


In the previous article, we looked at using "just" Model 2 as a Web framework. In this article, we will discuss the wildly popular Struts framework.

Overview
Struts is an open source framework for building Model 2 Web applications. It is part of the Jakarta project hosted by Apache. The primary areas of functionality in Struts are:
  • A controller servlet that dispatches requests to appropriate Action classes provided by the application developer
  • JSP custom tag libraries, and associated support in the controller servlet that assists developers in creating interactive form-based applications
  • Utility classes to support XML parsing, automatic population of JavaBeans properties based on the Java reflection APIs, and internationalization of prompts and message

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


Figure 1: The general information flow in a Struts application


The information flow in Struts is very similar to that of plain Model 2 applications. All requests are dispatched through a single Controller Servlet that is part of the framework. This controller provides numerous application wide services, such as database connection pooling and automatic request dispatching. 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. This is a perfect example of a reusable framework part - the controller is designed to create Action sub-classes to perform the work. This aspect of Struts is based on the Command design pattern, which allows for parameterizing activities.


The Action instances create Model Beans that perform domain specific activities. This includes executing business logic, connecting to databases, and calling other bean methods. The Model Beans encapsulate the real work of the application (just as in Model 2). Once the action instance has utilized the model beans to perform work, it forwards the models that contribute to the display via the controller to a View component, generally a JSP (although other view options are possible). The View extracts the model beans and presents the visual results to the user. Although, this is the same general information flow described in Model 2, Struts provides a great deal of the infrastructure to make it easy to accommodate this information flow.


Struts handles other details of application development as well. The framework includes numerous custom JSP tags to aid in constructing the view. It also includes classes to aid in internationalization, database connection pooling, flexible resource mapping, and a host of other facilities.

Building a Schedule Application using Struts
In this section, we'll see how to use Struts to build a properly designed Model 2 Web application--this application is very similar to the Schedule Application example built in the first part of this series (using "just" Model 2).

Schedule View
The first page shows a list of the currently scheduled events and the second page allows the user to add more events. The first page is shown in the following figure:


Figure 2: The first page shows a schedule of upcoming events

The Model
The model beans (ScheduleDb and ScheduleItem) are identical to the ones from the previous article except for one change to the ScheduleItem entity -- to allow it to interoperate with the Struts framework seamlessly, the entity now extends the Struts ActionForm base class, and the validate() method has changed to return Struts specific error objects. This is a pragmatic change to the model, not required. It is possible (and generally desirable) to completely decouple the framework from the model, and Struts allows this behavior. However, making this change will complicates the example considerably, and I'm primarly interested in showing the features of the framework here. For many more options on decoupling the framework completely from the model, consult Struts in Action, from Manning Publications (ISBN 1930110502).


Now, the definitionof the ScheduleItem class is:

public class ScheduleItem extends ActionForm implements Serializable {

and the modified validate method is shown in Listing 1.

Listing 1. The updated validate() method
public ActionErrors validate(ActionMapping actionMapping,
HttpServletRequest request) {
ActionErrors actionErrors = new ActionErrors();
if (duration < 0 || duration > 31)
actionErrors.add("duration",
new ActionError("error.invalid.duration", "8"));
if (text == null || text.length() < 1)

actionErrors.add("event text",
new ActionError("error.no.text"));
return actionErrors;
}

Struts' Actions
Struts has already implemented the controller servlet that understands the Action classes, which extend the Struts Action class. The Struts Action class encapsulates a great deal of behavior within the framework. These actions act as proxies for the controller servlet, so they are responsible for the interaction between the models and the views. The first action invoked is ViewScheduleAction, which is shown in Listing 2.


Listing 2: The first action invoked

package com.nealford.art.strutssched;

import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.apache.struts.action.*;

public class ViewScheduleAction extends Action {
private static final String ERR_POPULATE =
"SQL error: can't populate dataset";

public ActionForward perform(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws IOException,
ServletException {

DataSource dataSource = (DataSource)
servlet.getServletContext().
getAttribute(Action.DATA_SOURCE_KEY);
ScheduleBean sb = new ScheduleBean();
sb.setDataSource(dataSource);

try {
sb.populate();
} catch (SQLException sqlx) {
getServlet().getServletContext().log(ERR_POPULATE,sqlx);
}
request.setAttribute("scheduleBean", sb);
return new ActionForward("/ScheduleView.jsp");
}
}


The Struts developers have created helper classes that handle many of the details of building a Struts application. For example, the execute() method returns an ActionForward instance via the mapping parameter. This is a class that facilitates forwarding a request. It encapsulates the behavior of a RequestDispatcher and adds more functionality. The ViewScheduleAction first retrieves the DataSource instance created by the controller servlet by calling the getDataSource() method, which it inherits from Action. It then creates a ScheduleDb, populates it, adds it to the request, and dispatches to the appropriate view. The controller servlet is responsible for two tasks in the ViewScheduleAction class--it first creates the connection pool and adds it to the appropriate collection, and then it manages the mapping between requests and the appropriate Action instances.

Configuring Struts Applications
The connection pool, mappings, and other configuration information for Struts appear in the Struts configuration XML file. This configuration file is shown in Listing 3. The Struts framework defines this document's format.


Listing 3: struts-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
<data-sources>
<data-source
type="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<set-property property="url"
value="jdbc:mysql://localhost/schedule" />
<set-property property="user" value="root" />
<set-property property="password" value="marathon" />
<set-property property="maxCount" value="5" />
<set-property property="driverClass"
value="com.mysql.jdbc.Driver" />
<set-property value="1" property="minCount" />
</data-source>
</data-sources>
<form-beans>
<form-bean name="scheduleItem"
type="com.nealford.art.schedstruts.entity.ScheduleItem"
dynamic="no" />
</form-beans>
<action-mappings>
<action
type="com.nealford.art.schedstruts.action.ViewScheduleAction"
path="/sched">
<forward name="success" path="/ScheduleView.jsp" />
</action>
<action
type="com.nealford.art.schedstruts.action.ScheduleEntryAction"
path="/schedEntry">
<forward name="success" path="/ScheduleEntryView.jsp" />
</action>
<action name="scheduleItem"
type="com.nealford.art.schedstruts.action.AddToScheduleAction"
validate="true" input="/ScheduleEntryView.jsp"
scope="session" path="/add">
<forward name="success" path="/sched.do" />
<forward name="error" path="/ScheduleEntryView.jsp" />
</action>
</action-mappings>
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property
property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>
</struts-config>


As shown in the listing, we first define a Java Database Connectivity (JDBC) data source that is delivered from the Struts connection pooling utility class. This configuration file allows you to define all the characteristics of your database connection. The <form-bean> section of the document allows you to define form beans . These are the ActionForm subclasses utilized by the framework. The ScheduleItem class defined in Listing 3 is the example declared here. The <action> section of the document lists the action mappings. Each action mapping may define local forwards, which are Web resources the Action may reference. In the ViewScheduleAction in Listing 2, the return value is the mapping for success, which maps to the local forward defined in the configuration document for the /sched action.

Using Struts' Custom Tags
The ViewScheduleAction eventually forwards the request to ScheduleView.jsp, which is shown in Listing 4.


Listing 4: The ScheduleView.jsp page

<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html>
<head>
<title>
<bean:message key="title.view" />
</title>
</head>
<body>
<h2><bean:message key="prompt.listTitle" /></h2></p>
<table border="2">
<tr bgcolor="yellow">
<th><bean:message key="prompt.start" /></th>
<th><bean:message key="prompt.duration" /></th>
<th><bean:message key="prompt.text" /></th>
<th><bean:message key="prompt.eventType" /></th>
</tr>

<logic:iterate id="schedItem"
type="com.nealford.art.schedstruts.entity.ScheduleItem"
name="scheduleBean" property="list" >
<tr>
<td><bean:write name="schedItem" property="start" />
<td><bean:write name="schedItem"
property="duration" />
<td><bean:write name="schedItem" property="text" />
<td><bean:write name="schedItem"
property="eventType" />
</tr>
</logic:iterate>
</table>
<p>
<a href="schedEntry.do"> Add New Schedule Item</a>
</body>
</html>


While the Action class is very similar to the controller from the Model 2 Schedule Application in the previous article, this JSP is significantly different. The first major difference is the declaration of a number of taglibs at the top of the file. Struts defines numerous custom tags, in four different categories, to aid you in building JSPs. The four categories are listed in the following Table.



Continuing with our analysis of ScheduleView shown in Listing 4, the next item of interest concerns the text labels. In the Model 2 Schedule Application, the title was placed directly inside the JSP page. However, Struts defines a mechanism whereby you can isolate the labels and other user interface (UI) elements into a separate resource file and reference those resources via a Struts tag. The external resource is a java.util.Properties file, and the key attribute indicates the key value for the string resource. The properties file for this application is shown in Listing 5.


Listing 5: The resource properties file for the Schedule Application

prompt.duration=Duration
prompt.eventType=Event Type
prompt.start=Start Date
prompt.text=Text
prompt.listTitle=Schedule List
prompt.addEventTitle=Add New Schedule Entry

title.view=Schedule Items
title.add=Add Schedule Items

button.submit=Submit
button.reset=Reset

errors.header=Validation Error
errors.ioException=I/O exception rendering error messages: {0}
error.invalid.duration=
Duration must be a be positive and less than 1 month
error.no.text=You must supply text for this schedule item

errors.required={0} is required.
errors.minlength={0} can not be less than {1} characters.
errors.maxlength={0} can not be greater than {1} characters.
errors.invalid={0} is invalid.

errors.byte={0} must be a byte.
errors.short={0} must be a short.
errors.integer={0} must be an integer.
errors.long={0} must be a long.
errors.float={0} must be a float.
errors.double={0} must be a double.

errors.date={0} is not a date.
errors.range={0} is not in the range {1} through {2}.
errors.creditcard={0} is an invalid credit card number.
errors.email={0} is an invalid e-mail address.


This mapping mechanism serves two purposes. First, it allows you to ensure common labels and titles throughout the application. If you have a specific label for a button that appears in multiple locations, you can reference the same resource and change it everywhere with a simple change to the resource. The other benefit involves internationalization -- you can create other language properties files and have Struts automatically associate them with your application. Internationalization is beyond the scope of this article; please consult The Struts User's Guide for more information.


The next item of interest in Listing 4 is the iterate tag. In the Model 2 Schedule Application, we were forced to resort to scriptlet code and/or JSP Standard Tag Library (JSTL) tags for iterating over a list of items. Struts handles this situation with the iterate custom tag that works with different collections of objects, including arrays. It is a powerful tag because it takes care of typecasting and assignment. Within the tag body, you can freely reference the properties and methods of the objects from the collection without worrying about typecasting. Also notice that there is no longer any scriptlet code in the JSP, not even a useBean declaration. The code on this page is much cleaner than the corresponding code in a typical Model 2 application.


At the bottom of the file, an HTML <href> tag appears that points to SchedEntry.do. Clicking on this link invokes another Action object (ScheduleEntryAction) through the Struts controller.

Struts Data Entry
ScheduleEntryAction is invoked when the user clicks on the hyperlink at the bottom of the view page. It leads to the data-entry screen, shown in Figure 3.


Figure 3: The Data Entry Page

ScheduleEntryAction is responsible for setting up the edit conditions. The code is shown in Listing 6.


Listing 6: The ScheduleEntryAction class sets up the entry page

package com.nealford.art.schedstruts.action;

import javax.servlet.http.*;
import javax.servlet.ServletException;
import java.io.IOException;
import org.apache.struts.action.*;
import javax.sql.DataSource;
import com.nealford.art.schedstruts.boundary.*;

public class ScheduleEntryAction extends Action {
private static final String ERR_DATASOURCE_NOT_SET =
"ScheduleEntryAction: DataSource not set";

public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws IOException,
ServletException {

ScheduleDb sb = new ScheduleDb();
DataSource ds = getDataSource(request);
if (ds == null)
throw new ServletException(ERR_DATASOURCE_NOT_SET);
sb.setDataSource(ds);
//-- place the scheduleBean on the session in case the
//-- update must redirect back to the JSP -- it must be
//-- able to pull the scheduleBean from the session, not
//-- the request
HttpSession session = request.getSession(true);
session.setAttribute("eventTypes", sb.getEventTypeLabels());
return mapping.findForward("success");
}
}


The view JSP for this page must be able to pull event types from the ScheduleDb to display the m in the HTML select control. Adding the ScheduleDb to the request and forwarding it to the JSP would normally accomplish this. However, the automatic validation functionality of Struts adds some complexity to this scenario. We will see more about this later in the article. For now, trust that the session, not the request, must be used here. Before this mystery is unraveled, let's discuss the view portion of this request.

Building the Entry View
The action in Listing 6 forwards the schedule bean to the entry view JSP, which is shown in Listing 7.


Listing 7: ScheduleEntryView.jsp provides data entry user interface

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>

<html>
<head>
<title><bean:message key="title.add" /></title>
</head>
<body>
<h3><bean:message key="prompt.addEventTitle" /></h3>
<logic:messagesPresent>
<h3><font color="red">
<bean:message key="errors.header"/>
</font></h3>
<ul>
<html:messages id="error">
<li><bean:write name="error"/></li>
</html:messages>
</ul>
<p/>
</logic:messagesPresent>

<html:form action="add.do">
<table border="0" width="30%" align="left">
<tr>
<th align="right">
<bean:message key="prompt.duration"/>
</th>
<td align="left">
<html:text property="duration" size="16"/>
</td>
</tr>
<tr>
<th align="right">
<bean:message key="prompt.eventType"/>
</th>
<td align="left">
<html:select property="eventTypeKey">
<html:options collection="eventTypes" property="value"
labelProperty="label"/>
</html:select>

</td>
</tr>
<tr>
<th align="right">
<bean:message key="prompt.start"/>
</th>
<td align="left">
<html:text property="start" size="16"/>
</td>
</tr>
<tr>
<th align="right">
<bean:message key="prompt.text"/>
</th>
<td align="left">
<html:text property="text" size="16"/>
</td>
</tr>

<tr>
<td align="right">
<html:submit>
<bean:message key="button.submit"/>
</html:submit>
</td>
<td align="right">
<html:reset>
<bean:message key="button.reset"/>
</html:reset>
</td>
</tr>
</table>
</html:form>

</body>
</html>


This JSP provides two fertile topics, and they are covered in reverse order. The first topic appears in the body of the page with the custom Struts JSP tags. Using Struts tags instead of standard HTML tags provides at least two benefits for this page. The first benefit is the ability to define the text labels in the application-wide properties file, discussed earlier. The second benefit is the immense simplification of some HTML constructs. If you refer back to the Model 2 Schedule Application, you will see that 17 lines of mixed HTML and scriptlet code were required to generate the list of select options from the database; code that wasugly and hard to maintain. The annotation in Listing 7 shows how the same behavior is better accomplished with Struts.

Validation
Another topic of interest on the ScheduleEntryView page is validation. One of the most common tasks in Web applications is the validation of data entered by the user via an HTML POST. Generally, this is handled in the controller where the page posts. If the validations fail, the user is redirected back to the page to correct the errors. A friendly Web application will replace all the values the user typed in, so that the user only has to correct the errors, not type all the values back into the page. This behavior is coded by hand, frequently using the JSP * set property command to automatically repopulate the fields. However, this command presents some problems because it isn't very discriminating.


Struts provides a graceful alternative. Referring back to the struts-config document in Listing 3, one of the action entries (AddToScheduleAction) is associated with a <form-bean> tag . The tag associates a name (addItem) with a class that is in turn associated with the add action. Struts allows you to associate action forms with actions via the <form-bean> tag. In such cases, Struts performs some special handling of the action form classes. When a form bean is associated with an action, and that action is invoked, Struts looks for an instance of the form bean in the user's session. If it doesn't find one, it automatically instantiates it and adds it to the session.


Struts is intelligent enough to pull data automatically from the form bean and populate the HTML fields with the values. For example, if you have a getAddress() method in your form bean and an HTML input called address, Struts automatically fills in the value of the field. This mechanism makes it easy to build wizard-style interfaces with Struts, where the user supplies information across a series of screens. This mechanism also assists in validation.


Declarative validations allow the developer to define rules in a configuration document that are automatically enforced by the framework. Many Web applications have simple validation needs, usually falling into the categories of required fields, minimum and maximum values, and input masks. To configure declarative validations, you must first define the validation rules for your form in an XML document, whose format is mandated by Struts. Declarative validation is outside the scope of this article, and may be covered in future articles (if enough readers request for it!).

The AddToSchedule Action
The last piece of the Struts Schedule Application is the action object that is posted from the entry JSP. AddToScheduleAction is shown in Listing 8.


Listing 8: This action adds a record to the database through the boundary class

package com.nealford.art.schedstruts.action;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.*; // if you want you can make code shorter.
import com.nealford.art.schedstruts.boundary.ScheduleDb;
import com.nealford.art.schedstruts.entity.ScheduleItem;
import com.nealford.art.schedstruts.util.ScheduleAddException;
import org.apache.struts.action.*;
public class AddToScheduleAction extends Action {
private static final String ERR_INSERT =
"AddToScheduleAction: SQL Insert error";

public ActionForward execute(ActionMapping mapping,
ActionForm actionForm, HttpServletRequest request,
HttpServletResponse response) throws IOException,
ServletException {
ScheduleDb sb = new ScheduleDb();
sb.setDataSource(getDataSource(request));
ScheduleItem si = (ScheduleItem) actionForm;
try {
sb.addRecord(si);
} catch (ScheduleAddException sax) {
getServlet().getServletContext().log(ERR_INSERT, sax);
sax.printStackTrace();
}
//-- clean up extraneous session reference to eventTypes
HttpSession session = request.getSession(false);
if (session != null)
session.removeAttribute("eventTypes");
return mapping.findForward("success");
}
}


Note that the AddToScheduleAction class contains no code to handle the validation. When the action is invoked, Struts "notices" that the form bean is associated with it in the struts-config.xml document (see Listing 3). Because the form bean was created on the page that posted to this action, Struts validates the form based on the declarative validations. Failure of the validation automatically redirects to the entry JSP and fills in the form values. If the validation was successful, this action is invoked normally. To get the values entered via the form bean, we need only cast the actionForm instance that is passed to the execute() method. Once we have retrieved the value object, we pass it to the ScheduleDb to add it to the database and forward back to the listing page.


Because of the automatic form validation, this action may not be executed immediately. The event type list must be present for the HTML <select> tag to access the event types. However, if the user is automatically redirected back to the JSP because of a validation error, the list will no longer be available on the request. Thus, the event type list must be added to the session before invoking the page the first time round. While it is generally a bad idea to place long-lived objects on the session, this action is careful to remove them, once the work is complete.


The last order of business is the forward to the next resource via the mapping object. In this case, the target is another action object via Struts, not a JSP. The ActionForward (like a RequestDispatcher) can be directed to any Web resource, not just a JSP.

Evaluating Struts
Struts is not an overbearing framework. Most times, frameworks are so extensive that you can't get anything done outside the context of the framework. Or, 80 percent of what you want to do is extremely easy to do in the framework; another 10 percent is possible, but difficult; and the last 10 percent cannot be accomplished because of; or in spite of, the framework. Struts is much more lightweight. It fits into standard Model 2 type applications but does not preclude you from writing code that does not need, or want, to fit into Struts. I estimate that Struts saves developers from having to write between 30 and 40 percent of the plumbing code normally required for a typical Web application.


Struts provides support for building Model 2 applications by supplying a large part of the code necessary for every Web application. It includes a variety of powerful custom tags to simplify common operations. It offers a clean automatic validation mechanism, and eases building internationalized applications. Its disadvantages chiefly lie in its complexity. Because there are numerous moving parts in Struts, it takes some time to get used to how everything fits together. It is still a new framework, so you may experience some performance issues with extremely busy sites. However, my company has used it for several moderately busy Web applications and been pleased with its performance and scalability, and the lack of any serious bugs. At the time of writing, Struts is in its 1.2.1 beta release, and has garnered considerable developer support.


One apparent disadvantage of Struts goes hand in hand with one of its advantages. To fully exploit Struts' custom tags, you must write your JSPs in terms of Struts elements, replacing the standard HTML elements like <input>, <select>, and so on. However, one of the stated goals of the Model 2 architecture is a separation of responsibilities, ideally allowing graphics designers to work solely on the user interface. If they are forced to use Struts' tags, they can no longer use their design tools.


The Jakarta Web site contains links to resources for Struts. One of these is a plug-in that allows you to use custom JSP tags within Dreamweaver UltraDev, one of the more popular HTML development environments. By using this extension, HTML developers can still drop what looks like standard HTML elements (<input>, <select>, etc.), and the tool generates Struts tags. The extension is nice enough to allow the HTML developer to fill in attribute values for tags and generally work seamlessly with the Struts tags. We have used this within our company, and HTML designers who know virtually nothing about Java quickly become accustomed to working in this environment. Now you can have the Model 2 advantages of separation of responsibilities and still use Struts. Check out http://jakarta.apache.org/taglibs/doc/ultradev4-doc/intro.html for information on this and other useful Struts extensions.

Recent versions of Dreamweaver offers support for all custom JSP tags that include the Struts tags. Several Java development environments are adding support for Struts. Starting with version 8, Borland's JBuilder development environment has wizards and other designers to facilitate Struts development.

Next Framework
Next month, I'll investigate a much more heavyweight framework, Tapestry. While still a Model 2 framework, it could hardly be more different than Struts.

About the Author
Neal is the Chief Technology Officer at The DSW Group, Ltd. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, video presentations, and author of the books Developing with Delphi: Object-Oriented Techniques, from Prentice Hall Ptr (ISBN 0133781186); JBuilder 3 Unleashed, from SAMS (ISBN 0672315483) , and Art of Java Web Development, from Manning Publications (ISBN 1932394060). His primary consulting focus is the building of large scale enterprise applications. He is also an internationally acclaimed speaker, having spoken at numerous developers' conferences worldwide.

Links and Literature:

© 2004 Software & Support Verlag GmbH. Reproduction has to be permitted by the publisher. Questions?