Sunday, March 1, 2009

Maven Hack: Simple Skinny WARs

Reading Maven's skinny-war documentation, it might turn you off to the idea. A skinny war is where your WAR does not contain any jar files, but instead get's packaged within an EAR than instead contains the jars. So rather than a zipped up EAR that looks like this:
myear-1.0.ear
\__mywar-1.0.war
\__WEB-INF/lib/
|__ log4j-1.2.8.jar
\__ commons-logging-1.1.jar
It will instead look like this:
myear-1.0.ear
|__mywar-1.0.war
\__lib/
|__ log4j-1.2.8.jar
\__ commons-logging-1.1.jar
By default, the maven-ear-plugin assumes that any WAR dependencies already have the required jars packaged into it (in other words, it assumes a "fat" war). Because of this assumption, the EAR plugin will not package the WAR's transitive dependencies (why should it? If it did, you'd have duplicate dependencies, and the EAR would be twice the size).

The problem is, because of this assumption, when you instead provide it with a "skinny" war (one without JARs), the EAR plugin cannot know that the WAR it is given doesn't have those dependencies already. If you give the EAR as skinny WAR, the package will look like this:
myear-1.0.ear
\__mywar-1.0.war
Hardly what we wanted! The fix according to the documentation is to duplicate the WAR's dependencies in the EAR, so the EAR can download and install them. Note that the maven-ear-plugin only ignores WAR transitive dependencies, not JARs or EJBs. But duplicating all WAR dependencies isn't only a lot of typing, it breaks encapsulation of the WAR projects. What to do!? (NOTE: "breaks encapsulation" means, if your skinny WAR project depends on log4j:1.2.8 and commons-logging:1.1, then your EAR project will need to also add the dependencies log4j:1.2.8 and commons-logging:1.1. If you upgrade your WAR to use log4j:1.2.12, then you must make the change in your EAR too. Fail.)

Luckily, we can fix this egregious conundrum.

The maven-ear-plugin will package up transitive dependencies of all projects except for WARs (and possibly EAR - I've never really tried it). This means, we can add the WAR project's POM as it's own dependency.

So, your EAR dependency list will now contain two dependencies for each WAR only:

mywar:1.0:war and mywar:1.0:pom. Although the EAR won't package up your skinny WAR's dependencies, it _will_ package the POM's dependencies - which just so happen to be exactly the same as the WAR's! Now your EAR POM will look something like this.
<dependency>
<groupId>com.myorg</groupId>
<artifactId>mywar</artifactId>
<version>1.0</version>
<type>war</type>
</dependency>
<dependency>
<groupId>com.myorg</groupId>
<artifactId>mywar</artifactId>
<version>1.0</version>
<type>pom</type>
</dependency>
Yatta!

4 comments:

Anonymous said...

Very interesting... I was just searching for this solution for a colleague of mine (mostly because we couldn’t figure out why one older project was downloading the JARs into the lib folder of the EAR auto-magically… a trait we wanted in a newer project). Yes, I don't like the copy-and-paste everywhere that is proposed as a standard on the Maven website...

Rares Barbantan said...

Thanks for the great post. It is a very elegant solution compared to all the duplicate code that is the alternative.

Michał Minicki said...

Unfortunately Maven tries to download wars' poms from repository. So the build will fail on first clean use.

Eric Redmond said...

Run on first run:
mvn clean install
Viola.