Previous Section
Table of Contents
Next Section


Chapter 2: Building a Simple Struts Application

Now that you've reviewed the history of Web application development and the fundamentals of the Struts framework, it's time to move beyond theory and into practice. As you will see, a Struts application is a composite of several interrelated parts. The goal of this chapter is to give you a general understanding of these parts and show how they work together to form a complete program. To accomplish that goal, this chapter develops a simple application that highlights each Struts component. In the process, several key elements of Struts are introduced. Once you understand how this simple Struts application works, you will be able to easily understand other Struts programs because all share a common architecture. Subsequent chapters discuss in detail the many concepts introduced here.

This chapter also describes the steps necessary to compile, package, and run the application. You will use the same general procedure with the other examples in this book.

Application Overview

The sample application in this chapter deviates from the stereotypical 'Hello World' program found in most programming books. Instead, a bit more sophisticated example is needed to illustrate the components of Struts and the process required to build a Struts-based application. The example that we will use is a simple human resources (HR) application. Creating a full-blown HR application is a large undertaking that requires several pieces of functionality, from employee management to benefits management, so the sample application in this chapter will support only one common subset of functionality: Employee Search.

The sample application is called Mini HR and it will have a basic opening page that links to an Employee Search page. From the Employee Search page, users can search for employees by name or social security number. After executing the search, the Search page will be redisplayed with a list of employees that match the search criteria. Although quite simple, and limited in scope, this example illustrates the key features common to any Struts-based Web application.

The Mini HR Application Files

All Struts applications are comprised of several files, which contain the various parts of a Struts program. Some are Java source files, but others contain JSP and XML. A properties file is also required. Because of the relatively large number of files required by a Struts application, we will begin by examining the files required by Mini HR. The same general types of files will be needed by just about any Struts application.

The following table lists each file required by Mini HR and its purpose.

File

Description

index.jsp

Contains the JSP that is used as a gateway page for the Mini HR application and provides a link to the Employee Search page.

search.jsp

Contains the JSP that is used for performing employee searches and displaying the search results.

SearchForm.java

Contains the class that captures and transfers data to and from the Search page. This is a View class.

SearchAction.java

Contains the class code that processes requests from the Search page. This is a Controller class.

EmployeeSearchService.java

Contains the class that encapsulates the business logic and data access involved in searching for employees. This is a Model class.

Employee.java

Contains the class that represents an employee and encapsulates all of an employee's data. This is a Model class.

web.xml

Contains the XML that is used to configure the Java Web application properties for the Mini HR application.

struts-config.xml

Contains the XML that is used to configure the Struts framework for this application.

ApplicationResources.properties

Contains properties that are used to externalize application strings, labels, and messages so that they can be changed without having to recompile the application. This file is also used for internationalizing the application.

The following sections examine each of the Mini HR application files in detail, and in many cases line by line. First, though, it's necessary to explain where each file should be placed in a directory hierarchy. Because this application (and all other Struts applications) will be deployed to a J2EE servlet container, the application files have to be arranged in the standard J2EE Web Archive (.war) format, which is simply a Java Archive (.jar) file with a different extension (.war). The Web Archive format also specifies a few key requirements for the .jar file:

  • There must be a directory at the root level of the archive named WEB-INF. At run time this is a protected directory and thus any files beneath it will be inaccessible to browsers.

  • There must be a Web application deployment descriptor file named web.xml beneath the WEB-INF directory. This file will be explained later in this chapter in the 'web.xml' section.

  • Any libraries needed by the application should be under a directory called lib located beneath the WEB-INF directory.

  • Any class files needed by the application, which are not already packaged in a .jar file, should be under a directory called classes located beneath the WEB-INF directory.

For the Mini HR application, you will create a directory called MiniHR. In principle, you can place this directory anywhere, but to follow along with this example, put it at c:\java. You'll use the c:\java\MiniHR directory as the root of your Web application so that you can easily create a Web Archive file later. Following is the layout of the c:\java\MiniHR directory, shown in Figure 2-1, and the location of each file examined in this section. You will need to place the files in this exact structure.

Click To expand
Figure 2-1: The c:\java\MiniHR directory layout
c:\java\MiniHR\index.jsp
c:\java\MiniHR\search.jsp
c:\java\MiniHR\WEB-INF\web.xml
c:\java\MiniHR\WEB-INF\struts-config.xml
c:\java\MiniHR\WEB-INF\classes\com\jamesholmes\minihr\ApplicationResources.properties
c:\java\MiniHR\WEB-INF\lib
c:\java\MiniHR\WEB-INF\src\com\jamesholmes\minihr\Employee.java
c:\java\MiniHR\WEB-INF\src\com\jamesholmes\minihr\EmployeeSearchService.java
c:\java\MiniHR\WEB-INF\src\com\jamesholmes\minihr\SearchAction.java
c:\java\MiniHR\WEB-INF\src\com\jamesholmes\minihr\SearchForm.java
c:\java\MiniHR\WEB-INF\tlds

