Struts Action Mappings - configuring web application URLs

23 January 2006

Peter Hilton

by Peter Hilton

In the article on Struts URLs for Perfectionists I described one way of defining the action mappings for a Struts web application. One of Struts' double-edged swords is that there are several ways to do this, each with its pros and cons. This article considers a few alternatives.

Individual action mappings in struts-config.xml

Approach: manually add an action element to struts-config.xml for each Action mapping.

Pros: simplicity, and the most examples online.

Cons: effort to maintain struts-config.xml.

<action path='/search' type='myapp.web.SearchAction' name='SearchForm'>
	<forward name='search' path='/WEB-INF/jsp/Search.jspx' redirect='false'/>
</action>

This approach is most straightforward, and is a good way to start. However, if the number of actions continually grows it becomes a hassle to keep struts-config.xml in sync.

One thing that certainly helps is a strict convention for Action class names based on the URL path, so you do not have look at the configuration each time you want to look at a URL's action, or vice versa.

It also helps to keep the action mappings sorted by path, to make it easier to browse and to avoid duplicate entries.

Use DispatchAction to group related actions

Approach: combine related actions as separate methods in a DispatchAction subclass

Pros: simplified struts-config.xml with fewer entries

Cons: unpredictable action method names

<action path='/customer' type='myapp.web.CustomerDispatchAction'
name='SearchForm' parameter='method'>
<forward name='view' path='/WEB-INF/jsp/CustomerView.jspx' redirect='false'/>
<forward name='edit' path='/WEB-INF/jsp/CustomerEdit.jspx' redirect='false'/>
<forward name='delete' path='/WEB-INF/jsp/CustomerDelete.jspx' redirect='false'/>
</action>

DispatchAction was the first mechanism Struts provided for simplifying action mappings. It certainly makes sense for related URLs, such as /customer/view, /customer/edit, /customer/delete. Actions group naturally when they share an ActionForm, since each DispatchAction class is associated with one ActionForm in a single action mapping. However, it is less obvious how to group actions that do not use a form.

A harder problem comes when naming DispatchAction methods. It is useful to have a strict convention that simplifies the application's URL API, so there is less to remember. For example, if you group actions by domain type then you may have a CustomerDispatchAction and a ProductDispatchAction each with view, edit and delete methods. The first problem is that not every DispatchAction will use all of the possible method names, so you have to remember whether a URL like /product?method=delete is valid. The second problem is that some actions are difficult to fit into this method name scheme, which makes consistency hard. These are relatively minor, though.

Wildcard action mappings in struts-config.xml

Approach: reduce the number of action mappings by using wildcards to define generic mappings

Pros: fewer action mappings and a strict mapping from URLs

Cons: wrong case in class names or upper-case in URLs

	<action path="/*/*" type="com.example.web.{1}{2}Action"/>

A good way of avoiding the need to update struts-config.xml every time you add a new page is to add an action mapping with wildcards that you can use for all of your pages, as described in Struts URLs for Perfectionists. This works pretty well, and when I tried this it turned out to be useful for just about all of the pages - there were no exceptions where I could not use the generic action mapping.

The huge disadvantage came when I realised that I could not have both lower-case URLs and camel-case action class names. With the example above, the URL /customer/edit would require a class called customereditAction instead of CustomerEditAction, which is almost as ugly as upper-case letters in the URL.

XDoclet definitions in Action classes

Approach: use XDoclet tags in the action classes to define action mappings, instead of using struts-config.xml

Pros: flexible definition of URLs (action mapping paths) and actions (classes) in the same place

Cons: more complex build, requiring more Ant knowledge; no enforced mapping conventions

/**
 * @struts.action path="/customer/edit" name="CustomerForm" validate="false"
 * @struts.action-forward name="default" path="/WEB-INF/jsp/CustomerEdit.jspx"
 */
public class CustomerEditAction extends Action
My current favourite approach is to use XDoclet annotations in the action class itself to specify the action mapping. Once the Ant build script is set-up to generate struts-config.xml you can then use this approach to keep all of the definitions together, without any duplication. The main advantage is that you can do everything here that you can do in the basic method, of defining action mappings manually. However, this also gives you the disadvantage that your naming conventions for the URL, action class, action form and JSP are not automatic or enforced; in the above example I have four opportunities to misspell 'customer'.

Generating struts-config.xml another way

One thing that remains unsatisfying about the above Struts-based approaches is that action mappings are not usually a web application's only URL-specific meta-data. For example, you may wish to define a title for each page, to be used in a page template. It is therefore a shame, perhaps, that Struts does not provide a mechanism for extensible action meta-data. So far, at least, I have not come across an implementation of this idea.

Instead I have simply used a separate XML document containing a series of elements whose IDs match the Struts action mapping paths. It is then straightforward to parse this XML in a JSP page template, using the JSTL XML tag library, in order to fetch meta-data from the element whose ID matches the current URL.

In the end, the whole action mappings issue seems more like an unresolved design decision in Struts than useful flexibility. I would prefer to see a simpler model with a recommended way of mapping URLs to application code, and good support for making this work well. Perhaps it is time to revisit some of the alternatives to Struts that consider simplicity a virtue.