18.5 Handler mappings

Using a handler mapping you can map incoming portlet requests to appropriate handlers. There are some handler mappings you can use out of the box, for example, the PortletModeHandlerMapping, but let's first examine the general concept of a HandlerMapping.

Note: We are intentionally using the term “Handler” here instead of “Controller”. DispatcherPortlet is designed to be used with other ways to process requests than just Spring Portlet MVC’s own Controllers. A Handler is any Object that can handle portlet requests. Controllers are an example of Handlers, and they are of course the default. To use some other framework with DispatcherPortlet, a corresponding implementation of HandlerAdapter is all that is needed.

The functionality a basic HandlerMapping provides is the delivering of a HandlerExecutionChain, which must contain the handler that matches the incoming request, and may also contain a list of handler interceptors that are applied to the request. When a request comes in, the DispatcherPortlet will hand it over to the handler mapping to let it inspect the request and come up with an appropriate HandlerExecutionChain. Then the DispatcherPortlet will execute the handler and interceptors in the chain (if any). These concepts are all exactly the same as in Spring Web MVC.

The concept of configurable handler mappings that can optionally contain interceptors (executed before or after the actual handler was executed, or both) is extremely powerful. A lot of supporting functionality can be built into a custom HandlerMapping. Think of a custom handler mapping that chooses a handler not only based on the portlet mode of the request coming in, but also on a specific state of the session associated with the request.

In Spring Web MVC, handler mappings are commonly based on URLs. Since there is really no such thing as a URL within a Portlet, we must use other mechanisms to control mappings. The two most common are the portlet mode and a request parameter, but anything available to the portlet request can be used in a custom handler mapping.

The rest of this section describes three of Spring Portlet MVC's most commonly used handler mappings. They all extend AbstractHandlerMapping and share the following properties:

18.5.1 PortletModeHandlerMapping

This is a simple handler mapping that maps incoming requests based on the current mode of the portlet (e.g. ‘view’, ‘edit’, ‘help’). An example:

<bean class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
    <property name="portletModeMap">
        <map>
            <entry key="view" value-ref="viewHandler"/>
            <entry key="edit" value-ref="editHandler"/>
            <entry key="help" value-ref="helpHandler"/>
        </map>
    </property>
</bean>

18.5.2 ParameterHandlerMapping

If we need to navigate around to multiple controllers without changing portlet mode, the simplest way to do this is with a request parameter that is used as the key to control the mapping.

ParameterHandlerMapping uses the value of a specific request parameter to control the mapping. The default name of the parameter is 'action', but can be changed using the 'parameterName' property.

The bean configuration for this mapping will look something like this:

<bean class="org.springframework.web.portlet.handler.ParameterHandlerMapping”>
    <property name="parameterMap">
        <map>
            <entry key="add" value-ref="addItemHandler"/>
            <entry key="edit" value-ref="editItemHandler"/>
            <entry key="delete" value-ref="deleteItemHandler"/>
        </map>
    </property>
</bean>

18.5.3 PortletModeParameterHandlerMapping

The most powerful built-in handler mapping, PortletModeParameterHandlerMapping combines the capabilities of the two previous ones to allow different navigation within each portlet mode.

Again the default name of the parameter is "action", but can be changed using the parameterName property.

By default, the same parameter value may not be used in two different portlet modes. This is so that if the portal itself changes the portlet mode, the request will no longer be valid in the mapping. This behavior can be changed by setting the allowDupParameters property to true. However, this is not recommended.

The bean configuration for this mapping will look something like this:

<bean class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping">
    <property name="portletModeParameterMap">
        <map>
            <entry key="view"> <!-- 'view' portlet mode -->
                <map>
                    <entry key="add" value-ref="addItemHandler"/>
                    <entry key="edit" value-ref="editItemHandler"/>
                    <entry key="delete" value-ref="deleteItemHandler"/>
                </map>
            </entry>
            <entry key="edit"> <!-- 'edit' portlet mode -->
                <map>
                    <entry key="prefs" value-ref="prefsHandler"/>
                    <entry key="resetPrefs" value-ref="resetPrefsHandler"/>
                </map>
            </entry>
        </map>
    </property>
</bean>

This mapping can be chained ahead of a PortletModeHandlerMapping, which can then provide defaults for each mode and an overall default as well.

18.5.4 Adding HandlerInterceptors

Spring's handler mapping mechanism has a notion of handler interceptors, which can be extremely useful when you want to apply specific functionality to certain requests, for example, checking for a principal. Again Spring Portlet MVC implements these concepts in the same way as Web MVC.

Interceptors located in the handler mapping must implement HandlerInterceptor from the org.springframework.web.portlet package. Just like the servlet version, this interface defines three methods: one that will be called before the actual handler will be executed (preHandle), one that will be called after the handler is executed (postHandle), and one that is called after the complete request has finished (afterCompletion). These three methods should provide enough flexibility to do all kinds of pre- and post- processing.

The preHandle method returns a boolean value. You can use this method to break or continue the processing of the execution chain. When this method returns true, the handler execution chain will continue. When it returns false, the DispatcherPortlet assumes the interceptor itself has taken care of requests (and, for example, rendered an appropriate view) and does not continue executing the other interceptors and the actual handler in the execution chain.

The postHandle method is only called on a RenderRequest. The preHandle and afterCompletion methods are called on both an ActionRequest and a RenderRequest. If you need to execute logic in these methods for just one type of request, be sure to check what kind of request it is before processing it.

18.5.5 HandlerInterceptorAdapter

As with the servlet package, the portlet package has a concrete implementation of HandlerInterceptor called HandlerInterceptorAdapter. This class has empty versions of all the methods so that you can inherit from this class and implement just one or two methods when that is all you need.

18.5.6 ParameterMappingInterceptor

The portlet package also has a concrete interceptor named ParameterMappingInterceptor that is meant to be used directly with ParameterHandlerMapping and PortletModeParameterHandlerMapping. This interceptor will cause the parameter that is being used to control the mapping to be forwarded from an ActionRequest to the subsequent RenderRequest. This will help ensure that the RenderRequest is mapped to the same Handler as the ActionRequest. This is done in the preHandle method of the interceptor, so you can still modify the parameter value in your handler to change where the RenderRequest will be mapped.

Be aware that this interceptor is calling setRenderParameter on the ActionResponse, which means that you cannot call sendRedirect in your handler when using this interceptor. If you need to do external redirects then you will either need to forward the mapping parameter manually or write a different interceptor to handle this for you.