Section 19.5. The Action Class


19.5. The Action Class

The 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:

  1. The browser submits form data via an HTTP POST.

  2. The controller (RequestProcessor) interprets the path requested by the HTTP POST (/adminBooks.do) and uses the path to find the appropriate Action (AdminBooksAction). Then the controller populates the form bean (AdminBooksActionForm).

  3. The controller calls the action's execute method, passing a reference to the form bean.

  4. AdminBooksAction obtains a reference to a component of the model with getLibrary( ).getBooks( ). (getLibrary( ) is a superclass method in LibraryBaseAction.) In a more complicated application, the model might be accessed via a database connection or a message queue. The model might be anything else that should be manipulated based on user activity. We might also obtain a reference to a remote business object (say a stateless session EJB) whose state will be changed. In this example, the Map of books is simply a reference to data within the Library singleton.

  5. AdminBooksAction checks the properties from the form bean. Based on information submitted via the form bean, changes are made in the model.

  6. If need be, the Action adds, removes, or changes request and session scope attributes. (In the specific case of AdminBooksAction, no such changes are made.)

  7. AdminBooksAction uses the ActionMapping passed in via the execute method to look up an ActionForward to which Struts should pass control. In this case, we simply return an ActionForward named adminbooks (defined in the global-forwards section of struts-config.xml) that is aimed back at the path for a Struts Action that sets up the checkboxes for the /view/adminbooks.jsp page.

  8. The controller forwards control to the appropriate view, specified by the ActionForward.

  9. The view renders the page for the browser.

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 request


Of 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 Actions

The 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 File

Much 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.

Table 19-1. Typical configuration elements

Element name

Purpose

<form-beans>

Defines the form beans (ActionForms).

<global-forwards>

Defines names for "forwards," paths to which you can pass control. By specifying a name here, you can decouple the name used for a specific resource. Thus, instead of referring to login.jsp, you can refer to the forward named loginshould it happen that you need to introduce a new login JSP (perhaps login2.jsp), you can change the forward path in the configuration file without having to change any of your references elsewhere.

<action-mappings>

Defines the mappings (the relationship between a path, an Action, and an ActionForm).

<controller>

The controller to use. If not specified, the default, org.apache.struts.action.RequestProcessor, will be used.

<message-resources>

The name of a properties file that defines name/value pairs for messaging.

<plug-in>

A plug-in is a class that implements the interface org.apache.struts.action.PlugIn.


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).

Table 19-2. Attributes for struts-config.xml <action> element

Attribute name

Purpose

path

The relative path of the submitted request, starting with a / character, and without the filename extension if extension mapping is used.

name

Name of the form bean, if any, that is associated with this action mapping. That is, the name must correspond to the name of a form-bean.

type

Fully qualified Java class name of the Action subclass that will process requests for this action mapping.

scope

The scope (request or session) that is used to access our ActionForm bean, if any. Optional if name is specified, otherwise not valid. The default scope is session.

validate

Set to true if the validate method of the ActionForm bean should be called prior to calling the Action object for this action mapping or set to false if you do not want the validate method called. The default value is true.

input

The relative path of the action or other resource to which control should be returned if a validation error is encountered. Valid only when name is specified. Required if name is specified and the input bean returns validation errors. Optional if name is specified and the input bean does not return validation errors.


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.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net