Wednesday, July 29, 2009

Maven Profile- Dynamic compilation for different target platforms

Have you ever had the case where you have one project that has to be deployed to different servers? You certainly know that different servers include different JAR files. You certainly know that the configuration files are not necessary the same. This can give you a lot of headache, it can also make you maintain two different project just cause the configuration.

Well I recently had similar problems. Had to maintain the same project that should be deployed to JBoss and WebSphere. To keep the long story short, Maven gave me the perfect opportunity to do this.

Let us assume that your Maven project looks similar to this:

+main-project
|
|--+ web-app-project
| |
| |-+src/main/java
| |-+src/main/webapp
| | |
| | +WEB-INF
| | |--+web.xml
| | |--+faces-config.xml
| |
| +pom.xml
+ pom.xml

As you may know the main-project pom.xml is where you keep your dependency management. In the dependency management you enter artifact id and group id of the JAR file you want to be inluded and it's version. Usually, you can define the scope of the JAR here. There are several of such scopes. The compile scope is default and means that your JAR will be included in your application. The other common scope is the provided scope. It tells maven, that this JAR should not be included in your final product, and the application will find it either on the server or it's runtime. This dependency management in your main pom.xml should look something like this:





javax.faces
jsf-impl
${jsf.version}






The dependency management tag will tell maven which JAR files and it's versions are needed in your project. But this won't make this Jars be yet included in your project. To indeed include them, you should put the same dependency's with out version between separate dependencies tag.

This you can do either in the main-project pom.xml or in the web-app-project pom.xml. I would suggest the later one, which will make Maven put those Jars under the WEB-INF/lib folder.

Your web-app-project pom.xml could than include those dependencies as follow:




javax.faces
jsf-impl





Notice that the dependency management tag is missing in this part of the configuration.

I must admit that this is little error prone code, but if you keep it nice and clear, it will allow you to manage your dependencies very easy, and if you store your project in a SVN Repository, you will have no need to store the Jar files into the repository.

Thus far, we have gone through basic maven configuration. You certainly are interested how all those Jar files can be managed for different servers.

The answer is maven profiles. Every maven profile needs unique profile id. Let's start with the maven command line that will call certain profile.

mvn -Pprofileid clean install
The accent is set to the parameter -P followed with the profile id. It is important to understand that there is no space between the parameter and the id. If you execute this maven command at this point having the above configuration, the profile will be ignored and the project will be compiled normally.

To add a profile for certain project, you can do this in your web-app-project pom.xml as follows:




websphere



This defines the new profile with id websphere. It can be called as follows:
mvn -Pwebsphere clean install
As you correctly assumed this is a empty profile, thus won't do any difference. But before we go on and add our WebSphere dependencies, remove the previous dependencies we added above. Now adding the WebSphere dependencies is only a trivial task, and is done as follow:




websphere


javax.faces
jsf-impl







As you can see, no mysterious thing happened here. Actually, you can use about any of the normal maven tags inside profile.





...
...
...
...
...

...
...
...
...
...
...
...
...





You can see more here.

Now that we have managed the dependencies for WebSphere, we would also like to alter different configuration files at compile time. This files can be various XML files, such as web.xml or faces-config.xml. Why is this necessary will be shown on simple example on the web.xml file configuring the RichFaces for WebSphere and JBoss.

In WebSphere you configure the RichFaces mapping as follow:



richfaces
/*
REQUEST
FORWARD
INCLUDE
ERROR



In JBoss you don't configure the RichFaces filter through URL mapping, but through the Faces Servlet name, like this:


richfaces
Faces Servlet
REQUEST
FORWARD
INCLUDE
ERROR



As you see, this is very conflicting. While the WebSphere configuration will also work for JBoss, still you will need additional configuration, to get RichFaces running on WebSphere as follow:


richfaces
/faces/rfRes/*
REQUEST
FORWARD
INCLUDE
ERROR



So, the both mappings did not work for me on JBoss Portal, thus had problem to maintain the configuration of RichFaces for both servers in the same Project.
Luckily, Maven gives the opportunity to filter resources of all kind. Here we need to filter the web resources, thus can be done in few steps:
  1. In your web-app-project add new directory named conf
  2. In this directory you create new properties file named something like websphere.properties
  3. Add new property such as richfaces.servlet.name.mapping=
  4. Add another property such as richfaces.url.mapping=
    /*

Please notice that this properties are added in the websphere.properties file. To have JBoss profile filtering, you need another property file called for example jboss.properties. Inside this file, you have to define the same properties and assign them different values.
Also notice that the url mapping is not needed for WebSphere RichFaces configuration, thus the property is assigned no value, which will result in empty line in the web.xml file.

Now that we have this properties which define different patterns or configuration tags. As you assumed, this won't do anything. Now we will have to tell maven that he needs to take this property files and filter our web.xml. this is done as follow:




websphere



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




${basedir}/src/main/webapp/WEB-INF

WEB-INF
true






${basedir}/src/conf/jboss-portal.properties




javax.faces
jsf-impl






Here we add new maven plug in for this profile to the standard build. This plug in is the maven war plug in which enables filtering of the WEB-INF content of an web app. With the directory tag, we tell the plug in which directory should be filtered and set the filtering on true.

In order to add the values assigned to the properties in the websphere.properties file in to the web.xml, the following changes are made in to the web.xml file:


richfaces
${richfaces.servlet.name.mapping}
${richfaces.url.mapping}
REQUEST
FORWARD
INCLUDE
ERROR


At compile time, the properties will be replaced with their values, so the out coming file will result in what you preconfigured in your websphere.properties file.

So that is all about it. One needs time to get around this, but once one understands it, it is easy and clean.
I hope this helps.