How to introduce build profiles

Objectives

A common need  in projects id to use Maven build profiles in order to customize the produced application for different environments. For instance, you might want to use an in-memory database with schema created and test data inserted at webapp startup in your development environment whereas you will use a "normal" database in production.

This article is about introducing different build profiles to tackle down this problem. Using a standard Jspresso project structure, we will define 2 Maven build profiles, dev an prod. The dev build profile should be the default one, whereas the prod profile should be activated (using -Pprod on the command line) to produce a production-specific, deployable webapp.

 

Introduce an environment variable

The first step is to introduce an environment variable that will have its value assigned depending on the activated profile. Edit your project root pom.xml and add the following section (before the <build> tag for instance) :

  <properties>
<environment>dev</environment>
</properties>
<profiles>
<profile>
<id>prod</id>
<properties>
<environment>prod</environment>
</properties>
</profile>
</profiles>

This asssigns "dev" as the value of the environment property, unless the prod profile is activated in the Maven build, in which case the environment property is valued with "prod".

 

Create profile-specific webapp resource directories

In order to adapt the produced webapp to the targeted platform, we will create 2 extra webapp resource directories :

  • a dev specific resource directory, that will contain development specific resources :
      webapp/src/main/dev/resources
  • a prod specific resource directory, that will contain production specific resources :
      webapp/src/main/prod/resources

All other common webapp resources will go in the standard webapp/src/main/resources directory.

 

Distribute the resources among the different resources directories

For the sake of our example, let's say that we want to :

  1. use a different config.xml for both profiles (to change the targeted database)
  2. package a log4j.properties only for the dev profile (production logging relies on a server-wide logging configuration)
  3. use the same ehcache.xml Hibernate cache configuration for both profiles

So we will :

  1. copy and customize the existing config.xml in both webapp/src/main/dev/resources and webapp/src/main/prod/resources directories. Delete the original one from the common webapp/src/main/resources directory.
  2. copy the existing log4j.properties only in webapp/src/main/dev/resources. Delete the original one from the common webapp/src/main/resources directory.
  3. leave the existing ehcache.xml Hibernate cache configuration in the common webapp/src/main/resources directory.

 

Include the activated, profile-specific, webapp resource directory in the build

We now have to tell Maven to include the different webapp resources directories based on the activated profile. This is done by adding the following xml fragment in the <build> section of the webapp/pom.xml :

  <build>
    ...
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>false</filtering>
</resource>
<resource>
<directory>${basedir}/src/main/${environment}/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
...
  </build>

 

Customize the dependencies included in the different environments

One common use case consists in customizing the built package depending on the target environement. For instance, you can run your development environment using Tomcat and your pre-production / production environment using JBoss. In that case, there are some libraries that have to be packaged in your development webapp war but not in your production war. Most of the time this is because the target environment provides them as part of their runtime infrastructure and packaging them in the application itself can lead to unexpected results (mainly linked to the classloading architecture). In the example above, JBoss will provide several jars that have to be excluded from the war. This is achieved by marking those dependencies as provided in the prod profile, e.g. :

  <build>
    ...
</build>
<profiles>
<profile>
<id>prod</id>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jboss</groupId>
<artifactId>jbosssx</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jboss</groupId>
<artifactId>jboss-common</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</profile>
<profiles>

 

Create customized web.xml descriptors

One last thing we would like to variabilize depending on the targeted environment is the packaged webapp descriptor. For instance, we might want to use different session timeouts for both profiles, disable the insertion of test data on webapp startup for the production war, ...

The standard Jspresso project already contains 2 different web.xml :

  • webapp/src/main/webapp/WEB-INF/noulc-web.xml that is used by default.
  • webapp/src/main/webapp/WEB-INF/ulc-web.xml that is used when the ulc profile is activated, i.e. you want to use the Canoo ULC commercial library.

In order to further variabilize the used web.xml based on the targeted environment, we will :

  • rename noulc-web.xml and ulc-web.xml respectively to noulc-dev-web.xml and ulc-dev-web.xml.
  • clone them respectively to noulc-prod-web.xml and ulc-prod-web.xml. and customize the clone files for production.

 

Include the right web.xml in your maven build

We will now tell Maven to package the right web.xml depending on the environment build profile. Edit webapp/pom.xml and look for the 2 <webXml> sections. Replace :

  • noulc-web.xml by noulc-${environment}-web.xml
  • and ulc-web.xml by ulc-${environment}-web.xml

This way, depending on the environment property value, the right web.xml will be picked up for packaging.

 

Conclusion

Of course, this profile-base build customization can be extended to include different customizations for other domains (e.g. a platform-specific build).

Following our example, everytime you need to create a production artifact, just activate the prod build profile, i.e. :

mvn clean package -Pprod