Saturday, December 6, 2008

Using JBoss Seam Hot Deployment with a Maven Build

[UPDATE: The new plugin version is documented here]

As you might have read in previous posts, one of our favorite web application stacks consists of JBoss Seam. While combining this with our favorite tooling suite consisting of Eclipse and Maven, we had to realize that Seam has a much longer tradition with Ant, and Maven support is only slowly moving in. Today I want to share some ideas on how to tackle the combination issues.


While JBoss Tools provides a nice code hot deployment feature when working with Seam on JBoss, things can get a little hairy when using a Maven build with the project. Seam uses a special classloader and relies on monitoring the WEB-INF/dev folder for changed classes - where Maven is completely unaware of this and uses the standard WEB-INF/classes folder to deploy all classes in the project. More than once I've seen the situation where JBoss Tools and Maven got in each others way, ending with JBoss rejecting to restart the application due to "duplicate components". The reason was that Eclipse hot deployed classes to WEB-INF/dev where Seam expects them, but Maven being completely unaware of it packaging all classes in the common WEB-INF/classes folder.

In order to avoid trouble and also to allow using hot deployment from a command line build, I experimented with several approaches to achieve hot deployment with Maven. The main challenges here are:
  • Distinguishing compiler output: The Maven compiler plugin by default compiles everything in the same output folder. We need to be able to distinguish between hot and regular deployable code.
  • Publish updated resources to JBoss, including domain model classes, seam components as well as XHTML pages.
  • Make sure Seam unit tests still compile.
The solution I came up with was to write a new Maven plugin which extends the Maven compiler plugin with a new hotdeploy:compile goal and the following functionalities (source code as well as a simple Maven 2 repository is available in our Google Code account):
  • Hook into the compile phase and compile the source code to a dedicated directory. The source code location is configurable as well as the output directory.
  • Remove outdated hot deployable class files from the JBoss deployment. This is necessary for the Seam classloader to pick up the changes, only updating the file is not enough.
