Monday, August 15, 2011

Simple MVC Spring Portlet in Liferay6.0 in plug-sdk

Designing spring portlet in Liferay Plugin-SDK environment, before I directly move to configuration files like web.xml, portlet.xml etc. Let's know all the dependency jars spring.jar, spring-webmvc.jar, spring-webmvc-portlet.jar put these jar files in lib folder. Other are portal dependency jars (commons-logging.jar, commons-lang.jar, commons-collections.jar, commons-beanutils.jar, log4j.jar, jstl-api.jar, jstl-impl.jar, commons-dbcp.jar, commons-pool.jar, commons-digester.jar, spring-core.jar). Some of the jars are commonly required in designing simple portlet still I have listed it.
Note: In order to understand simple mvc spring portlet we need at least basic understanding of how spring frame work, If you are interacting first time please go through http://www.vaannila.com/spring/spring-mvc-tutorial-1.html at least once.
For designing spring portlet in Liferay6.0 plugin-sdk we need two more xml files than the regular portlet. For simple mvc spring portlet these files are common.xml, and SimpleSpringPortletContext.xml. You will get to know more once you see what is inside matter. Go through the folder structure for simple spring portlet



For simple spring mvc portlet we need some more entries in different xml files, such as listeners, loaders etc which must be available when our portlet is initialized. I will try to focus on these extra entries. Lets start with

1. web.xml


 <?xml version="1.0" encoding="UTF-8"?>
<web-app 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" version="2.4">
    <display-name>CCMC Billing-Portlet</display-name>
    <description>Spring Portlet MVC CalCAS application</description>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/context/common.xml</param-value>
    </context-param>
    <!--
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
    -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.liferay.portal.kernel.servlet.PortletContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>view-servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>view-servlet</servlet-name>
        <url-pattern>/WEB-INF/servlet/view</url-pattern>
    </servlet-mapping>  
    <jsp-config>
    </jsp-config>
    <login-config>
        <auth-method>BASIC</auth-method>
    </login-config>
</web-app>

As we can see common.xml has loaded as context parameter. Reason is in we declare bean for view resolver inside this file. This bean has two properties of prefix and suffix as bellow,

2. Common.xml

<?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:ehcache="http://www.springmodules.org/schema/ehcache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springmodules.org/schema/ehcache
http://www.springmodules.org/schema/cache/springmodules-ehcache.xsd">
   
        <!-- Message source for this context, loaded from localized "messages_xx" files -->
    <!--    
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>messages</value>
            </list>
        </property>                                       
    </bean>
     -->
    <!-- Default View Resolver -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
   
    <!-- This interceptor forwards the mapping request parameter from an ActionRequest to be used as a render parameter. -->
    <bean id="parameterMappingInterceptor" class="org.springframework.web.portlet.handler.ParameterMappingInterceptor"/>

    <!-- Abstract Default ExceptionHandler -->
    <!--
    <bean id="defaultExceptionHandlerTemplate" class="org.springframework.web.portlet.handler.SimpleMappingExceptionResolver" abstract="true">
        <property name="defaultErrorView" value="error"/>
        <property name="exceptionMappings">
            <props>
                <prop key="javax.portlet.PortletSecurityException">unauthorized</prop>
                <prop key="javax.portlet.UnavailableException">unavailable</prop>
            </props>
        </property> 
    </bean>
     -->
</beans>



For now you just create file and make respective entries only I will get into details at appropriate time in the flow.
Now we are done with web.xml now lets move forward for portlet specific xml files starting with portlet.xml

3. Portlet.xml 

<?xml version="1.0"?>
<portlet-app
    version="2.0"
    xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
>
    <portlet>
        <portlet-name>simple-spring</portlet-name>
        <display-name>Simple Spring</display-name>
        <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
        <init-param>
            <name>contextConfigLocation</name>
            <value>/WEB-INF/context/SimpleSpringPortletContext.xml</value>
        </init-param>
        <expiration-cache>0</expiration-cache>
        <supports>
            <mime-type>text/html</mime-type>
        </supports>
        <portlet-info>
            <title>Simple Spring</title>
            <short-title>Simple Spring</short-title>
            <keywords>Simple Spring</keywords>
        </portlet-info>
    </portlet>
</portlet-app>

