Tuesday, February 16, 2010

Rapid REST Development with maven jetty plugin

Ever wonder how much time is wasted by Java developers rebuilding and redeploying web applications? For me alone I can't imagine it. In fact, I'd be embarrassed if Google Buzz made it public knowledge without my consent. Two years ago I wrote an article "No more J2EE Apps" and I received a lot of great feedback. Let me first say that I think, IMHO, Java Developers are at a disadvantage when it comes to rapidly developing web applications with a static language. Developers using python, php, rails, or grails really don't even have to spend a second trying to solve this problem. On the other hand, Java Developers have to figure out how to best accomplish this, and every situation seems to be different: JBoss, Weblogic, Eclipse, Idea, Netbeans, Jetty, JRebel. Of all the solutions I think JRebel provides the best chance for success that solves any environment no matter the web container or developers IDE of choice.

I haven't really done much hardcore java development in awhile, but in order to improve my team's productivity, I am going to be exploring best practices the next couple of months concerning this area. First up, I am going to explain how I got the maven jetty plugin to work with our REST Services WAR and the steps necessary to redeploy changes. The nice thing about jetty is it's easy to use from maven and we could use it in our CI environment to possibly reduce our build times and provide quicker feedback. The downside is each change requires jetty to hotdeploy the new WAR. End the end I think the best solution will be a combination of JBoss+JRebel. But I won't get to for awhile.

Maven Jetty Plugin
My first prototype uses the maven-jetty-plugin version 6. The application we are testing is a WAR containing REST Services built with Jersey (JAX-RS). Here is a good posting by my co-worker Jeff Black "Jersey...Jetty and Maven style!". This example didn't work for me because for some insane reason the init-param, com.sun.ws.rest.config.property.packages, does not work in WebSphere. So I had to do some slight modifications to get it to work without that being declared. My pom.xml and jetty.xml files are below. Most "normal" non-Jersey applications don't need all of this, but it was necessary to get our legacy WAR working with jetty.

Here are the steps involved to start jetty and redeploy changes:
  1. mvn install jetty:run - this will first build the WAR and then start jetty while also deploying the WAR. Running install was necessary because I reference the exploded WAR directory under target in the jetty configuration.
  2. Make changes to source code
  3. Run "mvn compile war:exploded" in a separate terminal to compile your changes and copy the new class files to the location Jersey expects to find them. Which in my case is /target/myapp/WEB-INF/classes
  4. Click back to the terminal running jetty and hit the ENTER key. This causes jetty to reload the WAR. This is because by default I set the scan interval to 0 and jetty.reload to manual, so I can batch up multiple changes before reloading.
Overall I am happy with the results so far. Previously, it took 1-2 minutes and sometimes more to rebuild the war and hotdeploy to JBoss. Using the jetty plugin this now takes around 30 seconds. Again, I think this could be further improved by using JRebel.

Tips
I did have to update my MAVEN_OPTS environment variable to increase Java's PermGenSpace since jetty reloads the WAR each time and you'll quickly run out of memory. This was something I was already doing in JBoss. Here is what it is set to:

export MAVEN_OPTS="-Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m"

Sample Files
Here is my pom.xml
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.22</version>
<configuration>
<jettyConfig>${project.build.testOutputDirectory}/jetty.xml</jettyConfig>
<scanIntervalSeconds>${jetty.scan.sec}</scanIntervalSeconds>
<useTestClasspath>true</useTestClasspath>
<webAppConfig>
<baseResource implementation="org.mortbay.resource.ResourceCollection">
<resourcesAsCSV>${project.build.directory}/myapp</resourcesAsCSV>
</baseResource>
</webAppConfig>
<systemProperties>
<systemProperty>
<name>jetty.port</name>
<value>${jetty.port}</value>
</systemProperty>
</systemProperties>
<systemProperties>
<systemProperty>
<name>jetty.reload</name>
<value>${jetty.reload}</value>
</systemProperty>
</systemProperties>
</configuration>
<dependencies>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>net.sourceforge.jtds</groupId>
<artifactId>jtds</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.oracle.jdbc</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0</version>
</dependency>
</dependencies>
</plugin>
.....
<properties>
<jetty.port>8080</jetty.port>
<jetty.scan.sec>10</jetty.scan.sec>
<jetty.reload>manual</jetty.reload>
</properties>

Here is my jetty.xml located under /src/test/resources used to define the Datasource.
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">

<Configure id="Server" class="org.mortbay.jetty.Server">

<New id="MYAPP-DS" class="org.mortbay.jetty.plus.naming.Resource">
<Arg>jdbc/MYAPP-DS</Arg>
<Arg>
<New class="org.apache.commons.dbcp.BasicDataSource">
<Set name="driverClassName">oracle.jdbc.driver.OracleDriver</Set>
<Set name="url">jdbc:oracle:thin:@localhost:1521:XE</Set>
<Set name="username">user</Set>
<Set name="password">password</Set>
</New>
</Arg>
</New>

</Configure>
blog comments powered by Disqus