As you may have read it I have been working for some time on a new system to bootstrap OpenOffice.org from the Eclipse integration. I will take this occasion to explain how to bootstrap OOo in a Java application.

The problem

You may have already encountered that problem, because it is quite common: when using the Bootstrap class provided by OpenOffice.org jars a message like No office executable has been found is often displayed. After some investigations we often discover that this is a ClassLoader problem. There are many documentations and code snippets on the web explaining what are class loaders in Java, but it is often hard to apply to our problem.

In fact the Boostrap class will try to find the soffice file using its class loader. In Java each class is loaded by a class loader. Class loaders are organized in a tree structure: a class loader has a parent (generally the one used to load the class of the class loader). On the top of the tree there is the JVM classloader. When looking for a class or resource, a class loader will look if it can load the class or resource, and will delegate to its parent if that resource or class file can't be found be that class loader.

This means that I can create a simple Java application bootstrapping OOo with no custom class loader. But that implies that the LD_LIBRARY_PATH and CLASSPATH variables are properly set before starting the application. Defining a custom class loader using the OpenOffice.org installation path would be a much better idea: this is what I will show in the next lines.

A custom class loader for OOo classes

The class loader that we need to create has only to inherit from the URLClassLoader and be provided the URL to the OpenOffice.org jars and library directories. For OpenOffice.org 3 it means that you need to add the URLs for:

  • every Jar file of the Basis layer, in /path/to/ooo/basis-link/program/classes
  • every Jar file of the URE, in /path/to/ooo/basis-link/ure-link/share/java
  • the Basis layer program directory, ie: /path/to/ooo/basis-link/program
  • the URE lib directory, ie: /path/to/ooo/basis-link/ure-link/lib

We also need to add our application's own code to the URLs in order to load our OOo-specific classes by that class loader.

Here is a sample class loader extracted from the Eclipse integration code (simplified). IOOo is an interface describing the OpenOffice.org instance to use:

public class OfficeClassLoader extends URLClassLoader {

    public OfficeClassLoader(IOOo pOOo, ClassLoader pParent) {
        super(getUrls(pOOo), pParent);
    }

    private static URL[] getUrls(IOOo pOOo) {
        LinkedList oUrls = new LinkedList();
        try {
            String[] javaPaths = pOOo.getClassesPath();

            URL bundleUrl = OOEclipsePlugin.getDefault().getBundle().getResource("/"); //$NON-NLS-1$
            URL plugin = FileLocator.resolve(bundleUrl);

            for (String path : javaPaths) {
                File dir = new File(path);
                File[] jars = dir.listFiles(new FileFilter() {

                    public boolean accept(File pTested) {
                        return pTested.getName().endsWith(".jar"); //$NON-NLS-1$
                    }

                });

                for (File jar : jars) {
                    oUrls.add(jar.toURI().toURL());
                }
            }

            String[] libsPath = pOOo.getLibsPath();
            for (String path : libsPath) {
                oUrls.add(new File(path).toURI().toURL());
            }

            oUrls.add(plugin);
        } catch (Exception e) {
            PluginLogger.error("Failed to create the OfficeClassLoader", e);
        }
        return oUrls.toArray(new URL[oUrls.size()]);
    }
}

Now that we have that class loader, how should it be used? The only point to know is that we need to be sure that our classes bootstrapping OpenOffice.org and handling its API have to be loaded by this custom class loader. This means that we have to use the Java introspection API to instanciate the class and run the desired method. Here is a small example using the above class loader:

OfficeClassLoader oooClassLoader = new OfficeClassLoader.getClassLoader(getOOo(),
                    TypesGetter.class.getClassLoader());
Class clazz = oooClassLoader.loadClass("org.openoffice.test.MyOOoAction");
Object myAction = clazz.newInstance();

The MyOOoAction class can now call the famous Bootstrap.bootstrap() method and use the connection to OpenOffice.org.

Tips & Tricks

However, this is not exactly what I have done for the Eclipse integration. I had several needs:

  • the action classes needed to return some results to be used somewhere else. These results are classes of the Eclipse integration which are not related to the OpenOffice.org API at all. If I add the URL of the Eclipse plugin code to the class loader URLs, I can instanciate those classes in the OOo-specific classes. However Java does consider that class A loaded by ClassLoader1 is not the same thing than class A loaded by ClassLoader2. I cannot cast those results using classes loaded by an other class loader: the shared classes have to be loaded by the Eclipse plugin class loader.
  • the Eclipse integration can start and stop OpenOffice.org several times in the application lifetime. The same OpenOffice.org libraries can't be loaded twice by two different instances of the same class loader.

In order to address those two needs, I had to:

  • Redefine the loadClass method of the OfficeClassLoader in order to make it load only the OOo-specific classes of the Eclipse Integration. All the other classes have to be loaded by the parent class loader: the normal one of the Eclipse plugin. In order to do this I had to impose that all the OOo-specific classes are grouped into one precise package.
  • Cache the class loader and re-use them when I needed to bootstrap again the same instance of OOo.