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


JSP and Servlets

Learning Jakarta Struts, Part 3

11/14/2001

This is the final article in a three-part series on the Struts framework.

In the first article, Introduction to Jakarta Struts Framework, I defined the Struts framework, discussed what it can accomplish, and provided an overview of the various components used throughout the framework. In the second article, Learning Jakarta Struts, I described building a simple sample application from scratch using Struts 1.0. This article will show you how to use the Struts tags to access the ApplicationResource file from a JSP.

The action classes are the link between the Struts framework and your business application logic. You tend to want to keep action classes as thin as possible, primarily because the true logic in the application should be in a separate logic tier. If you are doing n-tier application development, you want to keep the interfaces as clean as possible between the tiers. The fact that the main method in the action class is called "perform" indicates that the action classes should do something. Every action needs to be extended from org.apache.struts.action.Action. For a small application, it is possible to do just that, extend action; however, I've found that there are usually common features for your action classes in a given application. It is therefore a better design (in my opinion) to create a base class used by all actions in the application. I've set the StrutsSample application up this way, with a typical method that might be of use in an application action base class as well as some samples of general forward definitions.

package com.oreilly.actions;
import java.io.IOException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.Enumeration;
import java.util.Properties;
import java.rmi.RemoteException;
import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

This is the base class from which all action classes that use Struts can be derived. It takes into consideration some general architecture that would most likely be needed in a real application. For the purpose of this article, those methods that are not directly related to the Struts framework will be black-boxed and commented so that you can use this as a skeleton and fill in those methods as you see fit while you are doing development. All action classes need to be derived from org.apache.struts.action.Action.

public abstract class AbstStrutsActionBase extends Action
{
/* Generic typical returns used so that the structs-config.xml can
* default global forwards.
*/


protected static final String SUCCESS = "success";
protected static final String FAILURE = "failure";
protected static final String ERROR = "error";
protected static final String LOGIN = "login";
protected static final String CONFIRM = "confirm";h
protected Context jndiContext = null;


/**
* Default constructor
*/

public AbstStrutsActionBase()
{
}

/**

This is a blackbox method -- lookup the EJB instance necessary. Typically, action classes work with EJB session beans (or just javabeans) that contain the business logic for your application. In large scale projects, you want to keep a clean seperation of tiers. You would get the JNDI context, get an instance of the context, and then do a lookup on the given JNDI name for the EJB you are interested in to retrieve the home interface. This is provided as just a sample of what is needed.

*/

public Object lookup(String jndiName) throws NamingException, MissingResourceException
{
//Set up the JNDI properties to get the initial context for calls to EJB objects
if (jndiContext == null) {
ResourceBundle resource = ResourceBundle.getBundle("strutssample.properties");
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, resource.getString(Context.INITIAL_CONTEXT_FACTORY));
properties.setProperty(Context.PROVIDER_URL, resource.getString(Context.PROVIDER_URL));
properties.setProperty(Context.SECURITY_PRINCIPAL, resource.getString(Context.SECURITY_PRINCIPAL));
properties.setProperty(Context.SECURITY_CREDENTIALS, resource.getString(Context.SECURITY_CREDENTIALS));
jndiContext = new InitialContext(properties);
}

Note: For production, you probably want a robust try/catch block here to log any errors or important information. For this sample, any exceptions will just be thrown and we'll return the home object.

return (jndiContext.lookup(jndiName));
}

Also in JSP and Servlets:

Learning the New Jakarta Struts 1.1, Part 2

Learning the New Jakarta Struts 1.1, Part 1

JSP Standard Tag Libraries, Part 2

This is the main action called from the Struts framework. You can have this method call an abstract performAction method that is then implemented by each action class, and do any specifics in this method that might be common to all actions, like logging. For this example, we are simply using the perform as the asbstract class.

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

/*


Having another abstract method here is useful so you can do things such as logging, as shown below:

{
ActionForward forward = null;
// Simple log to the servlet log for informational purposes

getServlet().log("AbstStrutsActionBase.perform() [Action Class: "+this.getClass().getName()+" ]");
getServlet().log("AbstStrutsActionBase.perform() [Form Class : "+(form == null ? "null" : form.getClass().getName())+" ]");
}

Each Action class would extend the AbstStrutsActionBase and implement their own perform method specific to the tasks of that action. If we look at the LoginAction, we can see how this is applied.

package com.oreilly.actions;
import java.io.IOException;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForward;
import com.oreilly.forms.LoginForm;

/*

LoginAction demonstrates how an Action class is called within the Struts framework. For the purposes of this sample, we simply demonstrate how the perform is called, a sample action, and a return.

*/

