Developing portlets for Liferay in Eclipse

In this blog I'd like to tell you how to use Eclipse with Liferay to develop portlets with the ability to change a class or a JSP in Eclipse and have that immediatelly reflected on the server (hot deployment).

Environment

  • Liferay Portal 5.2.2 running on Tomcat 5.5.27.
  • Eclipse IDE for Java EE Developers, version Ganymede 3.4.1.
  • (optional) Eclipse Maven plugin m2eclipse 0.9.6.

A note about Eclipse and Maven projects

My web project is actually also a Maven project because we use Maven for building our projects. That means that it has a specific directory structure (src/main/webapp, target/classes, etc.) and that it includes a pom.xml that defines its dependencies on other artifacts, usually JAR libraries. Thanks to the m2eclipse plugin, Eclipse is aware of these dependencies and makes them available to the project during development and deployment.

With the m2eclipse plugin you get the nice feature that if your web application project depends on an artifact produced from another Eclipse project then if you change something in this project you depend upon, Eclipse detect it and pushes the change to the web application deployed on a server.

Normally with maven alone you would need to run mvn install or mvn package on the library project and either copy the produced .jar manually to the deployed webapp's WEB-INF/lib or do a full rebuild (mvn package) and redeploy of the web app.

Preparing the portlet project for hot deployment to Liferay

Normally you deploy a WAR with portlets by putting them in the Liferay's hot deploy directory (<liferay>/deploy). It notices that, processes the WAR, and deploys it to Tomcat. However since Eclipse knows nothing about Liferay and can only deploy to Tomcat itself in the normal way, that means by copying the exploded WAR to <liferay>/tomcat-5.5.27/webapps/, we need to do the Liferay's modifications by ourselves and also modify the way that Eclipse does the deployment. Liferay does monitor Tomcat's webapps/ directory and will detect a change to an application and redeploy it however this deployment doesn't include all the operations that are performed when deploying via its hot deploy directory.

Configuring Eclipse

1. Define a Server Runtime Environment for Liferay

  1. Window > Preferences > Server > Runtime Environments > Add... .
  2. Select Apache Tomcat 5.5.
  3. For the Tomcat installation directory browse to or type <liferay>/tomcat-5.5.27 (replace <liferay> with you liferay installation directory, of course).
  4. Name it e.g. "Liferay 5.2.2@Tomcat 5.5.27".

2. Define a Server instance for Liferay

  1. Window > Show View > Other... > Server > Servers.
  2. Right-click somewhere in the view Servers > New > Server:
    • Server type Tomcat v5.5 Server.
    • Server runtime environment: select the one defined in the previous step (Liferay 5.2.2@Tomcat 5.5.27).

3. Adjust the server's configuration

