Struts request lifecycle


In this section you will learn about the Struts controller classes ‚ ActionServlet , RequestProcessor , ActionForm , Action , ActionMapping and ActionForward ‚ all residing in org.apache.struts.action package and struts-config.xml ‚ the Struts Configuration file. Instead of the traditional ‚“Here is the class ‚ go use it ‚½ approach, you will study the function of each component in the context of HTTP request handling lifecycle in Struts.

ActionServlet

The central component of the Struts Controller is the ActionServlet . It is a concrete class and extends the javax.servlet.HttpServlet . It performs two important things.

  1. On startup, its reads the Struts Configuration file and loads it into memory in the init() method.

  2. In the doGet() and doPost() methods , it intercepts HTTP request and handles it appropriately.

The name of the Struts Config file is not cast in stone. It is a convention followed since the early days of Struts to call this file as struts-config.xml and place it under the WEB-INF directory of the web application. In fact you can name the file anyway you like and place it anywhere in WEB-INF or its sub-directories. The name of the Struts Config file can be configured in web.xml . The web.xml entry for configuring the ActionServlet and Struts Config file is as follows .

 <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/config/myconfig.xml</param-value>     </init-param>     <load-on-startup>1</load-on-startup>   </servlet> 

In the above snippet, the Struts Config file is present in the WEB-INF/config directory and is named myconfig.xml . The ActionServlet takes the Struts Config file name as an init-param . At startup, in the init() method, the ActionServlet reads the Struts Config file and creates appropriate Struts configuration objects (data structures) into memory. You will learn more about the Struts configuration objects in Chapter 7. For now, assume that the Struts Config file is loaded into a set of objects in memory, much like a properties file loaded into a java.util.Properties class.

Like any other servlet, ActionServlet invokes the init() method when it receives the first HTTP request from the caller. Loading Struts Config file into configuration objects is a time consuming task. If the Struts configuration objects were to be created on the first call from the caller, it will adversely affect performance by delaying the response for the first user . The alternative is to specify load-on-startup in web.xml as shown above. By specifying load-on-startup to be 1, you are telling the servlet container to call the init() method immediately on startup of the servlet container.

The second task that the ActionServlet performs is to intercept HTTP requests based on the URL pattern and handles them appropriately. The URL pattern can be either path or suffix. This is specified using the servlet-mapping in web.xml . An example of suffix mapping is as follows.

 <servlet-mapping>     <servlet-name>action</servlet-name>     <url-pattern>*.do</url-pattern>   </servlet-mapping> 

If the user types http://localhost:8080/App1/submitCustomerForm.do in the browser URL bar, the URL will be intercepted and processed by the ActionServlet since the URL has a pattern *.do , with a suffix of " do ‚½.

Once the ActionServlet intercepts the HTTP request, it doesn ‚ t do much. It delegates the request handling to another class called RequestProcessor by invoking its process() method. Figure 2.1 shows a flowchart with Struts controller components collaborating to handle a HTTP request within the RequestProcessor ‚ s process() method. The next sub sections describe the flowchart in detail. It is very important that you understand and even memorize this flowchart. Most of the Struts Controller functionality is embedded in the process() method of RequestProcessor class. Mastery over this flowchart will determine how fast you will debug problems in real life Struts applications. Let us understand the request handling in the process() method step by step with an example covered in the next several sub sections.


Figure 2.1: Flowchart for the RequestProcessor process method.

RequestProcessor and ActionMapping

The RequestProcessor does the following in its process() method:

Step 1: The RequestProcessor first retrieves appropriate XML block for the URL from struts-config.xml . This XML block is referred to as ActionMapping in Struts terminology. In fact there is a class called ActionMapping in org.apache.struts.action package. ActionMapping is the class that does what its name says ‚ it holds the mapping between a URL and Action. A sample ActionMapping from the Struts configuration file looks as follows.

Step 2: The RequestProcessor looks up the configuration file for the URL pattern / submitDetailForm . (i.e. URL path without the suffix do ) and finds the XML block ( ActionMapping ) shown above. The type attribute tells Struts which Action class has to be instantiated . The XML block also contains several other attributes. Together these constitute the JavaBeans properties of the ActionMapping instance for the path / submitDetailForm . The above ActionMapping tells Struts to map the URL request with the path /submitDetailForm to the class mybank.example.CustomerAction . The Action class is explained in the steps ahead. For now think of the Action as your own class containing the business logic and invoked by Struts. This also tells us one more important thing.

Since each HTTP request is distinguished from the other only by the path , there should be one and only one ActionMapping for every path attribute. Otherwise Struts overwrites the former ActionMapping with the latter.

ActionForm

Another attribute in the ActionMapping that you should know right away is name . It is the logical name of the ActionForm to be populated by the RequestProcessor . After selecting the ActionMapping, the RequestProcessor instantiates the ActionForm . However it has to know the fully qualified class name of the ActionForm to do so. This is where the name attribute of ActionMapping comes in handy. The name attribute is the logical name of the ActionForm . Somewhere else in struts-config.xml , you will find a declaration like this:

 <form-bean  name="CustomerForm"                 type="mybank.example.CustomerForm"/> 

