How to mavenize a typical web application build: JasperServer 3.7 sample webapp

Now that JasperServer 3.7 has been released and the community edition hit sourceforge I thought there is a good example to showcase the conversion of a web application build from ant to maven and what tricks you can employ. The sample web application that ships with JasperServer is a good example, since it uses a standard ant build as created by Netbeans with the usual checked in jar files and all. It is also a good candidate, since it finally works again after being broken for ages all through the last 3.5 release cycle. So lets get started.

First lets get the binary distribution without the hazzle of jasperforge registration and more with a simple wget and extract the file

wget http://sourceforge.net/projects/jasperserver/files/JasperServer/JasperServer%203.7.0/jasperserver-ce-3.7.0-bin.zip/download
unzip jasperserver-ce-3.7.0-bin.zip
cd jasperserver-ce-3.7.0-bin/samples/java-webapp-sample/

As a next step for the conversion we create the maven default directory structure for a war project and copy files into place. This is not totally necessary, but I found that it is much easier to stick with the convention than to configure things just like you want it. After all Maven follows the convention over configuration mantra really well and you can configure everything. But why would you want to? So first the java code

mkdir -p src/main/java
mv src/com/ src/main/java/

and then the web application artifacts and resources

mkdir -p src/main/webapp
mv web/* src/main/webapp/
mv resources src/main/

Then we can get rid of stuff we wont need any more:

rm -rf build.xml nbproject/ readme.txt conf/

Now that we did the trivial first steps lets get this Maven thing going. After doing the install of Maven itself you just need the pom.xml file that you can edit in whatever IDE or editor you like. First we define our project with the following setup.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.simpligility.jasperserver.webservice</groupId>
  <artifactId>example-webapp</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>JasperServer Web Service Example Application</name>
</project>

The most important part here is the packaging type war. It tells Maven to build a war file as the project output using the maven-war-plugin rather than the default jar. This would theoretically already build the war for you if your code had no dependencies beyond JDK classes. But since that is rarely the case and certainly not the case for this app we have to add dependencies. The first one will be the servlet api that is located in the lib folder of the project. We replace that with

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.2</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>        

The scope of provided signals that the jar should not be included in the war file, since it is assumed to be available at runtime (which it is when you run the war in a servlet container…). That fact was reflected by the different location of the jar in the lib folder that we can now get rid of

rm -rf lib

The next part of the migration is more or less detective work depending on the actual project you are converting. If you are lucky you have a file like commons-codec-1-3.jar, that mostly tells you the details you need to find out what to use as a dependency text. You then go to the Sonatype Nexus instance, that aggregates most common, public Maven repositories and do a search. In most cases you will find the artifact of the right version with more or less digging. Definitely check out the advanced search while you are there! Once you got what you were looking for you just click on the line of the artifact and cut and paste the dependency segment from the XML part of the Artifact Information tab.

<dependency>
  <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
  <version>1.3</version>
</dependency>

If you did that yourself you would find artifact for all these files:

activation-1.1.jar                                                                                                                                    
axis.jar                                                                                                                                              
commons-codec-1.3.jar                                                                                                                                 
commons-collections-3.2.jar                                                                                                                           
commons-digester-1.7.jar                                                                                                                              
commons-discovery-0.4.jar                                                                                                                             
commons-httpclient-3.0.jar                                                                                                                            
commons-logging-1.0.4.jar                                                                                                                             
itext-2.1.0.jar
jaxrpc.jar
jcommon-1.0.15.jar
jfreechart-1.0.12.jar
jstl-1.1.2.jar
jxl-2.6.jar
mail-1.4.jar
png-encoder-1.5.jar
saaj.jar - is 1.2 but using 1.3 from maven central
spring-2.5.6.SEC01.jar
standard-1.1.2.jar
wsdl4j-1.5.2.jar
xercesImpl-2.7.1.jar
XmlSchema-1.0.2.jar - using 1.2 version

As you can see from the list above I had to adjust a couple of artifact versions, since the exact versions do not seem to be available on Maven central.

Now unfortunately JasperSoft does not publish all their artifacts into a public Maven repository (although some can be found here, amazing to look back and see when I launched the request) so for four artifacts you have to do a hack like this

    <dependency>
      <groupId>jasperreports</groupId>
      <artifactId>jasperreports-chart-themes</artifactId>
      <version>3.7.0</version>
      <scope>system</scope>
      <systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/jasperreports-chart-themes-3.7.0.jar</systemPath>
    </dependency>

The first trick here is that we use the system scope with an actual path within the project to contain the jar we need. The second trick is that the path is right where the jar needs to end up in WEB-INF/lib so that the war plugin pick it up like anything else in the webapp folder. Now we are nearly done getting the war file ready. A final cleanup for today is to remove all the jar files in src/main/webapp/WEB-INF/lib apart from the jasperreports and jasperserver ones.
Now we tell the compiler that we got Java 6 code in this project and for convenience we add the maven-tomcat-plugin as well.

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>tomcat-maven-plugin</artifactId>
        <version>1.0-beta-1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.0.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

With a little addition of an admin user with no password and the manager role to conf/tomcat-users.xml

 <role rolename="manager"/>
  <user username="admin" password="" roles="manager"/>

and starting tomcat you can build and deploy the war with

mvn tomcat:deploy

and access the application at the url from the log output.

So theoretically our build is now done and we are happy. Of course there are a bunch of things that should be improved. E.g. we could move the jasperserver artifacts into a local repository server or use the dependency plugin to see if all is well and check out some of the pom files of the dependencies we use to optimise our pom. Oh and by the way to test the application after deployment you need to have JasperServer installed, since it talks to it via the webservice interface. But for today we have done enough. Come back in a while and I will explain some of those steps next time.

PS 2010-02-03: I have uploaded the sample app after the conversion to github and will do all changes to it with git so you can track further changes.