Nicolas Frankel is an IT consultant with 10 years experience in Java / JEE environments. He likes his job so much he writes technical articles on his blog and reviews technical books in his spare time. He also tries to find other geeks like him in universities, as a part-time lecturer. Nicolas is a DZone MVB and is not an employee of DZone and has posted 232 posts at DZone. You can read more from them at their website. View Full User Profile

DRY and Skinny War

04.27.2010
| 6034 views |
  • submit to reddit

In this article, I will show you how the DRY principle can be applied when using the skinny war configuration of the maven-war-plugin.

While packaging an EAR, it is sometimes suitable that all libraries of the different WARs be contained not in their respective WEB-INF/lib folders but at the EAR level so they are usable by all WARs. Some organizations even enforce this rule so that this is not merely desiarable but mandatory.

Using Maven, nothing could be simpler. The maven-war-plugin documents such a use case and calls it skinny war. Briefly, you have two actions to take:

  • you have to configure every WAR POM so that the artifact will not include any library like so:
    <project>
    ...
    <build>
    <plugins>
    <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <configuration>
    <packagingExcludes>WEB-INF/lib/*.jar</packagingExcludes>
    <archive>
    <manifest>
    <addClasspath>true</addClasspath>
    <classpathPrefix>lib/</classpathPrefix>
    </manifest>
    </archive>
    </configuration>
    </plugin>
    </plugins>
    </build>
    </project>
  • you have to add every dependency of all you WARs in the EAR

The last action is the real nuisance since you have to do it manually. In the course of the project, a desynchronization is sure to happen as you add/remove dependencies from the WAR(s) and forget to repeat the action on the EAR. The DRY principle should be applied here, the problem lies in how to realize it.

There's an easy solution to this problem: if a POM could regroup all my WAR dependencies, I would only have to draw a dependency from the WAR to it, and another from the EAR to it and it would fulfill my DRY requirement. Let’s do it!

The pomlib itself

Like I said before, the pomlib is just a project whose packaging is POM and that happens to have dependencies. To be simple, our only dependency will be Log4J 1.2.12 (so as not have transitive dependency, let’s keep it simple).

The POM will be:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.frankel.blog.pomlib</groupId>
<artifactId>pomlib-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>pomlib-lib</artifactId>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
</dependencies>
</project>

Like for any other project module, I put the versions in the parent POM.

The EAR and the WAR

Both should now add a dependency to the pomlib. For brevity, only the EAR POM is displayed, the WAR POM can be found in the sources if the need be.

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.frankel.blog.pomlib</groupId>
<artifactId>pomlib-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>pomlib-ear</artifactId>
<packaging>ear</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>pomlib-lib</artifactId>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>pomlib-war</artifactId>
<type>war</type>
</dependency>
</dependencies>
</project>

Likewise, versions are put in the parent POM. Notice the import scope on the pomlib dependency, it’s the only magic.

Using mvn install from this point on will put the log4j dependency at the root of the generated EAR artifact and not in the WEB-INF/lib folder of the WAR.

Conclusion

Now that all dependencies are described in the pomlib, should you need to add/remove a dependency, it’s the only place you’ll need to modify. With just a little configuration, you’ve streamlined you delivery process and saved yourself from a huge amount of stress in the future.

By the way, if you use a simple Servlet container (like Tomcat or Jetty) as your development application server, you could even put the skinny war configuration in a profile. Maven will produce “fat” WARs by default, at development time. At delivery time, just activate the profile and here’s your EAR product, bundling skinny WARs.

You can find the source of this article in Maven/Eclipse format here.

 

From http://blog.frankel.ch/dry-and-skinny-war

Published at DZone with permission of Nicolas Frankel, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Slava Lo replied on Tue, 2010/04/27 - 7:44am

Be careful though with skinny wars and class loading issues. As now you going to have single jar shared across multiple webapps, you may end up with single instance of a particular class loaded by application server.

This is particularly interesting when you initialize log4j with intent to separate logging for each webapp in its own log file. If you have one instance of log4j factory, then all webapps will write to the same file of some webapp started first.

Sometimes you have to change the class loading settings to create a separate class loader tree for each webapp to address the issue described above.

Gabriel Forro replied on Wed, 2010/04/28 - 1:07am

Thanks for the article. We are forced to use similar approach in our ear files also.

In my opinion the <scope>import</scope> is ignored in the pomlib-ear's pom.xml. The whole stuff will work without it. In your example the pomlib-lib provides transitive dependencies and this is the reason, why it does work. The import scope does not provide any functionality in the example.

According to the Maven's user guide the import scope is used to import dependency declarations in the dependencyManagement section  Of course its usage does not break anything, it is just ignored.

Nicolas Frankel replied on Fri, 2010/04/30 - 12:42pm in response to: Gabriel Forro

I will look into it ASAP; if that's true, that's a very good comment!

After verification, kudo to tyou! Updated article to take this into account.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.