ABSTRACT

It's often convenient, and sometimes necessary, to handle Struts events,
such as when an action has its locale set, or when the action servlet
processes an action's form. This document proposes retrofitting Struts 
with the delegation event model. That model, which is used by the AWT
and
Swing, makes event handling simple, flexible, and scalable.


CREDITS

Delegation and Event Model for Struts? -- posted to struts-dev by 
                                          Robert Leland

INTRODUCTION

Currently, you can use inheritance to handle Struts events like those 
described above. Typically, that means extending ActionServlet and 
overriding a protected method, such as
ActionServlet.processActionPerform.

Inheritance-based event handling is inflexible and does not scale well
because event sources and listeners are tightly coupled at compile time.
This was evident to AWT engineers, who replaced the AWT's original
inheritance-based event model with the delegation event model.

The delegation event model, which has its roots in java.util, implements
the Observer design pattern to loosely couple event sources and event
listeners at runtime. That loose coupling makes it easy to associate
disparate types of objects, so that event listeners can easily react to
changes in event sources.


STRUTS AND THE DELEGATION EVENT MODEL

So what does it mean to retrofit Struts with the delegation event model?
It means that Struts will fire events when it performs certain 
functions. You can register with Struts as an event listener, and 
handle events as you see fit.

This proposal advocates firing events for all interesting Struts
functions; for example, the action servlet should fire a robust set of
events for processing actions and forms, performing mappings, etc. 
Implementing support for those events follows the same
design pattern discussed in this proposal for implementing action
events.

This proposal illustrates how to modify Struts to fire events just
before, and immediately after, a Struts action has its perform method
invoked. Those events are hereafter known as action events.


IMPLEMENTING ACTION EVENTS AND ACTION LISTENERS

Getting Struts to fire action events is easy. First, we define a
listener interface and an event class:

org/struts/apache/event/action/ActionListener.java
org/struts/apache/event/action/ActionEvent.java

Here's the listing for ActionListener:

   public interface ActionListener {
      public void beforeActionPerform(ActionEvent event) 
                                throws ServletException;
      public void afterActionPerform(ActionEvent event)  
                                throws ServletException;
   }

ActionListener methods are passed instances of ActionEvent. Here's the
listing for that class:

   public class ActionEvent extends java.util.EventObject {
      public static final int BEFORE_ACTION_PERFORM=0,
                              AFTER_ACTION_PERFORM=1;
      private int eventType;
      private HttpServletRequest request;
      private HttpServletResponse response;
      public ActionEvent(Action action, int eventType,
                         HttpServletRequest request,
                         HttpServletResponse response) {
         super(action);  // specifies action as the event source
         this.eventType = eventType;
         this.request   = request;
         this.response  = response;
      }
      public int getEventType() { return eventType; }
      public HttpServletRequest  getRequest()   { return request; }
      public HttpServletResponse getResponse()  { return response; }
   }

Through action events, action listeners have access to:

event type (BEFORE_ACTION_PERFORM, AFTER_ACTION_PERFORM)
action
request
response


HANDLING ACTION EVENTS

Here's how you use action events and listeners:

   // first, implement a listener that handles action events

   public class MyListener implements
                   org.apache.struts.event.ActionListener {
      public void beforeActionPerform(ActionEvent event) {
         // handle event
      }
      public void afterActionPerform(ActionEvent event) {
         // handle event
      }
   }

   // Then register your listener with an action:

   someAction.addActionListener(new MyListener());

Thereafter, MyListener.beforeActionPerform and
MyListener.afterActionPerform will be called before and after
someAction's perform method, respectively.

Let's see what changes need to be made to Struts to make this work.


STRUTS MODIFICATIONS FOR SUPPORTING ACTION EVENTS

Only two Struts classes need to be modified to support firing action
events: Action and ActionServlet. Methods are added to the Action class
for registering action listeners and firing events:

   // the following is added to org.apache.struts.action.Action:

   import java.util.Enumeration;
   import java.util.Vector;
   import org.apache.struts.event.action.ActionEvent;
   import org.apache.struts.event.action.ActionListener;

   public class Action {
      ...

      protected static Vector listeners = new Vector();

      public void addActionListener(ActionListener listener) {
         listeners.addElement(listener);
      }
      public void removeActionListener(ActionListener listener) {
         listeners.remove(listener);
      }
      public void beforeAction(ActionEvent event)
                                        throws ServletException {
         fireEvent(event);
      }
      public void afterAction(ActionEvent event)
                                        throws ServletException {
         fireEvent(event);
      }
      protected void fireEvent(ActionEvent event)
                                       throws ServletException {
         Enumeration it = listeners.elements();

         while(it.hasMoreElements()) {
            ActionListener listener =
                        (ActionListener)it.nextElement();

            switch(event.getEventType()) {
               case ActionEvent.BEFORE_ACTION_PERFORM:
                            listener.beforeActionPerform(event);
                            break;
               case ActionEvent.AFTER_ACTION_PERFORM:
                            listener.afterActionPerform(event);
                            break;
            }
         }
      }
      ...
   }

Now Struts actions can fire action events to registered action
listeners. ActionServlet.processActionCreate is modified to call
Action.fireEvent, like this:

   protected void processActionPerform(Action action,
                                        ActionMapping mapping,
                                        ActionForm formInstance,
                                        HttpServletRequest request,
                                        HttpServletResponse response)
                                        throws IOException,
                                               ServletException {
      action.fireEvent(new ActionEvent(action,
                       ActionEvent.BEFORE_ACTION_PERFORM,
                       (HttpServletRequest)request,
                       (HttpServletResponse)response));

       // Perform the requested action
      ActionForward forward =
            action.perform(mapping, formInstance, request, response);

      action.fireEvent(new ActionEvent(action,
                                ActionEvent.AFTER_ACTION_PERFORM,
                                (HttpServletRequest)request,
                                (HttpServletResponse)response));
      ...
   }


CONCLUSION

Struts will be a more powerful and extensible framework if developers
can handle Struts events, which can be accomplished as outlined in this
proposal with the delegation event model.

This proposal has illustrated modifying Struts to fire action events. If
this proposal is accepted, Struts should be modified to fire many
meaningful events. The ActionServlet class alone is rife with methods
that should fire events; for example, initApplication,
processActionForm, processLocale, etc.

A practical use of the action events discussed in this proposal can be
found in the proposal 'Tokens and Events: Handling Illicit Access to 
Sensitive Pages'. That proposal implements an action listener
that restricts access to actions that are sensitive to the back button,
reload button, or bookmarks.

Reply via email to