Setting up a Portlet Development Environment with Eclipse 3.5, Maven and Liferay 5.2
There are a lot of possibilities to set up a Portlet Development Environment. You can use several IDE's, different packaging strategies and of course diverse Portals. This guide will document a setup using Eclipse as an IDE, Maven for deploy actions and Liferay as a Portlet Container. It is based on this guide from Rene Gielen.
Download and configure the Liferay Portlet Container
Get yourself a copy of Liferay from the Liferay Portal Download Page. The following steps assume that you get the Community Edition Tomcat 5.x Bundle. Unzip the bundle into a directory of choice. Installation instructions can be found here. If you have an Environment Variable called CATALINA_HOME, then you should delete it, otherwise Liferay will not work fine.
After the installation, a new server configuration for our Liferay installation has to be defined in Eclipse. Activate the Server tab at the bottom, right click in the free area and chose "New > Server". Select Tomcat 5.5 as your server (if you have downloaded the Tomcat 5.x bundle). In the following dialog, chose the Tomcat directory within your Liferay installation folder and an appropriate JRE (1.6+ recommended). You are safe to click "Finish" now, since we have no resources to configure for the server yet. Double click the newly configured server in the server tab to open its configuration options. Adjust the options as described here. In addition, you should modify the launch configuration of the tomcat to simplify development, check the explanation here. Append the following to the VM Arguments:
-Xms256m -Xmx1024m -XX:PermSize=32m -XX:MaxPermSize=160m -Dfile.encoding=UTF8 -Duser.timezone=GMT+2 -Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config -Dorg.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false -Dexternal-properties=portal-developer.properties"
Once finished, run the server to check if everything has been correctly setup. You can login as user "test@liferay.com" and the password "test".
Create a new Portlet project
Follow these instructions to create a new maven project using the maven archetype for Portlets. Copy the following code into the pom.xml.
Code: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>eu.envision</groupId>
<artifactId> [ProjectName] </artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>Maven Portlet Archetype</name>
<url>http://maven.apache.org</url>
<properties>
<liferay.deploy.dir>[path to liferay]\liferay-portal-tomcat-5.5-5.2.3\liferay-portal-5.2.3\deploy</liferay.deploy.dir>
</properties>
<profiles>
<profile>
<id>ant</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<configuration>
<tasks>
<copy todir="${liferay.deploy.dir}" file="target/ [ProjectName]-0.0.1-SNAPSHOT.war" />
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.directwebremoting</groupId>
<artifactId>dwr</artifactId>
<version>3.0.M1</version>
</dependency>
<dependency>
<groupId>javax.portlet</groupId>
<artifactId>portlet-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc-portlet</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
The maven archetype creates only JSR 168 compatible portlets, which means portlet-api 1.0 is used. Event-based IPC requires version 2.0. The version numbers for the JSP libraries have to be upgraded as well to conform to the new servlet-api. If you get an exception similar to this: "The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application", we have a versioning conflict for the JSP standard libraries. Make sure you know if your portlet container does already ship with libraries (liferay does for the older versions). If yes, add to avoid have to copies in the classpath. The support of reverse ajax for inter-portlet communication makes use of DWR. The annotation-based configuration of DWR requires version 3.x, which is currently not yet final.
Update the dependencies of the project, and run "mvn package". The builder will compile the classes, run the tests, package them all into a WAR file, which is then copied to the Liferay Deploy directory. Every few seconds, Liferay scans this directory for new versions of the WAR file, and will automatically deploy it to the running Liferay instance. Check the Eclipse console to see when the portlets have been deployed and are ready to use. This is by the way only needed when you change the JSP-files or the configuration (including the annotations). Changes in the java code should be (as usual in Eclipse) by directly reflected due to Hot Code Replacement. To make this working, you have to add your project to the server (see again here )
Coupling Spring to the Portal
Since Spring should be responsible to handle the Portlet requests, we have to also update the Portlet and Servlet configuration. Copy the following code into the portlet.xml file in your WEB-INF directory (replace everything inside)
Code: /src/main/webapp/WEB-INF/portlet.xml
<?xml version="1.0" encoding="UTF-8"?> <portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0" 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>MyPortletName</portlet-name> <display-name>My First Portlet</display-name> <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <supported-locale>en</supported-locale> <portlet-info> <title>Simple Spring Example Portlet</title> <short-title>My Portlet</short-title> <keywords>example</keywords> </portlet-info> </portlet> </portlet-app>An introduction in the Dispatcher Servlet, and what is happening in Spring can be found here. We also replace everything in the web.xml. Note that we leave out the DWR configuration for now, this will be described in the guide about IPC.
Code: /src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="WebApp_ID"
version="2.4">
<!--
otherwise: java.lang.IllegalStateException: No WebApplicationContext
found: no ContextLoaderListener registered?
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--
in the applicationContext (common for all portlets in this project),
we define how resolve JSPs, and to enable DWR stuff
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.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>
<jsp-config>
<taglib>
<taglib-uri>http://java.sun.com/portlet</taglib-uri>
<taglib-location>/WEB-INF/tld/portlet.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
Again, the Spring documentation has some details on the ViewRendererServlet.
Configuring the Spring context
We are using Spring 3, which means most configuration is managed by the annotations. Still, we have to have a global configuration as defined in the web.xml, in this case "/WEB-INF/applicationContext.xml". Here, we define all the properties which are common for portlets in this project. If we only have on portlet, we don't really need it, otherwise create it and copy the following code into it.
Code: /src/main/webapp/WEB-INF/applicationContext.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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">
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="order" value="1" />
</bean>
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="order" value="2" />
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
Each portlet has additionally its own context in the file [PortletName]-portlet.xml configured. In our case, we would therefore need a file named MyPortletName-portlet.xml (see portlet.xml) in the WEB-INF directory. There is basically only one thing defined here: the package where we have to search for a Spring controller which maps the portlet request to our java code.
Code: /src/main/webapp/WEB-INF/MyPortletName-portlet.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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="my.portlet.package"/>
<context:annotation-config />
</beans>
Spring Annotations
The following example shows an excerpt of the java code generated with the maven archetype. We are now simply replacing this code using a Spring controller (note: this has not yet been tested).
public class MyPortlet extends GenericPortlet {
private static final String NORMAL_VIEW = "/normal.jsp";
private PortletRequestDispatcher normalView;
@Override
public void doView(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
normalView.include(request, response);
}
@Override
public void init(PortletConfig config) throws PortletException {
super.init(config);
normalView = config.getPortletContext().getRequestDispatcher(NORMAL_VIEW);
}
}
We are now going to replace this code with a spring based controller (which has to reside in the folder defined in the portlet context file).
@Controller
@RequestMapping("VIEW")
public class MyPortlet {
@RenderMapping
public String handleRenderRequest(RenderRequest request, RenderResponse response) throws Exception {
// return name of JSP file only
return "normal";
}
That's it. A request to the Portlet will be handled by DispatcherPortlet defined in the Portlet.xml.Since no special mode is defined initially, the portlet container will request the default view mode, which we define via the annotation @RenderMapping (without any parameter). We simply return the String "normal" here, which means we let the ViewResolver handle what to display. In the application context, we have configured this resolver by saying that the given string "normal" actually refers to the file "WEB-INF/jsp/normal.jsp", which is consequently returned.
If everything is setup and running, we can now start to have a look into inter-portlet communication.





