19.5. The Action ClassThe Action class is an adapter between servlet requests and your business logic. In other words, it is appropriate for your Action class to handle issues such as gathering data from the user interface, controlling flow through the application, and shaping the data for presentation. But it is inappropriate for the Action class itself to handle business functions. For example, if your Action class is updating a database itself, you have probably inappropriately put business functions into the web tier. You will need to extend org.apache.struts.action.Action for each Action you want to define. The most important method to override is execute( ). This example is from AdminBooksAction: public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { AdminBooksActionForm adminBooksForm = (AdminBooksActionForm) form; Map books = getLibrary( ).getBooks( ); // First set all books to checked in List bookList = getLibrary( ).getBookList( ); Iterator i = bookList.iterator( ); while (i.hasNext( )) { Book book = (Book) i.next( ); if (book != null) { book.setCheckedOut(false); } } // Now based on the checks in the form, set checked books // to "checked out" String[] picks = adminBooksForm.getPicks( ); if (picks != null) { for (int j = 0; j < picks.length; j++) { ((Book) books.get(picks[j])).setCheckedOut(true); } } return mapping.findForward("adminbooks"); } This Action handles the form submission from /view/adminbooks.jsp. The pattern here is classic and is simply a version of the request processor's earlier tasks list but with the focus on the Action:
We have now covered enough information to look at this in schematic form in Figure 19-2follow the numbers from the browser at the top. Figure 19-2. Struts components satisfying a requestOf course, your Action is playing its part in the larger context of Struts, so for more of the story, we must zoom out to the RequestProcessor . 19.5.1. RequestProcessor and ActionsThe Struts RequestProcessor will instantiate only one instance of your Action class; multiple threads will use the class at the same time. This means the class must be threadsafe: your instance and static variables must not store data for any specific request. Basically, your Actions are essentially delegates of the Struts RequestProcessor. Before control is handed over to your action, the RequestProcessor calls the processPreprocess( )method. If you extend RequestProcessor and implement your own processPreprocess( ), you will have a convenient means to perform some common tasks that run before all calls to the execute methods of Actions. In the example application, a UserSession object is used to aggregate all information associated with a user; a custom RequestProcessor ensures that the UserSession object is always available: import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.struts.action.RequestProcessor; public class LibraryRequestProcessor extends RequestProcessor { protected boolean processPreprocess(HttpServletRequest request, HttpServletResponse response) { UserSession userSession = null; HttpSession session = request.getSession( ); if (session != null) { userSession = (UserSession) session .getAttribute(WebGlobals.USER_SESSION_KEY); if (userSession == null) { userSession = new UserSession( ); session.setAttribute(WebGlobals.USER_SESSION_KEY, userSession); } } return super.processPreprocess(request, response); } } Your custom RequestProcessor must be specified in the Struts configuration file via the <controller> element: <controller processorClass= "com.oreilly.jent.struts.library.ui.LibraryRequestProcessor"/> Developers often provide convenience methods in a "base" Action class. In the example, LibraryBaseAction provides a protected method for retrieving a reference to a UserSession (which is available because of the work done in the LibraryRequestProcessor.processPreprocess( ) method). Here it is: import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.struts.action.Action; public abstract class LibraryBaseAction extends Action { protected Library getLibrary( ) { return (Library) servlet.getServletContext( ).getAttribute( WebGlobals.LIBRARY_KEY); } /** * Return the UserSession from the session. * * @param request * @return the UserSession * @throws RuntimeException */ protected UserSession getUserSession(HttpServletRequest request) { HttpSession session = (HttpSession) request.getSession( ); if (session == null) { throw new RuntimeException("Couldn't get a reference to the Session"); } UserSession userSession = (UserSession) session.getAttribute(WebGlobals.USER_SESSION_KEY); if (userSession == null) { throw new RuntimeException ("Couldn't retrieve UserSession attribute from Session"); } return userSession; } } The combination of overriding RequestProcessor.processPreprocess( ) to ensure that resources are available for an Action's execute method, coupled with convenience methods in a "base" Action that access such resources, can be a very helpful way to organize your code and avoid bloated Action classes and copy-and-paste-style code copying. 19.5.2. The Struts Configuration FileMuch of the power of Struts derives from its configuration file . Little is hardwired in the Struts management of your application's logic since the names for specific components, as well as the relationships between cooperating components, are specified in the struts-config.xml file. Because of this deployment-time level of abstraction, it is frequently possible to tweak a Struts setting, or substitute an implementation of some functionality, without recompiling any of your code. The struts-config.xml file provides control over the display name and description of your application, data sources that will be managed by Struts, form beans, action mappings, the specific implementation of the controller class, message resources, and Struts plug-ins, as well as exceptions and forwards that are available to actions globally. Typically you will find that you will define most of these elements. As mentioned earlier, the location of the Struts configuration file is defined in the web.xml file as a parameter ("config") on the definition of the ActionServlet. The conventional value is WEB-INF/struts-config.xml. The struts-config.xml file for the sample application starts and ends like this: <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"> <struts-config> <!-- Omitted --> </struts-config> Below the root element (<struts-config>), we define a number of elements that are found in almost all Struts applications. The DTD for the struts-config.xml file specifies that they must appear in this order; none are required (for a complete rundown of the elements, you will need to study the online Struts documentation and the DTD for the configuration file (http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd). Table 19-1 lists elements that are frequently used in Struts applications.
The struts-config.xml file for the sample application (Example 19-1) uses all of these elements (for the sake of this illustration, we have removed some internal comments regarding aspects of the application you might want to change). Example 19-1. struts-config.xml<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"> <struts-config> <form-beans> <form-bean name="LoginForm" type="com.oreilly.jent.struts.library.ui.LoginActionForm"/> <form-bean name="BookDisplayForm" type="com.oreilly.jent.struts.library.ui.BookDisplayActionForm"/> <form-bean name="AddBookForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="title" type="java.lang.String"/> <form-property name="author" type="java.lang.String"/> <form-property name="isbn" type="java.lang.String"/> <form-property name="addBookAction" type="java.lang.String"/> </form-bean> <form-bean name="AdminBooksForm" type="com.oreilly.jent.struts.library.ui.AdminBooksActionForm"/> </form-beans> <global-forwards> <forward name="home" path="/home.do"/> <forward name="login" path="/login/index.jsp"/> <forward name="logout" path="/logout.do"/> <forward name="viewbooks" path="/view/listbooks.jsp"/> <forward name="addbook" path="/view/addbook.jsp"/> <forward name="adminbooks" path="/adminBooksSetup.do"/> </global-forwards> <action-mappings> <action path="/home" parameter="/home.jsp" type="org.apache.struts.actions.ForwardAction" /> <action path="/login" name="LoginForm" scope="request" validate="true" input="/login/index.jsp" type="com.oreilly.jent.struts.library.ui.LoginAction"> <forward name="success" path="/login/success.jsp" redirect="false"/> <forward name="failure" path="/login/index.jsp" redirect="false"/> </action> <action path="/logout" type="com.oreilly.jent.struts.library.ui.LogoutAction"/> <action path="/bookDisplay" name="BookDisplayForm" scope="session" type="com.oreilly.jent.struts.library.ui.BookDisplayAction"/> <action path="/addBook" name="AddBookForm" scope="request" validate="true" input="/view/addbook.jsp" type="com.oreilly.jent.struts.library.ui.AddBookAction"/> <action path="/adminBooksSetup" name="AdminBooksForm" scope="request" type="com.oreilly.jent.struts.library.ui.AdminBooksSetupAction"> <forward name="adminBooksView" path="/view/adminbooks.jsp" redirect="false"/> </action> <action path="/adminBooks" name="AdminBooksForm" scope="request" type="com.oreilly.jent.struts.library.ui.AdminBooksAction"/> </action-mappings> <controller processorClass= "com.oreilly.jent.struts.library.ui.LibraryRequestProcessor"/> <message-resources parameter="application"/> <plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in> <plug-in className="com.oreilly.jent.struts.library.ui.LibraryPlugIn"/> </struts-config> First we define our form beans. The form bean LoginForm is bound to a class we wrote, LoginActionForm. Another alternative is to define a form bean dynamically, using DynaActionForm or a subclass. For dynamic form beans, the <form-property> element provides a means to define a property, giving it a name and type. An additional attribute not shown here is initial, allowing you to provide a value that will be injected into the form bean when it is created. Global forwards are extremely useful for providing common names for places to which control should be directed. Notice that the path can be an ordinary JSP or a path into the Struts ActionServlet; to forward to an action, you must make sure that you are observing the servlet's URL pattern specified in web.xml (in the example, we append .do to the name of the action's path). The <action-mappings> element comprises a list of <action> elements, which define ActionMappings. Yes, that's right; even though what is being defined is an ActionMapping, the name is <action>. The <action> element may be the single most significant element in the entire Struts configuration file, so let's pause to describe its attributes in Table 19-2 (note that the definitions in the Purpose column are quotations or paraphrases from the documentation in the DTD).
Look at this action mapping: <action path="/adminBooks" name="AdminBooksForm" scope="request" type="com.oreilly.jent.struts.library.ui.AdminBooksAction"/> It says, when a request is made to the relative path within the application context, /adminBooks.do, create in request scope an instance of the ActionForm type bound to the form bean named AdminBooksForm if it has not already been created. The Action to which execution should be delegated is AdminBooksAction. The form will be validated (since the default value of the validate attribute is true and we haven't specified it). By the way, a hidden assumption here is that the validation will never fail (no input attribute is specified). This seems a safe bet since only checkbox information is being submitted. To be sure, an evildoer might write a program to post a naughty value into the form; to barricade yourself against this possibility, specify an input attribute. The <controller> element defines the ControllerConfig bean, whose details are beyond the scope of this discussion. When left out, the standard Struts request processor (org.apache.struts.action.RequestProcessor) will be used. For reasons described earlier, our application uses a custom request processor, which is specified with the processorClass attribute. The <message-resources> element defines the message resource bundle for localization. The default class uses the parameter attribute to provide the base name of the resource bundle properties file. So in this case, the file will be application.properties. Note that message templates for error messages are found here. The <plug-in> element defines any Struts plug-ins; the <className> element provides the fully qualified Java class name of the plug-in. The subordinate <set-property> element gives you the means to pass initialization data to the plug-in. The idiom you see here for setting up the Struts validator is typical. |