While the Spring Framework supports servlet-based development via Spring Web MVC, PortletMVC4Spring supports JSR-362 portlet development. As much as possible, PortletMVC4Spring is a mirror image of the Spring Web MVC framework, and also uses the same underlying view abstractions and integration technology.
For more general information about portlet development, please review the JSR-362 Specification itself.
Note
|
Bear in mind that while most of the concepts of Spring Web MVC are the same in PortletMVC4Spring, there are some notable differences created by the unique workflow of JSR-362 portlets. |
The main way in which portlet workflow differs from servlet workflow is that the request to the portlet can have two
distinct phases: the ACTION_PHASE
and the RENDER_PHASE
. The ACTION_PHASE
is executed only once and is where any
'backend' changes or actions occur, such as making changes in a database. The RENDER_PHASE
then produces what is
displayed to the user each time the display is refreshed. The critical point here is that for a single overall request,
the ACTION_PHASE
is executed only once, but the RENDER_PHASE
may be executed multiple times. This provides (and
requires) a clean separation between the activities that modify the persistent state of your system and the activities
that generate what is displayed to the user. The Portlet 2.0 Specification added two more phases: The event phase and
the resource phase, both of which are supported by annotation-driven dispatching. The Portlet 3.0 Specification added
the header phase, but PortletMVC4Spring does not yet have annotation-driven dispatching support for it.
The multiple phases of portlet requests are one of the real strengths of the JSR-362 specification. Most other portlet
MVC frameworks attempt to completely hide the two phases from the developer and make it look as much like traditional
servlet development as possible - but this approach removes one of the main benefits of using portlets. This is why the
separation of the two phases is preserved throughout PortletMVC4Spring. The primary manifestation of this approach is
that where a Spring Web MVC controller might have a single handler method annotated with @RequestMapping
, an
equivalent PortletMVC4Spring controller might have multiple handler methods, each utilizing one of @ActionMapping
,
@EventMapping
, @RenderMapping
, or @ResourceMapping
.
The framework is designed around a DispatcherPortlet
that dispatches requests to handlers, with configurable handler
mappings and view resolution, just as the DispatcherServlet
in the web framework does. File upload is also supported
in the same way.
Locale resolution and theme resolution are not supported in PortletMVC4Spring - these areas are in the purview of the
portal/portlet container and are not appropriate at the Spring level. However, all mechanisms in Spring that depend on
the locale (such as internationalization of messages) will still function properly because DispatcherPortlet
exposes
the current locale in the same way as DispatcherServlet
.
The modern approach for developing PortletMVC4Spring controller classes is to use the annotation-based programming model. For detailed information, please refer to the section titled "Annotation-Based Controller Programming Model".
The legacy approach is to have a controller class that implements the Controller
interface (typically by extending
the AbstractController
class). For detailed information, please refer to the section titled "Legacy Controller Programming Model".
All the view rendering capabilities of Spring Web MVC are used directly via a special bridge servlet named
ViewRendererServlet
. By using this servlet, the portlet request is converted into a servlet request and the view can
be rendered using the entire normal Spring Web MVC infrastructure. This means all the existing renderers, such as JSP,
Thymeleaf, Velocity, etc., can be used within the portlet.
PortletMVC4Spring is a request-driven portlet MVC framework, designed around a portlet that dispatches requests to
controllers and offers other functionality facilitating the development of portlet applications. The PortletMVC4Spring
DispatcherPortlet
however, does more than just that. It is completely integrated with the Spring ApplicationContext
and allows you to use every other feature Spring has.
Like ordinary portlets, the DispatcherPortlet
is declared in the portlet.xml
file of your web application:
<portlet>
<portlet-name>sample</portlet-name>
<portlet-class>com.liferay.portletmvc4spring.DispatcherPortlet</portlet-class>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode>
</supports>
<portlet-info>
<title>Sample Portlet</title>
</portlet-info>
</portlet>
The DispatcherPortlet
now needs to be configured.
In PortletMVC4Spring, each DispatcherPortlet
has its own WebApplicationContext
, which inherits all the beans already
defined in the Root WebApplicationContext
. These inherited beans can be overridden in the portlet-specific scope, and
new scope-specific beans can be defined local to a given portlet instance.
The framework will, on initialization of a DispatcherPortlet
, look for a file named [portlet-name]-portlet.xml
in
the WEB-INF
directory of your web application and create the beans defined there (overriding the definitions of any
beans defined with the same name in the global scope).
The config location used by the DispatcherPortlet
can be modified through a portlet initialization parameter (see
below for details).
The PortletMVC4Spring DispatcherPortlet
has a few special beans it uses, in order to be able to process requests and
render the appropriate views. These beans are included in the Spring framework and can be configured in the
WebApplicationContext
, just as any other bean would be configured. Each of those beans is described in more detail
below. Right now, we’ll just mention them, just to let you know they exist and to enable us to go on talking about the
DispatcherPortlet
. For most of the beans, defaults are provided so you don’t have to worry about configuring them.
Expression | Explanation |
---|---|
handler mapping(s) |
(Handler Mappings) a list of pre- and post-processors and controllers that will be executed if they match certain criteria (for instance a matching portlet mode specified with the controller) |
controller(s) |
(Annotation-Based Controller Programming Model) the beans providing the actual functionality (or at least, access to the functionality) as part of the MVC triad |
view resolver |
(Views and View Resolvers) capable of resolving view names to view definitions |
multipart resolver |
(Multipart (File Upload) Support) offers functionality to process file uploads from HTML forms |
handler exception resolver |
([portlet-ann-exceptionhandler]]) offers functionality to map exceptions to views or implement other more complex exception handling code |
When a DispatcherPortlet
is setup for use and a request comes in for that specific DispatcherPortlet
, it starts
processing the request. The list below describes the complete process a request goes through if handled by a
DispatcherPortlet
:
-
The locale returned by
PortletRequest.getLocale()
is bound to the request to let elements in the process resolve the locale to use when processing the request (rendering the view, preparing data, etc.). -
If a multipart resolver is specified and this is an
ActionRequest
, the request is inspected for multiparts and if they are found, it is wrapped in aMultipartActionRequest
for further processing by other elements in the process. (See Multipart (File Upload) Support for further information about multipart handling). -
If a multipart resolver is specified and this is an
ResourceRequest
, the request is inspected for multiparts and if they are found, it is wrapped in aMultipartResourceRequest
for further processing by other elements in the process. (See Multipart (File Upload) Support for further information about multipart handling). -
An appropriate handler is searched for. If a handler is found, the execution chain associated with the handler (pre- processors, post-processors, controllers) will be executed in order to prepare a model.
-
If a model is returned, the view is rendered, using the view resolver that has been configured with the
WebApplicationContext
. If no model is returned (which could be due to a pre- or post-processor intercepting the request, for example, for security reasons), no view is rendered, since the request could already have been fulfilled.
Exceptions that are thrown during processing of the request get picked up by any of the handler exception resolvers that
are declared in the WebApplicationContext
. Using these exception resolvers you can define custom behavior in case such
exceptions get thrown.
You can customize Spring’s DispatcherPortlet
by adding context parameters in the portlet.xml
file or portlet
init-parameters. The possibilities are listed below.
Parameter | Explanation |
---|---|
|
Class that implements |
|
String which is passed to the context instance (specified by |
|
The namespace of the |
|
The URL at which |
The rendering process in PortletMVC4Spring is a bit more complex than in Spring Web MVC. In order to reuse all the view
technologies from Spring Web MVC, the PortletRequest
/ PortletResponse
must be converted to HttpServletRequest
/
HttpServletResponse
and then call the render
method of the View
. To do this, DispatcherPortlet
uses a special
servlet that exists for just this purpose: the ViewRendererServlet
.
In order for DispatcherPortlet
rendering to work, you must declare an instance of the ViewRendererServlet
in the
web.xml
file for your web application as follows:
<servlet>
<servlet-name>ViewRendererServlet</servlet-name>
<servlet-class>com.liferay.portletmvc4spring.ViewRendererServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ViewRendererServlet</servlet-name>
<url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>
To perform the actual rendering, DispatcherPortlet
does the following:
-
Binds the
WebApplicationContext
to the request as an attribute under the sameWEB_APPLICATION_CONTEXT_ATTRIBUTE
key thatDispatcherServlet
uses. -
Binds the
Model
andView
objects to the request to make them available to theViewRendererServlet
. -
Constructs a
PortletRequestDispatcher
and performs aninclude
using the/WEB-INF/servlet/view
URL that is mapped to theViewRendererServlet
.
The ViewRendererServlet
is then able to call the render
method on the View
with the appropriate arguments.
The actual URL for the ViewRendererServlet
can be changed using DispatcherPortlet’s `viewRendererUrl
configuration
parameter.
Version 2.5 of the Spring Framework introduced an annotation-based programming model for MVC controllers, using
annotations such as @RequestMapping
, @RequestParam
, @ModelAttribute
, etc. This annotation support is available for
both Spring Web MVC and PortletMVC4Spring. Controllers implemented in this style do not have to extend specific base
classes or implement specific interfaces. Furthermore, they do not usually have direct dependencies on Servlet or
Portlet APIs, although they can easily get access to Servlet or Portlet facilities if desired.
The following sections document these annotations and how they are most commonly used in a Portlet environment.
The @RequestMapping
annotation will only be processed if a corresponding HandlerMapping
(for type level
annotations) and/or HandlerAdapter
(for method level annotations) is present in the dispatcher. This is the case by
default in both DispatcherServlet
and DispatcherPortlet
.
However, if you are defining custom HandlerMappings
or HandlerAdapters
, then you need to make sure that a
corresponding custom DefaultAnnotationHandlerMapping
and/or PortletRequestMappingHandlerAdapter
is defined as well -
provided that you intend to use @RequestMapping
.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.liferay.portletmvc4spring.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="com.liferay.portletmvc4spring.mvc.method.annotation.PortletRequestMappingHandlerAdapter"/>
// ... (controller bean definitions) ...
</beans>
Defining a DefaultAnnotationHandlerMapping
and/or PortletRequestMappingHandlerAdapter
explicitly also makes sense if
you would like to customize the mapping strategy, e.g. specifying a custom WebBindingInitializer
(see below).
The @Controller
annotation indicates that a particular class serves the role of a controller. There is no need to
extend any controller base class or reference the Portlet API. You are of course still able to reference
Portlet-specific features if you need to.
The basic purpose of the @Controller
annotation is to act as a stereotype for the annotated class, indicating its
role. The dispatcher will scan such annotated classes for mapped methods, detecting @RequestMapping
annotations (see
the next section).
Annotated controller beans may be defined explicitly, using a standard Spring bean definition in the dispatcher’s
context. However, the @Controller
stereotype also allows for autodetection, aligned with Spring’s general support for
detecting component classes in the classpath and auto-registering bean definitions for them.
To enable autodetection of such annotated controllers, you have to add component scanning to your configuration. This is easily achieved by using the spring-context schema as shown in the following XML snippet:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.springframework.samples.petportal.portlet"/>
// ...
</beans>
The @RequestMapping
annotation is used to map portlet modes like 'VIEW'/'EDIT' onto an entire class or a particular
handler method. Typically the type-level annotation maps a specific mode (or mode plus parameter condition) onto a form
controller, with additional method-level annotations 'narrowing' the primary mapping for specific portlet request
parameters.
Tip
|
In the following discussion, we’ll focus on controllers that are based on annotated handler methods. |
The following is an example of a form controller from the PetPortal sample application using this annotation:
@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {
private Properties petSites;
public void setPetSites(Properties petSites) {
this.petSites = petSites;
}
@ModelAttribute("petSites")
public Properties getPetSites() {
return this.petSites;
}
@RequestMapping // default (action=list)
public String showPetSites() {
return "petSitesEdit";
}
@RequestMapping(params = "action=add") // RENDER_PHASE
public String showSiteForm(Model model) {
// Used for the initial form as well as for redisplaying with errors.
if (!model.containsAttribute("site")) {
model.addAttribute("site", new PetSite());
}
return "petSitesAdd";
}
@RequestMapping(params = "action=add") // ACTION_PHASE
public void populateSite(@ModelAttribute("site") PetSite petSite,
BindingResult result, SessionStatus status, ActionResponse response) {
new PetSiteValidator().validate(petSite, result);
if (!result.hasErrors()) {
this.petSites.put(petSite.getName(), petSite.getUrl());
status.setComplete();
response.setRenderParameter("action", "list");
}
}
@RequestMapping(params = "action=delete")
public void removeSite(@RequestParam("site") String site, ActionResponse response) {
this.petSites.remove(site);
response.setRenderParameter("action", "list");
}
}
There are dedicated @ActionMapping
and @RenderMapping
(as well as @ResourceMapping
and @EventMapping
)
annotations which can be used instead:
@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {
private Properties petSites;
public void setPetSites(Properties petSites) {
this.petSites = petSites;
}
@ModelAttribute("petSites")
public Properties getPetSites() {
return this.petSites;
}
@RenderMapping // default (action=list)
public String showPetSites() {
return "petSitesEdit";
}
@RenderMapping(params = "action=add")
public String showSiteForm(Model model) {
// Used for the initial form as well as for redisplaying with errors.
if (!model.containsAttribute("site")) {
model.addAttribute("site", new PetSite());
}
return "petSitesAdd";
}
@ActionMapping(params = "action=add")
public void populateSite(@ModelAttribute("site") PetSite petSite,
BindingResult result, SessionStatus status, ActionResponse response) {
new PetSiteValidator().validate(petSite, result);
if (!result.hasErrors()) {
this.petSites.put(petSite.getName(), petSite.getUrl());
status.setComplete();
response.setRenderParameter("action", "list");
}
}
@ActionMapping(params = "action=delete")
public void removeSite(@RequestParam("site") String site, ActionResponse response) {
this.petSites.remove(site);
response.setRenderParameter("action", "list");
}
}
Handler methods which are annotated with @RequestMapping
are allowed to have very flexible signatures. They may have
arguments of the following types, in arbitrary order (except for validation results, which need to follow right after
the corresponding command object, if desired):
-
Request and/or response objects (Portlet API). You may choose any specific request/response type, e.g. PortletRequest / ActionRequest / RenderRequest. An explicitly declared action/render argument is also used for mapping specific request types onto a handler method (in case of no other information given that differentiates between action and render requests).
-
Session object (Portlet API): of type PortletSession. An argument of this type will enforce the presence of a corresponding session. As a consequence, such an argument will never be
null
. -
Preferences (Portlet API): of type PortletPreferences.
-
org.springframework.web.context.request.WebRequest
ororg.springframework.web.context.request.NativeWebRequest
. Allows for generic request parameter access as well as request/session attribute access, without ties to the native Servlet/Portlet API. -
java.util.Locale
for the current request locale (the portal locale in a Portlet environment). -
java.util.TimeZone
/java.time.ZoneId
for the current request time zone. -
java.io.InputStream
/java.io.Reader
for access to the request’s content. This will be the raw InputStream/Reader as exposed by the Portlet API. -
java.io.OutputStream
/java.io.Writer
for generating the response’s content. This will be the raw OutputStream/Writer as exposed by the Portlet API. -
@RequestParam
annotated parameters for access to specific Portlet request parameters. Parameter values will be converted to the declared method argument type. -
java.util.Map
/org.springframework.ui.Model
/org.springframework.ui.ModelMap
for enriching the implicit model that will be exposed to the web view. -
Command/form objects to bind parameters to: as bean properties or fields, with customizable type conversion, depending on
@InitBinder
methods and/or the HandlerAdapter configuration - see the "webBindingInitializer`" property on `PortletRequestMappingHandlerAdapter
. Such command objects along with their validation results will be exposed as model attributes, by default using the non-qualified command class name in property notation (e.g. "orderAddress" for type "mypackage.OrderAddress"). Specify a parameter-levelModelAttribute
annotation for declaring a specific model attribute name. -
org.springframework.validation.Errors
/org.springframework.validation.BindingResult
validation results for a preceding command/form object (the immediate preceding argument). -
org.springframework.web.bind.support.SessionStatus
status handle for marking form processing as complete (triggering the cleanup of session attributes that have been indicated by the@SessionAttributes
annotation at the handler type level).
The following return types are supported for handler methods:
-
A
ModelAndView
object, with the model implicitly enriched with command objects and the results of@ModelAttribute
annotated reference data accessor methods. -
A
Model
object, with the view name implicitly determined through aRequestToViewNameTranslator
and the model implicitly enriched with command objects and the results of@ModelAttribute
annotated reference data accessor methods. -
A
Map
object for exposing a model, with the view name implicitly determined through aRequestToViewNameTranslator
and the model implicitly enriched with command objects and the results of@ModelAttribute
annotated reference data accessor methods. -
A
View
object, with the model implicitly determined through command objects and@ModelAttribute
annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring aModel
argument (see above). -
A
String
value which is interpreted as view name, with the model implicitly determined through command objects and@ModelAttribute
annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring aModel
argument (see above). -
void
if the method handles the response itself (e.g. by writing the response content directly). -
Any other return type will be considered a single model attribute to be exposed to the view, using the attribute name specified through
@ModelAttribute
at the method level (or the default attribute name based on the return type’s class name otherwise). The model will be implicitly enriched with command objects and the results of@ModelAttribute
annotated reference data accessor methods.
The @RequestParam
annotation is used to bind request parameters to a method parameter in your controller.
The following code snippet from the PetPortal sample application shows the usage:
@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {
// ...
public void removeSite(@RequestParam("site") String site, ActionResponse response) {
this.petSites.remove(site);
response.setRenderParameter("action", "list");
}
// ...
}
Parameters using this annotation are required by default, but you can specify that a parameter is optional by setting
@RequestParam’s `required
attribute to false
(e.g., @RequestParam(name="id", required=false)
).
@ModelAttribute
has two usage scenarios in controllers. When placed on a method parameter, @ModelAttribute
is used
to map a model attribute to the specific, annotated method parameter (see the populateSite()
method below). This is
how the controller gets a reference to the object holding the data entered in the form. In addition, the parameter can
be declared as the specific type of the form backing object rather than as a generic java.lang.Object
, thus increasing
type safety.
@ModelAttribute
is also used at the method level to provide reference data for the model (see the getPetSites()
method below). For this usage the method signature can contain the same types as documented above for the
@RequestMapping
annotation.
Note
|
|
The following code snippet shows these two usages of this annotation:
@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {
// ...
@ModelAttribute("petSites")
public Properties getPetSites() {
return this.petSites;
}
@RequestMapping(params = "action=add") // ACTION_PHASE
public void populateSite( @ModelAttribute("site") PetSite petSite, BindingResult result, SessionStatus status, ActionResponse response) {
new PetSiteValidator().validate(petSite, result);
if (!result.hasErrors()) {
this.petSites.put(petSite.getName(), petSite.getUrl());
status.setComplete();
response.setRenderParameter("action", "list");
}
}
}
The type-level @SessionAttributes
annotation declares session attributes used by a specific handler. This will
typically list the names of model attributes or types of model attributes which should be transparently stored in the
session or some conversational storage, serving as form-backing beans between subsequent requests.
The following code snippet shows the usage of this annotation:
@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {
// ...
}
To customize request parameter binding with PropertyEditors, etc. via the Spring Framework’s WebDataBinder
, you can
either use @InitBinder
-annotated methods within your controller or externalize your configuration by providing a
custom WebBindingInitializer
.
Annotating controller methods with @InitBinder
allows you to configure web data binding directly within your
controller class. @InitBinder
identifies methods which initialize the WebDataBinder
which will be used for
populating command and form object arguments of annotated handler methods.
Such init-binder methods support all arguments that @RequestMapping
supports, except for command/form objects and
corresponding validation result objects. Init-binder methods must not have a return value. Thus, they are usually
declared as void
. Typical arguments include WebDataBinder
in combination with WebRequest
or java.util.Locale
,
allowing code to register context-specific editors.
The following example demonstrates the use of @InitBinder
for configuring a CustomDateEditor
for all
java.util.Date
form properties.
@Controller
public class MyFormController {
@InitBinder
protected void initBinder(WebDataBinder webDataBinder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
webDataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
To externalize data binding initialization, you can provide a custom implementation of the WebBindingInitializer
interface, which you then enable by supplying a custom bean configuration for an PortletRequestMappingHandlerAdapter
,
thus overriding the default configuration.
If a controller method throws an exception, then methods annotated with @ExceptionHandler
will be called in order to
provide a way for the developer to gracefully handle exceptions. The method signatures can take various different
arguments. The typical return type is void
but if the method returns type String
then the return value will be
written to the response as portlet markup. For example:
@ExceptionHandler
public void handleException(Exception ex, Writer writer) throws IOException {
// Write the exception message to the response as portlet markup.
writer.write(ex.getMessage());
}
@ExceptionHandler(IOException.class)
public String handleIOException(IOException ex, PortletRequest request) {
// Write the portlet lifecycle and exception message to the response
// as portlet markup by returning a String.
return portletRequest.getAttribute(PortletRequest.LIFECYCLE_PHASE) + ":" + ex.getMessage();
}
@ExceptionHandler({ BindException.class, IllegalArgumentException.class })
public String handle1(Exception ex, PortletRequest request, PortletResponse response) {
// Write the exception class name to the response by returning a String.
return ClassUtils.getShortName(ex.getClass());
}
As mentioned previously, PortletMVC4Spring directly reuses all the view technologies from Spring Web MVC. This includes
not only the various View
implementations themselves, but also the ViewResolver
implementations. For more
information, refer to View
Technologies in the Spring Framework documentation.
Note
|
In order to help developers get started quickly, the PortletMVC4Spring project includes demos and Maven archetypes that show how to use JSP and/or Thymeleaf. See the Project Page at GitHub for more details. |
A few items on using the existing View
and ViewResolver
implementations are worth mentioning:
-
Most portals expect the result of rendering a portlet to be an HTML fragment. So, things like JSP/JSTL, Thymeleaf, Velocity, FreeMarker, and XSLT all make sense. But it is unlikely that views that return other document types will make any sense in a portlet context.
-
There is no such thing as an HTTP redirect from within a portlet (the
sendRedirect(..)
method ofActionResponse
cannot be used to stay within the portal). So,RedirectView
and use of the'redirect:'
prefix will not work correctly from within PortletMVC4Spring. -
It may be possible to use the
'forward:'
prefix from within PortletMVC4Spring. However, remember that since you are in a portlet, you have no idea what the current URL looks like. This means you cannot use a relative URL to access other resources in your web application and that you will have to use an absolute URL.
Also, for JSP development, the new Spring Taglib and the new Spring Form Taglib both work in portlet views in exactly the same way that they work in servlet views.
PortletMVC4Spring has built-in multipart support to handle file uploads in portlet applications, just like Spring Web
MVC does. The design for the multipart support is done with pluggable PortletMultipartResolver
objects, defined in the
com.liferay.portletmvc4spring.multipart
package. PortletMVC4Spring provides a StandardPortletMultipartResolver
for use with the JSR-362 standard file upload feature. For legacy purposes, it also provides a
PortletMultipartResolver
for use with Commons FileUpload. How uploading
files is supported will be described in the rest of this section.
By default, no multipart handling will be done by PortletMVC4Spring, as some developers will want to handle multiparts
themselves. You will have to enable it yourself by adding a multipart resolver to the web application’s context. After
you have done that, DispatcherPortlet
will inspect each request to see if it contains a multipart. If no multipart is
found, the request will continue as expected. However, if a multipart is found in the request, the
PortletMultipartResolver
that has been declared in your context will be used. After that, the multipart attribute in
your request will be treated like any other attribute.
Note
|
Any configured |
The following example shows how to use the StandardPortletMultipartResolver
:
<bean id="portletMultipartResolver"
class="com.liferay.portletmvc4spring.multipart.StandardPortletMultipartResolver">
</bean>
The following example shows how to use the legacy CommonsPortletMultipartResolver
:
<bean id="portletMultipartResolver"
class="com.liferay.portletmvc4spring.multipart.CommonsPortletMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="100000"/>
</bean>
If you are using the CommonsPortletMultipartResolver
, then you also need to put the appropriate jars in your classpath
for the multipart resolver to work. In the case of the CommonsMultipartResolver
, you need to use
commons-fileupload.jar
. Be sure to use at least version 1.3.3 of Commons FileUpload.
Now that you have seen how to set PortletMVC4Spring up to handle multipart requests, let’s talk about how to actually
use it. When DispatcherPortlet
detects a multipart request, it activates the resolver that has been declared in your
context and hands over the request. What the resolver then does is wrap the current PortletRequest
in either a
MultipartActionRequest
or MultipartResourceRequest
that has support for multipart file uploads. Using the wrapped
request, you can get information about the multiparts contained by this request and actually get access to the multipart
files themselves in your controllers.
Note
|
You can only receive multipart file uploads as part of an |
After the PortletMultipartResolver
has finished doing its job, the request will be processed like any other. To use
the PortletMultipartResolver
, you must declare it in your Spring configuration descriptor:
<bean id="portletMultipartResolver"
class="com.liferay.portletmvc4spring.multipart.StandardPortletMultipartResolver"/>
Next, create a form with an upload field:
<h1>Please upload one or more files</h1>
<portlet:actionURL name="uploadFiles" var="fileUploadActionURL"/>
<form:form action="${fileUploadActionURL}" enctype="multipart/form-data"
method="post" modelAttribute="transientUpload">
<input name="multipartFiles" multiple="multiple" type="file"/>
<input type="submit"/>
</form:form>
Next, create a Data Transfer Object (DTO) that will temporarily contain the uploaded file data during the scope of of the request as it exists in the model. The DTO must have a JavaBeans property that matches the name of the input field from the form (in this example, "multipartFiles") that will automatically be bound to the value of the submitted files:
public class TransientUpload {
private List<MultipartFile> multipartFiles = new ArrayList<>();
public List<MultipartFile> getMultipartFiles() {
return multipartFiles;
}
public void setMultipartFiles(List<MultipartFile> multipartFiles) {
this.multipartFiles = multipartFiles;
}
}
Finally, create the controller that processes the uploaded files from the model:
@Controller
@RequestMapping("VIEW")
public class FileUploadController {
@ActionMapping("uploadFiles")
public void uploadFiles(
@ModelAttribute("transientUpload") TransientUpload transientUpload) {
List<MultipartFile> transientMultipartFiles = transientUpload.getMultipartFiles();
if (transientMultipartFiles != null) {
for (MultipartFile transientMultipartFile : transientMultipartFiles) {
// process file
}
}
}
}
Note
|
Instead of receiving uploaded files as type |
The following code snippet shows an example of how to register custom editors that facilitate file upload conversion:
@InitBinder
protected void initBinder(WebDataBinder webDataBinder) {
// Ability to convert uploaded files to byte arrays so that the
// TransientFileUpload calss can contain List<byte[]> instead
// of List<MultipartFile>
webDataBinder.registerCustomEditor(byte[].class,
new org.springframework.web.multipart.support.ByteArrayMultipartFileEditor());
// Ability to convert uploaded files to strings so that the
// TransientFileUpload class can contain List<String> instead
// of List<MultipartFile>
webDataBinder.registerCustomEditor(String.class,
new org.springframework.web.multipart.support.StringMultipartFileEditor());
}
The process of deploying a PortletMVC4Spring application is no different than deploying any JSR-362 portlet application.
Generally, the portal/portlet container runs in one webapp in your servlet container and your portlets run in another
webapp in your servlet container. In order for the portlet container webapp to make calls into your portlet webapp it
must make cross-context calls to a well-known servlet that provides access to the portlet services defined in your
portlet.xml
file.
The JSR-362 specification does not specify exactly how this should happen, so each portlet container has its own mechanism for this, which usually involves some kind of "deployment process" that makes changes to the portlet webapp itself and then registers the portlets within the portlet container.
At a minimum, the web.xml
file in your portlet webapp is modified to inject the well-known servlet that the portlet
container will call. In some cases a single servlet will service all portlets in the webapp, in other cases there will
be an instance of the servlet for each portlet.
Some portlet containers will also inject libraries and/or configuration files into the webapp as well. The portlet container must also make its implementation of the Portlet JSP Tag Library available to your webapp.
The bottom line is that it is important to understand the deployment needs of your target portal and make sure they are met (usually by following the automated deployment process it provides). Be sure to carefully review the documentation from your portal for this process.
Once you have deployed your portlet, review the resulting web.xml
file for sanity. Some older portals have been known
to corrupt the definition of the ViewRendererServlet
, thus breaking the rendering of your portlets.
Just like Spring Web MVC, PortletMVC4Spring provides HandlerExceptionResolver
s to ease the pain of unexpected
exceptions that occur while your request is being processed by a handler that matched the request. PortletMVC4Spring
also provides a portlet-specific, concrete SimpleMappingExceptionResolver
that enables you to take the class name of
any exception that might be thrown and map it to a view name.
Note
|
The legacy controller programing model is not recommended for new development. Instead, the annotation-driven controller programming model should be considered. |
The legacy controller programming model in PortletMVC4Spring are very similar to Spring Web MVC controllers.
The basis for the PortletMVC4Spring controller architecture is the com.liferay.portletmvc4spring.mvc.Controller
interface, which is listed below.
public interface Controller {
/**
* Process the render request and return a ModelAndView object which the
* DispatcherPortlet will render.
*/
ModelAndView handleRenderRequest(RenderRequest request,
RenderResponse response) throws Exception;
/**
* Process the action request. There is nothing to return.
*/
void handleActionRequest(ActionRequest request,
ActionResponse response) throws Exception;
}
As you can see, the Portlet Controller
interface requires two methods that handle the two phases of a portlet request:
the action request and the render request. The ACTION_PHASE
should be capable of handling an action request, and the
RENDER_PHASE
should be capable of handling a render request and returning an appropriate model and view. While the
Controller
interface is quite abstract, PortletMVC4Spring offers several controllers that already contain a lot of the
functionality you might need; most of these are very similar to controllers from Spring Web MVC. The Controller
interface just defines the most common functionality required of every controller: handling an action request, handling
a render request, and returning a model and a view.
Of course, just a Controller
interface isn’t enough. To provide a basic infrastructure, all of PortletMVC4Spring’s
Controller
s inherit from AbstractController
, a class offering access to Spring’s ApplicationContext
and control
over caching.
Parameter | Explanation |
---|---|
|
Indicates whether or not this |
|
Use this if you want handling by this controller to be synchronized on the user’s session. To be more specific, the
extending controller will override the |
|
If you want your controller to actually render the view when the portlet is in a minimized state, set this to true. By default, this is set to false so that portlets that are in a minimized state don’t display any content. |
|
When you want a controller to override the default cache expiration defined for the portlet, specify a positive
integer here. By default it is set to |
The requireSession
and cacheSeconds
properties are declared on the PortletContentGenerator
class, which is the
superclass of AbstractController
) but are included here for completeness.
When using the AbstractController
as a base class for your controllers (which is not recommended since there are a lot
of other controllers that might already do the job for you) you only have to override either the
handleActionRequestInternal(ActionRequest, ActionResponse)
method or the handleRenderRequestInternal(RenderRequest,
RenderResponse)
method (or both), implement your logic, and return a ModelAndView
object (in the case of
handleRenderRequestInternal
).
The default implementations of both handleActionRequestInternal(..)
and handleRenderRequestInternal(..)
throw a
PortletException
. This is consistent with the behavior of GenericPortlet
from the JSR-362 Specification Portlet 3.0
API. So you only need to override the method that your controller is intended to handle.
Here is short example consisting of a class and a declaration in the web application context.
package samples;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import com.liferay.portletmvc4spring.mvc.AbstractController;
import com.liferay.portletmvc4spring.ModelAndView;
public class SampleController extends AbstractController {
public ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response) {
ModelAndView mav = new ModelAndView("foo");
mav.addObject("message", "Hello World!");
return mav;
}
}
<bean id="sampleController" class="samples.SampleController">
<property name="cacheSeconds" value="120"/>
</bean>
The class above and the declaration in the web application context is all you need besides setting up a handler mapping (see Handler Mappings) to get this very simple controller working.
Although you can extend AbstractController
, PortletMVC4Spring provides a number of concrete implementations which
offer functionality that is commonly used in simple MVC applications.
The ParameterizableViewController
is basically the same as the example above, except for the fact that you can specify
the view name that it will return in the web application context (no need to hard-code the view name).
The PortletModeNameViewController
uses the current mode of the portlet as the view name. So, if your portlet is in
View mode (i.e. PortletMode.VIEW
) then it uses "view" as the view name.
Instead of developing new controllers, it is possible to use existing portlets and map requests to them from a
DispatcherPortlet
. Using the PortletWrappingController
, you can instantiate an existing Portlet
as a Controller
as follows:
<bean id="myPortlet" class="com.liferay.portletmvc4spring.mvc.PortletWrappingController">
<property name="portletClass" value="sample.MyPortlet"/>
<property name="portletName" value="my-portlet"/>
<property name="initParameters">
<value>config=/WEB-INF/my-portlet-config.xml</value>
</property>
</bean>
This can be very valuable since you can then use interceptors to pre-process and post-process requests going to these portlets. Alternatively, you can use a portlet filter as defined by the JSR-362 Specification.
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
|
The term "Handler" is intentionally used here instead of "Controller". |
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 PortletMVC4Spring’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 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 theorg.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="com.liferay.portletmvc4spring.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="com.liferay.portletmvc4spring.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.
The bean configuration for this mapping will look something like this:
<bean class="com.liferay.portletmvc4spring.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 PortletMVC4Spring implements these concepts in the same way as Spring Web MVC.
Interceptors located in the handler mapping must implement HandlerInterceptor
from the com.liferay.portletmvc4spring
package. Just like Spring Web MVC, 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.