public class LoginAction extends AbstStrutsActionBase 
{

This is a blackbox method used to authenticate a user. There would typically be a bean (or EJB) here that actually does the work. A lookup would be done (as shown in the base class) and create an interface to authenticate the user against some backend database.

public boolean authenticate(String username, String password)
{

Here, do the appropriate lookup and calls. That code is not provided as part of this sample. Instead, it is just to demonstrate the interaction of the action class with business logic tier.

return(true);
}

Override base class with specific perform implementation for this class.

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

// Assume that the login fails, so that the login page is redisplayed
// if the user isn't authenticated

boolean validLogin=false;
ActionForward actionForward = mapping.findForward(LOGIN);

// Create the container for any errors that occur

ActionErrors errors = new ActionErrors();

// Extract attributes and parameters we will need from the incoming
// form.

LoginForm loginForm = (LoginForm) form;
String userName = null;
String password = null;
if (loginForm != null){
userName = loginForm.getUserName();
password = loginForm.getPassword();
validLogin = authenticate(userName, password);
}

if (validLogin){

// Forward control to the specified 'success' URI specified in the structs-config.xml

actionForward = mapping.findForward(SUCCESS);

// Save something into the session for later use.

request.getSession(true).setAttribute("USERNAME", userName);
} else {
errors.add("login", new ActionError("error.login.authenticate"));
}

// If any messages is required, save the specified error messages keys

// into the HTTP request for use by the <struts:errors> tag.

if (!errors.empty()) {
saveErrors(request, errors);
}

// Forward control to the appropriate URI as determined by the action.

return (actionForward);
}

}

Remember, the LoginAction is defined as the instance of the login action in the struts-config.xml file. When the class is instantiated, the Struts framework calls the perform method. The method signature

Related Reading

Java Servlet Programming, 2nd EditionJava Servlet Programming, 2nd Edition
By Jason Hunter with William Crawford
Table of Contents
Index
Sample Chapter
Full Description

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

includes: mapping, a mechanism to locate the available action mappings, the form that is providing data for this action, and the standard HttpServletRequest and HttpServletResponse.

With this information, an Action can gather any information it needs to perform its task. In our sample, the necessary information is pulled from the form, mainly the username and password. This is needed in order to authenticate the user. The authenticate method call is the sole point of the business logic for this application. Within the authenticate method would typically be the lookup of an EJB, or a database access. You can see now why I included a simple lookup method in the AbstStrutsActionBase class -- this lookup could be used by all Action classes if this were an EJB-based system.

Based on the return value, the action will take the appropriate steps. If the user was authenticated, then we are good to go, so we save some info in the request for use later, and return a success forward. This forward is used by the ActionMapping in the struts-config.xml file to determine where to go next. If we look back at the ActionMapping for login, success will bring us to Welcome.jsp. On failure, an error is set, and the appropriate error page will be displayed.

Develop the application business logic

In a real application, this is where you would incorporate your business logic. In our sample, that would include writing the EJB that is called in the authenticate method of the LoginAction. As you can see, it's possible to get things up and running by "blackboxing" the true application logic. I actually recommend getting your application framework up and running first, and then going back and doing the development for the logic tier. It's just one less thing to debug if you know that your framework is set up correctly. We are going to skip over the business logic because it's beyond the scope of this article, but I'm sure you get the picture.

Create JSPs to match the workflows using the ActionMappings

Here is where all of the pieces should start to fall into place. Using the struts-config.xml file, you want to make sure that there is a JSP for one defined in the ActionMappings. In our case, these are Login.jsp, Welcome.jsp, and Errorpage.jsp. It's important to note that even though I've had the forward map to a JSP, it is perfectly logical and necessary in a real application to map to other actions. Our simple Login.jsp looks like:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-form.tld" prefix="form" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

<html>
<head>
<title><bean:message key="login.title"/></title>
</head>
<body>
<html:errors/>
<h3>Enter your username and password to login:</h3>
<html:form action="login.action" focus="userName" >
<html:text property="userName" size="30" maxlength="30"/>
<html:password property="password" size="16" maxlength="16" redisplay="false"/>
<html:submit property="submit" value="Submit"/>
<html:reset/>
</html:form>
</body>
</html>

Struts provides a comprehensive tag library (based on the JSP custom tag libraries). These tag libraries make it easy to construct the user interface. The advantage of using these tag libraries is that they provide some additional functionality. For example, the following snippet may be found in an JSP form:

<input type="text" name="userName" value="">

Using Struts custom tag libraries, the above would become:

<html:text property ="userName">

An example of a Struts tag library is defined by the statement

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

While I'm not going to go into details on all of the available tags in the various Struts taglibs, I've used a few in this sample. If you are going to be building complicated JSPs, I suggest that you familiarize yourself with what's available. They can prove very helpful and save a lot of time. You can find more details in the Struts Developers Guides.

