Sunday, August 05, 2007

maven-jetty-plugin and JSR168 portlets - Part 2

My last post outlined how I made the maven-jetty-plugin work with a maven 2 portlet project. As I mentioned, there were some issues with the approach if you're using Spring in your application. Also, there were some files you needed to include in your project. That's not too big of a problem solving using excludes in your pom so the files are not included in your build, but it's still a bit annoying having them around. And finally, there were no portlet controls, so changing modes and window states were impossible unless you manually created links in your portlet. So the solution had to improve...

The first obstacle to overtake was how to enable the controls for switching modes and window states. I thought this was going to be easy, but trial and (mostly) error should prove me wrong. It became clear to me that the approach of adding the pluto dependencies to the plugin instead of the application caused problems with loading a resource bundle that the pluto taglib required. I solved this temporarily by adding the ToolTips.properties file to the pluto-portal-driver-1.1.4.jar file. Good enough for now. This brought me one step further: The pluto:modeAnchor and pluto:windowStateAnchor tags now rendered correctly, but they required some stylesheets and javascript files that comes with the pluto web application to actually show the controls. This forced me to add a bunch of new files to my application, and it was really starting to pollute my project.

The next task was Spring support. Having Spring on the application- and plugin classpath will cause classloader problems (you'll get ClassCastExceptions since the Spring classes are loaded from two different classloaders). Another problem is that both your application and pluto requires the ContextLoaderListener and the contextConfigLocation context parameter to be set. Basically, to make this work with just maven-jetty-plugin configuration options would require me to split the configuration options into three separate files; a webdefaults descriptor, web.xml and an override descriptor. This is really starting to look bad...

Coming this far, I realized that the current solution wasn't going to be a very good. It would work, but it wasn't good enough. First of all, the classloading issues with the resource bundle and Spring classes convinced me that it was better adding the dependendices to the application instead of the plugin. I also needed to find a solution to the problem with the ContextLoaderListener. And last, I needed to get rid of all the stylesheets, images, javascripts, jsp files and configuration files from the application, into one single artifact that could be included as a dependency to the application. This means that I would have to serve all the required resources from one jar file.

The ContextLoaderListener problem was solved using a custom, jetty specific, context listener that use the internal jetty context classes to modify the context parameters and ensuring that only one context loader listener instance exists. I then added this listener to the webdefaults file, ensuring it is the very first listener that is executed. Quite brutal, but it works...

Identifying and collecting all the required stylesheets, JSP files, images etc into one jar file was easy. The hard part was how to make them available to the pluto driver, when they always were meant to be a part of the running web application... So all lookups/URLs to files like /pluto.css, /images/controls/edit.png, /WEB-INF/pluto-portal-driver-config.xml, and not to forget, the jsp that acts as the entry point to the portlet container (I've named it /pluto/index.jsp), would have to be served from this jar. Taking inspiration from the Struts 2 FilterDispatcher, serving static resources referenced from URLs to the web application was easy using a servlet filter. But how was I going to "intercept" the lookups to the resources in /WEB-INF? And how can one serve a JSP file from within a JAR file? Looking through the source of the RenderConfigServiceImpl (which loads the /WEB-INF/pluto-portal-driver-config.xml file) and the Jasper JspServlet, it appears that they both look up resources using ServletContext.getResource and ServletContext.getResourceAsStream. If I could intercept these calls and look up the resources from the classpath instead, the problem should be solved. So I wrote a wrapper servlet for the JspServlet, and a wrapper context listener for the PortalStartupListener (which in turn initializes the RenderConfigServiceImpl) that just delegates to the wrapped objects, passing them a wrapped instance of ServletContext that looks up the resources it does not find in /WEB-INF with the ClassLoader.getResource and ClassLoader.getResourceAsStream methods instead. Problem solved!

This left me with two resources; the pluto.tld file and the customized webdefaults descriptor. For the pluto.tld file, I just placed it in the META-INF folder of the jar file, and renamed it to taglib.tld. This is discovered by the servlet container automatically when it is referenced through the correct uri in the jsp files using it. One file left; the webdefaults descriptor. After a lot of trial and error (again...) (I even tried extending the maven-jetty-plugin to add a new configuration option, but this proved fruitless as I discovered that it is not possible to extend a maven 2 plugin...), I could not find a solution. So it remains unsolved until the maven-jetty-plugin is modified to look up a webdefaults descriptor by some other means than just a file path (I'll probably create a JIRA issue for this. Update: JIRA issue created). But still, I'm now left with only one jar file added to the dependency of the application, and one webdefaults file that I add to the application itself (and is easily excluded in the pom). I've also added the dependency to a profile, so it's only active when I specify this profile on the command line. It looks like this:


Update: The maven-jetty-pluto-embedded artifact is available in the Maven 2 repositories.



<profiles>
<profile>
<id>pluto-embedded</id>
<build>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<configuration>
<webXml>${project.build.directory}/pluto-resources/web.xml</webXml>
<webDefaultXml>src/main/webapp/WEB-INF/jetty-pluto-web-default.xml</webDefaultXml>
<systemProperties>
<systemProperty>
<name>org.apache.pluto.embedded.portletId</name>
<value>MyPortletAsDefinedInPortletXml</value>
</systemProperty>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.bekk.boss</groupId>
<artifactId>maven-jetty-pluto-embedded</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</profile>
</profiles>


Of course, you need the maven-pluto-plugin as well, as described in the previous post. Also, note the system property configuration specifying the id of the portlet to run.

I run this setup using the command mvn jetty:run -P pluto-embedded. Now I'm a lot more happy with the solution. Below is a screenshot how this looks running with the Struts 2 sample portlet application (click the image to enlarge):

maven jetty plugin running embedded with Pluto

You can download the jetty-pluto-web-default.xml file and try it yourself. Just put the xml file in your /WEB-INF folder. All feedback is appreciated.

34 comments:

Coop said...

Nils - great work. Thanks!

One issue though - How can I get jetty:run to fork off in the background? I want to run integration tests using selenium with this work. Unfortunately, jetty:run stops maven until you press ctrl-c, so no tests get run.

I tried using cargo, but it uses the standard jetty container, with no ability to configure e.g. overrideWebXml and other necessities.
Thoughts?

/Nils-H said...

Johannes Brodwall has written a great article about running Jetty in your integration tests. In that case, you're instantiating Jetty in your tests instead of using the maven-jetty-plugin, but all of the configuration and setup of pluto should be similar.

Luke Daley said...

Your plugin seems to be missing several classes. http://boss.bekk.no/repos/projects/maven-jetty-pluto-embedded/trunk/src/main/java/org/apache/pluto/embedded/ is empty.

Any chance you could update this please? I really want to use this plugin :)

/Nils-H said...

Seems like a Subversion accident... But you can still get the code for the 1.0 version at http://boss.bekk.no/repos/projects/maven-jetty-pluto-embedded/tags/maven-jetty-pluto-embedded-1.0/

Luke Daley said...

Nope, that one is empty too. I first tried the one from the central mvn repo. It's missing the classes too.

http://boss.bekk.no/repos/projects/maven-jetty-pluto-embedded/tags/maven-jetty-pluto-embedded-1.0/src/main/java/org/apache/pluto/embedded/

Can you please push a new version with the classes.

/Nils-H said...

If you try https instead of http it should work.

Luke Daley said...

Do you mean access the svn repo via https? If so, that doesn't work either.

/Nils-H said...

https://boss.bekk.no/repos/projects/maven-jetty-pluto-embedded/trunk/ works like a charm for me...

Anyway, if you're just going to use the plugin, it's available in the maven repostitory with groupId com.bekk.boss, artifactId maven-jetty-pluto-embedded and version 1.0.

Luke Daley said...

That directory is still empty for me.

The problem is that the jar doesn't contain all the classes that are required, such as org.apache.pluto.embedded.jetty.util.OverrideContextLoaderListener and org.apache.pluto.embedded.util.PortalStartupListener among others. What I think has happened here is that you have run mvn install from your local copy which installed a working jar in your local repo. But those classes aren't in svn.

I pulled apart the jar from the maven central repo, it doesn't have the classes either.

I could easily be wrong about this and have done something wrong. But when I try to use this plugin, jetty starts but complains of not being able to find http://org.apache.pluto.embedded.jetty.util.OverrideContextLoaderListener.

/Nils-H said...

If you manually download the artifact from http://repo1.maven.org/maven2/com/bekk/boss/maven-jetty-pluto-embedded/1.0/ you'll see that it includes all the required classes. Have you tried deleting it from youor local maven repository so it will force a fresh download?

Luke Daley said...

Worked out what is going on.

The http://static.nilshelgeogmaria.com/struts2/jetty-pluto-web-default.xml file references ORG.APACHE.pluto.embedded.jetty.util.OverrideContextLoaderListener, but it should be COM.BEKK.BOSS.pluto.embedded.jetty.util.OverrideContextLoaderListener.

Can't test at the moment, but I'll be fixing that makes it work.

Sorry for the noise. Thanks for the plugin :)

Sarris said...

Hi, Trying to get this plugin to work. Everything starts up nice but when requesting the porlet page the following message appears, do you know how to solve this one?

2008-10-30 20:06:41.226::WARN: /test/pluto/index.jsp
java.lang.IllegalArgumentException: Portlet ID &{org_apache_pluto_embedded_portletId}' does not contain a dot or the dot is the first or last character
at org.apache.pluto.driver.services.portal.PortletWindowConfig.getSeparatorIndex(PortletWindowConfig.java:170)
at org.apache.pluto.driver.services.portal.PortletWindowConfig.parseContextPath(PortletWindowConfig.java:121)
at org.apache.pluto.driver.services.portal.PortletWindowConfig.fromId(PortletWindowConfig.java:176)

/Nils-H said...

Hard to say without looking at your pom.xml, but it might be that you have misspelled portlet id property in your configuration for the setup in the pom.xml file.

Sarris said...

Hmm yeah would sound like that, but I already looked if I misspelled something but could not find it..

hmmzzz not able to paste (part of) pom.xml.

I also tried to replace the org.apache.pluto.embedded.portletId with org_apache_pluto_embedded_portletId but no luck either

/Nils-H said...

What is the value that you have used for the property?

Sarris said...

Just downloaded the 1.0.1 version of the but I now get this message:

Portlet ID '${portlet}' does not contain a dot

In my pom is configured this:

< systemProperty >
< name org.apache.pluto.embedded.portletIds< /name >
< value >ArchiveTaskList,EditSalesContract< /value >
< /systemProperty >

I guess the porlet should contain one of the values defined. I tried to put a dot in the name but that didn't solve anything. Any help will be appreciated...

/Nils-H said...

Could you post your pom.xml and web.xml (for instance at pastebin)? The error indicates that either the portlet ids was not found in the configuration, or the expression is not evaluated.

Sarris said...

http://pastebin.com/m7cb035af and http://pastebin.com/d46984b6d

/Nils-H said...

Could you paste your portlet.xml and the generated web.xml as well?

Sarris said...

Pasted them on pastebin, portlet.xml and generated web.xml

/Nils-H said...

It might be because your web.xml is set to use servlet 2.3. Try updating to the 2.4 schema and see if that works. Your initial configuration of portletIds should be correct.

Sarris said...

I tried updating to servlet 2.4 (at least I think) and still got the same problem. I've placed the new generated web.xml on pastebin

/Nils-H said...

I need the link to the paste as well...

Sarris said...

http://pastebin.com/d792abbd5

/Nils-H said...

It works just fine with me. Ensure that you don't have conflicting versions of servlet-api and jsp-api on the maven runtime classpath. I guess if you remove the portletIds, you get a different error message? Looks to me like the EL-expression isn't evaluated properly...

Sarris said...

if I don't define portlet ids I get this:
javax.servlet.ServletException: No portlet id specified. Please set the system property "org.apache.pluto.embedded.portletIds"
Still cannot get it to work..

/Nils-H said...

could you provide a link to a download? Or send it to me at nilsga at gmail dot com.

Sarris said...

Hmm ok, got further. put another c.tld and now i've got something on my screen

Sarris said...

hmm other question now, in the porlet I read the userprincipal from the request. This results in an NPE, is there a way to simulate a logged in user?

/Nils-H said...

Yes, you can create a servlet filter that wraps the request to return a dummy user principal. Have not made it possible to configure this though, so you have to add it to web.xml. I'll try to get it into the next release.

Sarris said...

Hmm owkee... then I only have to find a way to differentiate the web.xml for test purposes and non test purposes..

/Nils-H said...

Yes, that's what maven profiles are for :)

/Nils-H said...

I have committed a fake user principal filter in trunk at https://boss.bekk.no/repos/projects/maven-jetty-pluto-embedded/trunk/

If you check out and build, and update to 1.0.2-SNAPSHOT, you should be able to configure it with system properties. Look at the FakeUserPrincipalFilter to find out how it works.

Sarris said...

Cool, did write a filter myself but it doesn't filter for some strange reason ...