Monday, April 18, 2011

Maven Token Replacement

I've been trying to create a maven pom file that would do the same stuff that the current ant build file does. In the process, I've been able to learn some good stuff about Maven.
One thing I had to accommodate is the directory structure that we have, which unfortunately is not the same as maven's default. Once I got the basic stuff working, I wanted to try couple of things:

* Add profiles, so I can build the app for different environments
* Add tokens to files that can replaced during build process

To setup the profiles, I just had to add something like this:
<profiles>
  <profile>
    <id>dev</id>
    <activation>
      <activebydefault>true</activebydefault>
    </activation>
    <properties>
      <env.properties>dev.properties</env.properties>
    </properties>
  </profile>

  <profile>
    <id>sso</id>
    <properties>
      <env.properties>sso.properties</env.properties>

    </properties>
  </profile>
</profiles>

This can be part of the pom itself or it could be saved into a file called profiles.xml

To get the token replacement working, I ran into some trouble. First I tried using filters which is specifically meant for this purpose, or so I thought.

Add the filter:
<filters>
  <filter>dev.properties</filter>
</filters>


Enable the filter (w/o this, filtering wont work):
<resource>
  <filtering>true</filtering>
  <directory>src/main/webapp/WEB-INF</directory>
  <includes><include>web.xml</include></includes>
</resource>

This did replace the token specified in web.xml file, but filtering actually just copies the included resources to WEB-INF/classes after replacing the token. So filters were out and had to look for another option. Then I ran into maven-replacer-plugin which does exactly what I need to do.

<plugin>
  <groupid>com.google.code.maven-replacer-plugin</groupid>
  <artifactid>maven-replacer-plugin</artifactid>
  <version>1.3.5</version>
  <executions>
    <execution>
    <id>replaceAuth</id>
    <phase><b>package</b></phase>
    <goals>
      <goal>replace</goal>
    </goals>
    </execution>
  </executions>
  <configuration>
    <file>target/simple-webapp/WEB-INF/web.xml</file>
    <replacements>
      <replacement>
          <token>AUTH_METHOD</token>
          <value>${auth.method}</value>
      </replacement>
    </replacements>
  </configuration>
</plugin>

I first tried the 'prepare-pacakage' phase as mentioned in some forums, but the file that I need to run replacement on wasnt copied over to the target directory at that point. So the maven build fails. I had to use the 'package' phase to apply the token replacement.

But when I ran
> mvn package
the final war file didnt have the replacement applied, even though the file under exploded directory had replaced value. The reason for this being the war:war task was run before the replacer:replace task.

Back to the forums and found that I also had to use the maven-war-plugin to get this working.
<plugin>
  <groupid>org.apache.maven.plugins</groupid>
  <artifactid>maven-war-plugin</artifactid>
  <version>2.1.1</version>
  <executions>
    <execution>
      <phase>package</phase>
      <goals><goal>war</goal></goals>
      <configuration>
         <webxml>target/simple-webapp/WEB-INF/web.xml</webxml>
      </configuration>
    </execution>
  </executions>
</plugin>


The forums suggested that I had to use 'exploded' goal, but that didnt do the job for me. I had to use the 'war' goal and also added the webXml element that points to the modified file. This finally got what I wanted.. all the tokens replaced in the target directory and also in the final .war file.
Glad I got it working, but Im still uncertain about the impact of including webXml. Theoretically, I should've had this working without specifying the configuration portion. What if I have to modify a file other that web.xml? I'll edit this after I try that and if I gain any further understanding.

Update: 
Turns out that I really didnt need the <webXml> tag defined within the war plugin. But I did notice that that the war goal runs twice...once by default and once after the replacement. Another note to keep in mind is that since both the plugins (replacer & war) are bound to the same phase (package), the order is important. If the war runs before replacer, then the final war will not have the replaced values. So make sure that the replacer plugin comes AFTER the war plugin.

To replace multiple tokens:
Use the following config in replacer plugin ...

<configuration>
       <includes>
             <include>target/${project.build.finalName}/sample1.txt</include>
             <include>target/${project.build.finalName}/sample2.txt</include>
       </includes>
       <replacements>
               <replacement>
                        <token>token1</token>
                        <value>value1</value>
                </replacement>
                <replacement>
                        <token>token2</token>
                        <value>value2</value>
                </replacement>
        </replacements>                       
</configuration>

Externalize the tokens in a properties file:
Use maven properties plugin to load the properties file as shown below:

<plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>maven-properties-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>
        <executions>
          <execution>
            <phase>initialize</phase>
            <goals>
              <goal>read-project-properties</goal>
            </goals>
            <configuration>
              <files>
                <file>etc/config/dev.properties</file>
              </files>
            </configuration>
          </execution>
        </executions>
</plugin>

3 comments:

  1. Nice post! After reading I've managed to fix the same issues in my project. Thanks.

    But in my case I had to run first maven-war-plugin and then maven-replacer-plugin to replace tokens correctly. Hope it doesn't depend on some other maven settings.

    ReplyDelete
  2. Hi,

    For some reason, the war plugin gets executed twice first followed by the replace plugin execution for me. This happens irrespective of keeping the replace plugin above as well as below the war plugin. This results in the tokens getting correctly replaced in the target folder but not in the war file. Any ideas?

    Here is my war plugin entry in the pom file:


    org.apache.maven.plugins
    maven-war-plugin
    2.2


    package
    war

    target/ffm-casemanagement-server/WEB-INF/web.xml





    Thanks!

    ReplyDelete
  3. Same phase in Maven is defined in more than one execution then it executes as defined in pom.xml.

    i.e. first define maven-replacer-plugin with goal replace

    there after

    define maven-war-plugin with goal war

    It start working.

    ReplyDelete