3.3 Struts Controller Components

The controller component in an MVC application has several responsibilities, including receiving input from a client, invoking a business operation, and coordinating the view to return to the client. Of course, the controller may perform many other functions, but these are a few of the primary ones.

For an application built using the JSP Model 2 approach, the controller is implemented by a Java servlet. This servlet is the centralized point of control for the web application. The controller servlet maps user actions into business operations and then helps to select the view to return to the client based on the request and other state information. As a reminder, Figure 3-5 shows the figure used in Chapter 1 to illustrate how this works.

Figure 3-5. The Struts framework uses a servlet as a controller

figs/jstr_0305.gif

In the Struts framework, however, the controller responsibilities are implemented by several different components, one of which is an instance of the org.apache.struts.action.ActionServlet class.

3.3.1 The Struts ActionServlet

The ActionServlet extends the javax.servlet.http.HttpServlet class and is responsible for packaging and routing HTTP traffic to the appropriate handler in the framework. The ActionServlet class is not abstract and therefore can be used as a concrete controller by your applications. Prior to Version 1.1 of the Struts framework, the ActionServlet was solely responsible for receiving the request and processing it by calling the appropriate handler. In Version 1.1, a new class, called org.apache.struts.action.RequestProcessor , was introduced to process the request for the controller. The main reason for decoupling the request processing from the ActionServlet is to provide you with the flexibility to subclass the RequestProcessor with your own version and modify how the request is processed.

For the banking application example, we will keep it simple and use the default ActionServlet and RequestProcessor classes provided by the framework. For brevity in this chapter, we will refer to these two components simply as "the controller." Chapter 5 describes in detail how these classes can be extended to modify the default controller behavior and explains the roles and responsibilities of each component.

Like any other Java servlet, the Struts ActionServlet must be configured in the deployment descriptor for the web application. We won't go into detail about the deployment descriptor here, though—it's covered in Chapter 4.

Once the controller receives a client request, it delegates the handling of the request to a helper class. This helper knows how to execute the business operation associated with the requested action. In the Struts framework, this helper class is a descendant of the org.apache.struts.action.Action class.

Is the Action Part of the Controller or Model?

The various articles, tutorials, and other resources available on the Struts framework disagree about whether the Action class is part of the controller or the model. The argument for it being part of the controller is that it isn't part of the "real" business logic. If Struts were replaced with an alternative framework, chances are the Action class would be replaced with something else. Therefore, it really isn't part of the model domain, but rather is tightly coupled to the Struts controller. It doesn't make sense to put business logic into the Action, because other types of clients can't easily reuse it.

Another reason to consider the Struts Action class part of the controller is that it has access to the ActionServlet, and therefore all of the controller resources, which the model domain shouldn't know about. Hypothetically, the Action class's behavior could have been left in the servlet, and the servlet would call the appropriate method on itself. If this were the case, there would be no doubt about whether this was controller or model functionality.

With all of that said, the Action class may invoke operations on the business model, and many developers end up trying to insert too much of their business logic into the Action classes. Eventually, the line becomes blurry. Perhaps this is why some developers consider it part of the model. However, this book will take the approach that the Action class is part of the controller.

3.3.2 Struts Action Classes

An org.apache.struts.action.Action class in the Struts framework is an extension of the controller component. It acts as a bridge between a client-side user action and a business operation. The Action class decouples the client request from the business model. This decoupling allows for more than a one-to-one mapping between the user request and an Action. The Action class also can perform other functions, such as authorization, logging, and session validation, before invoking the business operation.

The Struts Action class contains several methods, but the most important is the execute( ) method. Here is the method signature:

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

The execute( ) method is called by the controller when a request is received from a client. The controller creates an instance of the Action class if one doesn't already exist. The Struts framework will create only a single instance of each Action class in your application. Because there is only one instance for all users, you must ensure that all of your Action classes operate properly in a multithreaded environment, just as you would do when developing a servlet. Figure 3-6 illustrates how the execute( ) method is invoked by the controller components.

