Coding Clarity

Writing simple, clear and readable code.

Browsing Posts published in February, 2008

The typical way to solve most problems in languages such as Java is to create one more level of abstraction. One example of this is Apache Commons Logging. There are many different logging libraries used in Java. Log4j is the big one that has been around for many years. Developers had been looking for a logging library to be part of the Java platform for years and this finally happened in Java 1.4 with the java.util.logging package. At this time, log4j was already the defacto standard for logging on the Java platform. The java.util.logging package lacked in features and integration with existing logging infrastructure like log4j. To this day, not many people are using the built in logging capabilities because things like log4j are just so much better.

Apache Commons Logging enters the scene to provide a level of abstraction around different logging packages. It provides a thin layer around many different logging implementations like Log4j, java.util.logging, LogKit and others. This is a great solution if you are creating an embedded application where the logging needs to be done through some other application’s facility.

A lot of projects started using Commons Logging but encountered a lot of problems using it. This is specifically problematic in application servers when dealing with complex classloader hierarchies. The Think again before adopting the commons-logging API article explains some of these issues in more detail. There is even an apology by the author of Commons Logging.

“The purpose of Commons Logging is not to isolate your code from changes in the underlying logging framework. (That’s certainly easy enough to do on your own, and not really worth doing in the first place given the ease of switching from one logging framework to another.) The purpose of Commons Logging is not to somehow be more useful than actual logging frameworks by being more general. The purpose of Commons Logging is not to somehow take the logging world by storm. In fact, there are very limited circumstances in which Commons Logging is useful. If you’re building a stand-alone application, don’t use commons-logging. If you’re building an application server, don’t use commons-logging. If you’re building a moderately large framework, don’t use commons-logging. If however, like the Jakarta Commons project, you’re building a tiny little component that you intend for other developers to embed in their applications and frameworks, and you believe that logging information might be useful to those clients, and you can’t be sure what logging framework they’re going to want to use, then commons-logging might be useful to you.” – http://radio.weblogs.com/0122027/2003/08/15.html

Tomcat uses commons logging. This can create all sorts of strange classloading problems when you deploy a web application that also uses commons logging. There are different versions of commons logging to contend with and difficulties in commons logging to find the actual logging implementation because of the parent tomcat classloader.

It is clear that one more level of abstraction is not always the key to solving all of your problems. In this case, Commons Logging was created for one specific problem and that is for small embedded components. This abstraction may make sense for things like an web application server. When it comes right down to it though, if you are writing a web application server, it is up to you to dictate how logging is to be done for your application. Most Commons Logging implementations in software will end up using Log4j anyway. So if you control the application environment, just use Log4j.

Executing an application in Java is hard. When you work with a language that compiles to native code, the artifact created from your build process is a native executable. If you are using an interpreted language or a language that compiles to bytecode, a runtime environment is required to run the program.

Invoking the Java runtime is simple but for a typical enterprise application, many parameters are required to be passed to the runtime. These parameters consist of arguments to the virtual machine (like memory options), program parameters and classpath parameters.

For a large application, there are often many .jar files that need to be included on the classpath. The software author can create a shell script or small native executable that calls Java with all of the appropriate jar files.

A good alternative to having to specify all of the .jar files in the script is to use a simple boot loader jar. The following two classes make a very simple boot loader that can be used to start a much larger application.

public class ClassLoaderFactory {
    public static ClassLoader createClassLoader(File[] files) {
        List<URL> urlList = new ArrayList<URL>();

        for (File file: files) {
            addURLs(file, urlList);
        }
        URL[] urls = urlList.toArray(new URL[0]);
        return new URLClassLoader(urls,
            ClassLoaderFactory.class.getClassLoader());
    }

    private static void addURLs(File file, List<URL> list) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                addURLs(files[i], list);
            }
        }
        else if (file.isFile() && file.getName().endsWith(".jar")) {
            try {
                list.add(file.toURI().toURL());
            }
            catch (MalformedURLException e) {
                // This should not happen. Ignore this file.
            }
        }
    }
}
public class Launcher {
    public static void main(String[] args) {
        String mainClass = null;
        List<File> libraryList = new ArrayList<File>();
        List<String> actualArgsList = new ArrayList<String>();

        for (String arg: args) {
            if (arg.startsWith("-l")) {
                String libraryPath = arg.substring(2);
                libraryList.add(new File(libraryPath));
            }
            else if (mainClass == null) {
                mainClass = arg;
            }
            else {
                actualArgsList.add(arg);
            }
        }

        if (mainClass == null) {
            System.err.println("No main class specified");
            System.exit(-1);
        }

        // Load classloader based on the library path
        ClassLoader classLoader =
            ClassLoaderFactory.createClassLoader(
            libraryList.toArray(new File[0]));
        Thread.currentThread().setContextClassLoader(classLoader);

        // Execute the main class
        Class bootClass;
        try {
            bootClass = classLoader.loadClass(mainClass);
            Method main = bootClass.getMethod("main",
                new Class[] {String[].class});
            main.invoke(null, new Object[] {
                actualArgsList.toArray(new String[0])});
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

You will also need to create a META-INF/MANIFEST.MF file along with these classes. This should specify which is the main class to be run. The contents should be as follows:

Manifest-Version: 1.0
Implementation-Title: XML Transport Bootloader
Main-Class: com.whitehill.bootstrap.Launcher

Those classes and manifest should be packaged into a single jar file called boot.jar. Note that these should be the only classes in boot.jar and it should not include any classes for your running application. If not, you may end up with some classloading issues.

Now to run your application, create the following directory structure

/ – Root directory where the application is run from. You can place a batch file or .exe to start the software here.
/lib – Directory to contain all the .jar files for the application. You can break it up into subdirectories for organizational purposes.

Now to run the application, you can simply run the following:

	java -jar boot.jar -llib com.softwareco.MainClass

This command will start the Java Virtual Machine and run the boot.jar application. This application parses the arguments “-llib com.softwareco.MainClass” and adds all .jar files in the lib directory to the classpath and runs the com.softwareco.MainClass of your application. This command can be placed into a script to make it easier for the user and include any JVM options.

Powered by WordPress Web Design by SRS Solutions © 2010 Coding Clarity Design by SRS Solutions