Having been a happy user of Maven2 on my personal projects for a few months now, I finally tried to start using it at work. As I expected, development is quicker and more enjoyable as I didn't have to build the project manually, then copy and tailor an existing Ant build.xml to the new project. Creating the POM file is usually the most painful part of creating a new Maven2 project, but I have kind of a personal super-POM template which contains all the library dependencies and plugins I am likely to use. Examples of libraries are all the Apache Commons JARs, and examples of plugins are the Java 1.5 and the Jetty6 plugins, to name a few.
However, because my use of Maven2 was restricted to personal/toy projects, I had never had to worry about having to deploy the code to multiple environments. However, being able to deploy to multiple environments is a standard requirement for business applications, so this was obviously something I needed to address.
I vaguely remembered having read something about filtering support in Maven2, but I did not remember the details because I never had to use it. After about a day of reading up on Maven2's filter and profile support and tinkering with the pom.xml file, I had a Maven2 based setup which looked a lot like an Ant-based setup at a previous job, and which I knew from experience was easy to use and understand. This article describes this setup and provides cookbook style instructions on how to replicate it.
The basic idea of filtering is that you set up a named properties file and specify one or more filesets to apply the substitutions in the properties file. For example:
1 2 3 4 5 6 7 8 9 | <filters>
<filter>src/main/filters/filter-${env}.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
|
The only difference from the snippet above and that provided in most Maven2 documentation is that the filter.properties file has a variable portion ${env}, which in our case will come from the selected profile. So assuming we want the database URL to be our replaceable parameter in each case (obviously there will be many more replaceable parameters per environment, but this is only an example), we set up our src/main/resources/applicationContext.properties (Spring configuration file) to contain the placeholder for the URL, something like this:
1 2 3 4 5 | <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
...
<property name="url" value="${jdbc.url}" />
...
</bean>
|
Assuming now that we want to filter for three different environments, dev, test and live, we will create three filter-${env}.properties files in src/main/filters directory. The urls are incorrect and are only for illustration, replace with valid values that make sense for your application.
1 2 3 4 5 6 7 8 9 10 11 | # filter-dev.properties
jdbc.url=jdbc:path:to:dev/database
...
# filter-test.properties
jdbc.url=jdbc:path:to:test/database
...
# filter-live.properties
jdbc.url=jdbc:path:to:live/database
...
|
Finally, we need a way to specify to Maven2 that we want to build an artifact for one the specified environments. We use profiles for this. The following profiles need to be declared in the POM.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <!-- default environment -->
<properties>
<env>dev</env>
</properties>
<!-- profiles -->
<profiles>
<profile>
<id>dev</id>
<properties>
<env>dev</env>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<env>test</env>
</properties>
</profile>
<profile>
<id>live</id>
<properties>
<env>live</env>
</properties>
</profile>
</profiles>
|
Notice that the profile definition is really slim, all it contains is the setting for the ${env} variable. I guess this is a personal preference, but I like all my environment specific information in one place, makes life easier when trying to figure out cross-environment configuration issues. Notice also the default setting for ${env}, this is used in case a profile is not specified.
Now we have all our pieces in place, running targets for different environments is simply a matter of specifying the profile in the mvn command. So:
1 2 3 | $ mvn war:war -P dev # builds a war file with filter-dev.properties values
$ mvn war:war # same as above, uses default ${env} setting
$ mvn war:war -P live # builds a war file with filter-live.properties values
|
I hope this article was informative. There does not seem to be too much information on how to do this sort of thing with Maven2, perhaps because this process is not standard and teams and organizations have evolved different strategies to deal with this problem. For a while I thought that there was no way to do this natively with Maven2, and I started playing with the maven-antrun-plugin to delegate this work to an Ant build.xml file, but I was not able to pass the environment (passed into Maven2 with -Denv=dev) from Maven to Ant, so I gave up. I am glad I did, and many thanks to Eric Redmond for his article, because otherwise I would not have come up with this.
Resources
- Eric Redmond's article "Keep Your Maven Projects Portable Throughout the Build Cycle" helped immensely for me to arrive at my final implementation. If it were not for this article, I would probably have gone for the embedded Ant approach, which is obviously not as neat.
- Another very informative and helpful page is Maven2's own documentation - Introduction to Build Profiles.
- Better Builds with Maven - the definitive sourcebook for Maven2 from Mergere, Inc. I found a lot of information on basic filtering here. This is a free downloadable ebook and is a must for people working with Maven2.