The plugin configuration is straight forward as it's based on the regular compiler plugin and is shown in the XML snippet below. Note that it is recommended to create a profile for this build setup as both your continuous build as well as your operations team might not care too much about using hot deployment.
<plugin>
<groupId>com.ctp.seam.maven</groupId>
<artifactId>maven-hotdeploy-plugin</artifactId>
<version>0.1.0</version>
<configuration>
<source>${java.source.version}</source>
<target>${java.source.version}</target>
<hotdeployOutputDirectory>${directory.hotdeployables}</hotdeployOutputDirectory>
<includes>
<include>**/session/**</include>
</includes>
<deployDirectory>
${directory.deploy.jboss}/${build.finalName}.${project.packaging}
</deployDirectory>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>

The main additions to a regular compiler plugin setup are:
  • This variant uses the common /src/main/java source folder and uses in- and excludes to distinguish the hot deployable sources. Alternatively you can set the sourceDirectory configuration value pointing to a dedicated source folder (similar as JBoss Tools does the setup for you). Don't forget that this requires then the Build Helper Maven plugin to add source folders to your regular build.
  • The deployDirectory is a reference to your exploded application directory inside the JBoss deploy folder. This is needed in order to remove outdated hot deployable classes.
  • You can specify the hotdeployOutputDirectory directory where the code should be compiled to. Note that this defaults to the value shown in the example - feel free to skip this part. The plugin will compile into this folder after appending a WEB-INF/dev to the path.
The rest of the setup we can do by simply reconfiguring existing plugins. Note the Google Code Wiki contains a complete sample POM ready for you to start your own Seam webapp. The details on the configuration are:
  • Reconfiguring the WAR plugin so the webappDirectory points inside our JBoss deploy folder. Maven uses this folder to assemble the WAR file, which perfectly corresponds to an exploded deployment. Additionally we define our hot deployment compilation folder as a webResource, which will copy our compilation exactly to the WEB-INF/dev folder in the exploded deployment.

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
<directory>${directory.hotdeployables}</directory>
<filtering>false</filtering>
</resource>
</webResources>
<webappDirectory>
${directory.deploy.jboss}/${build.finalName}.${project.packaging}
</webappDirectory>
</configuration>
</plugin>

  • Similarly, we have to add our output folder to the test classpath. This is simplest done by adding it as a resource folder, which will copy the classes to the target/test-classes directory before the test classes are compiled:
<testResources>
<testResource>
<directory>${directory.hotdeployables}/WEB-INF/dev</directory>
<filtering>false</filtering>
</testResource>
...
</testResources>

If you want to run a similar luxury setup as you get from JBoss Tools, you can add a Maven builder to your project. Simply go to the Eclipse Project properties and click "New" on the "Builders" properties. The new Maven build should look like the following:



Make sure that the following conditions apply to your Eclipse setup:
  • The JRE you have configured is actually a JDK - otherwise the builder will fail. Once you see that the builder does not fail, you can also switch off the console allocation in the "Common" tab.

  • Also take care that you always build with your specific hot deploy profile - otherwise make sure to clean before using the build again.

  • Assert that Eclipse uses a dedicated output folder and not the Maven target/classes.

Unfortunately when activating the builder Eclipse might prompt you when editing files and (depending on the speed of your machine) hang for a second or two after saving a file. Still, this is much better than waiting a minute for your JBoss server to restart.

Happy coding!

13 comments:

David said...

Is it possible to get this working in EAR type seam project?

Thomas said...

I haven't looked at this yet (EARs will probably get out of fashion with EJB 3.1 anyway ;-), but from the WAR perspective this gets just deployed inside the [application].ear folder in the JBoss deploy directory.

In terms of the sample POM, it might be enough to just tweak the directory.deploy.jboss property to point inside the exploded EAR directory. I guess you also need to add a similar profile to the EAR module (resp a common one in the parent POM), using the unpackTypes configuration.

Additional note to the article: Seam 2.1.1.GA includes improved hot deployment, and the solution outlined here might not work as expected (still to be verified :-)

Rafique Anwar said...

Hi Thomas,
g8 work. Can you verify if it still works with seam 2.1.1.GA?

Thomas said...

Hi Rafique
So far I didn't run into issues using the plugin with 2.1.1.GA. Some funky effects popped up with hot deployed session components, but browsing the Seam source code it looked much like this is related to the new hot deploy implementation.

BTW: Just commited a new plugin version yesterday. The new version deals better with an Eclipse Maven project and has a simplified configuration. Unfortunately the documentation didn't make it to the Google code wiki yet :-) but I'll be working on it next week.

Rafique Anwar said...

Thanks Thomas for your quick response. I will be looking forward to it and meanwhile check out the old one.

Guillaume said...

Hi Thomas,

This is great stuff, I have a problem though, I need to filter web.xml with maven properties at build time. I added the following in my war plugin config:

<webResources>
<resource>
<directory>${basedir}/src/main/webapp/WEB-INF</directory>
<targetPath>WEB-INF</targetPath>
<filtering>true</filtering>
</resource>
</webResources>


but the filtering does not happen when running hotdeploy:exploded goal. Do you have any pointers? The filtering works fine for other resources in src/main/resources but I cannot put web.xml in src/main/resources because these files are copied under WEB-INF/classes ...

Thomas said...

Hi Guillaume

Try to add
<filteringDeploymentDescriptors>true</filteringDeploymentDescriptors>

to the hotdeploy configuration if you have to filter web.xml. Seams to be a quite new option for the WAR plugin (since Maven 2.0.9 I think). At least in my quick test run here it worked fine.

Hope this helps...

Ashok said...

Has anyone tried to make this work for an ear type seam project? If so I would greatly appreciate any pointers. In my configuration, I have many application modules(as jars) packed inside the ear along with the war and ejb jars.

Thomas said...

Took a while, but here we go with the EAR project support.

Miloslav Vlach said...

Hello,

I have mysterious problem and I can't do my work. I'm changing the source code and the hotdeploy:explode task generate result file with old date (from today have all *.class files date 2009-08-31 21:35. The parent 2009-09-04 14:39. I have deleted the target directory, called clean but the problem is still alive.
Can somebody point me to good direction ? Where could be problem ?

Thanks Mila

Thomas said...

Can you file this on the google code issues, including plugin configuration, plugin version, and maven version?

Please also describe which class files get out of date (the hot deployable, the regular, or the one that end up in WEB-INF/dev, /classes).

Miloslav Vlach said...

I have to apologize, the fault was on my side. I have do synchronization from my college and I have copied the compiled class file (and libraries) to the /src/main/webapp/WEB-INF folder.

So, the plugin work great.
Thanks for yours time.

Mila

Thomas said...

Oh I see - had that issue also once with a Maven project and WEB-INF/lib.

Anyway, thanks for posting the cause of the issue!