Friday, July 30, 2010

Spring MVC portlet with annotations

Java portlet framework is awesome for portal developers. Different portal frameworks are there which supports portlet development in their environment. They also support spring portlet. Spring's portlet support is exposed through Spring MVC interface. Spring provides MVC based portlet development which is so easy and neatly architectured.

Spring provides class org.springframework.web.portlet.DispatcherPortlet which is to be difined in portlet.xml.
  <portlet>  
     <description xml:lang="en">Portlet Description</description>  
     <portlet-name>portlet-name</portlet-name>  
     <display-name xml:lang="en">Portlet Display Name</display-name>  
     <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>  
     <init-param>  
       <name>contextConfigLocation</name>  
       <value>/WEB-INF/appContext.xml</value>  
     </init-param>  
     <expiration-cache>0</expiration-cache>  
     <supports>  
       <mime-type>text/html</mime-type>  
       <portlet-mode>VIEW</portlet-mode>  
     </supports>  
     <portlet-info>  
       <title>Portlet Name</title>  
       <short-title>Portlet Name</short-title>  
       <keywords>Spring Portlet MVC</keywords>  
     </portlet-info>  
   </portlet>   

The init param contextConfigLocation defines the application context XML file to be loaded for this portlet. This file has the spring bean definition for classes implementing Controller interface OR annotated classes with @Controller.

Annotated Controller class is as follows,
 @Controller  
 @RequestMapping  
 public class MyController {  
   
     private static final Logger _LOG = Logger.getLogger(MyController.class);  
       
     //Default render method will call this method...  
     @SuppressWarnings("unchecked")  
     @RequestMapping({"VIEW","/demoportlet/jsp2.do"})  
     public Object defaultRender(Model model, PortletRequest request,RenderResponse response) {  
         response.setContentType("text/html; charset=UTF-8");  
         String action = request.getParameter("action");  
         if("action1".equals(action)) {  
              ...  
             return "demoportlet/jsp1";  
         } else {              
              ...  
             return "demoportlet/jsp2";  
         }  
     }  
       
     // Direct request mapping based on action parameter value = 'someAction'  
     @RequestMapping(params = "action=someAction")   
     public Object actionOne(RenderRequest actionRequest, RenderResponse actionResponse) throws Exception {  
         actionResponse.setContentType("text/html; charset=UTF-8");  
          ...          
         return "demoportlet/jsp2";  
     }  
 }  
   

In web.xml we need to define a servlet org.springframework.web.servlet.DispatcherServlet and org.springframework.web.servlet.ViewRendererServlet for JSP resolving as follows,

<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>  
  <servlet-name>normal</servlet-name>  
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  <load-on-startup>1</load-on-startup>  
  </servlet>   
   
  <servlet-mapping>  
  <servlet-name>normal</servlet-name>  
  <url-pattern>*.do</url-pattern>  
  </servlet-mapping>   
    
  <servlet-mapping>  
     <servlet-name>view-servlet</servlet-name>  
      <url-pattern>/WEB-INF/servlet/view</url-pattern>  
  </servlet-mapping>  

In order for DispatcherPortlet rendering to work, you must declare an instance of the ViewRendererServlet .

In the Portlet MVC framework, 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 DispatcherServlet will look for normal-servlet.xml as we declared its name as 'normal'. This file will contain the definitions for beans which are needed to handle the portlet scanning, annotation handling etc... So first of all, the request will come to DispatcherServlet as its Spring MVC, and then it will scan for portlet controllers using spring's <context:component-scan> tag.

We also need to define the context param contextConfigLocation in web.xml which is loaded by DispatcherServlet on server startup. This will have all the beans required by portlets.

 <context-param>  
  <description>Spring Context XML location</description>  
  <param-name>contextConfigLocation</param-name>  
  <param-value>classpath:conf/spring/global.xml</param-value>  
  </context-param>  

The normal-servlet.xml will look like follows, where we need to define beans for annotation handling.
 <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:util="http://www.springframework.org/schema/util"  
     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  
         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">  
           
     <context:annotation-config />          
   
     <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">  
       <property name="cookieName" value="lang"/>  
       <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->  
       <property name="cookieMaxAge" value="100000"/>  
     </bean>  
   
     <bean id="localeChangeInterceptor"  
    class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">  
       <property name="paramName" value="locale"/>  
     </bean>  
       
     <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">  
         <property name="interceptors">  
             <list>  
                 <ref bean="localeChangeInterceptor"/>  
             </list>  
         </property>  
         <property name="order" value="10" />  
     </bean>    
     
   <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>  
       
     <context:component-scan base-package="com.spring.controller" />      
 </beans>  
   

The <context:component-scan> will finds the controller classes from given package which are annotated with @Controller.

Other than is, we need to enter portlet information in portal framework's configuration files in which we want to deploy and use this portlet. When we add portlet from the portal, the controller methods will get call.

3 Comments:

Shams said...

As per my requirement I have to convert spring mvc sample project to liferay spring mvc portlet project. Here spring mvc using with annotations. Here issue with handler mapping , which handler mapping should available in applicationContext.xml which we are giving in portlet.xml with DispatcherPortlet.

Appreciate replies at the earliest.
Thanks in advance.


Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'guessNumberController' defined in PortletContext resource [/WEB-INF/context/GuessNumberPortletAnnotation-context.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'commandClass' of bean class [com.prokarma.liferay.SimpleGuessNumberController]: Bean property 'commandClass' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?

Shams said...

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'guessNumberController' defined in PortletContext resource [/WEB-INF/context/GuessNumberPortletAnnotation-context.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'commandClass' of bean class [com.prokarma.liferay.SimpleGuessNumberController]: Bean property 'commandClass' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?

Could not figure out which handlermapping need to use in applicationContext.xml file.
Please let me know if you need any more info regarding the issue.

Paul Phoenix said...

could it be posible to inherit from a BaseController which provides a set of common operations executed everytime we invoke an action of our controller??
I mean, I'd like to have two attributes in my BaseController wich must be populated just before of every action request (manipulating data stored on the request)

Thanks