index.jsp

The index.jsp file, shown here, is a very simple JSP that is used to render Mini HR's opening screen:

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

<html>
<head>
<title>ABC, Inc. Human Resources Portal</title>
</head>
<body>

<font size="+1">ABC, Inc. Human Resources Portal</font><br>
<hr width="100%" noshade="true">

&#149; Add an Employee<br>
&#149; <html:link forward="search">Search for Employees</html:link><br>

</body>
</html>

You'll notice that index.jsp is comprised mostly of standard HTML, with the exception of the JSP tag library definition at the top of the file and the 'Search for Employees' link. The index.jsp file uses Struts' HTML Tag Library to render the Search link. Before you can use the HTML Tag Library, you have to 'import' it into the JSP with the following line at the top of the JSP:

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

This line associates the tag library located at /WEB-INF/tlds/struts-html.tld with a prefix, or 'handle,' of html. That way, any time a tag from the Struts HTML Tag Library is used, it will be prefixed with html. In index.jsp's case, the link tag is used with the following line:

<html:link forward="search">Search for Employees</html:link>

Of course, if you wanted to use another prefix for the tag library, you could do so by updating the prefix attribute of the tag library import on the first line of the file.

The HTML Tag Library's link tag is used for rendering an HTML link, such as http://www.jamesholmes.com/. The link tag goes beyond basic HTML, though, by allowing you to access link, or forward definitions, from the Struts configuration file, struts-config.xml, which is covered later in this chapter in the 'struts-config.xml' section. In this case, the tag looks for a forward definition named 'search' defined in the struts-config.xml file to use for the link being generated. If you skip ahead to the 'struts-config.xml' section of this chapter, you'll see that the forward definition is as follows:

<!-- Global Forwards Configuration -->
<global-forwards>
  <forward name="search" path="/search.jsp"/>
</global-forwards>

Forward definitions allow you to declaratively configure the location to which a link points instead of hard-coding that information into your JSP or application. As you'll see in Chapter 5, forward definitions are used throughout Struts to direct the flow of an application from the Struts configuration file.

The following is the source code generated after index.jsp has been requested in the browser. Notice that the Search page link has been converted into a standard HTML link.

<html>
<head>
<title>ABC, Inc. Human Resources Portal</title>
</head>
<body>

<font size="+1">ABC, Inc. Human Resources Portal</font><br>

<hr width="100%" noshade="true">

&#149; Add an Employee<br>
&#149; <a href="/MiniHR/search.jsp">Search for Employees</a><br>

</body>
</html>

Here is how index.jsp looks in the browser.

Click To expand

search.jsp

The search.jsp file is responsible for the bulk of the Employee Search functionality in the Mini HR application. When the Employee Search link is selected from the index.jsp page, search.jsp is executed. This initial request for search.jsp renders the basic Employee Search screen shown here:

Click To expand

Each time a search is performed, Struts' Controller servlet is executed and eventually search.jsp is executed to handle the rendering of the Employee Search screen, with the search results, as shown here:

Click To expand

Similarly, if there are any errors with the search criteria when the search is submitted, search.jsp is executed to report the errors, as shown here:

Click To expand

The contents of search.jsp are

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

<html>
<head>
<title>ABC, Inc. Human Resources Portal - Employee Search</title>
</head>
<body>

<font size="+1">
ABC, Inc. Human Resources Portal - Employee Search
</font><br>
<hr width="100%" noshade="true">

<html:errors/>

<html:form action="/search">

<table>
<tr>
<td align="right"><bean:message key="label.search.name"/>:</td>
<td><html:text property="name"/></td>
</tr>
<tr>
<td></td>
<td>-- or --</td>
</tr>
<tr>
<td align="right"><bean:message key="label.search.ssNum"/>:</td>
<td><html:text property="ssNum"/> (xxx-xx-xxxx)</td>
</tr>
<tr>
<td></td>
<td><html:submit/></td>
</tr>
</table>

</html:form>

<logic:present name="searchForm" property="results">

<hr width="100%" size="1" noshade="true">

<bean:size id="size" name="searchForm" property="results"/>
<logic:equal name="size" value="0">
<center><font color="red"><b>No Employees Found</b></font></center>
</logic:equal>