There are a couple of points to be made with our simple JSP. First the

<title><bean:message key="login.title"/></title>

shows the use of the ApplicationResource file that we mentioned earlier. This frees you from hard-coding text into your application.

Also, <html:errors/> is what uses the ActionErrors collection that we created to return any errors that occurred during validation in the LoginForm, or LoginAction.

The form defined within the

<html:form action="login.action" focus="userName">

tag is really what starts the Struts process. The action that is defined here, login.action, must match an ActionMapping in the struts-config.xml file. From there, the appropriate actions, forms, and forwards are defined. On the submit of this form, the action is trapped by the Struts ActionServlet. We'll discuss how that is defined below.

Our Welcome.jsp simply demonstrates how it's possible to use the standard mechanisms available in JSP to pass information from actions to pages:

<html>
<title>Welcome to Struts</title>
<body>
<p>Welcome <%= (String)request.getSession().getAttribute("USERNAME") %></p>
</p>You have logged in successfully!</p>
</body>
</html>

The USERNAME attribute was set in the LoginAction perform().

Build the appropriate configuration files

We've spoken a lot about the struts-config.xml file. Usually, the contents of this file are built along the way. However, in this step in the development flow, it's good to take a step back and clearly examine the config file and make sure that everything matches. The final names of all of the Action classes, JSPs, and forms should be well-known and defined in this file. The one thing that we haven't talked about yet is the web.xml file. This file is used by the JSP container, in this case Tomcat, to give specfic information regarding the application. The web.xml file for the StrutsSample application looks like:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
This is the web-app configuration that allow the strutsSample to work under
Apache Tomcat.
-->

<!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>oreilly</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>application</param-name>
<param-value>com.oreilly.ApplicationResources</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>
<init-param>
<param-name>validate</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>oreilly</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>Login.jsp</welcome-file>
</welcome-file-list>

<!-- Struts Tag Library Descriptors -->
<taglib>
<taglib-uri>/WEB-INF/struts.tld</taglib-uri>
<taglib-location>/WEB-INF/struts.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-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>

<taglib>
<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>

<taglib>
<taglib-uri>/WEB-INF/struts-form.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-form.tld</taglib-location>
</taglib>

</web-app>

The <servlet> tag is used to define the instance of the org.apache.struts.action.ActionServlet. In this case, we are calling it oreilly. Two init parameters are worth calling out here: the application that defines the resource file that holds all of the strings for the applications, and the location of the struts-config.xml file. If you noticed, the url-pattern of the servlet mapping is the same extension we used in our form action attribute. The servlet mapping tells Tomcat to send all requests that end in .action to the oreilly servlet. You can specify whatever pattern you want here. You might see references to the .do extension in Struts, but I think .action is more explicit. The welcome file list is the default page to display when accessing the application. Finally, we list any of the Struts taglibs we might be using.

Build/Test/Deploy

Now that we have all of the pieces in place, it is relatively easy to build, test, and deploy your application. Using Ant to build the application is relatively painless. If you haven't used Ant before, I would definitely recommend looking into it. It is easy to learn and provides a clean way to maintain a build environment. I've put together a build.xml file that works with this sample. You can download all of the files used in this article, build and then create a strutsSample.war file by simply typing Ant in the directory where build.xml is located. You need to first download and install the Ant release. It takes about 10 minutes to download and set up.

Our application structure is as follows:

StrutsSample directory
*.jsp
WEB_INF directory
Struts config files (struts-config.xml, web.xml)
Classes directory (strutsSample application package structure)
Lib directory (struts.jar)

Once you have your application in a .war file, you just put it in the webapps directory of Tomcat, and start Tomcat. The .war file will be expanded and a new context will be created in Tomcat for the application. The web.xml file that we created tells Tomcat where to get the rest of the information it needs about the application. You should then be able to access the application by typing http://localhost:8080/strutsSample. Tomcat defaults to use port 8080, so no special setup is required. Login.jsp will display as the welcome page, and you can test away.

Conclusion

In this series of articles, we walked through the entire process of taking an application requirement and building an application from scratch using the Struts framework. While there are more files involved with Struts than with JSP, there are definite benefits in terms of being able to use an MVC model and apply it to a complex environment. The first application takes the most time just getting accustomed to where all the pieces fit together.

Hopefully with the help of this series of Struts articles, you now have a good understanding of exactly what the components are, where they fit, what's necessary, and a good development flow to follow. Struts is just in its infancy and I think will prove to be a valuable tool for doing what we all like to do -- build applications.

Sue Spielman is an associate editor for ONJava.com, covering JSP and Servlets technologies. She is also President and Senior Consulting Engineer for Switchback Software LLC.


Read more JSP and Servlets columns.

Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.