Liz Douglass

Archive for October 2008

A JAR of JARs

with 3 comments

Last week I was working on packaging some JARs and a LiquiBase changelog file into a single JAR, so that we can easily populate a HSQLDB database with little hassle . We wanted someone to pick up the bundled JAR and be able to do something like this:

java –jar migration.jar

This should achieve the same result as had that person entered this on the command line:

java -jar \
$LIQUIBASE_HOME/liquibase-core/1.8.1/liquibase-core-1.8.1.jar \
--changeLogFile=$LIQUIBASE_HOME/changelog.xml \
--username=myUser \
--url=jdbc:hsqldb:hsql://localhost/xdb \
--classpath=$HSQLDB_HOME/1.8.0.7/hsqldb-1.8.0.7.jar \
--driver=org.hsqldb.jdbcDriver \
--logFile=liquibase.log \
--logLevel="finest" \
update

(Note that this assumes that the HSQLDB server is already running.)

Initially we thought that this task was as simple as creating a single JAR (which I’ll refer to as the Migration JAR) that contained the LiquiBase and HSQLDB JARs (in the resources directory), the LiquiBase changelog file, as well as a lone class with main method… like this one:

package my.package;

import liquibase.commandline.Main;
import liquibase.exception.CommandLineParsingException;

import java.io.IOException;

public class DatabaseMigration {

    private static final String CHANGELOG_FILE = "changelog.xml";

    public static void main(String[] args) {

        String[] liquibaseArgs = {
                "--changeLogFile=" + CHANGELOG_FILE,
                "--username=myUser",
                "--url=jdbc:hsqldb:hsql://localhost/xdb",
                "--driver=org.hsqldb.jdbcDriver",
                "--logFile=liquibase.log",
                "--logLevel=finest",
                "update"
        };
        try {
            Main.main(liquibaseArgs);
        } catch (CommandLineParsingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

We tried adding the LiquiBase and HSQLDB JARs into the classpath of the Migration JAR by adding a Class-Path attribute in the META-INF/MANIFEST of the Migration JAR. As the Java tutorial points out, this is not possible:

The Class-Path header points to classes or JAR files on the local network, not JAR files within the JAR file… To load classes in JAR files within a JAR file into the class path, you must write custom code to load those classes.

After some googling we discovered Uberjar and One-JAR. We tried out Uberjar but found the configuration to be confusing and heavy. In comparison One-JAR was relatively simple to get up and running. Following the instructions (or so we thought) on the One-JAR website, we edited our Migration JAR manifest file to look include these statements:

'One-Jar-Main-Class'=>'myPackage.DatabaseMigration’
'Main-Class'=>'com.simontuffs.onejar.Boot'

When we ran it, we got this…

Exception in thread "main" java.lang.NoClassDefFoundError: liquibase/exception/CommandLineParsingException

After some head scratching I came across this article by Simon Tuffs about how he created One-JAR. The article compliments the One-JAR website very well, and it provided the solution to our problem in this statement:

In brief, the delegation mode for classloaders required that I put the main class com.main.Main into its own JAR file so that it would be able to locate the library classes (on which it depends).

We had mistakenly thought that we could add the One-JAR content and configuration into our Migration JAR, when in actual fact we needed to another JAR that wraps the entire bundle (the Migration JAR and its dependencies). The resulting (working) structure looks like this (drumroll):

jarname.jar
| /META-INF
| | MANIFEST.MF
| | | Main-Class: com.simontuffs.onejar.Boot
| | | One-Jar-Main-Class: my.package.DatabaseMigration
| /main
| | Migration.jar
| | | my.package.DatabaseMigration.class
| /lib
| | hsqldb-1.8.0.7.jar
| | liquibase-core-1.8.1.jar
| /com.simontuffs.onejar
| | Boot.class
| | etc.
|

While One-JAR has provided us with much a appreciated solution, it is more complex than we envisaged. Effectively having two bundling JARs does seem like overkill but I can appreciate that in most One-JAR use cases the Migration JAR would be more meaty.

Advertisements

Written by lizdouglass

October 28, 2008 at 10:16 pm

Posted in JARs, Java

Tagged with , , ,