<logic:greaterThan name="size" value="0">
<table border="1">
<tr>
<th>Name</th>
<th>Social Security Number</th>
</tr>
<logic:iterate id="result" name="searchForm" property="results">
<tr>
<td><bean:write name="result" property="name"/></td>
<td><bean:write name="result" property="ssNum"/></td>
</tr>
</logic:iterate>
</table>
</logic:greaterThan>

</logic:present>

</body>
</html>

Because of its size and importance, we will examine it closely, line by line.

Similar to index.jsp, search.jsp begins by declaring the JSP tag libraries that will be used by the JSP:

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

In addition to the HTML Tag Library used by index.jsp, search.jsp uses Struts' Bean and Logic libraries. These additional tag libraries contain utility tags for working with Java beans and using conditional logic in a page, respectively.

The next several lines are comprised of basic HTML tags:

<html>
<head>
<title>ABC, Inc. Human Resources Portal - Employee Search</title>
</head>
<body>

<font size="+1">
ABC, Inc. Human Resources Portal - Employee Search
</font><br>
<hr width="100%" noshade="true">

Immediately following this basic HTML is this errors tag definition:

<html:errors/>

Recall that search.jsp is used to render any errors that occur while validating that the search criteria are sound. The HTML Tag Library's errors tag will emit any errors that are passed to the JSP from the SearchForm object. This is covered in more detail in the 'SearchForm.java' section in this chapter.

The next several lines of search.jsp are responsible for rendering the HTML for the search form:

<html:form action="/search">

<table>
<tr>
<td align="right"><bean:message key="label.search.name"/>:</td>
<td><html:text property="name"/></td>
</tr>
<tr>
<td></td>
<td>-- or --</td>
</tr>
<tr>
<td align="right"><bean:message key="label.search.ssNum"/>:</td>
<td><html:text property="ssNum"/> (xxx-xx-xxxx)</td>
</tr>
<tr>
<td></td>
<td><html:submit/></td>
</tr>
</table>

</html:form>

Before discussing the specifics of the search form, let's review the use of the Bean Tag Library in this snippet. This snippet uses the library's message tag, as shown here:

<td align="right"><bean:message key="label.search.name"/>:</td>

The message tag allows externalized messages from the ApplicationResources .properties file to be inserted into the JSP at run time. The message tag simply looks up the key passed to it in ApplicationResources.properties and returns the corresponding message from the file. This feature is especially useful to internationalize a page and to allow easy updating of messages outside the JSP. Internationalization is the process of providing content specific to a language, locale, or region. For instance, internationalization would be to create both English and Spanish versions of the same JSP.

Note 

The acronym I18N is sometimes used in place of the word internationalization, because it is such a long word to type. I18N represents the first letter i, followed by 18 characters, and then the final letter n. The I18N acronym is used occasionally in this book.

Now, it's time to examine the form. Struts' HTML Tag Library has a tag for each of the standard HTML form tags, such as

<form>

<input type=''>

and so on. Instead of using the standard HTML tags, you'll use the HTML Tag Library's equivalent tag, which ties the form to Struts. For example, the text tag (<html:text>) renders an <input type='text' …> tag. The text tag goes one step further, though, by allowing a property to be associated with the tag, as shown here:

<td><html:text property="name"/></td>

The property 'name' here corresponds to the field named name in the SearchForm object. That way, when the tag is executed, it places the value of the name field in the HTML at run time. Thus, if the name field had a value of 'James Holmes' at run time, the output from the tag would look like this:

<td><input type="text" name="name" value="James Holmes"></td>

At the beginning of this next snippet, the HTML Tag Library's form tag is used to render a standard HTML <form> tag. Notice, however, that it specifies an action parameter of '/search' as shown here:

<html:form action="/search">

The action parameter associates an Action object mapping from the struts-config.xml file with the form. That way, when the form is submitted, the processing will be handled by the specified Action object.

The final section of the search.jsp file contains the logic and tags for rendering search results:

<logic:present name="searchForm" property="results">

<hr width="100%" size="1" noshade="true">

<bean:size id="size" name="searchForm" property="results"/>
<logic:equal name="size" value="0">
<center><font color="red"><b>No Employees Found</b></font></center>
</logic:equal>

<logic:greaterThan name="size" value="0">
<table border="1">
<tr>
<th>Name</th>
<th>Social Security Number</th>
</tr>
<logic:iterate id="result" name="searchForm" property="results">
<tr>
<td><bean:write name="result" property="name"/></td>
<td><bean:write name="result" property="ssNum"/></td>
</tr>
</logic:iterate>
</table>
</logic:greaterThan>

