Handling events with action dispatcher

It is commonly accepted that Struts has no concept of events. This is not true, it is possible to assign events to input elements of an HTML form or even to regular links, and to process these events in a uniform fashion by a single action class.

Dispatcher Actions

Struts distribution includes dispatcher action classes that send input events to their respective handlers in an action class. These actions are: DispatchAction, LookupDispatchAction and MappingDispatchAction. These classes are not perfect and show their age:

  • User's custom action class must extend one of these classes to use dispatch features.
  • DispatchAction requires submit button caption to be the same as handler method name; usage of an arbitrary caption requires Javascript.
  • LookupDispatchAction uses reverse lookup to property file and requires button-to-method map; button caption cannot be changed without application restart.
  • MappingDispatchAction can process only one event per action mapping definition, so you have one class but several action mappings and several URLs.

Thankfully, there is a better solution.

Action Dispatcher

Struts 1.2.7+ distribution includes ActionDispatcher class that combines features of all dispatch actions. It allows to select a particular behavior or flavor and, what is the most appealing, it allows to dispatch events to any action class.

Struts 1.2.9 and 1.3.1+ contains the extended version of ActionDispatcher called EventActionDispatcher. It solves all known dispatch issues:

  • Any arbitrary action class can handle several events.
  • One action mapping can contain several event definitions.
  • Submit buttons can have any caption; caption can be changed in runtime; Javascript is not required.
  • No event-to-method or property-to-caption map is needed.
  • Submit button or a link can either directly refer to action method name, or can use a separate event name as an alias; aliases are defined in "parameter" attribute of action mapping.
  • It is possible to specify a default method if event is not recognized in the request.
  • If event is not recognized and default method is not specified, the dispatcher invokes unspecified method.
  • Standard Cancel buttons is hooked up to cancelled method and does not have to be defined explicitly.

Dispatching events using EventActionDispatcher

Dispatching events with EventActionDispatcher is easy. Usually you will define two action classes: submit action will handle all input events , while setup/render action will choose an appropriate page and display it. EventActionDispatcher allows you to combine both input and render functions in one class as well.

Action class

Below is the full definition of an action class that handles user events. All handler methods have the same signature as execute method. Do not forget to define unspecified and cancelled methods.

This action does not implement a separate render phase. It simply processes incoming events and forwards to a corresponding page.

package com.acme.dispatch;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.Action;
import org.apache.struts.actions.ActionDispatcher;
import org.apache.struts.actions.EventActionDispatcher;

public class DispatcherSampleAction extends Action {

  /**
   * Instantiate event dispatcher
   */
  protected ActionDispatcher dispatcher = 
      new EventActionDispatcher(this);

  /**
   * Use event dispatcher to call an appropriate event handler. 
   * By using a dispatcher an action class does not need 
   * to extend DispatchAction.
   */
  public ActionForward execute(ActionMapping mapping,
                               ActionForm form,
                               HttpServletRequest request,
                               HttpServletResponse response)
      throws Exception {
      return dispatcher.execute(mapping, form, request, response);
  }

  // Handler of Add button
  public ActionForward add(ActionMapping mapping,
                           ActionForm form,
                           HttpServletRequest request,
                           HttpServletResponse response)
          throws IOException, ServletException {
      return mapping.findForward("addpage");
  }

  // Handler of Delete button
  public ActionForward delete(ActionMapping mapping,
                              ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
          throws IOException, ServletException {
      return mapping.findForward("deletepage");
  }

  // Handler of Login button
  public ActionForward login(ActionMapping mapping,
                             ActionForm form,
                             HttpServletRequest request,
                             HttpServletResponse response)
          throws IOException, ServletException {
      return mapping.findForward("loginpage");
  }

  // Handler of standard Cancel button
  public ActionForward cancelled(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response)
          throws IOException, ServletException {
      return mapping.findForward("cancelpage");
  }

  // Handler of button that is unknown
  public ActionForward unspecified(ActionMapping mapping,
                                   ActionForm form,
                                   HttpServletRequest request,
                                   HttpServletResponse response)
          throws IOException, ServletException {
      return mapping.findForward("unspecifiedpage");
  }
}

Action mapping

Action mapping defines possible events. Notice that delete is both an event and a method name, it is not aliased. On the other hand, add and login methods are aliased with addEvent and loginEvent events.

<action path = "/sampleactiondispatcher"
  type = "com.acme.dispatch.DispatcherSampleAction"
  parameter = "addEvent=add,delete,loginEvent=login,default=unspecified"
  validate = "false">

  <forward name = "addpage" path = "/actiondispatcher/selectadd.html"/>
  <forward name = "deletepage" path = "/actiondispatcher/selectdelete.html"/>
  <forward name = "loginpage" path = "/actiondispatcher/selectlogin.html"/>
  <forward name = "cancelpage" path = "/actiondispatcher/selectcancel.html"/>
  <forward name = "unspecifiedpage" path = "/actiondispatcher/selectunspecified.html"/>
</action>

Input page

Events are sent as a request key. Links can specify event as URL query parameter, submit element in an HTML form specify event as "name" attribute.

<html>
  <form action="sampleactiondispatcher.do" method="post">
    <p>Image button works too.</p>
    <input type="image" name="loginEvent" src="login.gif" value="Log In"><hr>

    <p>Button and link that invoke "add" handler.</p>
    <input type="submit" name="addEvent" value="Add button" />
    <a href="sampleactiondispatcher.do?addEvent=true">Add link</a><hr>

    <p>Button and link that invoke "delete" handler.</p>
    <input type="submit" name="delete" value="Delete button fancy caption" />
    <a href="sampleactiondispatcher.do?delete=true">Delete link</a><hr>

    <p>Button and link that invoke "unspecified" handler.</p>
    <input type="submit" name="anythingGoes" value="No handler" />
    <a href="sampleactiondispatcher.do?anythingGoes=true">No handler</a><hr>

    <p>This is a standard <code>&lt;html:cancel/></code> button; 
       it is autowired to <code>cancelled</code> method.
       Link works as well, but client validator will not be disabled, 
       because bCancel is not set to true.</p>
    <input type="submit" name="org.apache.struts.taglib.html.CANCEL" 
           value="Standard Cancel button works too" onclick="bCancel=true;">
    <a href="sampleactiondispatcher.do?org.apache.struts.taglib.html.CANCEL=true">
       Standard Cancel handler works for links</a>
  </form>
</html>

The events assigned to links have value of "true". This is a dummy value just to ensure that request parameter is processed correctly and not lost; the value is optional and is not used.

The code above is pure HTML. Of course, you can use Struts tags as well. For <html:submit> tag specify event in "property" attribute. For Cancel button just use <html:cancel> tag.

  • No labels