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.

19 comments:

Anonymous said...

Great stuff Nils!
Thank you for the detailed writeup.

Anonymous said...

This is great work! Thanks!
I downloaded your project, and having put the pluto staging repository in its pom.xml, I'm able to start jetty. However, I get the following error when I try to hit the URL:
java.lang.NoSuchMethodError: javax.servlet.jsp.tagext.TagAttributeInfo.(Ljava/lang/String;ZLjava/lang/String;ZZLjava/lang/String;ZZLjava/lang/String;Ljava/lang/String;)V

This method has been around since JSP 2.0, which you also specify in your dependency as provided.

I figure I'll investigate this tomorrow or over the weekend.

Thanks again!

Nils-Helge Garli Hegvik said...

Hi coop!

Have you resolved the issue yet? I tried stripping down the example, removing dependencies to JSP 2.0 and almost everything else, and it still worked here. Have you tried running it with "mvn -U" to update the plugins?

Anonymous said...

I haven't had much time for this today, but I found that it's picking up the class in question from servlet-api-2.3.jar.

I can't find a constructor for TagAttributeInfo which takes 5 strings as its arguments in any release of J2EE.

Perhaps I'm reading the error message wrong:

javax.servlet.ServletException: javax.servlet.jsp.tagext.TagAttributeInfo.(Ljava/lang/String;ZLjava/lang/String;ZZLjava/lang/String;ZZLjava/lang/String;Ljava/lang/String;)V

Anonymous said...

I gave up on the sample project, but I did get my portlet configured with your excellent write-up here.

There is one thing, which you alluded to in your post. I have a dependency on Spring, but a different version - 2.0.5.

I get this error on startup (and a 503 error thereafter)
java.lang.ClassCastException: org.springframework.web.context.support.XmlWebApplicationContext

Any thoughts?

Anonymous said...

I get the same error even when I switch to the 2.0.2 version of spring. Must be that the jar is loaded twice or something.

Nils-Helge Garli Hegvik said...

Regarding the first error you had, have you tried setting the plugin version to 6.1.4rc0? It appears that a 6.1.5 version has been released tha t does not work for me either.

Regarding the second error, as I mentioned in the entry, there are some issues when you're using Spring in your application which I'm trying to solve. You can try adding a system property to the plugin:
<configuration>
<systemProperties>
<systemProperty>
<key>org.mortbay.jetty.webapp.parentLoaderPriority</key>
<value>true</value>
</systemProperty>
</systemProperties>
</configuration>

Anonymous said...

I'm soo close now. Thank you for all your help. Sorry for this long comment, but I hope it will be helpful to others.

The version change you gave me fixed the sample application - thanks. The action links don't do anything, and I was hoping to see a portlet titlebar with edit/maximize links. That's a small matter compared to actually getting the app running.

Here's what I ended up doing with my application which also uses Spring.