</logic:present>

The beginning of this snippet uses Struts' Logic Tag Library for using conditional logic in a JSP. The Logic Library's present tag checks an object to see if a particular property is present. In this case, the logic tag checks to see if the results field of the SearchForm has been set. If so, then all of the HTML and JSP tags inside the <logic:present …> tag will be executed. Otherwise, they will be ignored.

The rest of the tags in this snippet are responsible for rendering the search results. First, the Bean Library's size tag gets the size of the results ArrayList from the SearchForm object. Next, the size is checked to see if it is 0 using the Logic Library's equal tag. If the size is in fact 0, then a 'No Employees Found' message will be rendered. Otherwise, each of the employees returned from the search will be displayed. The Logic Library's iterate tag is used to iterate over each of the search results. Each search result is assigned to a variable named result by the iterate tag. Inside the iterate tag the Bean Library's write tag is used to access the result variable's name and ssNum fields.

SearchForm.java

The SearchForm class, shown next, is a View class that is used to capture and transfer data to and from the Employee Search page. When the HTML form on the Search page is submitted, Struts' ActionServlet will populate this class with the data from the form. Notice that there will be a one-to-one mapping between fields on the page and fields in the class with getter and setter methods. Struts uses encapsulation and Java's reflection mechanism to call the method corresponding to each field from a page. Additionally, when SearchAction (the Controller class for the Search page) executes, it will populate this object with the search results so that they can be transferred back to the Search page.

package com.jamesholmes.struts;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;

public class SearchForm extends ActionForm
{
  private String name = null;
  private String ssNum = null;
  private List results = null;

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setSsNum(String ssNum) {
    this.ssNum = ssNum;
  }

  public String getSsNum() {
    return ssNum;
  }

  public void setResults(List results) {
    this.results = results;
  }

  public List getResults() {
    return results;
  }

  // Reset form fields.
  public void reset(ActionMapping mapping, HttpServletRequest request)
  {
    name = null;
    ssNum = null;
    results = null;
  }

  // Validate form data.
  public ActionErrors validate(ActionMapping mapping,
    HttpServletRequest request)
  {
    ActionErrors errors = new ActionErrors();

    boolean nameEntered = false;
    boolean ssNumEntered = false;

    // Determine if name has been entered.
    if (name != null && name.length() > 0) {
      nameEntered = true;
    }

    // Determine if social security number has been entered.
    if (ssNum != null && ssNum.length() > 0) {
      ssNumEntered = true;
    }

    /* Validate that either name or social security number
       has been entered. */
    if (!nameEntered && !ssNumEntered) {
      errors.add(null, 
        new ActionError("error.search.criteria.missing"));
    }

    /* Validate format of social security number if
       it has been entered. */
    if (ssNumEntered && !isValidSsNum(ssNum.trim())) {
      errors.add("ssNum",
        new ActionError("error.search.ssNum.invalid"));
    }

    return errors;
  }

  // Validate format of social security number.
  private static boolean isValidSsNum(String ssNum) {
    if (ssNum.length() < 11) {
      return false;
    }

    for (int i = 0; i < 11; i++) {
      if (i == 3 || i == 6) {
        if (ssNum.charAt(i) != '-') {
          return false;
        }
      } else if ("0123456789".indexOf(ssNum.charAt(i)) == -1) {
        return false;
      }
    }

    return true;
  }
}

ActionForm subclasses, including SearchForm, are basic Java beans with a couple of extra Struts-specific methods: reset( ) and validate( ). The reset( ) method is used to clear out, or 'reset,' an ActionForm's data after it has been used for a request. Because Struts reuses ActionForms instead of creating new ones for each request, this method is necessary to ensure that data from different requests is not mixed. Typically, this method is used to just set class fields back to their initial states, as is the case with SearchForm. However, as you'll see in Chapter 4, this method can be used to perform other necessary logic for resetting an ActionForm object.

The validate( ) method of ActionForm is called to perform basic validations on the data being transferred from an HTML form. In SearchForm's case, the validate( ) method first confirms that a name and social security number have been entered. If a social security number has been entered, SearchForm goes one step further and validates the format of the social security number with the isValidSsNum( ) method. The isValidSsNum( ) method simply ensures that an 11-character string was entered and that it conforms to the following format: three digits, hyphen, two digits, hyphen, four digits (e.g., 111-22-3333). Note that business-level validations, such as looking up a social security number in a database to make sure it is valid, are considered business logic and should be in a Model layer class. The validations in an ActionForm are meant to be very basic, such as just confirming that data was entered, and should not be used for performing any real business logic.

