Wednesday, September 24, 2008

[Maven] Converting/Changing a project from using Ant to use Maven

As a developer I often meet old Java projects based on maven. As I very much prefer Maven over Ant, I try to convert to maven whereever it is possible and makes sense.

Here's how I usually convert a simple web (war) project from Ant to Maven.

Start out by installing maven and creating a new structure for your maven project in a different directory than the one containing your Ant project:
mvn archetype:create -DgroupId=com.maaloe.myproject -DartifactId=myproject -DarchetypeArtifactId=maven-archetype-webapp

Where com.maaloe.myproject matches whatever base package you use in your project structure and artifactId matches the name of your project

Now... The objective is to make the war file that Maven creates when running "mvn clean install" as similar to the one that Ant creates as possible, so we should compare the two war files by doing a "jar -tf projectname.war" on both projects.

The existing project in my case contains a build.xml file with three targets:
  1. "ant -Dmode=dev"
  2. "ant -Dmode=test"
  3.  "ant -Dmode=prod"
All the targets copies a property file "myproject/conf//ApplicationResources.properties" (mode being dev, test or prod) to the war file so make sure the correct environment properties are used when installing the war file.


In my case, the Ant war file looks like this:
>> jar -tf ninjaspwebinterface.war

META-INF/
META-INF/MANIFEST.MF
WEB-INF/
WEB-INF/classes/
WEB-INF/classes/com/
WEB-INF/classes/com/maaloe/
WEB-INF/classes/com/maaloe/myproject/
WEB-INF/classes/com/maaloe/myproject/modules/
WEB-INF/classes/com/maaloe/myproject/modules/user/
WEB-INF/classes/com/maaloe/myproject/tags/
WEB-INF/classes/com/maaloe/myproject/tags/utils/
WEB-INF/classes/com/maaloe/myproject/utils/
WEB-INF/classes/com/maaloe/myproject/webapp/
WEB-INF/classes/com/maaloe/myproject/webapp/actions/
WEB-INF/lib/
WEB-INF/admin-config.xml
WEB-INF/classes/ApplicationResources.properties
WEB-INF/classes/log4j.properties
WEB-INF/classes/com/maaloe/myproject/modules/user/User.class
WEB-INF/classes/com/maaloe/myproject/utils/Constants.class
WEB-INF/classes/com/maaloe/myproject/webapp/ActionServlet.class
WEB-INF/classes/com/maaloe/myproject/webapp/actions/LogonAction.class
WEB-INF/classes/com/maaloe/myproject/webapp/actions/LogonForm.class
WEB-INF/lib/activation.jar
WEB-INF/lib/commons-collections.jar
WEB-INF/lib/commons-httpclient-2.0.jar
WEB-INF/lib/commons-logging-api.jar
WEB-INF/lib/commons-logging.jar
WEB-INF/lib/log4j-1.2.8.jar
WEB-INF/lib/nls_charset12.jar
WEB-INF/lib/servlet.jar
WEB-INF/lib/struts.jar
WEB-INF/lib/project_utils.jar
WEB-INF/lib/xalan.jar
WEB-INF/lib/xercesImpl.jar
WEB-INF/lib/xml-apis.jar
WEB-INF/struts-bean.tld
WEB-INF/struts-config.xml
WEB-INF/struts-config_1_0.dtd
WEB-INF/struts-form.tld
WEB-INF/struts-html.tld
WEB-INF/struts-logic.tld
WEB-INF/struts-template.tld
WEB-INF/struts.tld
WEB-INF/web.xml
index.html
logon.jsp

There are several problems in creating a war file that matches the one Ant creates.
  1. We need to figure out what versions of the jar files in WEB-INF/lib/ to use as in most cases, this is not specified.
  2. WEB-INF/lib/project_utils.jar is not a jar file we can find in any maven repository, so we need to find a solution for this.
  3. As the existing Ant build.xml contains targets for deploying to both development, test and production environment, we need to make sure that out maven conversion adds the correct properties to the war file based on a maven target.

Let's adress the problems one at a time.