This form-bean declaration associates a logical name CustomerForm with the actual class mybank.example.CustomerForm .

Step 3: The RequestProcessor instantiates the CustomerForm and puts it in appropriate scope ‚ either session or request. The RequestProcessor determines the appropriate scope by looking at the scope attribute in the same ActionMapping .

Step 4: Next, RequestProcessor iterates through the HTTP request parameters and populates the CustomerForm properties of the same name as the HTTP request parameters using Java Introspection. (Java Introspection is a special form of Reflection using the JavaBeans properties. Instead of directly using the reflection to set the field values, it uses the setter method to set the field value and getter method to retrieve the field value.)

Step 5: Next, the RequestProcessor checks for the validate attribute in the ActionMapping. If the validate is set to true, the RequestProcessor invokes the validate() method on the CustomerForm instance. This is the method where you can put all the html form data validations. For now, let us pretend that there were no errors in the validate() method and continue. We will come back later and revisit the scenario when there are errors in the validate() method.

Action

Step 6: The RequestProcessor instantiates the Action class specified in the ActionMapping ( CustomerAction ) and invokes the execute() method on the CustomerAction instance. The signature of the execute method is as follows.

 public ActionForward execute(ActionMapping mapping,                 ActionForm form,                 HttpServletRequest request,                 HttpServletResponse response) throws Exception 

Apart from the HttpServletRequest and HttpServletResponse , the ActionForm is also available in the Action instance. This is what the ActionForm was meant for; as a convenient container to hold and transfer data from the http request parameter to other components of the controller, instead of having to look for them every time in the http request. The execute() method itself should not contain the core business logic irrespective of whether or not you use EJBs or any fancy middle tier . The first and foremost reason for this is that business logic classes should not have any dependencies on the Servlet packages. By putting the business logic in the Action class, you are letting the javax.servlet.* classes proliferate into your business logic. This limits the reuse of the business logic, say for a pure Java client. The second reason is that if you ever decide to replace the Struts framework with some other presentation framework (although we know this will not happen), you don ‚ t have to go through the pain of modifying the business logic. The execute() method should preferably contain only the presentation logic and be the starting point in the web tier to invoke the business logic. The business logic can be present either in protocol independent Java classes or the Session EJBs.

The RequestProcessor creates an instance of the Action ( CustomerAction ), if one does not exist already. There is only one instance of Action class in the application. Because of this you must ensure that the Action class and its attributes if any are thread-safe. General rules that apply to Servlets hold good. The Action class should not have any writable attributes that can be changed by the users in the execute() method.

ActionForward

The execute() method returns the next view shown to the user. If you are wondering what ActionForward is, you just have found the answer. ActionForward is the class that encapsulates the next view information. Struts, being the good framework it is, encourages you not to hardcode the JSP names for the next view. Rather you should associate a logical name for the next JSP page. This association of the logical name and the physical JSP page is encapsulated in the ActionForward instance returned from the execute method. The ActionForward can be local or global. Look again at the good old ActionMapping XML block in Listing 2.1. It contained sub elements called forwards with three attributes ‚ name, path and redirect as shown below.

Listing 2.1: A sample ActionMapping from struts-config.xml
 <action  path="/submitDetailForm"  type="mybank.example.CustomerAction"      name="CustomerForm"      scope="request"      validate="true"      input="CustomerDetailForm.jsp">           <forward name="success"                      path="ThankYou.jsp"                    redirect="true"/>           <forward name="failure"  path="Failure.jsp"  />        </action> 
 

The name attribute is the logical name of the physical JSP as specified in the path attribute. These forward elements are local to the ActionMapping in Listing 2.1. Hence they can be accessed only from this ActionMapping argument in the CustomerAction ‚ s execute() method and nowhere else. On the other hand, when the forwards are declared in the global forwards section of the struts-config.xml , they are accessible from any ActionMapping . (In the next section, you will look closely at Struts Config file.) Either ways, the findForward() method on the ActionMapping instance retrieves the ActionForward as follows.

 ActionForward forward = mapping.findForward(success); 

The logical name of the page (success) is passed as the keyword to the findForward() method. The findForward() method searches for the forward with the name ‚“ success ‚½, first within the ActionMapping and then in the global-forwards section. The CustomerAction ‚ s execute() method returns the ActionForward and the RequestProcessor returns the physical JSP to the user. In J2EE terms, this is referred to as dispatching the view to the user. The dispatch can be either HTTP Forward or HTTP Redirect. For instance, the dispatch to the success is a HTTP Redirect whereas the dispatch to ‚“failure ‚½ is a HTTP Redirect.

Difference between HTTP Forward and HTTP Redirect