Figure 3-6. The execute( ) method is called by the controller

figs/jstr_0306.gif

Although the execute( ) method is not abstract, the default implementation returns null, so you will need to create your own Action class implementation and override this method.

There is some debate over how best to implement Action classes using Struts. Whether you create a different Action class for each operation or put several business operations in the same Action class is subjective, and each approach has pros and cons.

In Chapter 5, we'll discuss an action provided by the Struts framework called org.apache.struts.actions.DispatchAction . This action gives you the ability to create a single Action class and implement several similar operations, such as Create, Read, Update, and Delete (CRUD), within it. This has the effect of creating a smaller number of Action classes, but it might make maintenance a little more cumbersome.

For the banking application, we will create a unique Action class for each action that the user can perform:

·         Login

·         Logout

·         GetAccountInformation

·         GetAccountDetail

Each of the banking Action classes will extend the Struts Action class and will override the execute( ) method to carry out a specific operation. In Chapter 5, you'll learn that it's best to create an abstract base Action class, which all of your other Action classes extend. The application-specific base Action extends the Struts Action class and provides you with added flexibility and extensibility by allowing common Action behavior to be handled by a single parent class. For example, if you want to verify for each request that the user's session has not timed out, you can put this behavior in the abstract base Action before calling the subclass. For the banking application, however, things will be kept simple, and the actions will be direct descendants of the Struts Action class.

The com.oreilly.struts.banking.action.LoginAction class is shown in Example 3-3. It extends the Struts Action class and is invoked by the controller when a user attempts to log in to the banking application.

Example 3-3. The LoginAction used by the banking application
package com.oreilly.struts.banking.action;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import com.oreilly.struts.banking.IConstants;
import com.oreilly.struts.banking.service.IAuthentication;
import com.oreilly.struts.banking.service.SecurityService;
import com.oreilly.struts.banking.service.InvalidLoginException;
import com.oreilly.struts.banking.view.UserView;
import com.oreilly.struts.banking.form.LoginForm;
/**
 * This Action is called by the ActionServlet when a login attempt
 * is made by the user. The ActionForm should be an instance of
 * a LoginForm and contain the credentials needed by the SecurityService.
 */
public class LoginAction extends Action {
  public ActionForward execute( ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response )
    throws Exception {
 
      // The ActionForward to return when completed
      ActionForward forward = null;
      UserView userView = null;
 
      // Get the credentials from the LoginForm
      String accessNbr = ((LoginForm)form).getAccessNumber(  );
      String pinNbr = ((LoginForm)form).getPinNumber(  );
 
      /*
       * In a real application, you would typically get a reference
       * to a security service through something like JNDI or a factory.
       */
      IAuthentication service = new SecurityService(  );
 
      // Attempt to log in
      userView = service.login(accessNbr, pinNbr);
 
      // Since an exception wasn't thrown, login was successful
 
      // Invalidate existing session if it exists
      HttpSession session = request.getSession(false);
      if(session != null) {
        session.invalidate(  );
      }
 
      // Create a new session for this user
      session = request.getSession(true);
 
      // Store the UserView into the session and return
      session.setAttribute( IConstants.USER_VIEW_KEY, userView );
      forward = mapping.findForward(IConstants.SUCCESS_KEY );
      return forward;
  }
}

The LoginAction in Example 3-3 gets the credentials from the ActionForm that was passed in as an argument in the execute( ) method.

The ActionForm class will be discussed later in this chapter in Section 3.5.

A SecurityService is then created, and the security credentials are passed to the login( ) method. If the login succeeds, a new HttpSession is created for the user, and the UserView returned from the login( ) method is put into the session. If authentication fails, an InvalidLoginException is thrown. Note that there is no try/catch block for the InvalidLoginException in the execute( ) method. This is because one of the new features of Struts 1.1 is its declarative exception-handling capabilities, which remove much of the burden of exception handling from the developer. With the declarative exception handling in Struts, you specify what exceptions can be thrown from the actions and what you want the framework to do with them. You specify this information in the Struts configuration file:

