Thursday, September 30, 2010

Convention over configuration, maven style

Maven is regarded as a very nice example of convention over configuration. I do not dispute that, it is true ... to some extent, until you try something just a bit out of maven's notion of "convention". And then you have a problem.

I mentioned this in one of the earlier posts:

But maven makes creation of such an EAR hmmm ... interesting. I just hate to think what amount of work I would need to do if I needed some dependencies in EAR/lib and some in WEB-INF/lib and most importantly what amount of work would it be to maintain this structure.

The time has come. I need some jars in WEB-INF/lib (tag libraries) and the rest in <EAR>/lib. So let's look at "convention over configuration" a little bit closer. This is what I have to do:

  1. WAR's pom.xml: declare dependencies on external jars and on some other subprojects (couple of EJBs), nothing unusual, should be done anyway. I mention it here for completeness.

  2. EAR's pom.xml: again, nothing unusual, declare dependency on EJB en WAR projects.

That is all. Maven does its magic producing correct WAR and EAR files. Convention over configuration does wonders, does it not?

Sorry, got carried away. Ah, dreams, dreams...

Reality is quite different. Sure, if I do what I just mentioned, the resulting WAR file is correct. But EAR is completely screwed up because the WAR is included "as is". Some dependency jars are present in <EAR>/lib and in <WAR>/WEB-INF/lib. All EJB jars are also present twice, in <WAR>/WEB-INF/lib and in the root of EAR.

Maven offers some ... hmmm ... solution for the problem, documented here: "Solving the Skinny Wars problem". They honestly warn you saying:
The Maven WAR and EAR Plugins do not directly support this mode of operation but we can fake it through some POM and configuration magic.

Let's look at their "configuration magic". It is consists of 2 pieces of configuration that has to be applied to WAR and EAR pom files:

  1. You have to configure WAR pom.xml to remove jars from WEB-INF/lib and to add their names in MANIFEST.MF. Note that these options are orthogonal and you have to specify both.

    And if you need to leave some of the jars in WEB-INF/lib (I need to), then the configuration becomes really crazy. Watch the "POM and configuration magic":

    • option 1:

      <plugin>
      <artifactId>maven-war-plugin</artifactId>
      <configuration>
      <packagingExcludes>
      WEB-INF/lib/C*.jar,
      WEB-INF/lib/R*.jar,
      WEB-INF/lib/a*.jar,
      WEB-INF/lib/b*.jar,
      WEB-INF/lib/c*.jar,
      WEB-INF/lib/d*.jar,
      WEB-INF/lib/e*.jar,
      WEB-INF/lib/h*.jar,
      WEB-INF/lib/jb*.jar,
      WEB-INF/lib/js*.jar,
      WEB-INF/lib/joda-time-1*.jar,
      WEB-INF/lib/joda-time-h*.jar,
      WEB-INF/lib/sl*.jar
      </packagingExcludes>
      <archive>
      <manifest>
      <addClasspath>true</addClasspath>
      <classpathPrefix>lib/</classpathPrefix>
      </manifest>
      </archive>
      </configuration>
      </plugin>
    • option 2:

      <plugin>
      <artifactId>maven-war-plugin</artifactId>
      <configuration>
      <packagingIncludes>
      WEB-INF/lib/standard-1.1.2.jar,
      WEB-INF/lib/joda-time-jsptags-1.0.2.jar,
      **/*.xml,
      public/**/*.*,
      images/**/*.*,
      css/**/*.*
      static/**/*.*
      </packagingIncludes>
      <archive>
      <manifest>
      <addClasspath>true</addClasspath>
      <classpathPrefix>lib/</classpathPrefix>
      </manifest>
      </archive>
      </configuration>
      </plugin>
    At one time I also thought of extracting TLD files from tag libraries, placing them under WEB-INF, and still removing all jar files from WEB-INF/lib. But I decided against it.

    No matter how you look at the problem it is not really "convention". Hell, it is "configuration, configuration, configuration, and even more configuration". And, worst of all, the resulting WAR file cannot be deployed separately, only as part of an EAR. Oops, here go all your nice integration tests. Fortunately it is not a problem for our current project because we do not deploy WAR files separately. But I can imagine some other people would like to do it.

    This "solution" has a minor problem that the resulting MANIFEST.MF is also incorrect: it has all jars mentioned in Class-Path: attribute, even those that stay in WAR/WEB-INF/lib.

    Oh yeah, and some annoyance: I have to specify exclusion by file name and not by dependency. I have only 4 dependencies in WAR's pom.xml and look at that exclusion list!

  2. But it is not all! More "configuration magic": you also have to change EAR's pom.xml and add all WAR-only dependencies that are not included in <WAR>/WEB-INF/lib. Otherwise they are not packaged into EAR/lib. Lucky you, it is WAR-only dependencies, and not dependencies of EJBs.
Wow, finally you get the packaging you want. Now you have to make sure everybody on the team knows that updating projects' dependencies (not only WAR project but also all EJB projects) or adding some directories under src/main/webapp is not that easy as it should be. Because it will sure break WAR packaging.

This is of course not new. The related bug MWAR-9 has just turned 5 years. Congratulations, maven! Happy anniversary, MWAR-9!

This post was brought to you by maven notion of convention over configuration.

1 comment:

  1. There is other solution to exclude specific jar from lib:


    %regex[lib/(?log4j*).*]

    ReplyDelete