February 18, 2004

Spring: Setting a default action for MultiActionController

The Spring web application framework has the notion of a controller, generally mapping to the ‘C’ in ‘MVC’. (See here for reasons why MVC in GUIs and MVC in webapps may not mean the same thing.) A common controller is one that combines multiple tasks into one class – searching records, displaying and possibly editing a particular record is a typical usage. Spring implements this with a MultiActionController (or MAC for this post).

In typical Spring fashion the MAC figures out which action to execute using a MethodNameResolver. A typical implementation (such as ParameterMethodNameResolver) is to use the value of a GET/POST parameter (e.g., 'action') as the task you wish to run. You tell the resolver which parameter to use in the configuration, which like everything else in Spring is done using the 'bean' tag:

<bean id="actionResolver"
    class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
    <property name="paramName"><value>action</value></property>
</bean>

You then wire together this resolver with your controller, referencing the 'id' attribute of the bean you've configured in the 'ref' tag:

<bean id="myController"
      class="com.optiron.web.MyController">
    <property name="methodNameResolver">
        <ref bean="actionResolver"/>
    </property>
</bean>

And finally map a URL to that controller:

<bean id="urlMapping"
      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/user.do">myController</prop>
        </props>
    </property>
</bean>

So a URL: http://foo/user.do?action=showSearchForm would execute the method showSearchForm() in the class 'com.optiron.web.MyController' which is mapped to the URL user.do. Easy enough.

But what happens when the user doesn't specify a value for action? This is fairly common -- you just want to use 'http://foo/user.do' instead of appending the query string. The implementation referenced above will throw a NoSuchRequestHandlingMethodException with the message "No handling method can be found for request". Oops.

Fortunately, like everything else in Spring you can just implement your own MethodNameResolver. All you have to do is implement the getHandlerMethodName() method specified by the MethodNameResolver interface. Here we'll allow the configuration to specify a parameter name and a default method (I left the imports out for brevity):

public class DefaultParameterMethodNameResolver
        implements MethodNameResolver
{
    private String paramName, defaultMethod;
   
    public String getHandlerMethodName( HttpServletRequest request )
            throws NoSuchRequestHandlingMethodException

{ String name = request.getParameter( paramName ); if ( StringUtils.isEmpty( name ) ) { name = defaultMethod; } if ( name == null ) { throw new NoSuchRequestHandlingMethodException( request ); } return name; } public void setParamName( String paramName ) { this.paramName = paramName; } public void setDefaultMethod( String defaultMethod ) { this.defaultMethod = defaultMethod; } } </pre>

Easy enough. And here's how to configure it, this one with a default task of 'list':

<bean id="actionResolverListDefault"
    class="com.optiron.cd.web.DefaultParameterMethodNameResolver">
    <property name="paramName"><value>action</value></property>
    <property name="defaultMethod"><value>list</value></property>
</bean>

Now just reference this bean's ID in your controller, replacing the old one, and you're good to go:

<bean id="myController"
      class="com.optiron.web.MyController">
    <property name="methodNameResolver">
        <ref bean="actionResolverListDefault"/>
    </property>
</bean>

I may wind up implementing something like this in OpenInteract2. In fact, as much as OI2 is already a Spring-like framework (with only bits of AOP here and there) it would be interesting to see if it could be configured in much the same way...

Next: Emerging mailman on Gentoo
Previous: Nifty CPAN tool: module diff