Liz Douglass

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.

About these ads

Written by lizdouglass

October 28, 2008 at 10:16 pm

Posted in JARs, Java

Tagged with , , ,

3 Responses

Subscribe to comments with RSS.

  1. […] my earlier post I described how we used One-JAR to create a single JAR containing all the bits required to […]

  2. Hi Liz: nice article, thanks for using (and persisting with) One-JAR. I just released a new version (0.97), checkout http://one-jar.sourceforge.net/

    A major part of this release was making it easier to set up One-JAR projects, to which end there is an application generator (one-jar-appgen) which will create a basic One-JAR directory tree, with support for building under Eclipse/Ant/Maven2, and which contains a JUnit test harness. I’d be interested in hearing your thoughts on this if you’re still working with the product.

    –simon.

    simontuffs

    July 16, 2010 at 1:16 am

  3. I forgot to mention that the 0.97 release has a more accommodating classloader delegation model that now supports placing the main class at the top-level of the One-JAR archive, so a main/main.jar should no longer be required (although the One-Jar-Main-Class manifest attribute is still needed). I would be interested in hearing if this works for you.

    simontuffs

    July 16, 2010 at 8:41 am


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: