Sunday, February 24, 2013

Integrating Spring, Vaadin & Liferay

As there are numerous materials on how to integrate Vaadin with Spring, Spring with Liferay (using portlet dispatcher) and Vaadin with Liferay, the integration of all three technologies at once is not documented well.
In such approach, there are two major differences between Vaadin with Spring in a standard web application and portlets-based one. This post is based on Vaadin 6, but the rules itself apply to Vaadin 7 as well.

The setup

The solution requires that we will overwrite two methods defined in com​.vaadin​.terminal​.gwt​.server​.AbstractApplicationPortlet2.
To achieve that, we have to create our own subclass:
package com.tomeklipski.blog.sample.vaadin;

import com.vaadin.Application;
import com.vaadin.terminal.gwt.server.ApplicationPortlet2;

import javax.portlet.*;
import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 *
 * @author tomek@lipski.net.pl
 *         Date: 2/23/13 4:06 PM
 */
public class MyApplicationPortlet extends ApplicationPortlet2 {

/* overwritten methods here */

}
The newly created class has to be used in web.xml:
<servlet>
    <servlet-name>VaadinPortletServlet</servlet-name>
    <servlet-class>com.liferay.portal.kernel.servlet.PortletServlet</servlet-class>
    <init-param>
        <param-name>portlet-class</param-name>
        <param-value>com.tomeklipski.blog.sample.vaadin.MyApplicationPortlet</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
</servlet>
And in portlet.xml as well:
<portlet>
    <portlet-name>MyPortlet</portlet-name>
    <display-name>My Sample Portlet</display-name>
    <portlet-class>com.tomeklipski.blog.sample.vaadin.MyApplicationPortlet</portlet-class>

    <init-param>
        <name>application</name>
        <value>com.tomeklipski.sample.vaadin.SomeApplication</value>
    </init-param>
    <init-param>
        <name>widgetset</name>
        <value>com.vaadin.portal.gwt.PortalDefaultWidgetSet</value>
    </init-param>
    <supports>
        <mime-type>text/html</mime-type>
        <portlet-mode>view</portlet-mode>
    </supports>
    <portlet-info>
        <title>My Sample Portlet</title>
        <short-title>Sample</short-title>
    </portlet-info>
</portlet>

Accessing Spring context in portlet

First of all - portlets are configured using listeners as well. Liferay adds its listeners to web.xml during deploy, so listeners in web.xml might look like this:
<listener>
    <listener-class>
        com.liferay.portal.kernel.servlet.PluginContextListener
    </listener-class>
</listener>
<listener>
    <listener-class>
        com.liferay.portal.kernel.servlet.SerializableSessionAttributeListener
    </listener-class>
</listener>
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener       
    </listener-class>
</listener>
<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>
<listener>
    <listener-class>
        com.liferay.portal.kernel.servlet.PortletContextListener
        </listener-class>
</listener>


As you can see, they are all mixed up and we cannot rely that during invocation of method javax.portlet.Portlet.init(PortletConfig config) the spring context is available.
If we want to refer to our application as a Spring bean, we have to overwrite method com​.vaadin​.terminal​.gwt​.server​.AbstractApplicationPortlet​.getNewApplication​(​PortletRequest request), and attempt to retrieve Spring context here:
@Override
protected Application getNewApplication(PortletRequest request) {
    PortletContext portletContext = getPortletContext();
    ApplicationContext webApplicationContext
            = PortletApplicationContextUtils.getWebApplicationContext(portletContext);
    Application app = (Application) webApplicationContext.getBean("app");
    return app;
}
The fetching of portlet and web application contexts can of course be optimized.

Accessing "session" scope

The "session" scope in Spring is a convient way to store objects related to a session, for example:
<bean name="app" class="com.tomeklipski.sample.vaadin.SomeApplication" scope="session">
    <property name="dataManager" ref="dataManager" />
    <property name="i18nManager" ref="i18nManager" />
</bean>
In standard web application, we can enable it using HttpRequestListener in web.xml:
<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>
But, in JSR286 portlet, the listener is not invoked as it should and portlet specification does not provide the ability to add portlet request listeners as above.
Luckily, we can overwrite the method com​.vaadin​.terminal​.gwt​.server​.AbstractApplicationPortlet​.handleRequest​(​PortletRequest request, PortletResponse response) with a code, that will set appropriate ThreadContext values:
@Override
protected void handleRequest(PortletRequest request,
                             PortletResponse response)
        throws PortletException, IOException {
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContextHolder.setLocaleContext(
            new SimpleLocaleContext(request.getLocale()), false);

    // Expose current RequestAttributes to current thread.
    RequestAttributes previousRequestAttributes = RequestContextHolder
            .getRequestAttributes();
    PortletRequestAttributes requestAttributes = null;
    if (previousRequestAttributes == null ||
            previousRequestAttributes.getClass()
                    .equals(PortletRequestAttributes.class)) {
        requestAttributes = new PortletRequestAttributes(request);
        RequestContextHolder.setRequestAttributes(requestAttributes,
                false);
    }

    try {
        super.handleRequest(request, response);
    } finally {
        LocaleContextHolder.setLocaleContext(previousLocaleContext,
                false);
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(
                    previousRequestAttributes, false);
            requestAttributes.requestCompleted();
        }
    }
}

Summary

It is fairly easy to use Spring in Vaadin portlets - but there are some differences between standard Vaadin-Spring application approach and portletized one. We can easily overcome them by overwriting two methods in com​.vaadin​.terminal​.gwt​.server​.AbstractApplicationPortlet.

1 comment :

  1. Wouldn't it be more elegant to use portlet filters:

    https://portlet-container.java.net/docs/jsr286.html#Portlet_Filters

    instead of overriding handleRequest​(​PortletRequest request, PortletResponse response)?

    ReplyDelete