HTTP Forward is the process of simply displaying a page when requested by the user. The user asks for a resource (page) by clicking on a hyperlink or submitting a form and the next page is rendered as the response. In Servlet Container, HTTP Forward is achieved by invoking the following.

 
 RequestDispatcher dispatcher =         httpServletRequest.getRequestDispatcher(url); Dispatcher.forward(httpServletRequest, httpServletResponse); 

HTTP Redirect is a bit more sophisticated. When a user requests a resource, a response is first sent to the user. This is not the requested resource. Instead this is a response with HTTP code ‚“302 ‚½ and contains the URL of the requested resource. This URL could be the same or different from original requested URL. The client browser automatically makes the request for the resource again with the new URL. And this time, the actual resource is sent to the user. In the web tier you can use HTTP redirect by using the simple API, sendRedirect() on the HttpServletResponse instance. The rest of the magic is done by HTTP. HTTP Redirect has an extra round trip to the client and is used only in special cases. Later in this book, we will show a scenario where HTTP redirect can be useful.

ActionErrors and ActionError

So far, we have covered Struts request handling lifecycle as a happy day scenario ‚ from the point the user submits an html form till the user sees the next page. In reality, users of your web application may submit incorrect data or sometimes no data at all. You have to catch these as close to the user interface as possible, rather than waiting for the middle tier or the database to tell you that a column cannot be inserted in the database because it was expecting a non-null value. There are two consequences of such programming practice.

  1. Server time and resources are precious since they are shared. Spending too much of server ‚ s time and resources on a request, that we know is going to fail eventually is a waste of server resources.

  2. It has a negative impact on the code quality. Since one has to prepare for the possibility of having null data, appropriate checks have to be put (or NumberFormatException s have to be caught) everywhere in the code. Generally business logic is the toughest code of the system and contains enough if-else blocks as such. More if-else blocks for null checks can only mean two things ‚ bad code and maintenance nightmare. Not an elegant programming to say the least. If only you could verify the validity of the user data as close to the user, then the rest of the code only has to deal with business logic and not invalid data.

Struts provides validate() method in the ActionForm to deal with user input validations. Let us now look at how you can validate the user input and report errors to the framework. We will postpone the discussion of how Struts reports the errors to the end user when we discuss View Components later in this chapter. As shown in the flowchart (Figure 2.1), the validate() method is called after the ActionForm instance is populated with the form data. A sample validate() method is shown in Listing 2.2.

Listing 2.2: validate() method in the CustomerForm
 public ActionErrors validate(ActionMapping mapping,                               HttpServletRequest request) {     // Perform validator framework validations     ActionErrors errors = super.validate(mapping, request);     // Only need crossfield validations here     if (parent == null) {         errors.add(GLOBAL_ERROR,                         new ActionError("error.custform"));     }     if (firstName == null) {         errors.add("firstName",                   new ActionError("error.firstName.null"));     }     return errors; } 
 

In the validate() method, you will notice an object called ActionErrors is instantiated. All error checks are performed with the usual if-else blocks. If there are errors, then an individual ActionError object is created for the culprit field and added to the ActionErrors . Think of ActionErrors as a Map for the individual ActionError objects. You can associate one or more ActionError objects for each key. The form field name is generally chosen as the key and can have multiple ActionError objects associated with it. The ActionError is either specific to a field in the ActionForm or it is global to the entire form. When the error is specific to a form field, the field name is used as the key in the ActionErrors . When the error is global to the form, the key name is always GLOBAL_ERRORS. Both of the cases are shown in the Listing 2.2.

You might also notice that the ActionError constructor takes a rather cryptic key as the argument. This key is declared in a properties file whose value is the actual error message. The properties file is selected based on the user chosen Locale. The technical term for this properties file where the messages are externalized is Message Resource Bundle . It is based on the Java ‚ s concept of Localization using the java.util.ResourceBundle and has a whole lot of bells and whistles. We will cover Message Resource Bundle in depth in Chapter10 on Internationalization and Localization. For now it suffices to know that the properties file also serves another purpose apart from Localization. It lets you change the messages without recompiling the code, and is quite handy while maintaining the code. An entry in the Message Resource Bundle properties file looks like:

 error.firstName.null=First Name cannot be null 

The RequestProcessor stops any further processing when it gets the ActionErrors object with ActionError objects. The Action instance never gets the control (and never gets a chance to return ActionForward ). Hence the RequestProcessor consults the ActionMapping object to find the page to be displayed. Notice that the ActionMapping has an attribute named ‚“ input ‚½. This attribute specifies the physical page to which the request has to be forwarded on error. Generally this page is the original page where user entered the data since it is natural that user would want to reenter the data in the same page on error and resubmit .

That completes our overview of the working of Struts Controller components. Now, let us formally look at the Struts configuration file in detail.




Struts Survival Guide. Basics to Best Practices
Struts Survival Guide: Basics to Best Practices (J2ee Survival Series)
ISBN: 0974848808
EAN: 2147483647
Year: 2004
Pages: 96

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