You'll notice that the validate( ) method returns an ActionErrors object and the validations inside the method populate an ActionErrors object if any validations fail. The ActionErrors object is used to transfer validation error messages to the screen. Remember from the discussion of search.jsp that the HTML Tag Library's errors tag will emit any errors in a JSP if they are present. Following is the snippet from search.jsp:

<html:errors/>

Here in the ActionForm class, you simply place the keys for messages into the ActionErrors object, such as 'error.search.criteria.missing'. The errors tag will use these keys to load the appropriate messages from the ApplicationResources.properties file, discussed in the section of the same name later in this chapter.

Note that the validate( ) method will be invoked only if the validate parameter of the <action> tag is set to 'true' in the struts-config.xml file.

SearchAction.java

The SearchAction class, shown next, is a Controller class that processes requests from the Search page:

package com.jamesholmes.struts;

import java.util.ArrayList;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public final class SearchAction extends Action
{
  public ActionForward execute(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
    throws Exception
  {
    EmployeeSearchService service = new EmployeeSearchService();
    ArrayList results;

    SearchForm searchForm = (SearchForm) form;

    // Perform employee search based on what criteria was entered.
    String name = searchForm.getName();
    if (name != null && name.trim().length() > 0) {
      results = service.searchByName(name);
    } else {
      results = service.searchBySsNum(searchForm.getSsNum().trim());
    }

    // Place search results in SearchForm for access by JSP.
    searchForm.setResults(results);

    // Forward control to this Action's input page.
    return mapping.getInputForward();
  }
}

Remember from the discussion of search.jsp that the HTML form on the page is set to post its data to the '/search' action. The strut-config.xml file maps the search action to this class so that when ActionServlet (Controller) receives a post from the Search page, it delegates processing for the post to this Action subclass. This mapping is shown here:

<!-- Action Mappings Configuration -->
<action-mappings>
  <action path="/search"
          type="com.jamesholmes.struts.SearchAction"
          name="searchForm"
          scope="request"
          validate="true"
          input="/search.jsp">
    <forward name="results" path="/results.jsp"/>
  </action>
</action-mappings>

Struts' Action subclasses manage the processing of specific requests. You can think of them as mini servlets assigned to manage discreet Controller tasks. For instance, in the preceding example, SearchAction is responsible for processing employee search requests and acts as a liaison between the Model (EmployeeSearchService) and the View (search.jsp).

SearchAction begins by overriding Struts' Action class execute( ) method. The execute( ) method is the single point of entry for an Action class by Struts' ActionServlet. You'll notice that this method takes an HttpServletRequest object and an HttpServletResponse object as parameters, similar to a servlet's service( ), doGet( ), and doPost( ) methods. Additionally, execute( ) takes a reference to the ActionForm associated with this Action and an ActionMapping object reference. The ActionForm reference passed to this Action will be an instance of SearchForm, as discussed in the previous section, 'SearchForm.java.' The ActionMapping reference passed to this Action will contain all of the configuration settings from the struts-config.xml file for this Action.

The execute( ) method begins by instantiating a few objects, and then the real work gets underway with a check to see what search criteria was entered by the user. Notice that the ActionForm object passed in is cast to its native type: SearchForm. Casting the object allows you to access SearchForm's methods for retrieving the search criteria. Based on the criteria entered, one of EmployeeSearchService's methods will be invoked to perform the employee search. If an employee name was entered, the searchByName( ) method will be invoked. Otherwise, the searchBySsNum( ) method will be invoked. Both search methods return an ArrayList containing the search results. This results ArrayList is then added to the SearchForm instance so that search.jsp (View) can access the data.

The execute( ) method concludes by forwarding control to SearchAction's input page: search.jsp. The input page for an action is declared in the struts-config.xml file, as shown here for SearchAction, and is used to allow an action to determine from which page it was called:

<action path="/search"
        type="com.jamesholmes.struts.SearchAction"
        name="searchForm"
        scope="request"
        validate="true"
        input="/search.jsp">

EmployeeSearchService.java

EmployeeSearchService is a Model class that encapsulates the business logic and data access routines involved in searching for employees. The SearchAction Controller class uses this class to perform an employee search and then shuttles the resulting data to the View layer of the Mini HR application. EmployeeSearchService is shown here:

package com.jamesholmes.struts;

import java.util.ArrayList;

public class EmployeeSearchService
{
  /* Hard-coded sample data. Normally this would come from a real data
     source such as a database. */
  private static Employee[] employees = 
  {
    new Employee("Bob Davidson", "123-45-6789"),
    new Employee("Mary Williams", "987-65-4321"),
    new Employee("Jim Smith", "111-11-1111"),
    new Employee("Beverly Harris", "222-22-2222"),
    new Employee("Thomas Frank", "333-33-3333"),
    new Employee("Jim Davidson", "444-44-4444")
  };

  // Search for employees by name.
  public ArrayList searchByName(String name) {
    ArrayList resultList = new ArrayList();

    for (int i = 0; i < employees.length; i++) {
      if(employees[i].getName().toUpperCase().indexOf(name.toUpperCase())
         != -1)
      {
        resultList.add(employees[i]);
      }
    }

    return resultList;
  }

  // Search for employee by social security number.
  public ArrayList searchBySsNum(String ssNum) {
    ArrayList resultList = new ArrayList();

    for (int i = 0; i < employees.length; i++) {
      if (employees[i].getSsNum().equals(ssNum)) {
        resultList.add(employees[i]);
      }
    }

    return resultList;
  }
}

In order to simplify Mini HR, the EmployeeSearchService class will not actually communicate with a real data source, such as a database, to query employee data. Instead, EmployeeSearchService has some sample Employee data hard-coded at the top of the class, as shown here:

/* Hard-coded sample data. Normally this would come from a real data
   source such as a database. */
private static Employee[] employees = 
{
  new Employee("Bob Davidson", "123-45-6789"),
  new Employee("Mary Williams", "987-65-4321"),
  new Employee("Jim Smith", "111-11-1111"),
  new Employee("Beverly Harris", "222-22-2222"),
  new Employee("Thomas Frank", "333-33-3333"),
  new Employee("Jim Davidson", "444-44-4444")
};

The sample data is comprised of a few Employee objects. As you'll see in the next section, the Employee class is a simple class for encapsulating employee data.

The searchByName( ) and searchBySsNum( ) methods use the hard-coded data when performing a search. The searchByName( ) method loops through each of the Employee objects in the employees array looking for any employees that match the name specified. If a match is found, it is added to the return ArrayList that will eventually be used by search.jsp to display the results. Note that the name search is case insensitive by virtue of uppercasing the Strings before comparison. You should also note that the use of String's indexOf( ) method allows for partial matches instead of only exact matches.

Similar to the searchByName( ) method, searchBySsNum( ) loops through the hard-coded employee list looking for any employees that match the specified social security number. Note that searchBySsNum( ) will capture only exact matches. Because social security numbers are unique to an individual, only one match should ever be returned for a social security number-based search.

Employee.java

The Employee class, shown next, is a simple bean for encapsulating the data for an employee. The class is straightforward, comprised simply of setters and getters for the Employee class data.

package com.jamesholmes.struts;

public class Employee
{
  private String name;
  private String ssNum;

  public Employee(String name, String ssNum) {
    this.name = name;
    this.ssNum = ssNum;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setSsNum(String ssNum) {
    this.ssNum = ssNum;
  }

  public String getSsNum() {
    return ssNum;
  }
}

This class is used by EmployeeSearchService for transferring employee search results data from the Model (EmployeeSearchService) to the View (search.jsp). Oftentimes, this 'transfer' object is referred to as a Data Transfer Object (DTO) or Value Object (VO) and has the simple responsibility of being a data container and abstracting the Model from the View.

web.xml

The web.xml file, shown next, is a standard Web Archive deployment descriptor used to configure the Mini HR application. Because the file contains several configuration details, it will be reviewed section by section.

<?xml version="1.0"?>

<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <!-- Action Servlet Configuration -->
  <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>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- Action Servlet Mapping -->
  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

  <!-- The Welcome File List -->
  <welcome-file-list>
    <welcome-file>/index.jsp</welcome-file>
  </welcome-file-list>

  <!-- Struts Tag Library Descriptors -->
  <taglib>
    <taglib-uri>/WEB-INF/tlds/struts-bean.tld</taglib-uri>
    <taglib-location>/WEB-INF/tlds/struts-bean.tld</taglib-location>
  </taglib>
  <taglib>
    <taglib-uri>/WEB-INF/tlds/struts-html.tld</taglib-uri>
    <taglib-location>/WEB-INF/tlds/struts-html.tld</taglib-location>
  </taglib>
  <taglib>
    <taglib-uri>/WEB-INF/tlds/struts-logic.tld</taglib-uri>
    <taglib-location>/WEB-INF/tlds/struts-logic.tld</taglib-location>
  </taglib>

</web-app>

The following is the first section of the web.xml file. It declares the Struts Controller servlet, ActionServlet, and configures it.

<!-- Action Servlet Configuration -->
<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>
  <load-on-startup>1</load-on-startup>
</servlet>

This declaration starts by assigning a name to the servlet that will be used in the next section for mapping the servlet to specific application requests. After defining the servlet's name and class, the config initialization parameter is defined. This parameter tells Strut's ActionServlet where to find its central configuration file: struts-config.xml. Finally, the <load-on-startup> tag is used to instruct the servlet engine how many instances of the servlet should be instantiated when the server starts.

The second section of the web.xml file causes ActionServlet to respond to certain URLs:

<!-- Action Servlet Mapping -->
<servlet-mapping>
  <servlet-name>action</servlet-name>
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

Notice that the <servlet-name> tag references the same name declared in the previous section. This associates the previous servlet declaration with this mapping. Next, the <url-pattern> tag is used to declare the URLs that ActionServlet will respond to. In this case, it is saying that ActionServlet should process any requests for pages that end in .do. So, for example, a request to

http://localhost:8080/MiniHR/page.do

or a request to

http://localhost:8080/MiniHR/dir1/dir2/page2.do

will be routed to Struts' ActionServlet for processing.

The next section of the web.xml file declares the Welcome File list that the Mini HR application will use:

<!-- The Welcome File List -->
<welcome-file-list>
  <welcome-file>/index.jsp</welcome-file>
</welcome-file-list>

The Welcome File list is a list of files that the Web server will attempt to respond with when a given request to the Web application goes unfulfilled. For example, in Mini HR's case, you can enter a URL of http://localhost:8080/MiniHR/ and index.jsp will be executed, because no page has been specified in the URL. The servlet engine detects this and references the Welcome File list for pages that should be tried to respond to the request. In this case, the servlet engine will try to respond with a page at /index.jsp. If that page is unavailable, an error will be returned. Note that the Welcome File list can span several pages. In that case, the servlet engine will iterate through the list until a file is found that can be served for the request.

The final section of the web.xml file declares the JSP tag libraries that should be available to JSPs in the Mini HR application:

<!-- Struts Tag Library Descriptors -->
<taglib>
  <taglib-uri>/WEB-INF/tlds/struts-bean.tld</taglib-uri>
  <taglib-location>/WEB-INF/tlds/struts-bean.tld</taglib-location>
</taglib>
<taglib>
  <taglib-uri>/WEB-INF/tlds/struts-html.tld</taglib-uri>
  <taglib-location>/WEB-INF/tlds/struts-html.tld</taglib-location>
</taglib>
<taglib>
  <taglib-uri>/WEB-INF/tlds/struts-logic.tld</taglib-uri>
  <taglib-location>/WEB-INF/tlds/struts-logic.tld</taglib-location>
</taglib>

A tag library definition associates a URI (or simple identifier) with the actual location of a *.tld file beneath a Web application, so essentially they are aliases. Using these aliases allows JSPs to reference an alias for a Tag Library Descriptor instead of the actual descriptor location. That way, the actual location of the tag library definitions can change without each JSP having to be changed as long as the aliases stay consistent.

Notice in the web.xml file that the URI (alias) and the actual location of the TLD files are the same. This is done for simplicity's sake and is a common practice. Note, despite the fact that the URL and location are the same, the definitions in web.xml are necessary for JSPs to access the tag libraries.

struts-config.xml

The struts-config.xml file, shown next, is the central location for all of a Struts application's configuration settings. Remember from the previous description of the web.xml file that the struts-config.xml file is used by ActionServlet to configure the application. The basic configuration information is covered here, but a complete description will have to wait until you know more about Struts. (A complete discussion of configuration is found in Chapter 16.)

<?xml version="1.0"?>

<!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>

  <!-- Form Beans Configuration -->
  <form-beans>
    <form-bean name="searchForm"
               type="com.jamesholmes.minihr.SearchForm"/>
  </form-beans>

  <!-- Global Forwards Configuration -->
  <global-forwards>
    <forward name="search" path="/search.jsp"/>
  </global-forwards>

  <!-- Action Mappings Configuration -->
  <action-mappings>
    <action path="/search"
            type="com.jamesholmes.minihr.SearchAction"
            name="searchForm"
            scope="request"
            validate="true"
            input="/search.jsp">
    </action>
  </action-mappings>

  <!-- Message Resources Configuration -->
  <message-resources
    parameter="com.jamesholmes.minihr.ApplicationResources"/>

</struts-config>

Struts configuration files are XML-based and should conform to the Struts Configuration Document Type Definition (DTD). The struts-config.xml file just shown begins by declaring its use of the Struts Configuration DTD:

<!DOCTYPE struts-config PUBLIC
  "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
  "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

Next, is the Form Beans Configuration section, which is used to specify all of the ActionForm objects used in your Struts application. In this case, you're only using one Form Bean: SearchForm. The definition of the Form Bean, shown here, allows you to associate a name or alias of 'searchForm' with the SearchForm object:

<form-bean name="searchForm"
           type="com.jamesholmes.minihr.SearchForm"/>

That way, the application code (i.e., JSPs, Action objects, and so on) will reference 'searchForm' and not 'com.jamesholmes.minihr.SearchForm'. This allows the class definition to change without causing the code that uses the definition to change.

The next section of the file, Global Forwards Configuration, lists the forward definitions that your application will have. Forward definitions are a mechanism for assigning a name to the location of a page. For example, for the Mini HR application, you assign the name 'search' to the 'search.jsp' page:

 <forward name="search" path="/search.jsp"/>

Similar to Form Beans, the use of forward definitions allows application code to reference an alias and not the location of pages. Note that this section of the file is dedicated to 'Global' forwards, which are made available to the entire Struts application. You can also specify action-specific forwards that are nested in an <action> tag in the config file:

<action ...>
  <forward .../>
</action>

The issue of action-specific forward definitions is examined later in this book.

After the Global Forwards Configuration section comes the Action Mappings Configuration section of the file. This section is used to define the Action classes used in your Struts application. Remember from the previous section on SearchAction.java that Action objects are used to handle discreet Controller tasks. Because the SearchAction mapping, shown here, has many settings, each is examined in detail.

<action path="/search"
        type="com.jamesholmes.minihr.SearchAction"
        name="searchForm"
        scope="request"
        validate="true"
        input="/search.jsp">
</action>

The first part of the Action Mappings Configuration section defines the path associated with this action. This path corresponds to the URL used to access your Struts application. Remember from the 'web.xml' section that your application is configured to have any URLs ending in .do be handled by ActionServlet. Setting the path to '/search' for this action essentially says that a request to '/search.do' should be handled by SearchAction. Struts removes the .do from the URL (resulting in '/search') and then looks in the struts-config.xml settings for an Action Mapping that corresponds to the URL.

The next <action> attribute, type, specifies the Action class that should be executed when the path is requested. The name attribute corresponds to the name of a Form Bean defined in the struts-config.xml file. In this case, 'searchForm' corresponds to the Form Bean you set up earlier. Using the name attribute tells Struts to populate the specified Form Bean with data from the incoming request. The Action object will then have access to the Form Bean to access the request data.

The next two attributes, scope and validate, are related to the Form Bean defined with the name attribute. The scope attribute sets the scope for the Form Bean associated with this action. For example, use 'request' for request scope or 'session' for session scope. The validate attribute is used to specify whether or not the Form Bean defined with the name attribute should have its validate( ) method called after it has been populated with request data.

The final <action> attribute, input, is used to inform the Action object what page is being used to 'input' data to (or execute) the action; in this case, it is 'search.jsp'.

The last section of the file, Message Resources Configuration, is used to define the location of the ApplicationResources.properties file. Notice that the file is specified using Java's package mechanism: package.package.class (i.e., 'com.jamesholmes.minihr .ApplicationResources'). This allows ActionServlet to load the properties file from the same place that classes are loaded.

ApplicationResources.properties

The ApplicationResources.properties file, shown next, is based on Java's Resource Bundle functionality for externalizing and internationalizing application strings, messages, and labels.

# Label Resources
label.search.name=Name
label.search.ssNum=Social Security Number

# Error Resources
error.search.criteria.missing=<li>Search Criteria Missing</li>
error.search.ssNum.invalid=<li>Invalid Social Security Number</li>
errors.header=<font color="red"><b>Validation Error(s)</b></font><ul>
errors.footer=</ul><hr width="100%" size="1" noshade="true">

You'll notice that this file is simply comprised of name-value pairs, where the name is a key and the value is a message corresponding to the key. Each of the name-value pairs is then used by your Struts application whenever a string, message, or label needs to be displayed. Externalizing these strings in a separate file instead of embedding them in your application allows the strings to be changed without having to recompile the application (separation of concerns). Externalizing the strings also allows your application to support internationalization so that it can be tailored to different locales. As you'll see in Chapter 10, I18N with Struts is straightforward and easy with the use of properties files for strings, messages, and labels.



Previous Section
Table of Contents
Next Section