Some weeks ago I started planning a dedicated online community backed by a Liferay portal instance. Up to now I spent most of my time defining use case scenarios, evaluating software tools and convincing people from my idea. Lately I began to write smaller portlets that provide basic features which are not included in Liferay and by the way give me a deeper insight into portlet development using Liferay.
I will write more about that platform in one of the upcoming posts but for now I would like to report about my odyssey collecting information on how to develop JSR 286 portlets supported by the Spring 3.0 framework in a Liferay environment.
If you want to develop portlet for any version of the Liferay portlet, you might use the ext environment provided by the Liferay team. Although I appreciate the help to develop applications for their portal, I definately don't want to learn how to use a proprietary development environment but use my knowledge about standard portlet development and get onto the road quickly.
I searched a while until I came across some expedient documentation about how to integrate Spring 3 and standard portlets (JSR 168 and 286). The Spring framework provides you with a dispatcher portlet that maps incoming portlet requests to registered handlers.
After having assembled all required information, I started developing my first Liferay portlet. The following steps are the ones I carried out (abbreviated):
The first step creates a directory layout as follows:
/myPortlet
/myPortlet/src/
/myPortlet/src/main
/myPortlet/src/main/java
/myPortlet/src/main/java/sourceCodeGoesHere
/myPortlet/src/main/webapp/
/myPortlet/src/main/webapp/WEB-INF/
/myPortlet/src/main/webapp/WEB-INF/classes/
The next step is to add configuration files required by Liferay:
/myPortlet/src/main/webapp/WEB-INF/liferay-display.xml
/myPortlet/src/main/webapp/WEB-INF/liferay-portlet.xml
/myPortlet/src/main/webapp/WEB-INF/portlet.xml
/myPortlet/src/main/webapp/WEB-INF/web.xml
- The liferay-display.xml configuration file defines the category the user can find the tool in when adding a new applicaton to a portal page (see Google for more information).
- If you want to control the access to the portlet, you need to modify liferay-portlet.xml which holds the names of the role that are allowed to use the portlet (see Google for more information).
- The file portlet.xml contains the core definition of you portlet: display name, class, init parameters and alike. Normally you would provide the name of your custom portlet class but since we are using Spring, we must tell the portlet container to forward all incoming portlet requests to the Spring dispatcher portlet. Additionally we need to give the path of the Spring context configuration file.:
<portlet-app>
.
.
<portlet-name>mySamplePortlet</portlet-name>
<display-name>My Sample Portlet</display-name>
<portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
<init-param>
<name>contextConfigLocation</name>
<value>/WEB-INF/classes/my-sample-portlet-context.xml</value>
</init-param>
.
.
</portlet-app>
- At last we have the web.xml file. Internally the Spring framework handles the whole custom portlet as web application with the dispatcher portlet as entry gate. Thus the web.xml configures the custom portal application like in any other Spring application: it needs a path to the Spring context definition file, names a set of listeners and defines how to handle incoming requests (-> ViewRendererServlet).
<web-app ...>
.
.
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>mySamplePortletportlet</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/my-sample-portlet-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>ViewRendererServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ViewRendererServlet</servlet-name>
<url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>
.
.
</web-app>
Now I need to implement a controller for handling incoming requests. Since I am mapping incoming requests to a common Spring MVC application, the implementation of a request controller takes the same steps as for a standalone web application. See Google for extended information on how to implement Spring MVC controllers, define request, action or render mappings and how to identify incoming request parameters.
I omit the source code and continue with the definition of the Spring context. As defined in the web.xml and the portlet.xml I create a new file in /WEB-INF/classes/ named my-sample-portlet-context.xml. In order to the get application running the following beans must be defined at minimum:
<bean id="mySamplePortletViewController" class="com.me.MySamplePortletViewController">
<property name="debugMode" value="true"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="cache" value="false" />
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/" />
<property name="suffix" value=".jsp" />
</bean>
<bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<bean class="org.springframework.web.portlet.handler.ParameterMappingInterceptor"/>
</property>
</bean>
The first bean names the controller which should receive incoming requests (as defined by the RequestMapping annotations in the source code). The second bean defines the type of view used for displaying contents to the user. In this case I use JSP backed by JSTL. The last bean is the one that handles the mappings defined in the source code of the controller implementation. The provided interceptor implementation makes sure that the incoming request parameters are mapped correctly to method parameters.
Finally you need to create and implement the file (JSP) referenced in the controller source code as destination used for displaying information to the user. That's all. ... well not everything ... there are some pitfalls I stumpled upon that I would like to write about so that you are not going to make the same mistakes as I did.
RequestMappings
The JSR 168 standard only knew about request mappings, whereas JSR 286 portlets differentiate incoming requests by the phase they originate from and which purpose they serve:
- Action Request (Annotation: ActionMapping)
- Render Request (Annotation: RenderMapping)
- Even Request (Annotation: EventMapping)
- Resource Request (Annotation: ResourceMapping)
If you want to learn about these types, please check out Google for further information. I urgently advise you to use these specialised annotations although Spring lets you use the generalized RequestMapping annotation. First of all it helps to make the source code more readable, second: you know which method handles which request type, confusions excluded!
RenderMapping requires String result
As mentioned above, after processing an incoming request, the portlet controller sends the response to a defined view page. The name of that page is provided by the result of the method that handles render requests -> the method must return a string.
If it returns 'view', the user will be redirected to 'view.jsp'.
Unit testing
If you want to test your portlet controller, check out the test cases for the Spring framework itself. Since the usage of init methods is quite common, you should be aware that the init method of your controller is executed before the setUp method within a JUnit test case. So, if you plan to access data in your init method which is created by the setUp method, you need to explicitly call the init method in your test method.