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:
interceptors
: The list of
interceptors to use.
HandlerInterceptor
s are discussed in
Section 18.5.4, “Adding HandlerInterceptors”.
defaultHandler
: The default
handler to use, when this handler mapping does not result in a
matching handler.
order
: Based on the value of the
order property (see the
org.springframework.core.Ordered
interface), Spring will sort all handler mappings available in the
context and apply the first matching handler.
lazyInitHandlers
: Allows for lazy
initialization of singleton handlers (prototype handlers are always
lazily initialized). Default value is false. This property is
directly implemented in the three concrete
Handlers.
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>
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>
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.
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.
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.
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.