In the view Servers, double-click on the newly created server, which will open its configuration. Do the following modifications:
  • In Server Locations change "Use workspace metadata" to "Use Tomcat installation".
    • Notice: If you have any projects deployed to the server, this section is greyed-out and cannot be edited. Remove all projects from the server, publish, perhaps restart and you should be able to modify this settings.
  • In Server Locations change Deploy path from wtpwebapps to webapps. (Maybe this isn't necessary but I wanted to be sure I won't create any troubles for the Liferay's monitoring.)
  • In Timeouts perhaps increase the Start timeout to some period long enough, e.g. 300 s. (My Liferay usually starts in ~ 70s but once it took over 200 s.)
  • During server startup, if you get

    Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

    then click Open launch configuration in the server's configuration and add something like -XX:MaxPermSize=128m to Arguments > VM arguments.
Now you should be able to run the server, deploy your portlet webapp to it (view Servers > right-clik the server > Add and Remove Projects ...) and see your portlets in the Liferay portal (login, Add application > find it under the category you've defined in your liferay-display.xml > add it to a page).

When you deployed the application to the server, you should have been able to see in the log (i.e. in the Eclipse's Console view) that Liferay has detected it and deployed the portlets. There should be a line like

INFO [PortletHotDeployListener:303] 1 portlets for <your webapp's name> are available for use

telling you that your portlet is deployed and ready to use.

Adjusting the portlet project

When you add you portlet to a portal page, you will be most likely surprised to see there The requested resource (/example-portlet/MyPortlet/invoke) is not available. instead of the expected portlet content. (Of course it will differ if your project/webapp context root isn't example-portlet and the portlet's name isn't MyPortlet.)

Google won't reveal any solution but I'll will :-). Try to deploy the application first from Eclipse (which you've just done) and then in the regular way by exporting it as a WAR file from Eclipse and putting it into <liferay>/deploy/ and waiting for Liferay to pick it up and deploy it to Tomcat. Then compare the two resulting directories from Tomcat's webapps/ folder. Thus you will learn what you will need to modify. Or simply keep on reading :).

Adjust web.xml

Liferay needs to have a servlet defined for each portlet. The error above (The requested resource (/example-portlet/MyPortlet/invoke) is not available.) indicates that a servlet MyPortlet is missing. So we will add it together with the proper servlet mapping:

web.xml modification:
<servlet>
        <servlet-name>MyPortlet</servlet-name>
        <servlet-class>com.liferay.portal.kernel.servlet.PortletServlet</servlet-class>
        <init-param>
            <param-name>portlet-class</param-name>
            <param-value>eu.ibacz.example.MyPortlet</param-value> <!--replace with your portlet class -->
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
        ....
    <servlet-mapping>
        <servlet-name>MyPortlet</servlet-name>
        <url-pattern>/MyPortlet/*</url-pattern>
    </servlet-mapping>

Add portlet TLD

We will likely (though I'm not 100% sure about this) need to replicate yet another change done by Liferay and that is adding it's TLD for portlet 2.0 and a link for it to web.xml. Liferay adds taglibs also for all its other taglibs but I suppose you don't need them. (I suppose you develop a JSR 286 portlet but you could do the same for a JSR 168 portlet by using its specific uri and tld file.)

1. Copy liferay-portlet.tld to WEB-INF/tld/liferay-portlet.tld. Get it from the Liferay-deployed WAR or find in somewhere under <liferay>.

2. Add a taglib declaration to web.xml:
        <taglib>
            <taglib-uri>http://java.sun.com/portlet_2_0</taglib-uri>
            <taglib-location>/WEB-INF/tld/liferay-portlet.tld</taglib-location>
        </taglib>

Adding Liferay libs

You will likely need to add at least the Liferay library <liferay>/tomcat-6.0.18/webapps/ROOT/WEB-INF/lib/util-taglib.jar to your projet's WEB-INF/lib. If you ommit that you may, when accessing the portlet, get for example one of the following errors:
 javax.portlet.PortletException: javax.servlet.ServletException: java.lang.NoClassDefFoundError: com/liferay/taglib/portlet/DefineObjectsTag
JasperException: Impossible de charger ou d'instancier la classe TagExtraInfo: com.liferay.taglib.portlet.ActionURLTei

That's all, folks!

That's all! Your portlet should work now (tough Eclipse may require Tomcat restart for that). Enjoy!

About the portlet project

Selected files of the project's WAR:
  • WEB-INF/
    • lib/
      • util-taglib.jar
    • tld/
      • liferay-portlet.tld
    • classes/
      • eu.ibacz.example.MyPortlet.class
    • liferay-display.xml
    • liferay-plugin-package.properties
    • liferay-portlet.xml
    • portlet.xml
    • web.xml
  • myPortlet_view.jsp
eu/ibacz/example/MyPortlet.java:

package eu.ibacz.example;

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

public class MyPortlet extends GenericPortlet { @Override protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {

PortletRequestDispatcher dispatcher = getPortletConfig().getPortletContext().getRequestDispatcher( "/myPortlet_view.jsp"); if (dispatcher != null) { //dispatcher.forward(request, response); // this displays an empty page instead of the JSP?! dispatcher.include(request, response); // this works } else { throw new IllegalStateException("Failed to get a PortletRequestDispatcher, can't forward to the JSP"); } } }
liferay-display.xml:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE display PUBLIC "-//Liferay//DTD Display 5.2.0//EN" "http://www.liferay.com/dtd/liferay-display_5_2_0.dtd">
<display>
  <category name="My Experiments">
    <portlet id="MyPortlet">My Test Portlet</portlet>
  </category>
</display>
liferay-plugin-package.properties (I suppose this actually isn't need or could be empty):
portal.dependency.jars=commons-logging.jartags=portlet
liferay-portlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 5.2.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_5_2_0.dtd">
<liferay-portlet-app>

    <portlet>
        <portlet-name>MyPortlet</portlet-name>
        <instanceable>true</instanceable>
    </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>
portlet.xml:
<?xml version='1.0' encoding='UTF-8' ?>
<portlet-app 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' version='2.0'>

    <portlet>
        <description></description>
        <portlet-name>MyPortlet</portlet-name>
        <display-name>MyPortlet</display-name>
        <portlet-class>eu.ibacz.example.MyPortlet</portlet-class>
        <expiration-cache>0</expiration-cache>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>
        <portlet-info>
            <title>MyPortlet</title>
            <short-title>MyPortlet</short-title>
        </portlet-info>
    </portlet>

</portlet-app>
web.xml:
<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" version="2.4" 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"> <display-name>My Test Portlet Webapp</display-name> <description/>

<servlet> <servlet-name>JHPokusy</servlet-name> <servlet-class>com.liferay.portal.kernel.servlet.PortletServlet</servlet-class> <init-param> <param-name>portlet-class</param-name> <param-value>eu.ibacz.studna.PokusnyPortlet</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>JHPokusy</servlet-name> <url-pattern>/JHPokusy/*</url-pattern> </servlet-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>

<jsp-config> <taglib> <taglib-uri>http://java.sun.com/portlet_2_0</taglib-uri> <taglib-location>/WEB-INF/tld/liferay-portlet.tld</taglib-location> </taglib> </jsp-config>

</web-app>
myPortlet_view.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%>

<h1>MyPortlet is alive!</h1>

Known issues

Omission of .properties during deploy

Sometimes Eclipse omits to copy .properties files to the server. I don't know whether it's Eclipse problem or my Eclipse Maven plugin problem. I don't know why because .settings/org.eclipse.wst.common.component does contain <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>. I've open the bug report MECLIPSE-556 regarding this.

Enforcing a particular JDK version

If you use the maven enforcer plugin to enforce a particular version of Java, like this:
<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-enforcer-plugin</artifactId>
		<configuration>
				<rules>
						<requireJavaVersion>
								<version>[1.5,1.6)</version>
								<message>...</message>
						</requireJavaVersion>
				</rules>
		</configuration>
		<executions>
				<execution>
						<id>enforce-versions</id>
						<goals>
								<goal>enforce</goal>
						</goals>
				</execution>
		</executions>
</plugin>
then you may have troubles building your Maven project with Eclipse if running Eclipse under another JVM version. For example my default system JVM used also for Eclipse is 1.6 but for a particular project I need 1.5. When you try to build the project, it will fail with
Build errors for jpivot-portlet-liferay-classes; org.apache.maven.lifecycle.LifecycleExecutionException:
 Internal error in the plugin manager executing goal 'org.apache.maven.plugins:maven-enforcer-plugin:1.0-beta-1:enforce':
 Mojo execution failed.
I've tried the following to force Maven to use JVM 1.5 for building the project:
  1. Set the JRE System Library in Project - Properties - Java Build Path - Libraries.
  2. Set the Eclipse's command line option -vm to point to the desired JVM in eclipse.ini:
    -vm /usr/lib/jvm/java-1.5.0-sun
  3. Set JAVA_HOME to point to the desired JVM prior to starting Eclipse:
    bash$ export JAVA_HOME="/usr/lib/jvm/java-1.5.0-sun/jre"bash$ ./eclipse
  4. Setting PATH to point to 1.5 JVM prior to startin Eclipse:
    bash$ export PATH="/usr/lib/jvm/java-1.5.0-sun/bin:$PATH"bash$ ./eclipse
Of those only nr. 4, setting the PATH, had the desired effect of running Eclipse and thus also the Maven build under the required JVM 1.5. The conclusion is that you must run Eclipse using the same JVM you want to use for Maven builds.

There is the bug report MNGECLIPSE-1091 regarding this problem with many votes but no resolution or workaround.

Why not to use m2eclipse

A nice post (6/2009) lists some reasons why not to use m2eclipse for Maven-Eclipse integration:
  1. No real support for separate output folder.
  2. It uses the Maven Embedder, which is not Maven but more of an experiment.
  3. It wants to use the JDK associated with the JRE that launched Eclipse.
According to Brian Fox, the upcoming M2e 0.9.9 should address this issues.

Another reader , Benedikt, recommends solutions:
  1. Of course you can change the output-folder by modifying your pom.xml to have separate output-folders for your class-files.
  2. You can tell the plugin which maven installation it should use and it doesn’t break anything if you do so.
  3. If you don’t use the internal maven builder then maven will use the JRE configured under “Installed JREs”.
So maybe its usable after all? Or you may also give try to Eclipse IAM (former Q4E), the new Maven-Eclipse integration, currently v. 0.1.0. It seems to be yet no as mature as m2eclipse, see a comparison.

Additional notes

You may want to follow the instructions on Liferay site and use Liferay in a development mode by using special portal.properties that prevent some caching etc. (Add -Dexternal-properties=$CATALINA_BASE/webapps/ROOT/WEB-INF/classes/portal-developer.properties to <liferay>/tomcat-5.5.27/bin/catalina.sh.)

You may also want to try to install JBoss Tools into your Eclipse and use its Servers view, which also supports Tomcat and may be better than the standard one.

Another interesting SW - Eclipse Portal Pack for developing JSR168 and JSR286 portlets with Eclipse, currently v2.0M1.

An alternative: the Liferay way

The upcoming book Liferay in Action by R. Sezov describes how to set up portlet development environment for Liferay 6 using either Eclipse or NetBeans in its chapter two. He recommands to create and deploy a portlet using the Ant-based Liferay Plugins SDK and use an IDE only to import and develop the portlet while invoking ant deploy for the deployment. I don't know wheter that supports hot deployment of only changed files (provided that it matters at all) and of course it has a completely different structer than Maven assumes, complicating if not rendering it impossible to use Maven.

Tags: library


Copyright © 2024 Jakub Holý
Powered by Cryogen
Theme by KingMob