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 byClassLoader1
is not the same thing than classA
loaded byClassLoader2
. 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 theOfficeClassLoader
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.