The sample application in this chapter deviates from the stereotypical "Hello World" program found in many 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 called Mini HR. 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.
All Struts applications consist of several files, which contain the various parts of a Struts program. Some are Java source files, but others contain JSP and XML. A resource bundle 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 servlet container properties for the Mini HR Java Web application. |
struts-config.xml | Contains the XML that is used to configure the Struts framework for this application. |
MessageResources.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 servlet container, the application files have to be arranged in the standard 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 direct access by 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 section "web.xml".
Any libraries (.jar files) needed by the application should be under a directory called lib located beneath the WEB-INF directory.
Any class files or resources 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.
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\MessageResources.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
The index.jsp file, shown here, is a very simple JSP that is used to render Mini HR's opening screen:
<%@ taglib uri="http://struts.apache.org/tags-html" 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"> • Add an Employee<br> • <html:link forward="search">Search for Employees</html:link><br> </body> </html>
You'll notice that index.jsp consists 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 the 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="http://struts.apache.org/tags-html" prefix="html" %>
This line associates the tag library descriptor with a URI of http://struts.apache.org/tagshtml with a prefix 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 (Struts terminology), from the Struts configuration file (e.g., struts-config.xml), which is covered later in this chapter, in the section "struts-config.xml". 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 "strutsconfig.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"> • Add an Employee<br> • <a href="/MiniHR/search.jsp">Search for Employees</a><br> </body> </html>
Here is how index.jsp looks in the browser.
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:
Each time a search is performed, the 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:
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:
The contents of search.jsp are
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-logic" 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 name="searchForm" property="results"/> <logic:equal name="size" value="0"> <center><font color="red"><cTypeface:Bold>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 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="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>
In addition to the HTML Tag Library used by index.jsp, search.jsp uses the 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 consist 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 section "SearchForm.java" 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 MessageResources.properties resource bundle file to be inserted into the JSP at run time. The message tag simply looks up the key passed to it in MessageResources.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. |
Now, it's time to examine the form. The Struts HTML Tag Library has a tag for each of the standard HTML form tags, such as
<form>
<input type="text">
<input type="checkbox">
<input type="radio">
<select>
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 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 configuration 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 name="searchForm" property="results"/> <logic:equal name="size" value="0"> <center><font color="red"><cTypeface:Bold>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 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 the Struts Logic Tag Library for implementing 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 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.
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, the 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.minihr; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; 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 ActionMessage("error.search.criteria.missing")); } /* Validate format of social security number if it has been entered. */ if (ssNumEntered && !isValidSsNum(ssNum.trim())) { errors.add("ssNum", new ActionMessage("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. When using session-based Form Beans, Struts reuses ActionForm instances instead of creating new ones for each request. The reset( ) 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 a 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 MessageResources.properties resource bundle file, discussed in the section of the same name later in this chapter.
The SearchAction class, shown next, is a Controller class that processes requests from the Search page:
package com.jamesholmes.minihr; 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 the criteria 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.minihr.SearchAction" name="searchForm" scope="request" validate="true" input="/search.jsp"> </action> </action-mappings>
Struts Action subclasses manage the processing of specific requests. You can think of them as mini-servlets assigned to manage discrete 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 the Action class' execute( ) method. The execute( ) method is the single point of entry for an Action class by the 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 preceding 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 were entered by the user. Notice that the ActionForm object passed in is cast to its native type: SearchForm. Casting the object allows the SearchForm methods to be accessed for retrieving the search criteria. Based on the criteria entered, one of the EmployeeSearchService 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 the SearchAction input page: search.jsp. The input page for an action is declared in the Struts configuration 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.minihr.SearchAction" name="searchForm" scope="request" validate="true" input="/search.jsp">
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.minihr; 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 consists 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.
The Employee class, shown next, is a basic class for encapsulating the data for an employee. The class is straightforward, consisting simply of setters and getters for the Employee class data.
package com.jamesholmes.minihr; 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.
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> </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 informs the Struts ActionServlet where to find its central configuration file: struts-config.xml. Finally, the <load-on-startup> tag is used to specify the order in which servlets are loaded for a given Web application when the servlet container starts. The value specified with the <load-on-startup> tag is essentially a priority, and servlets with a higher priority (lower value) are loaded first.
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 preceding 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 will 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 the Struts ActionServlet for processing.
The final 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 container detects this and references the Welcome File list for pages that should be tried to respond to the request.
In this case, the servlet container 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 encompass several pages. In that case, the servlet container will iterate through the list until a file is found that can be served for the request.
The struts-config.xml file, shown next, is the central location for all of a Struts application's configuration settings. Recall 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 18.)
<?xml version="1.0"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.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.MessageResources"/> </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.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.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, only one Form Bean is being used: SearchForm. The definition of the Form Bean, shown here, allows you to associate a logical name or alias of "searchForm" with the SearchForm object:
<form-bean name="searchForm" type="com.jamesholmes.minihr.SearchForm"/>
That way, your 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 logical name to the location of a page. For example, for the Mini HR application, the name "search" is assigned to the "search.jsp" page:
<forward name="search" path="/search.jsp"/>
As in the case of 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 topic of action-specific forward definitions is examined in Chapter 5.
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 classes are used to handle discrete 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. Recall 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 configuration file 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 specified with the path attribute 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 set up earlier. Using the name attribute informs 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 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". Struts will forward to the page specified by the input attribute if validation fails.
The last section of the file, Message Resources Configuration, is used to define the location of the application resource bundle file (e.g., MessageResources.properties). Notice that the file is specified using Java's package mechanism: package.package.class (i.e., "com. jamesholmes.minihr.MessageResources"). This allows ActionServlet to load the properties file from the same place that classes are loaded. An extension of .properties is automatically appended to the resource bundle file name by Struts and thus should not be specified in the Struts configuration file.
The MessageResources.properties file, shown next, is based on the Java 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=Search Criteria Missing error.search.ssNum.invalid=Invalid Social Security Number errors.header=<font color="red"><cTypeface:Bold>Validation Error(s)</b></font><ul> errors.footer=</ul><hr width="100%" size="1" noshade="true"> errors.prefix=<li> errors.suffix=</li>
Notice that this file is simply composed 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 the Struts application whenever a string, message, or label needs to be displayed. Externalizing these strings in a separate file instead of embedding them in the application allows the strings to be changed without having to recompile the application (separation of concerns). Externalizing the strings also allows the application to support internationalization so that it can be tailored to different locales. As you'll see in Chapter 10, internationalization with Struts is straightforward and easy with the use of resource bundle properties files for strings, messages, and labels.