Dynamic POM

With this guide, you can easily manage your Java or Java EE dependencies. It takes you from basic to advanced dependency management techniques in easy, logical steps. Swap your homebrew processes for automated solutions.

(For more resources related to this topic, see here.)

Case study

Our project meets the following requirements:

  • It depends on org.codehaus.jedi:jedi-XXX:3.0.5. Actually, the XXX is related to the JDK version, that is, either jdk5 or jdk6.
  • The project is built and run on three different environments: PRODuction, UAT, and DEVelopment
  • The underlying database differs owing to the environment: PostGre in PROD, MySQL in UAT, and HSQLDB in DEV.
  • Besides, the connection is set in a Spring file, which can be spring-PROD.xml, spring-UAT.xml, or spring-DEV.xml, all being in the same src/main/resource folder.

The first bullet point can be easily answered, using a jdk-version property.

The dependency is then declared as follows:

<dependency> <groupId>org.codehaus.jedi</groupId> <!--For this dependency two artifacts are available, one for jdk5 or and a second for jdk6--> <artifactId>jedi-${jdk.version}</artifactId> <version>${jedi.version}</version> </dependency>

Still, the fourth bullet point is resolved by specifying a resource folder:

<resources> <resource> <directory>src/main/resource</directory> <!--include the XML files corresponding to the environment: PROD, UAT, DEV. Here, the only XML file is a Spring configuration one. There is one file per environment--> <includes> <include> **/*-${environment}.xml </include> </includes> </resource> </resources>

Then, we will have to run Maven adding the property values using one of the following commands:

  • mvn clean install –Denvironment=PROD –Djdk.version=jdk6
  • mvn clean install –Denvironment=DEV –Djdk.version=jdk5

By the way, we could have merged the three XML files as a unique one, setting dynamically the content thanks to Maven's filter tag and mechanism.

The next point to solve is the dependency to actual JDBC drivers.

A quick and dirty solution

A quick and dirty solution is to mention the three dependencies:

<!--PROD --> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.1-901.jdbc4</version> <scope>runtime</scope> </dependency> <!--UAT--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> <scope>runtime</scope> </dependency> <!--DEV--> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.3.0</version> <scope>runtime</scope> </dependency>

Anyway, this idea has drawbacks. Even though only the actual driver (org. postgresql.Driver, com.mysql.jdbc.Driver, or org.hsqldb.jdbcDriver as described in the Spring files) will be instantiated at runtime, the three JARs will be transitively transmitted—and possibly packaged—in a further distribution.

You may argue that we can work around this problem in most of situations, by confining the scope to provided, and embed the actual dependency by any other mean (such as rely on an artifact embarked in an application server); however, even then you should concede the dirtiness of the process.

A clean solution

Better solutions consist in using dynamic POM. Here, too, there will be a gradient of more or less clean solutions.

Once more, as a disclaimer, beware of dynamic POMs! Dynamic POMs are a powerful and tricky feature of Maven. Moreover, modern IDEs manage dynamic POMs better than a few years ago. Yet, their use may be dangerous for newcomers: as with generated code and AOP for instance, what you write is not what you execute, which may result in strange or unexpected behaviors, needing long hours of debug and an aspirin tablet for the headache. This is why you have to carefully weigh their interest, relatively to your project before introducing them.

With properties in command lines

As a first step, let's define the dependency as follows:

<!-- The dependency to effective JDBC drivers: PostGre, MySQL or HSQLDB--> <dependency> <groupId>${effective.groupId}</groupId> <artifactId> ${effective.artifactId} </artifactId> <version>${effective.version}</version> </dependency>

As you can see, the dependency is parameterized thanks to three properties: effective.groupId, effective.artifactId, and effective.version. Then, in the same way we added earlier the –Djdk.version property, we will have to add those properties in the command line, for example,:

mvn clean install –Denvironment=PROD –Djdk.version=jdk6 \ -Deffective.groupId=postgresql \ -Deffective.artifactId=postgresql \ -Deffective.version=9.1-901.jdbc4

Or add the following property

mvn clean install –Denvironment=DEV –Djdk.version=jdk5 \ -Deffective.groupId=org.hsqldb \ -Deffective.artifactId=hsqldb \ -Deffective.version=2.3.0

Then, the effective POM will be reconstructed by Maven, and include the right dependencies:

<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.2.3.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.codehaus.jedi</groupId> <artifactId>jedi-jdk6</artifactId> <version>3.0.5</version> <scope>compile</scope> </dependency> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.1-901.jdbc4</version> <scope>compile</scope> </dependency> </dependencies>

Yet, as you can imagine, writing long command lines like the preceding one increases the risks of human error, all the more that such lines are "write-only". These pitfalls are solved by profiles.

Profiles and settings

As an easy improvement, you can define profiles within the POM itself. The profiles gather the information you previously wrote in the command line, for example:

<profile> <!-- The profile PROD gathers the properties related to the environment PROD--> <id>PROD</id> <properties> <environment>PROD</environment> <effective.groupId> postgresql </effective.groupId> <effective.artifactId> postgresql </effective.artifactId> <effective.version> 9.1-901.jdbc4 </effective.version> <jdk.version>jdk6</jdk.version> </properties> <activation> <!-- This profile is activated by default: in other terms, if no other profile in activated, then PROD will be--> <activeByDefault>true</activeByDefault> </activation> </profile>

Or:

<profile> <!-- The profile DEV gathers the properties related to the environment DEV--> <id>DEV</id> <properties> <environment>DEV</environment> <effective.groupId> org.hsqldb </effective.groupId> <effective.artifactId> hsqldb </effective.artifactId> <effective.version> 2.3.0 </effective.version> <jdk.version>jdk5</jdk.version> </properties> <activation> <!-- The profile DEV will be activated if, and only if, it is explicitly called--> <activeByDefault>false</activeByDefault> </activation> </profile>

The corresponding command lines will be shorter:

mvn clean install

(Equivalent to mvn clean install –PPROD)

Or:

mvn clean install –PDEV

You can list several profiles in the same POM, and one, many or all of them may be enabled or disabled.

Nonetheless, multiplying profiles and properties hurts the readability. Moreover, if your team has 20 developers, then each developer will have to deal with 20 blocks of profiles, out of which 19 are completely irrelevant for him/her. So, in order to make the thing smoother, a best practice is to extract the profiles and inset them in the personal settings.xml files, with the same information:

<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ SETTINGS/1.0.0 http://maven.apache.org/xsd/ settings-1.0.0.xsd"> <profiles> <profile> <id>PROD</id> <properties> <environment>PROD</environment> <effective.groupId> postgresql </effective.groupId> <effective.artifactId> postgresql </effective.artifactId> <effective.version> 9.1-901.jdbc4 </effective.version> <jdk.version>jdk6</jdk.version> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> </profiles> </settings>

Dynamic POMs – conclusion

As a conclusion, the best practice concerning dynamic POMs is to parameterize the needed fields within the POM. Then, by order of priority:

  • Set an enabled profile and corresponding properties within the settings.xml.

    mvn <goals> \ [-f <pom_Without_Profiles.xml> \] [-s <settings_With_Enabled_Profile.xml>]

  • Otherwise, include profiles and properties within the POM

    mvn <goals> \ [-f <pom_With_Profiles.xml> \] [-P<actual_Profile> \] [-s <settings_Without_Profile.xml>]

  • Otherwise, launch Maven with the properties in command lines

    mvn <goals> \ [-f <pom_Without_Profiles.xml> \] [-s <settings_Without_Profile.xml>] -D<property_1>=<value_1> \ -D<property_2>=<value_2> \ (...) -D<property_n>=<value_n>

Summary

In this article we learned about Dynamic POM. We saw a case study and also saw its quick and easy solutions.

Resources for Article:


Further resources on this subject:


Books to Consider

Microsoft Dynamics CRM 2011 Reporting
$ 29.99
Microsoft Dynamics NAV 2013 Application Design
$ 35.99
comments powered by Disqus