Beyond Code
There is more to Programming than programming...

Overview over the Dispatcher Framework

Introduction

When working with Struts, I quickly developed the following wishlist of things that I wanted Struts to do:

  1. Be able to bind a user request to an arbitrary function in an Action class, not just to perform().
  2. Be able to bind each button in a form with multiple submit buttons to an arbitrary function (such that control is transferred to a specific functions based on which button was clicked).
  3. Provide the ability to restrict access to actions as part of the configuration (ie in struts-config.xml).
  4. Reduce the number of runtime String lookups (such as when writing: mapping.findForward( "success" ) ).

The Dispatcher Framework addresses points 1. - 3., and also suggest a step towards dealing with point 4.

interface DispatcherMapping

When attempting to dispatch requests to individual methods within an action class, additional mapping information is necessary. Fortunately, the struts-config DTD allows for the <set-property> element, so that such information can be included in struts-config.xml without having to change the DTD. The interface DispatcherMapping provides two additional properties: "method" (ie the name of the method that this action should be dispatched to) and "access" (which can be used to implement access control to individual actions=methods).

Any ActionMapping implementation to be used with the present dispatcher framework needs to implement this interface. By making this functionality available as an interface, rather than as a specific mapping class, applications are still free to use other mapping implementations, as needed.

See the section on "Standard Forwards" below for the other properties defined in this interface.

class MethodDispatcherAction

This is the central class of the framework. On startup (ie in its constructor) it reflects itself and builds up a HashMap of member functions having the same signature as the perform method. It is to these functions that requests can be dispatched.

Of course, this class takes a major leaf from org.apache.struts.actions.DispatchAction. However, several things are different: introspection is "eager" rather than "lazy". Since the HashMap is accessed read-only once the ctor has completed, we can dispense w/ synchronization (??!?). This also allows to check at boot-time, whether all required methods are found (more on this below). Finally, this class requires the mapping to be used to implement DispatcherMapping, because it expects the additional properties.

(Note: org.apache.struts.actions.DispatchAction puts the method name to dispatch to onto the request as request parameter. I am unhappy with the actual names of my methods being accessible to the client. I also wanted actions (or rather action mappings) to be dispatchable to methods, not requests.)

Furthermore, MethodDispatcherAction provides access control on the granularity level of individual member functions (ie individual actions). Before the request is dispatched, the member function hasAccess( dispatcherMapping, request ) is called. The default implementation of this method always returns true. Application developers can override this method to implement their own access control. Note that this method has access to the "access" property from struts-config.xml, as well as to the HttpSession (through request). The access property can be used to hold an access mask, which must match some information contained either in the request or on the session.

class FormDispatcherAction

This class is meant to be used with forms with multiple submit buttons. It does no work itself, it just forwards incoming requests to other actions. As opposed to the MethodDispatcherAction above, it is not meant to be extended for individual applications.

Each submit button generates a request parameter, the name of which is the value of the "property" attribute in the JSP. The FormDispatcherAction class looks for a local ActionForward with the same name, and dispatches the request to this action, as a local forward (ie redirect=false).

There is no access control in this class - it is expected that all forwards from this class are dispatched to subclasses of MethodDispatcherAction.

class DispatcherMappingImpl

This is a ready-to-use implementation of the DispatcherMapping interface mentioned above. Otherwise it just extends the original ActionMapping class.

See the section on "Standard Forwards" below for more info.

class DispatcherTestServlet

This is an alternate ActionServlet, which overrides the initMapping() method and tries to perform boot-time consistency checks on it (cf. below).

Appendix: Standard Forwards

It seems that there are quite a few places in Struts where information is available (and could therefore be checked) at compile time (or at least at boot time), but is not accessed until actual run time! Examples are: findForward() (String lookup), action mappings (consistency between JSP and struts-config), bean-property/request parameter names (consistency between JSP and code), etc.

Ever made an innocuos change to a JSP, exercised the application (manually!) to the respective page, only to have the page blow up on you because you made a typo (so that no corresponding bean property could be found)? That's what I mean!

I sometimes dream of a lint-like tool that I could run over an entire Struts application and that would flag all inconsistencies between classes, JSPs, struts-config, and the file system (ie referring to a non-existent JSP in struts-config) for me.

While a complete compile-time checking tool might be a challenge, some boot-time consistency checking seems possible.

DispatcherMapping and DispatcherTestServlet attempt to show how this could be done for the case of findForward(). It seems to me that there are 4 standard forwards which are appropriate for most actions: "success", "failure", "error", and "access denied" (the latter makes sense only in the context of access control to individual actions). DispatcherMapping therefore provides eponymously named methods. The DispatcherTestServlet examines all action mappings at boot time, and reports those for which any of the standard forwards return null. The DispatcherMappingImpl finally tries to provide meaningful defaults, by looking for appropriate global forwards, when no "error" or "denied" forward is defined locally. All other forwards are of course still accessible via findForward().

Rather than finding out during functional testing, it is now possible to find out whether you mistyped "succes" simply by looking into the servlet log.

http://www.beyondcode.org/projects/dispatcher/overview.html