<global-exceptions>
 <exception 
       key="global.error.invalidlogin"
       path="/login.jsp"
       scope="request"
       type="com.oreilly.struts.banking.service.InvalidLoginException"/>
</global-exceptions>

This fragment from the banking configuration file tells the framework that if an InvalidLoginException is thrown by any action, it should forward the request to the login.jsp resource and build an error message using the key global.error.invalidlogin from the resource bundle. You also have the ability to override the default exception-handling behavior with whatever functionality you need it to perform. Exception handling is covered at length in Chapter 10.

3.3.3 Mapping the Actions

At this point, you might be asking yourself, "How does the controller know which Action instance to invoke when it receives a request?" It determines this by inspecting the request information and using a set of action mappings.

Action mappings are part of the Struts configuration information that is configured in a special XML file. This configuration information is loaded into memory at startup and made available to the framework at runtime. Each action element is represented in memory by an instance of the org.apache.struts.action.ActionMapping class. The ActionMapping object contains a path attribute that is matched against a portion of the URI of the incoming request. We'll talk more about action mappings and the Struts configuration file in Chapter 4.

The following XML fragment illustrates the login action mapping from the configuration file used by the banking application:

<action
  path="/login"
  type="com.oreilly.struts.banking.action.LoginAction"
  scope="request"
  name="loginForm"
  validate="true"
  input="/login.jsp">
  <forward name="Success" path="/action/getaccountinformation" redirect="true"/>
  <forward name="Failure" path="/login.jsp" redirect="true"/>
</action>

The login action mapping shown here maps the path "/login" to the Action class com.oreilly.struts.banking.LoginAction. Whenever the controller receives a request where the path in the URI contains the string "/login", the execute( ) method of the LoginAction instance will be invoked. The Struts framework also uses the mappings to identify the resource to forward the user to once the action has completed. We'll talk more about configuring action mappings in Chapter 4.

3.3.4 Determining the Next View

So far, we've discussed how the controller receives the request and determines the correct Action instance to invoke. What hasn't been discussed is how or what determines the view to return to the client.

If you looked closely at the execute( ) method signature in the Action class from the previous section, you might have noticed that the return type for the method is an org.apache.struts.action.ActionForward class. The ActionForward class represents a destination to which the controller may send control once an Action has completed. Instead of specifying an actual JSP page in the code, you can declaratively associate an action forward mapping with the JSP and then use that ActionForward throughout your application.

The action forwards are specified in the configuration file, similar to action mappings. They can be specified at an Action level, as this forward is for the logout action mapping:

<action
  path="/logout"
  type="com.oreilly.struts.banking.action.LogoutAction"
  scope="request">
  <forward name="Success" path="/login.jsp" redirect="true"/>
</action>

The logout action declares a forward element named "Success", which forwards to the "/login.jsp" resource. The redirect attribute is specified and set to "true". Now, instead of performing a forward using a RequestDispatcher, the request will be redirected.

The action forward mappings also can be specified in a global section, independent of any specific action mapping. In the previous case, only the logout action mapping could reference the action forward named "Success", However, all action mappings can reference forwards declared in the global-forwards section. Here is an example global-forwards section from the banking configuration file:

<global-forwards>
 <forward name="SystemFailure" path="/systemerror.jsp" />
 <forward name="SessionTimeOut" path="/sessiontimeout.jsp" />    
</global-forwards>

The forwards defined in this section are more general and don't apply to specific action mappings. Notice that every forward must have a name and path, but the redirect attribute is optional. If you don't specify a redirect attribute, its default value of "false" is used, and thus the framework will perform a forward. The path attribute in an action forward also can specify another Struts Action. You'll see an example of how to do this in Chapter 5.

Now that you understand from a high level how the Struts controller components operate, it's time to look at the next piece of the MVC puzzle—the model.