Tuesday, April 8, 2008

Wrapping all dependencies in a jar

Yesterday I encountered this issue: I have been writing a client code that contacts a web service. I wanted to implement a modularized solution by wrapping all web service related code and dependencies in a library, so that I could jsut use this library in my client code, without having to worry about the WS specifics (ie the web service location, how to establish connection with it etc).

So I created a package that operates as a middle layer between the WS and the client code and then packaged it into a jar file. However, when I tried to use that in an applet I realised that I also had to distribute all of the jar's dependencies with the applet. (This problem wasn't obvious while running the program as a Java application, cause JVM was able to locate the dependencies in the file system.)

I made a little research and I found out that it is not possible to include other jars in a jar file, just like you can do with war files. The suggested solution is to unpackage all jars and re-package them into your own jar. Of course this isn't something that you can easily do by hand, especially if you think that the whole process has to be repeated each time you compile your project.

So this process has to be automated and ant is one way to do that. I will be demonstrating how this can be done on Netbeans.

First a few words about how Netbeans uses ant. In your project folder you will find two build files:

  • $PROJ_HOME/build.xml
  • $PROJ_HOME/nbproject/build-impl.xml
The second one is the actual build file that Netbeans automatically creates using the project properties, and is updated each time you add a new source file, or add a new JAR to the project classpath. You should not edit this file. (Actually even if you do, your changes might be overwritten when the file is automatically regenerated.) You should only edit the first one. This file defines a number of pre and post targets that are built just before or just after the basic targets of build-impl.xml.

In our case we will use the -pre-jar target. The commands of this target will extract the library jar files into the built directory just before the jar target is called, so the latter will include them when creating the project's jar file. An example of the definition of this target is shown below.

    <target name="-pre-jar">
        <unjar src="${file.reference.axis-ant.jar}" 
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.activation.jar}"  
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.axis.jar-1}" 
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.commons-discovery-0.2.jar}" 
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.commons-logging-1.0.4.jar}" 
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.jaxrpc.jar-1}" 
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.log4j-1.2.8.jar}"  
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.saaj.jar-1}" 
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.wsdl4j-1.5.1.jar}" 
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.activation.jar}" 
                  dest="${build.classes.dir}"/>
        <unjar src="${file.reference.mail.jar}" 
                  dest="${build.classes.dir}"/>
    </target>

We need an unjar element for each jar we want to repackage in our jar. The dest attributes specifies that the jars should be extracted in the project's build directory; you should leave that as it is.
Now, the value of the src attribute contains a reference to the jar file that will be unjar-ed. You can find the values you should use in the file $PROJ_HOME/nbproject/private/private.properties. This properties file contains key-value pairs with the names used by Netbeans for library files and the actual location of these files.

No comments: