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.

Monday, September 20, 2010

Multiple Hibernate configuration files

I must be living in a stone age, at least, with regard to Java persistency. Recently I had to add another EJB module to an application that is using Hibernate as its persistency. The application uses a much recommended HibernateUtil helper class to start up and access the org.hibernate.SessionFactory. It is almost a copy-paste from the Hibernate tutorial.

In the newly added module I want to add some HQL queries and possibly some more mapped classes. I do not want to change the existing configuration file because the new module is optional. Besides, the existing configuration lives in a different maven module. So adding new queries is possible, albeit not "clean", but adding new mapped classes is out of the question. I have to have a second configuration file but I want it to be available via the same HibernateUtil class. I do not want to copy-paste it into my module.

Quick googling shows that I am not alone but proposed solutions (at least linked from the first couple of result pages) are not really satisfying. Duplicating HibernateUtil was there as well as using org.hibernate.cfg.Configuration API. Or using a system property that defines which file to load. Yeah, this would work, except that I did not like it either: I would need to define the property in multiple places (unit tests or appserver deployment) depending on which modules are packaged. Of course, at the moment the application has max. 2 modules with a configuration file, so I can rely on a "default property value" when the new EJB module is not deployed and set the property explicitly only when the new EJB module is deployed. Will it work? Yes. But for the rest: no thanks.

Instead I have changed HibernateUtil to have more or less the following:


private static SessionFactory buildSessionFactory() {
Configuration cfg = new Configuration();

try {
ClassLoader ldr = Thread.currentThread().getContextClassLoader();
Enumeration en = ldr.getResources("hibernate.cfg.xml");
Set processed = new HashSet();

while (en.hasMoreElements()) {
URL next = en.nextElement();

if (processed.add(next)) {
cfg.configure(next);
}
}
if (processed.isEmpty()) {
FAIL
}

return cfg.buildSessionFactory();
}
catch (Exception ex) {
FAIL
}
}


I do not need to use any system properties. The code just "discovers" the configuration files and works correctly in unit tests and in the real container, with the new EJB module or without it.