- Hello World! Application Requirements
- Applying the Model-View-Controller Pattern
- The View Component: The HTML Form and the Form Bean
- MessageResources and Application.properties Files
- The Struts Form Bean: HelloForm.java
- Data Validation and ActionErrors
- The Controller Component: HelloAction.java
- The Model Component (HelloModel.java)
- Passing Data to the View Using Attributes: Constants.java
- Tying It All Together: The struts-config.xml File
- Conclusions
The Controller Component: HelloAction.java
It's finally time to go over the Action classthe center of all the action! The Action class for the Hello World! application follows in Listing 3.4.
Listing 3.4 The Struts Action Class for Hello World! (HelloAction.java)
package ch03.hello; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.util.MessageResources; import org.apache.commons.beanutils.PropertyUtils; /** * The <strong>Action</strong> class for our "Hello" application.<p> * This is the "Controller" class in the Struts MVC architecture. * * @author Kevin Bedell */ public final class HelloAction extends Action { /** * Process the specified HTTP request, and create the corresponding HTTP * response (or forward to another web component that will create it). * Return an <code>ActionForward</code> instance describing where and how * control should be forwarded, or <code>null</code> if the response has * already been completed. * * @param mapping The ActionMapping used to select this instance * @param actionForm The optional ActionForm bean for this request (if any) * @param request The HTTP request we are processing * @param response The HTTP response we are creating * * @exception Exception if business logic throws an exception */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // These "messages" come from the ApplicationResources.properties file MessageResources messages = getResources(request); /* * Validate the request parameters specified by the user * Note: Basic field validation done in HelloForm.java * Business logic validation done in HelloAction.java */ ActionErrors errors = new ActionErrors(); String person = (String) PropertyUtils.getSimpleProperty(form, "person"); String badPerson = "Atilla the Hun"; if (person.equals(badPerson)) { errors.add("person", new ActionError("ch03.hello.dont.talk.to.atilla", badPerson )); saveErrors(request, errors); return (new ActionForward(mapping.getInput())); } /* * Having received and validated the data submitted * from the View, we now update the model */ HelloModel hm = new HelloModel(); hm.setPerson(person); hm.saveToPersistentStore(); /* * If there was a choice of View components that depended on the model * (or some other) status, we'd make the decision here as to which * to display. In this case, there is only one View component. * * We pass data to the View components by setting them as attributes * in the page, request, session or servlet context. In this case, the * most appropriate scoping is the "request" context since the data * will not be nedded after the View is generated. * * Constants.HELLO_KEY provides a key accessible by both the * Controller component (i.e. this class) and the View component * (i.e. the jsp file we forward to). */ request.setAttribute( Constants.HELLO_KEY, hm); // Remove the Form Bean - don't need to carry values forward request.removeAttribute(mapping.getAttribute()); // Forward control to the specified success URI return (mapping.findForward("SayHello")); } }
This is the biggest file so far in the application, so let's take it a step at a time and not go too deep for now.
How the Action Class Works
To begin with, the Action class gets its name from the fact that it is a class that extends the base class:
org.apache.struts.action.Action
Thus, the name Action class.
The primary method that must be written in an Action class is the execute() method. The framework calls this method after the form bean is populated and validated correctly. This is great because the Action class can assume that the form bean has passed it data that's approved by at least a basic level of validation.
Here's the signature of the execute() method in any Action class:
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
This is the same every Action class. As you can see, the four parameters passed into an Action class are
ActionMapping mappingThe ActionMapping provides access to the information stored in the configuration file (struts-config.xml) entry that configures this Action class.
ActionForm formThis is the form bean. By this time, the form bean has been prepopulated and the validate() method has been called and returned with no errors (assuming that validation is turned on). All the data entered by the user is available through the form bean.
HttpServletRequest requestThis is the standard JSP or Servlet request object.
HttpServletResponse responseThis is the standard JSP or Servlet response object.
It's also important to point out that the execute() method in an Action class must return an ActionForward object. ActionForward objects will be discussed in more detail in Chapter 8, "The Controller: Directing the Action," but for now understand that they represent the View chosen to display the results of the Action.
The Action class uses an execute() method to accomplish its work. It is passed a form bean containing data from the View and an ActionMapping containing configuration information, along with the standard JSP request and response objects.
Accessing the Locale-Specific Text in MessageResources
Now let's review the first section of code in the execute() method:
// These "messages" come from the ApplicationResources.properties file MessageResources messages = getResources(request);
This section of code loads a copy of the MessageResources that were defined in the Application.properties file that you saw earlier. Now the Action class has full access to all the locale-specific text needed for the application.
Business LogicLevel Validation
The next section of code performs the business logic validation that was discussed earlier:
/* * Validate the request parameters specified by the user * Note: Basic field validation done in HelloForm.java * Business logic validation done in HelloAction.java */ ActionErrors errors = new ActionErrors(); String person = (String) PropertyUtils.getSimpleProperty(form, "person"); String badPerson = "Atilla the Hun"; if (person.equals(badPerson)) { errors.add("person", new ActionError("ch03.hello.dont.talk.to.atilla", badPerson )); saveErrors(request, errors); return (new ActionForward(mapping.getInput())); }
At times there is a need to perform data validation based on more complex logic than is appropriate to put in a form bean. This is a relatively simple example.
In other situations, validation might be based on information retrieved from a Model component. For example, having to type your mother's maiden name on a "Forgot My Password" form requires that the maiden name be retrieved from a user account Model component.
More on accessing Model components is covered in the following section.
Interacting with Model Components
In the next section of code, the Controller component directs interaction with the Model component:
/* * Having received and validated the data submitted * from the View, we now update the model */ HelloModel hm = new HelloModel(); hm.setPerson(person); hm.saveToPersistentStore();
Here the Controller creates a new Model component, sets a value in it, and calls a method to save the data to a persistent store. This is common way that Controller components will interact with a Model.
This is a very simple example. In other situations, a controller component might
Read data back from the model for display by the View
Interact with more than one Model component
Choose the View component (ActionForward) to display based on information retrieved from a Model
The HelloModel.java component itself will be presented later in this chapter. Model components are discussed in more detail in Chapter 9, "Model Components: Modeling the Business."
Passing Data to the View Component
The Action class passes information to the View component using standard JSP/Servlet setAttribute() and getAttribute() method calls.
The following is the code fragment from HelloAction.java that passes data to the View:
/* * If there was a choice of View components that depended on the model * (or some other) status, we'd make the decision here as to which * to display. In this case, there is only one View component. * * We pass data to the View components by setting them as attributes * in the page, request, session or servlet context. In this case, the * most appropriate scoping is the "request" context since the data * will not be needed after the View is generated. * * Constants.HELLO_KEY provides a key accessible by both the * Controller component (i.e. this class) and the View component * (i.e. the jsp file we forward to). */ request.setAttribute( Constants.HELLO_KEY, hm); // Remove the Form Bean - don't need to carry values forward request.removeAttribute(mapping.getAttribute());
This code actually accomplishes two things:
Sets the HelloModel instance as an attribute on the request to be passed to the View component.
Removes the form bean from the request object. In this case the form bean is not needed, so it is discarded.
In some situations, the form bean attribute should not be removed. For example, if completing a process in your application requires several data entry pages, you might want to have only a single form bean that, by the end, will hold all the data entered in each of the steps.
Forwarding to the Appropriate View Component
The final step in this Controller component is to forward control to the view chosen to display the results of the action:
// Forward control to the specified success URI return (mapping.findForward("SayHello"));
The ActionForward SayHello is defined in the struts-config.xml file.