As we can observe few changes than that of simple mvc portlet
1.  portlet class => For simple mvc portlet its our CustomPortletClass extending GenericPortlet. For spring portlet    we just need to make an entry of DispatcherPortlet which is spring specific and CustomPortletClass extends AbstractController we will get into details in few minutes. 
2.  Init-Param => In simple mvc portlet we an entry of different jsp's for different modes. For spring portlet     It's new xml file in our case its SimpleSpringPortletContext.xml lets see what is inside this file

4. SimpleSpringPortletContext.xml : This xml is responsible for handling portlet modes as bellow

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- Controllers -->
    <bean id="simpleSpringPortletController" class="com.simple.spring.portlet.controller.SimpleSpringPortletController">
       
    </bean>
    <!-- Handler Mappings -->
    <bean id="portletModeParameterHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping">
      <property name="order" value="1"/>
      <property name="interceptors">
            <list>
                <ref bean="parameterMappingInterceptor"/>
            </list>
        </property>
        <property name="defaultHandler">
            <ref bean="simpleSpringPortletController"/>
        </property>
        <property name="portletModeParameterMap">
            <map>
                <entry key="view">
                    <map>
                       
                    </map>
                </entry>
            </map>
        </property>
    </bean>
    <bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
        <property name="order" value="2"/>
        <property name="portletModeMap">
            <map>
            </map>
        </property>
    </bean>   
</beans>

This xml is for portlet handler mapping more than that is

First bean we create of type SimpleSpringPortletController. We can have a question that what is this controller While explaining portlet.xml I said that in spring portlet our portlet class extends AbstractController.
"com.simple.spring.portlet.controller" is package name.
Now create one SimpleSpringPortletController.java extends AbstractController with above package name inside src folder. which should look like this

SimpleSpringPortletController.java

package com.simple.spring.portlet.controller;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.portlet.mvc.AbstractController;
import org.springframework.web.portlet.ModelAndView;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.portlet.*;


public class SimpleSpringPortletController extends AbstractController{
   
    public ModelAndView handleRenderRequest(RenderRequest request,RenderResponse response) throws Exception{
        ModelAndView modelAndView = null;
        String renderParam = request.getParameter("renderParam");
        String userName = request.getParameter("userName");
        if(renderParam != null && renderParam.equalsIgnoreCase("displayName")){
            modelAndView = new ModelAndView("displayName");
        }else{
            modelAndView = new ModelAndView("view");
        }
        return modelAndView;
    }

    public void handleActionRequest(ActionRequest request, ActionResponse response) throws Exception {
        //logger.info("handleActionRequest(request,response) ends");
        String userName = request.getParameter("userName");
        response.setRenderParameter("userName", userName);
        response.setRenderParameter("renderParam", "displayName");
    }
}

Use of prefix and suffix :
While working with spring framework we need to extend AbstractController in which we return ModelAndView object with name of jsp. In common.xml we have defined prefix which is prepend to name of ModelAndView and suffix is appended to the same. As in my case I am setting name as "view" so it will look at location "/jsp/view.jsp" and your request will be moved to view.jsp.

Create view.jsp on the /jsp/view.jsp path.

Remaining portlet specific files liferay-portlet.xml, and liferay-display.xml will remain same just like simple mvc portlets.as

5. Liferay-portlet.xml

<?xml version="1.0"?>
<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.0.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_6_0_0.dtd">

<liferay-portlet-app>
    <portlet>
        <portlet-name>simple-spring</portlet-name>
        <icon>/icon.png</icon>
        <instanceable>true</instanceable>
        <header-portlet-css>/css/main.css</header-portlet-css>
        <footer-portlet-javascript>/js/main.js</footer-portlet-javascript>
        <css-class-wrapper>simple-spring-portlet</css-class-wrapper>
    </portlet>
    <role-mapper>
        <role-name>administrator</role-name>
        <role-link>Administrator</role-link>
    </role-mapper>
    <role-mapper>
        <role-name>guest</role-name>
        <role-link>Guest</role-link>
    </role-mapper>
    <role-mapper>
        <role-name>power-user</role-name>
        <role-link>Power User</role-link>
    </role-mapper>
    <role-mapper>
        <role-name>user</role-name>
        <role-link>User</role-link>
    </role-mapper>
</liferay-portlet-app>

6.and at the last Liferay-Display.xml

<?xml version="1.0"?>
<!DOCTYPE display PUBLIC "-//Liferay//DTD Display 6.0.0//EN" "http://www.liferay.com/dtd/liferay-display_6_0_0.dtd">

<display>
    <category name="category.sample">
        <portlet id="simple-spring" />
    </category>
</display>