Maven profile management

Introduction

Maven is a great tool when it comes to build and package Java/JEE projects. Maven offers a standard for almost all of the tasks performed during a build or a packaging; tasks that used to be performed through several custom ant tasks are now usually provided out of the box. Even the structure of the build control file, the pom.xml file, is a standardized XML file thus allowing the build and packaging of a project
Additionally, the high modularity of the tool where each task, even the most commons are provided by Maven plugins allows a seemingly unlimited amount of extensions of the core features.

Of the interesting features that come out of the box is profile management. Any standard parameter can be overridden in a profile definition. One of several profiles can be activated at any execution. There are several possible profile activation mechanisms whether explicit activation through the -P command line option or conditional based on the value of a property, the execution machine operating system, …

The most common use case for Maven profiles is the specialization of the build according to the target environment. Most projects are installed on development, integration, pre-production and production environment and usually each environment requires a different configuration.

Use case for this article

The management of these configuration file is typically handled using maven profiles with one profile per target environment. A common scenario is to handle configuration files as resources in the project and stored in environment-specific resources directory.
The problem that can arise is that some resources will be profile independent, while others like configuration files will have a different version for each profile.

The typical solution is to redefine the resource directory for each profile like for example :

</profiles>
	:
	:
	<profile>
		<id>production</id>
		<build>
			<resources>
				<resource>
					<directory>${project.basedir}/profiles/prod/main/resources</directory>
				</resource>
			</resources>
		</build>
	</profile>	
</profiles>

this however is not ideal as profile independent resources need to be copied into each profile resource directory with obvious maintenance hurdles attached.

This article shows how to use maven profiles to manage at the same time both common and profile-specific resources. This method:

  • uses the standard src/main/resources directory to store resources common to all profiles
  • uses the build-helper-maven-plugin plugin to adds an additional resource directory for profile-specific resources

Project layout

The project layout is defined below. It is basically a standard maven structure :

  • resources common to all profiles are stored in src/main/resources
  • profile-specific resources are stored in profile//main/resources
maven-profiles/
├── pom.xml
├── profiles
│   ├── dev
│   │   └── main
│   │       └── resources
│   │           └── profile.properties
│   ├── prod
│   │   └── main
│   │       └── resources
│   │           └── profile.properties
│   └── qualif
│       └── main
│           └── resources
│               └── profile.properties
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── app
    │   │               └── HelloWorld.java
    │   └── resources
    │       └── messages.properties
    └── test
        └── java
            └── com
                └── example
                    └── app
                        └── HelloWorldTest.java

pom.xml

Setting up the profiles

In order to make maven profile-aware, the pom.xml defines explicitely all the profiles with the development profile active by default. Each profile defines the value for the property profile.name to be used elsewhere in pom.xml.

	<profiles>
		<profile>
			<id>dev</id>
			<activation>
				<activeByDefault>true</activeByDefault>
			</activation>
			<properties>
				<profile.name>dev</profile.name>
			</properties>
		</profile>

		<profile>
			<id>qualif</id>
			<properties>
				<profile.name>qualif</profile.name>
			</properties>
		</profile>
		
		<profile>
			<id>prod</id>
			<properties>
				<profile.name>prod</profile.name>
			</properties>
		</profile>
		
	</profiles>
[/xml]

<h3>Adding an additional resource directory</h3>

<p>
The profile specific resources directory is introduced as an additionnal directory using the <code>build-helper-maven-plugin</code> maven plugin.
The code is shown below; it simply states an additional the resource directory that should be added to the project whose location changes according to the active profile.
</p>


<build>
	<plugins>
		:
		:
		<!-- Add resource directory  -->
		<plugin>
			<groupId>org.codehaus.mojo</groupId>
			<artifactId>build-helper-maven-plugin</artifactId>
			<executions>
				<execution>
					<id>add-resource</id>
					<phase>generate-resources</phase>
					<goals>
						<goal>add-resource</goal>
					</goals>
					<configuration>
						<resources>
							<resource>
								<directory>${basedir}/profiles/${profile.name}/main/resources</directory>
							</resource>
						</resources>
					</configuration>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

As a result of these, at build time, the compiled project will contain all resources stored in the standard resource directory and also all resources defined in the profile-specific resource directory.

Testing the method

Test class

Ut laoreet orci id purus accumsan commodo. Nulla dignissim tempus urna ac rutrum. Mauris dui tortor, tincidunt ac malesuada vitae, laoreet id libero. Suspendisse imperdiet rhoncus gravida. Donec rhoncus sodales nisi, at molestie arcu euismod quis. Phasellus ut eros non velit iaculis pharetra. Nulla ultrices quam leo. In faucibus mi in sem auctor mollis.


package com.example.app;

import java.util.ResourceBundle;


public class HelloWorld {

	private static final String PROPFILE_MESSAGE = "messages";
	private static final String PROPFILE_PROFILE = "profile";
	
	private String message;
	private String profile;
	
	public HelloWorld() {
		super();
		
		ResourceBundle messageBundle = ResourceBundle.getBundle(PROPFILE_MESSAGE);
		ResourceBundle profileBundle = ResourceBundle.getBundle(PROPFILE_PROFILE);
		
		this.setMessage(messageBundle.getString("com.example.app.message"));
		this.setProfile(profileBundle.getString("com.example.app.profile"));	
	}
		
	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public String getProfile() {
		return profile;
	}

	public void setProfile(String profile) {
		this.profile = profile;
	}
	
	@Override
	public String toString() {
		return "HelloWorld [message=" + message + ", profile=" + profile + "]";
	}
	
	public static void main(String[] args) {
		HelloWorld helloWorld = new HelloWorld();	
		System.out.println(helloWorld.toString());
	}		
}

properties file

messages.properties

com.example.app.message=message property!

profile.properties

  • profile/dev/main/resources/profile.properties :

    com.example.app.profile=DEV
  • profile/qualif/main/resources/profile.properties :

    com.example.app.profile=QUALIF
  • profile/prod/main/resources/profile.properties :

    com.example.app.profile=PROD

Executing the test class

Development profile

$ mvn  clean package && \
	java -cp target/maven-profiles-1.0.0-SNAPSHOT.jar \
	com.example.app.HelloWorld
	:
	:
HelloWorld [message=message property!, profile=DEV]

Pre-production profile

$ mvn -P qualif clean package && \
	java -cp target/maven-profiles-1.0.0-SNAPSHOT.jar \
	com.example.app.HelloWorld
	:
	:
HelloWorld [message=message property!, profile=QUALIF]

Production profile

$ mvn -P prod clean package && \
	java -cp target/maven-profiles-1.0.0-SNAPSHOT.jar \
	com.example.app.HelloWorld
	:
	:
HelloWorld [message=message property!, profile=PROD]
Advertisements