Monday, March 23, 2009

Finally! Practical Maven Skinny Wars

So, Maven 2.1 was finally released. I can't say I'm overly impressed with the pace, but considering it was driven almost exclusively by John Casey, it's a great achievement, nonetheless. Deterministic Build Lifecycle Planning (or DeBLiP, as we [well, just me] in the industry call it) is such a huge leap forward in build repeatability, it's hard to comment on at this stage. It's something best held to the clearer lens of retrospect.

But enough about that. Something of immediate practical value: real skinny WARs. Until now the maven-war-plugin left you with two choices: no jars, or most jars. Sure, you could manually exclude all but a set, but it was a PITA and unpractical. Behold my surprise to find the newest WAR config addition to 2.1-beta-1: packagingIncludes.

Use case:
I want a mostly skinny WAR that filters out all JARs except for struts.

Solution:
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1-beta-1</version>
<configuration>
<packagingIncludes>WEB-INF/lib/struts*.jar</packagingIncludes>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
Thanks WAR plugin guys! Now just fix the EAR problem and I'll be set.

Friday, March 13, 2009

Code Te Ching - Verse 49

The elite rests entirely, his mind is balanced and at ease.
He is not attached to methods or language, but he knows them well anyway.

A pebble cannot halt a stream, but can block a dam ten thousand times larger.
So too does the elite know, that though a language may have no use to the masses,
There are times where it simply outshines the most elaborate of methods.

The wise respect the direction of the elite,
The wise knows he is not blind to the world.

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!