How to develop portlets with Spring MVC Portlet in Liferay

July 7, 2015

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:

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:

@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:

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.

Useful Links

About the author: Alberto Martínez Ballesteros

Free software enthusiast, beta-tester of new technologies by vocation and software architect by profession. I love new technologies, software development, playing with almost all the gadgets that have a screen or a lithium battery and, in short, getting computers to do what I want and not what they want ... Skynet, you will not win this battle.

Comments
Join us