First of all, we need to figure out what dependencies exist in the Ant war file. Some is straight forward like "commons-httpclient-2.0.jar" as this exists on an existing repository (check http://www.mvnrepository.com/) so we can just add the following dependency to the pom.xml file:

 commons-httpclient
 commons-httpclient
 2.0


Others, like struts.jar, we have no idea what version is used. In those cases you can usually extract the struts.jar file (jar -xvf ) from the Ant war file and check the "META-INF/MANIFEST.MF" file. In this (text) file you can find the version (if the jar file was packages properly).
If you have the guts, you can upgrade to the latest release of the jar file, but that could go wrong.
In my case, the MANIFEST.MF contains:

Manifest-Version: 1.0
Implementation-Version: 1.0.2
Specification-Title: Struts Framework
Specification-Version: 1.0
...

Eg. Current ant war file depends on Struts 1.0.2, so we'll add the following to the pom.xml:

 struts
 struts
 1.0.2



Secondly, we need to figure out what to do with the "WEB-INF/lib/project_utils.jar" file as this is a local project jar file that we depend on. This such cases, we need to deploy this to a repository from where others can download the dependency when they need to compile the new version of the project build on Maven.
What I have done is to install "Artifactory". It's a simple application file that you install on a central server and then point your maven pom.xml file to your new repository. For more information on installing Artifactory, check here.
You can ofcourse just put the jar file in your local repository but then no one will be able to compile the project out of the box which is one of the main arguments I have for switching to Maven.

This is what I have added to the pom.xml file for Maven to download the jar files:

 
  central
  http://:8080/artifactory/repo
  
   false
  
 
 
  snapshots
  http://:8080/artifactory/repo
  
   false
  
 



Third point. We want to be able to handle the three targets for the three different environments. This can be done by adding a profile to Maven and then using the "maven-antrun-plugin" plugin. In my case I can write:

 
  
   
    environment
   
  
  
   
    
     maven-antrun-plugin
     
      
       test
       
        run
       
       
        
         
         
         
         
        
       
      
     
    
   
  
 


The above copies two property files from the conf// directory (log4j.properties and ApplicationResources.properties) to the target directory where it will be included in the war file.

Now, I can simply call Maven with the following command to package the development version of the project war:

mvn clean install -Denvironment=dev



Here's the full pom.xml file I used for the above project.

 4.0.0
 com.maaloe.myproject
 myproject
 war
 1.0
 MyProject Maven Webapp
 
  Maaloe.com
  http://dev.maaloe.com
 
 
  
   jm
   Jakob Maaloe
   jam@maaloe.com
   
    Developer
    Consultant
   
   +1
  
 
 
  
   javax.servlet
   servlet-api
   2.5
   provided
  
  
   javax.servlet.jsp
   jsp-api
   2.1
   provided
  
  
   javax.activation
   activation
   1.1
   provided
  
  
   log4j
   log4j
   1.2.15
  
  
   struts
   struts-ninja
   1.0.2
  
  
   commons-collections
   commons-collections
   3.2.1
  
  
   commons-httpclient
   commons-httpclient
   3.1
  
  
   xerces
   xercesImpl
   2.8.1
  
  
  
   project_utils
   project_utils
   0.1
  

  
   junit
   junit
   3.8.1
   test
  
 
 
  myproject
 
 
  
   central
   http://repo.maaloe.com:8080/artifactory/repo
   
    false
   
  
  
   snapshots
   http://repo.maaloe.com:8080/artifactory/repo
   
    false
   
  
 
 
  
   central
   http://repo.maaloe.com:8080/artifactory/repo
   
    false
   
  
  
   snapshots
   http://repo.maaloe.com:8080/artifactory/repo
   
    false
   
  
 
 
  
   
    
     environment
    
   
   
    
     
      maven-antrun-plugin
      
       
        test
        
         run
        
        
         
          
          
          
          
         
        
       
      
     
    
   
  
 


So, that's it for changing from Ant to Maven. I have marked some of the dependencies as provided as they actually don't need to be part of the war file, since the web container (Tomcat in my case) provides this.

Of course you may run in to problems that I haven't taking in to account, but the above hopefully gives you a few hints to the way I usually solves the conversion process.
Please, if you run in to interesting problems that you think others could benefit from, comment on the blog!

1 comment:

blogger_sanyer said...

Informative post. Thank you. I am trying to convert a huge (3000+classes)ant web project to maven. The problem I am running into is when there are path references to /WEB-INF/classes/<file-name> and when I want to use embedded jetty. So that 'mvn jetty:run' just works on my webapp source code. The problem is WEB-INF is is in webapp and classes is in target. I used to think that the both of them are 'seen' together runtime. But, I get something like this: java.lang.IllegalArgumentException: Invalid log4jConfigLocation parameter: Log4j config file [{base-dir}/src/main/webapp/WEB-INF/classes/log4j.xml] not found.

Any ideas, how to make this work? May be I am not very clear so please ask if more info is needed. Thanks again.