How to develop portlets with Spring MVC Portlet in Liferay
If you work as a portlet developer on Liferay Portal based applications, you will probably know that Liferay allows you to use lots of different frameworks in order to accomplish this goal. For instance, you are free to use Struts, JSF, Vaadin, Spring or any other framework you prefer. In adition, Liferay provides out of the box two extra frameworks/tools that can help us in our development tasks:
- Liferay MVC Portlet framework: a simple, lightweight and easy to understand framework for portlets development. Using this framework, we can manage easily portlet lifecycle and all the requests that it can serve. This is an alternative to third party frameworks such as Spring MVC Portlet, Struts or JSF.
- Service Builder: using this tool, you will be able to build in an easy way persistence and service layer for your application. This tool is used by Liferay in order to implement its own portlets.
Both of them are great framewoks and, probably, we will write about them in next articles. But, this article, is intended to be a simple guide for those Liferay developers who are interested in use Springframework in their portlets. In this article, we will talk about Spring MVC Portlet but, in coming articles, we will talk also about Spring Data and JPA Repositories. If you are wondering why to use Springframework if Liferay provides its own frameworks, the answer is simple: because you can. We live in a world of possibilities, and, if we work with Liferay Portal, we are free to chose how to develop our applications, so let's do it. Furthermore, maybe our team are used to work with some specific frameworks in other cotexts so, if we can continue to use these frameworks, we become even more productive.
Spring MVC Portlet
Set up
First of all, we will learn how to set up our portlet in order to use Spring MVC Portlet instead of Liferay MVC Portlet or anyother framework. Please, notice that the examples used in this article are based on Liferay CE 6.2, but they could be simply ported to previous versions of the product. We will need to add into our project some dependencies. The way in which we achieve this task will depend on the tools we are using to manage our project. For instance, if we use Maven, we will add to our pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc-portlet</artifactId>
<version>${org.springframework.version}</version>
</dependency>
If we use liferay plugins SDK (with Ivy), gradle or any other tool we will need to adapt the previous configuration to our preferred tool. Now, we have all the dependencies that we need, so it's time to start setting up our portlet:
web.xml
This file is located in the WEB-INF folder of our portlet (if this file doesn't exists, we will create it). The content of this file will be the following:
<?xml version="1.0"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-portlet.xml</param-value>
</context-param>
<servlet>
<servlet-name>ViewRendererServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ViewRendererServlet</servlet-name>
<url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>
In this step, we declare an instance of the ViewRendererServlet. The purpose of this servlet is to convert PorltetRequest and PortletResponse to HttpServletRequest and HttpServletResponse in order to leverage all the view technologies that Spring Web MVC provides. So, this servlet acts as a bridge from Portlet requests to Servlet requests. On the other hand, notice that, using the context-param element, we define the file that is going to act as the application context for this portlet.
Portlet.xml
In this file, we need to setup which class will manage the portlet lifecycle and, if needed, where is the context configuration file:
<portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
<init-param>
<name>contextConfigLocation</name>
<value>/WEB-INF/spring-portlet.xml</value>
</init-param>
The purpose of this class is to dispatch to registered handlers for processing a portlet request. It will use a ViewResolver implementation for the views resolution. If we don't specify the contextConfigLocation param, Liferay will use the default one which has the same name as the portlet plus ".xml" and which is located in the WEB-INF folder. If we configure a new one, commonly, it should be the same that we define in web.xml (basically, the one defined in web.xml is used by Spring ContextLoaderListener, and the other one, is loaded by Liferay).
Application context
Now, we need to add a new file for configure our application. The name and location of this file are defined in the web.xml file with the contextParamName parameter. For us, this file is: WEB-INF/spring-portlet.xml. In this first stage of the portlet, the content of this file can be as follows:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.mimacom.sample.spring.portlet.*" />
<!-- Default View Resolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:cache="false" p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
</beans>
As we can see, this is a traditional application context definition for Spring applications, there is nothing special due to this application being a portlet. In this case, Spring is configure to auto discovery components or beans within the base package "com.mimacom.sample.spring.portlet.*", so it will register every portlet into the Spring container automatically. In addition, in this file we define the view resolver which is going to be used by the Spring dispatcher. According to this configuration, our views will be located in the folder /WEB-INF/jsp with the .jsp extension.
And now… let's use it
If we take a look at Spring MVC Portlet, we will notice that is very similar to Spring MVC. But, keep in mind that we are now developing a portlet, so according to the Portlet 2.0 Specification (JSR 286), we need to control the whole portlet lifecycle. This means that we will have some new annotations, and we must to know them in order to use the correct one for each mode and eache phase of the portlet:
@Controller and @RequestMapping(mode)
We will use both of them to annotate our controller class. @Controller is a well-known annotation and, if we have configured spring to auto discovery components, we will need it to register our controller into the Spring container. On the other hand, @RequequestMapping is used to specify in which portlet mode this controller is going to work. At least, Spring requires one controller for VIEW mode:
@Controller
@RequestMapping("VIEW")
public class ViewController {
…
}
There is no problem about having more than one class for the same mode, we can use as many as needed in our application. Basically, this will depend on our architecture approach:
@Controller
@RequestMapping("VIEW")
public class AddUserController {
// ...
}
@Controller
@RequestMapping("VIEW")
public class RemoveUserController {
// ...
}
@RenderMapping, @ActionMapping, @ResourceMapping and @EventMapping
These are method level annotations which are related with the phase of our portlet. Thereby, these annotations map requests to handler methods. So, if a method is intended to be invoked in the render phase, we will annotate this method with @RenderMapping and the same for Action, ServeResource and Event phases. We have some optional elements that we can add to the annotation in order to make it more specific: @RenderMapping accepts the following elements:
- String value: this element will specify the window state of the portlet that the annotated mehod applies for (MAXIMIZED, MINIMIZED, NORMAL, etc.). If not specified, the mehotd will be invoked for any window state.
- String[] params: the parameters that the request must contain in order to invoke this method. The format of this params is: "paramName = paramValue". If we don't add any param here, the method will be invoked as the default render method in the mode in which the controller applies. Spring MVC Portlet requires one and only one default render method for VIEW mode.
@ActionMaping accepts the same optional elements as @RenderMapping. On the other hand, @ResourceMapping accepts the element "String value", which identify the resource to be handled. This id must be unique per portlet mode. If not specified, this method will be invoked for any resource request. Finally, @EventMapping, also accepts the optional element "String value", which, in this case, will be used to identified the name of the event to be handled. As occurred with the @ResourceMapping annotations, this name must be unique per portlet mode and, if not specified, this mehotd will be invoked for any event request. In the following example, we can see these annotations working together:
@RenderMapping
public String defaultView() {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Default View");
}
return Views.DEFAULT_VIEW;
}
@RenderMapping(params = "render=alternative-view")
public String alternativeView() {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Alternative view");
}
return Views.ALTERNATIVE_VIEW;
}
@ActionMapping(params = "action=action-one")
public void actionOne() {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Action one");
}
// Returns control to default view
}
@ActionMapping(params = "action=action-two")
public void actionTwo(ActionResponse actionResponse) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Action two");
}
// Returns control to alternative view
actionResponse.setRenderParameter("render", "alternative-view");
}
@ResourceMapping(value = "resource-one")
public void resourceOne() {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Resource one");
}
}
We notice a couple of things from the previous example:
- As we know, all the requests handled by a portlet finish in a render (except ServeResource, which takes control of the whole response). So, there is a way to redirect our portlet to a specific method from an Action or Event method. This way is the setRenderParameter from the ActionResponse and the EventResponse. If not specified, default render will handle render phase.
- Adaptable method signature: we can define any controller method signature we need. We can add specific portlet request and response, session object, Locale, @RequestParam, command/form objects, etc. Thereby, we can use only what we need.
Really, if we are used to work with Spring MVC, all of this will be familiar to us. We only need to keep in mind the portlet lifecycle and use these new annotations. So, we can leverage Spring MVC framework in the same way that we would do in any other web application: forms, validators, spring taglibs, etc. Anyway, we can deal with this topic in next articles in this blog. In this link, you can find a complete example with Spring MVC Portlet.