An Introduction to using Jakarta Struts for Web Application Development
By Santosh Shanbhag, OCI Senior Software Engineer
March 2003
Introduction
The Jakarta Struts framework is based on the well-known Model-View-Controller (MVC) architecture. When Java servlets came into existence, there was great excitement about developing lightweight web applications that spawned a new thread for each request as opposed to the heavyweight CGI methodology of starting a new process for each request. But the excitement soon died when developers started to get frustrated with embedding html tags in servlet code making it extremely difficult to maintain the code. JavaServer Pages (JSPs) came as a breath of fresh air but essentially they are servlets turned inside out. Even using custom tags, there was still a potential for tight coupling of presentation and business logic and the lack of a framework made development less and less agile. Clearly there was a need for a structured technique to route the requests, produce the responses, trigger data accesses, and perform validations.
Jakarta Struts was developed to address these concerns and in addition to providing a robust framework, it also provides a rich set of custom tags neatly classified into appropriate categories. Struts also provides built-in support for localizing an application using message resources which lets developers define labels and messages in a separate file. In addition to these, Struts also includes declarative exception handling which means you can specify exception handlers for specific exceptions in the Struts configuration file. All these features make the framework extremely useful for developing web applications rapidly and keeping the code maintainable while taking out the pain of developing modular web applications. To quote from the Jakarta website: "Struts combines Java Servlets, Java ServerPages, custom tags, and message resources into a unified framework."
Struts is currently at version 1.1-RC1. A number of improvements and fixes have been made to the current stable version of 1.0.2. A few new classes and tags have also been added to facilitate ease of development.
The Struts Mechanism
The heart of any Struts application is the controller which not only acts as the entry point to the application but also intercepts all client requests. All client requests including subsequent form submissions must pass through the controller. The controller functionality is provided by the org.apache.struts.action.ActionServlet
class. The controller reads information from a configuration file (struts-config.xml)
to determine where the requests are to be routed. Each request is generally routed to an org.apache.struts.action.Action object
which invokes the business objects relevant to the request and returns an org.apache.struts.action.ActionForward
object. This object acts as the key which is mapped in the configuration file to a view object (generally a JSP) that is presented to the client as the response. Fig-1 below shows a bird's eye view of Struts. Fig-2 below shows the steps involved in the Struts controller mechanism in response to a request.
A basic Struts application can be constructed using the five core classes: ActionServlet, ActionForm, Action, ActionForward, and ActionMapping. They are explained below:
ActionServlet - As explained above, this class provides the controller logic for the application. Most developers do not need to create a subclass of ActionServlet
. The web.xml file contains an entry for the ActionServlet
as shown below:
- <servlet>
- <servlet-name>action</servlet-name>
- <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
-
- <init-param>
- <param-name>config</param-name>
- <param-value>/WEB-INF/struts-config.xml</param-value>
- </init-param>
- .
- .
- (other init params here)
- .
- .
- </servlet>
-
- <servlet-mapping>
- <servlet-name>action</servlet-name>
- <url-pattern>*.do</url-pattern>
- </servlet-mapping>
This associates the logical name "action" with the ActionServlet
class. Also from the url pattern matching, all *.do file requests are intercepted by the servlet. The init param config is one of the necessary params associated with the controller servlet. It specifies the configuration xml file that is to be used to configure the controller.
ActionForm - reads data submitted from an HTML form using either GET or POST method. Automatically maps the data submitted by the request to the corresponding setter methods using JavaBeans reflection mechanism. The developer can code validation logic by overriding the validate()
method. It also has a reset()
method that can be overridden to reset the fields to their default values when the reset button on the HTML form is pressed.
Action - has access to the ActionMapping
and ActionForm
objects and thus knows the parameters that were sent in the request. In the execute()
method, it invokes business operations on the model components using these parameters. The developer should take care not to code business logic here because keeping business code separately in business objects will ensure their reusability in other applications. Rather it should be used just to make the method calls on business objects. An Action
can be reused simply by using it in different action mappings. Only a single instance of an Action
object is created for the lifetime of the application.
ActionForward - After an action has been executed, there are certain outcomes of the action. For example, a logon to an application may be a success or a failure. If the outcome is a success, we want the request to be forwarded to a different page than that of a failure. Struts allows you to specify these kind of forwards in the configuration file. This keeps the developer free from putting this information in the code. ActionForwards can be configured with different keys pointing to different paths. In the above example, "success" and "failure" can be defined as keys. The execute()
method in the Action
object is usually return mapping.findForward(key)
which returns an ActionForward
object.
ActionMapping - This object contains configuration information about which Action
is invoked for a particular URI and the ActionForm
associated with it. It also contains the mapping information for keys that will be used by the Action
object to forward the request to a resource based on the key value.
How To Configure A Struts Application
Having acquired a basic knowledge of the Struts framework, we now move on to one of the most interesting aspects of a Struts application - the configuration file. The configuration file is an XML file analogous to a deployment descriptor used in EJBs. The configuration file contains all the declarations that tie the various objects of the application together, thereby insulating them from each other. A simple config file is as shown below:
- <?xml version="1.0" encoding="ISO-8859-1" ?>
-
- <!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-beans>
- <form-bean name="loginForm" type="com.ociweb.struts.LoginForm"></form>
- </form-beans>
-
- <action-mappings>
- <action path="/login"
- type="com.ociweb.struts.LoginAction"
- name="loginForm">
- <forward name="success" path="/login-success.html"></forward>
- <forward name="failure" path="/login-failure.html"></forward>
- </action>
- </action-mappings>
- </struts-config>
The configuration above specifies that for a path of "/login", there is a LoginAction
object which uses a form that has a logical name of "loginForm". The exact class of "loginForm" is specified under the tag by the tag. The configuration also specifies two ActionForward
objects that could be used with the logical names "success" and"failure". These are mapped to "login-success.html" and "login-failure.html", respectively.
Global Exceptions
The Struts framework allows you to declare exception handlers in the config file. These handle exceptions that are thrown by an Action object when it is executing a request. In the struts config file, you can specify both local (to the action) and global exceptions. Global exception handlers are declared as:
- <global-exceptions>
- <exception
- type="com.ociweb.struts.UserNameNotFoundException"
- key="username.notfound"
- path="/registerNewUser.jsp"></exception>
- </global-exceptions>
The "type" attribute specifies the exception handler class. The "key" attribute provides a key to the message that should be displayed to the user when the exception occurs. The value of the key is in a message resources file stored as key/value pairs. The "path" attribute specifies the resource that will be fetched when the exception occurs.
Internationalizing and Localizing Content
With Struts, all of your message strings can be stored in an external file instead of being hard-coded in the program code. Each message string is stored with a corresponding key that is used as the identifier of the message. To configure this, you must specify the file that contains the key mappings for the messages in the struts-config.xml file. This is shown below:
.
.
<message-resources parameter="application"></message>
.
.
</servlet>
This implies that the message resource file will be found at /WEB-INF/classes/application.properties. If a specific locale is being requested, for example, the controller will search for files application_en_US.properties and application_es_US.properties for English/USA and Spanish/USA, respectively.
Data Sources
To facilitate working with database connections, Struts allows developers to configure multiple DataSource connections and use them by their logical names. This is shown below:
<data-sources>
<data-source>
<set-property property="driverClass" value="org.gjt.mm.mysql.Driver"></set>
<set-property property="autoCommit" value="true"></set>
<set-property property="user" value="oci-admin"></set>
<set-property property="password" value="admin-oci"></set>
<set-property property="url" value="jdbc:mysql://localhost:8100"></set>
</data-source>
</data-sources>
Tag Libraries
Six core tag libraries are included with the Struts framework. They can be used independently or in collaboration with each other. Struts also provides developers with the capability to create their own custom tags to implement application specific functionality. The core tag libraries are HTML, Bean, Logic, Template, Nested, and Tiles.
HTML - provides a set of common controls available in most browsers. Includes buttons, textfields, checkboxes, and radio buttons. Also provides hidden controls to transfer data between forms.
Bean - contains JSP custom tags useful in defining new beans (in any desired scope) from a variety of possible sources, as well as a tag to render a particular bean (or bean property) to the output response.
Logic - contains tags that are useful in managing conditional generation of output text, looping over object collections for repetitive generation of output text, and application flow management.
Nested - Nested tags and supporting classes extend the base struts tags to allow them to relate to each other in a nested nature. The fundamental logic of the original tags don't change, except in that all references to beans and bean properties will be managed in a nested context.
Template - contains tags that are useful in creating dynamic JSP templates for pages which share a common format. These templates are best used when it is likely that a layout shared by several pages in your application will change.
Tiles - builds on the "include" feature provided by the JavaServer Page specification to provided a full-featured, robust framework for assembling presentation pages from component parts. Each part, or tile, can be reused as often as needed throughout your application.
In order to use the tag libraries, you must make an entry for each tag library in the application deployment descriptor (web.xml). The example below shows entries for the html and bean libraries:
<taglib>
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
NOTE: Some of the tags in Bean and Logic library are already available in the JavaServer Pages Standard Tag Library (JSTL). A detailed description and usage of the tag library may be available in a future article.
A Struts Application For Creating a Login Form
Struts is pretty huge to learn in one article. As such, the purpose of this code is to demonstrate how a very simple Struts based application can be created. I have deliberately left out complex applications and focussed instead on the basics to get a user started with Struts and then work her way up. In future articles, we may demonstrate some of the cool advanced features of Struts. To run the code, you will need to have Tomcat (I used version 4.0.4) installed and running and the following jar files in your server classpath
(like ${CATALINA_HOME}/lib/common
or in ${root}/WEB-INF/lib
directory of your application struts_1_1_b2.jar, commons-beanutils.jar, commons-collections.jar, commons-digester.jar, and commons-logging.jar
All of these jar files are available from the latest struts build.
- <?xml version="1.0" encoding="ISO-8859-1"?>
-
- <!DOCTYPE web-app
- PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
- "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
-
-
- <web-app>
-
- <servlet>
- <servlet-name>action</servlet-name>
- <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
-
- <init-param>
- <param-name>application</param-name>
- <param-value>messages</param-value>
- </init-param>
- <init-param>
- <param-name>config</param-name>
- <param-value>/WEB-INF/struts-config.xml</param-value>
- </init-param>
- <init-param>
- <param-name>debug</param-name>
- <param-value>2</param-value>
- </init-param>
- <init-param>
- <param-name>detail</param-name>
- <param-value>2</param-value>
- </init-param>
- <load-on-startup>2</load-on-startup>
- </servlet>
-
- <servlet-mapping>
- <servlet-name>action</servlet-name>
- <url-pattern>/action/*</url-pattern>
- </servlet-mapping>
-
- <welcome-file-list>
- <welcome-file>index.jsp</welcome-file>
- </welcome-file-list>
-
- <taglib>
- <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
- <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
- </taglib>
- <taglib>
- <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
- <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
- </taglib>
- <taglib>
- <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
- <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
- </taglib>
- </web-app>
- <?xml version="1.0" encoding="ISO-8859-1" ?>
-
- <!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>
-
- <!-- associate form beans with logical names -->
- <form-beans>
- <form-bean name="loginForm" type="com.ociweb.struts.LoginForm"></form>
- </form-beans>
-
- <!-- define global forwards here -->
- <global-forwards>
- <forward name="loginPage" path="/action/loginPage"></forward>
- </global-forwards>
-
- <!-- define action mappings here -->
- <action-mappings>
-
- <action path="/loginPage"
- type="org.apache.struts.actions.ForwardAction"
- parameter="/Login.jsp"></action>
-
- <action path="/loginSubmit"
- type="com.ociweb.struts.LoginAction"
- name="loginForm"
- scope="request"
- validate="true"
- input="/Login.jsp">
- <forward name="success" path="/success.html"></forward>
- <forward name="failure" path="/failure.html"></forward>
- </action>
- </action-mappings>
-
- </struts-config>
-
errors.header = <ul>
errors.footer = </ul>
errors.prefix = <li>
errors.suffix = </li>
error.username.notentered = You have not entered the user name
error.username.incorrect = You have entered an incorrect user name
error.password.notentered = You have not entered the password
error.password.incorrect = You have entered an incorrect password
error.lazy.user = You are too lazy to enter either user name or password
- /*
- * LoginForm.java
- */
- package com.ociweb.struts;
-
- import org.apache.struts.action.ActionForm;
- import org.apache.struts.action.ActionErrors;
- import org.apache.struts.action.ActionMapping;
- import org.apache.struts.action.ActionError;
-
- import javax.servlet.http.HttpServletRequest;
-
- public class LoginForm extends ActionForm {
- protected String userName;
- protected String password;
-
- public String getUserName() {
- return userName;
- }
-
- public void setUserName(String userName) {
- this.userName = userName;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
- ActionErrors retErrors = new ActionErrors();
-
- // if user name is null or empty string
- if(userName == null || "".equals(userName)) {
- retErrors.add("userName", new ActionError("error.username.notentered"));
- }
-
- // if password is null or empty string
- if(password == null || "".equals(password)) {
- retErrors.add("password", new ActionError("error.password.notentered"));
- }
-
- // just for fun, make a check to see if both fields have not been entered.
- // if not, then we can print this message at the top of the page
- if( (userName == null || userName.equals("") ) &&
- (password == null || password.equals("") ) ) {
- retErrors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.lazy.user"));
- }
-
- return retErrors;
- }
-
- public void reset(ActionMapping mapping, HttpServletRequest request) {
- this.userName = "";
- this.password = "";
- }
- }
- /*
- * LoginAction.java
- */
- package com.ociweb.struts;
-
- import org.apache.struts.action.Action;
- import org.apache.struts.action.ActionForward;
- import org.apache.struts.action.ActionMapping;
- import org.apache.struts.action.ActionForm;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- public class LoginAction extends Action {
-
- public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
- boolean success = false;
-
- LoginForm loginForm = (LoginForm) form;
- String userName = loginForm.getUserName();
- String password = loginForm.getPassword();
-
- // actual validation could be performed by passing user name and password to business objects.
- // here we are just checking if user name and password are same.
- if(userName.equals(password)) {
- success = true;
- }
-
- // return the resource for "success" or "failure" as defined in the action mapping
- // in the struts config file for this action.
- return success? mapping.findForward("success")
- : mapping.findForward("failure");
- }
- }
- <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
- <html>
- <head>
- <title>Welcome to the Login Application!!</title>
- <html:base></html:base>
- </head>
- <body>
- <h3>Hello And Welcome To The Login Application!!!</h3>
- <html:errors></html:errors>
- <br/>
- <html:link forward="loginPage">Login to the application</html:link>
- </body>
- </html>
- <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
- <%@ page import="org.apache.struts.action.ActionErrors" %>
-
- <html>
- <head><title>Login to the Login Application!!</title></head>
- <body>
-
- <p><html:errors property="<%=ActionErrors.GLOBAL_ERROR%>"/></p>
- <hr/>
-
- <html:form action="/loginSubmit">
- <b>Please enter your user name here:</b>
- <html:text property="userName"></html:text>
- <html:errors property="userName"></html:errors>
- <br/>
- <b>Please enter your password here(hint: same as user name):</b>
- <html:password property="password" ></html:password>
- <html:errors property="password" ></html:errors>
- <br/>
- <html:submit></html:submit> <html:reset></html:reset>
- </html:form>
- </body>
- </html>
- <html>
- <body>
- Congratulations! You have made a successful login.
- </body>
- </html>
- <html>
- <body>
- You have failed to login because your user name or password or both are invalid..
- </body>
- </html>
Steps to run the application
- create a
${root}
directory under "webapps" of Tomcat. This will be the root of your application. Also create${root}/WEB-INF
,${root}/WEB-INF/lib
, and${root}/WEB-INF/classes
- copy files
index.jsp
,Login.jsp
,success.html
,failure.html
to${root}
- copy
messages.properties
${root}/WEB-INF/classes
- copy files
web.xml
andstruts-config.xml
to${root}/WEB-INF
- copy files
struts_1_1_b2.jar
,commons-beanutils.jar
,commons-collections.jar
,commons-digester.jar
, andcommons-logging.jar
to${root}/lib
- Compile
LoginForm.java
andLoginAction.java
and place the compiled.class
files under${root}/WEB-INF/classes/com/ociweb/struts
- Restart Tomcat. Open a web browser and start your web application. The figures below show the various screens of the application
So how does the application really work?
Initially, the index.jsp
page comes up with a link that is generated by the Struts <html:link forward="loginPage">Login to the application</html:link>
tag.
Struts looks up the struts-config.xml
file and finds an entry for "loginPage" in the <global-forwards>
section under the <forward>
tag and finds that the path maps to /action/loginPage
.
So the page is generated by the browser as (see Fig 3 above):
- <html>
- <head>
- <title>Welcome to the Login Application!!</title>
- <base href="http://localhost/oci-struts/index.jsp">
- </head>
- <body>
- <h3>Hello And Welcome To The World Of Login Application!!!</h3>
- <br/>
- <a href="/oci-struts/action/loginPage">Login to the application</a>
- </body>
- </html>
Next, the user clicks on the link. Since the link has the /action/*
pattern it is intercepted by the ActionServlet
as configured in the web.xml file. The controller servlet invokes the Login.jsp
resource since that is the action mapping specified in the Struts config file for the path /loginPage
. The action mapping is shown below:
- <action path="/loginPage"
- type="org.apache.struts.actions.ForwardAction"
- parameter="/Login.jsp"></action>
Next, the user enters information in the user name and password fields. For the simplicity of demonstration, if the user name and password are same, the login is successful. Struts looks up the action mapping for "/loginSubmit" in the config file and finds that an instance of com.ociweb.struts.LoginForm
JavaBean is needed to store the information submitted by the form. It also finds out that if the result of the submission is a "success" key, then it must forward the request to "success.html" and for "failure" key to "failure.html". The "loginForm" contains the validation logic in the validate()
method as shown below:
- public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
- ActionErrors retErrors = new ActionErrors();
-
- // if user name is null or empty string
- if(userName == null || "".equals(userName)) {
- retErrors.add("userName", new ActionError("error.username.notentered"));
- }
-
- // if password is null or empty string
- if(password == null || "".equals(password)) {
- retErrors.add("password", new ActionError("error.password.notentered"));
- }
-
- // just for fun, make a check to see if both fields have not been entered.
- // if not, then we can print this message at the top of the page
- if( (userName == null || userName.equals("") ) &&
- (password == null || password.equals("") ) ) {
- retErrors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.lazy.user"));
- }
-
- return retErrors;
- }
The validation logic specified that if the user name is null or empty, then it adds an ActionError
with the "userName" key to the ActionErrors
that are to be returned. This associates the error with the "userName" field. The parameter passed to the ActionError
constructor is the key that represents the message in the "messages.properties" file. In the code, we also specify an error with a GLOBAL_KEY
(general error) if both user name and password are null or empty. Thus we can add general errors as well as specific errors to the list of generated errors. This is returned by the validate method. If there are no errors, this list is empty. All messages are keys that are mapped to the actual message in the messages.properties file. In the Login.jsp
file, we specify place holders for the errors messages as shown below:
- <-- this is a place holder for general messages -->
- <p>html:errors property="<%=ActionErrors.GLOBAL_ERROR%>"/></p>
-
- ...
- <html:form action="/loginSubmit">
- <b>Please enter your user name here:</b>
- <html:text property="userName"></html:text>
- <html:errors property="userName"></html:errors>
- <br/>
- <b>Please enter your password here(hint: same as user name):</b>
- <html:password property="password" ></html:password>
- <html:errors property="password" ></html:errors>
- <br/>
- <html:submit></html:submit> <html:reset></html:reset>
- </html:form>
Figures 5 and 6 show the errors generated in the browser. The control remains on the form and the errors are shown in their place holders. If the login is successful, the execute()
method of the associated LoginAction
is executed. If the user name matches the password, a "success" key is returned otherwise a "failure" key is returned. The action mapping specifies the resources for these keys as discussed above.
The error.header and error.footer keys in messages.properties files specify how the errors are formatted. In our case it is a bulleted list so we use <ul>
and </ul>
, respectively. The errors.prefix and errors.suffix specify how individual error messages are prefixed and suffixed.
Summary
In the J2EE world, model components are implemented with EJBs while view components are implemented with JSPs. Struts is the missing link that provides the controller functionality that glues the components together while de coupling them from each other at the same time. The navigational control layer provided by Struts is extremely flexible as it requires a change only in the Struts configuration file and does not mandate that the whole application be recompiled. Struts has gained wide acceptance in the industry and, being open source, it encourages developers enterprise wide to use a reliable and tested framework instead of creating their own and producing "islands of obfuscating code" between development teams. Struts provides a standard and uniform way to create applications that are dynamic, reusable, and understandable. Struts applications can be conveniently adapted to any locale by creating message bundles thus avoiding exorbitant overheads and costs to make a successful world-wide application deployment.
References
- [1] Jakarta Struts Home Page
http://jakarta.apache.org/struts - [2] Learning the new Jakarta Struts 1.1, Part 1 - OReilly OnJava article by Sue Spielman
http://www.onjava.com/pub/a/onjava/pub/a/2002/11/06/struts1.html - [3] Learning the new Jakarta Struts 1.1, Part 2 - OReilly OnJava article by Sue Spielman
http://www.onjava.com/pub/a/onjava/2002/11/13/jsp_servlets.html - [4] Jakarta Struts: Seven Lessons from the Trenches - OReilly OnJava article by Chuck Cavaness
http://www.onjava.com/pub/a/onjava/2002/10/30/jakarta.html - [5] UI Design With Tiles And Struts - JavaWorld article by Prakash Malani
http://www.javaworld.com/javaworld/jw-01-2002/jw-0104-tilestrut.html - [6] Book: Struts In Action - Building Web Applications With the Leading Java Framework - Ted Husted, et al., Manning Publications, Jan 2003.
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.