First, I took your advice and put the parent first class loader in place. That got me past the classcast exception, but then I got NoClassDefFound exceptions for hibernate classes. :-(

I started pulling more and more of my classpath into the jetty classpath, then it struck me. I took spring and pluto *out* of the jetty classpath, and instead put them in my application classpath (I'll switch them to scope "test" once I get this working). That got me a heck of a lot further.

Since my app uses a jndi datasource, I configured one (hsqldb) and had to put dbcp, commons-pool, hsqldb and some other dependencies on the jetty classpath, but now I've gotten to the point where it fully initializes my spring context root, and then blows up with a null pointer exception here:

java.lang.NullPointerException at org.apache.pluto.driver.PortalStartupListener.contextInitialized(PortalStartupListener.java:102)


Here's what I ended up having for a plugin configuration:
<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>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.5</version>
<configuration>
<jettyConfig>src/main/webapp/WEB-INF/jetty-ds.xml</jettyConfig>
<useTestClasspath>true</useTestClasspath>
<systemProperties>
<systemProperty>
<key>org.mortbay.jetty.webapp.parentLoaderPriority</key>
<value>false</value>
</systemProperty>
</systemProperties>
<contextPath>/HCIMedRec</contextPath>
<overrideWebXml>src/test/web.xml</overrideWebXml>
<connectors>
<connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
<port>9090</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webXml>target/pluto-resources/web.xml</webXml>
<webDefaultXml>src/main/webapp/WEB-INF/jetty-pluto-web-default.xml</webDefaultXml>
</configuration>
<dependencies>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.portlet</groupId>
<artifactId>portlet-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils-core</artifactId>
<version>1.7.0</version>
<scope>provided</scope>
</dependency>

</dependencies>
</plugin>


The nullpointer exception may have to do with how I ended up configuring my spring ContextListener - I moved it to an overrideWebXml (I split my spring context so that the datasource is in a separate file, and I used hsqldb for testing like this). I'll keep investigating. I just thought my configuration would be useful for others using Hibernate and Spring in a JSR 168 portlet and wanting to test in Pluto running in Jetty via Maven (there - that should cover the keywords I usually google on :-)

Stephen said...

Thanks to this wiki entry, I was able to diagnose that only my spring context is being loaded, and not pluto's.

Since I want to keep my web.xml as the production one, and only add in pluto stuff for testing, I need to figure out how to override the context parameter which lists what spring files to load when running in jetty. More soon.

Nils-Helge Garli Hegvik said...

Thanks for all the detailed information. As I mentioned in the blog, there are some issues to be resolved when you're using Spring in your portlet as well. I have written a servlet context listener that overrides the ContextLoaderListener so all the Spring contexts are initialized correctly. I'll post more about this later, when I have my internet connection back...

Stephen said...

As it turns out, you can't specify all three of <defaultWebXml>, <overrideWebXml> and <webXml>. By moving your defaultWebXml to my overrideWebXml, I got Spring to load the testing context parameters as I wanted.

Then it didn't like the pluto-portal-driver-config.xml - it was missing the <portlet-app> and <render-config> sections. Fixed that easy enough.

Now I'm faced with the next issue. I'm getting this odd error: Caused by: org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'pluto-portal-driver'.

I hope this is the last error to work out. Once I get this in place, I think I'll make a sample app which uses your portlet to start with, but which adds in spring and hibernate (and JSF). It will also separate out the pluto/jetty stuff to under src/test instead of under src/main. (No critisim meant to you - I'm just planning on using this for automated maven integration testing, and since I'm not deploying production to Pluto or Jetty, I don't want them in the main/src directory tree)

Stephen said...

Hope you get your internet connection back. :-)

We should work off a common shared repository. My vision for this is to have a project which, during integration testing, fires up pluto in jetty, then executes some selenium tests against it. After all the selenium tests are done, then jetty is shut down.

Information about which unit tests passed or failed would be aggregaed into the surefire reports.

Code coverage statistics would be added to the code coverage stats from the surefire reports.

Then everything gets published to your maven site.

Oh yea, let's make this a single, easy to configure plugin too. We could call it: maven-surefire-pluto-jetty-selenium-plugin. :-)

I just have simple needs. :-)

Stephen said...

I figured out the issue with the Namespace handler. I had put the pluto-driver-config.xml file in the ContextListener parameter, so Spring was trying to parse that xml file. *blush*.

My web application starts now, all that remains is figuring out how to place the portlet on a page. I noticed that your pluto-portal-driver-config.xml file was incomplete - it was missing the portlet-app and render-config sections. Yet your portlet rendered. I'll have to figure out how you did that.

Nils-Helge Garli Hegvik said...

I have posted a new entry explaining how I solved some of the problems and improved the solution.

Anonymous said...

I couldn't help but notice the issues with servlet APIs being in different versions etc. I have had my fair share with these problems already, and I think I may have a solution to the problem. If you use Spring, then Spring is dependent on commons-logging (I hate commons-logging btw) which, of course has a dependency to servlet-api. I am not kidding. Try to add commons-logging explicitly to your pom and then exclude the servlet API from that dependency.

Nils-Helge Garli Hegvik said...

Yup, that's what I ended up doing, Kristoffer. If you take a look at the pom of the sample portlet in my follow up article, you can see that the servlet api is excluded.

Unknown said...

Great work... Can you check the link http://static.nilshelgeogmaria.com/struts2/myportlet.zip . I am getting page not found message here. Thanks.

Nils-Helge Garli Hegvik said...

There's really no need for that download anymore. It's all configured and set up in the struts2-portlet example application, which you can download instead.

Anonymous said...

Nice post
for further reading on jsr-168 you can visit

http://java2us.blogspot.com/

Please pay a visit to it. And don't forget to write a comment.