Tuesday, July 31, 2007

maven-jetty-plugin and JSR168 portlets

Inspired by Don Brown's Developing a Portlet using Eclipse tutorial for Struts 2 and some information I found on the Pluto FAQ, I started playing around with embedding pluto in a Maven 2 project. The goal was to be able to use the maven-jetty-plugin to run and test my portlets with Jetty just by typing mvn jetty:run. That would definitively be a step up to increase developer productivity!

After googling a lot and collecting information from various sources, I managed to get things up and running! Here's the steps needed to get the maven-jetty-plugin to work with your Maven 2 portlet project.

The formula

First of all, this only works with Pluto 1.1.4, which is not currently released, but probably will be soon. The reason is that Pluto 1.1.3 does not work with JSP 2.1. So the first thing to do is to get the Pluto sources from SVN from the pluto-1.1.4 tag and build it according to the istructions. This will install the pluto 1.1.4 artifacts in your local maven repository. Alternatively, I guess you could just add the apache maven staging repository to your pom.xml (which does sound a bit easier when I think of it...)

The next step is to add the maven-pluto-plugin to your pom.xml. This is needed to add the web.xml entries for the wrapper servlets for the portlet.

<plugin>
<groupId>org.apache.pluto</groupId>
<artifactId>maven-pluto-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>assemble</goal>
</goals>
</execution>
</executions>
</plugin>


And of course, we need the jetty plugin as well:


<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
</plugin>


Now, we need to add the dependencies needed by the pluto portlet container. I chose to add these to the plugin classpath, but you could also add them to the project dependencies instead. So within the scope of the plugin tag for the jetty plugin, add a section of dependencies:


<dependencies>
<dependency>
<groupId>org.apache.pluto</groupId>
<artifactId>pluto-portal-driver</artifactId>
<version>1.1.4</version>
</dependency>
<dependency>
<groupId>org.apache.pluto</groupId>
<artifactId>pluto-portal-driver-impl</artifactId>
<version>1.1.4</version>
<dependency>
<dependency>
<groupId>org.apache.pluto</groupId>
<artifactId>pluto-container</artifactId>
<version>1.1.4</version>
</dependency>
<dependency>
<groupId>org.apache.pluto</groupId>
<artifactId>pluto-taglib</artifactId>
<version>1.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<<version>2.0.2</version>
</dependency>
</dependencies>


(That's right, there's a Spring dependency in there, which can cause some troubles if you're using Spring already, but let's skip that for now...)

The maven-pluto-plugin generates a web.xml file and puts it in the ${project.build.directory}/pluto-resources/ folder. So we need to customize the configuration of the plugin a bit. So add this to the maven-jetty-plugin definition in the pom.xml:


<configuration>
<webXml>${project.build.directory}/pluto-resources/web.xml</webXml>
</configuration>


Still, that's unfortunately not entirely enough for the jetty plugin. We also need to add a context-parameter, filter, filter-mapping(s) and some servlet context listeners to the web.xml. Now, I don't want to pollute my original web.xml with this stuff, so I considered a couple of options. Either, I could add an overrideweb.xml file, which is added to the web application configuration after the jetty:run goal is invoked, or I could create a new webdefault xml file, which is applied before. After trying both options, I decided to go for the second solution (I'm not going into that now, but it's the only way I could get it to work with Spring in a way I was happy with). Therefore, I extracted the webdefault.xml file from the jetty jar file, and added the required cofigurations:


<!-- Spring configuration file for the portlet container services -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/pluto-portal-driver-services-config.xml</param-value>
</context-param>

<!-- The Spring context loader listener and the startup listener for the dummy portal -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.apache.pluto.driver.PortalStartupListener</listener-class>
</listener>

<!-- The pluto driver that will intercept our JSP "portal" -->
<filter>
<filter-name>plutoPortalDriver</filter-name>
<filter-class>org.apache.pluto.driver.PortalDriverFilter</filter-class>
</filter>

<!-- The filter will intercept our /test-portal/index.jsp file and everything beneath it -->
<filter-mapping>
<filter-name>plutoPortalDriver</filter-name>
<url-pattern>/test-portal/index.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>plutoPortalDriver</filter-name>
<url-pattern>/test-portal/index.jsp/*</url-pattern>
</filter-mapping>

<!-- The container taglibs that invoke our portlet in the portlet container -->
<jsp-config>
<taglib>
<taglib-uri>http://portals.apache.org/pluto</taglib-uri>
<taglib-location>/WEB-INF/tld/pluto.tld</taglib-location>
</taglib>
</jsp-config>


For now, I just put this new webdefault xml file (I named it jetty-pluto-web-default.xml) in the /WEB-INF folder of the application, and added a new configuration option for the jetty plugin:


<configuration>
<webDefaultXml>src/main/webapp/WEB-INF/jetty-pluto-web-default.xml</webDefaultXml>
<webXml>target/pluto-resources/web.xml</webXml>
</configuration>


Almost done now! We need three files from pluto. These are configuration- and taglib files that initialize the portlet container, and instead of listing them here, just download the pluto-portal-driver-config.xml and the pluto-portal-driver-services-config.xml and put them in the WEB-INF folder of your application. Then create a tld folder in the same folder, and download the pluto.tld file to this folder.

The last steps is to create a "dummy portal" entry point. I created a folder named test-portal in the src/main/webapp folder, and created an index.jsp file that acts as the entry point to the portlet container. The index.jsp file looks like this:


<%@ taglib uri="http://portals.apache.org/pluto" prefix="pluto" %>
<pluto:portlet portletId="/struts2-portlet.StrutsPortlet">
<div class="portlet" id="struts2-portlet.StrutsPortlet">
<div class="header">
<h2 class="title"><pluto:title></pluto:title></h2>
</div>
<div class="body">
<pluto:render></pluto:render>
</div>
</div>
</pluto:portlet>


The portletId in the <pluto:portlet> tag is composed of the context root of the web application, and the name of the portlet as defined in the portlet.xml file.

Now, everything should be set up, and you should be able to view your portlet by pointing your browser to http://localhost:8080/<context-root>/test-portal/index.jsp (note that you need to point to the index.jsp file, it will not work just pointing it to /test-portal/ due to the filter mappings).

If you don't want to do all these steps manually, I have created an archived maven project based on the struts2-archetype-portlet, as described in Don's tutorial, and added all the required files and settings.

Conclusion

This might seem like an awful lot of work to get the jetty plugin to work with portlets, but I believe it's worth the effort. I have never been more productive developing portlets than after I got this up and running. And when you have been through this once, just add it to your set of templates/archetypes so you won't have to do it again.

And please, if you find any errors in this article, please drop me a comment so I can correct them.

Update: See part 2 to see how the solution has improved.

Update 2: Pluto 1.1.4 has been released and should be available from the regular maven repositories.