2008-01-08

Blackmagic with NetBeans and OpenOffice

This one is one of the weirdest (and useful) things I ever seen in 16 years of my life as a developer (I'm 26, BTW)... OpenOffice is a very powerful Office Automation solution... NetBeans Plaform is a very powerful application platform... Can you imagine what you get when you mix both together? Yup, this is possible. With NB and OOo installed, you just need to create a NB Module with following JARs from OOo:
  • juh.jar
  • jurt.jar
  • officebean.jar
  • ridl.jar
  • unoil.jar

I created another module with a single TopComponent, and add this snippet:

private OOoBean oo;
@Override
public void componentOpened() {
try {
add(oo = new OOoBean());
oo.loadFromURL("private:factory/swriter", null);
oo.aquireSystemWindow();
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
}

But, this will NOT work... If you try to run it, an exception will be thrown, because OOo libs (.DLL/.SO) will not be found. They only exists in OOo install folder, and are automagically found by OOo classes - only if they aren't moved from the "classes" folder in OOo installation.

The workaround is a couple of classes in OOo SDK and The Ultimate Blackmagic(TM) - patent pending:

  1. Grab odk / source / com / sun / star / lib / loader from OOo's CVS and add it to integration module.

  2. Add unowinreg.dll to Windows Path (if you are on Linux, skip this step).

  3. Alter Loader.java and add this after line 205 (after "UnoInfo" call):
    vec.add(new File(fClassesDir, "officebean.jar").toURL());

    This part of the magick adds "officebean" to classpath. Here lies OOoBean, and is optional if you are only using UNO interfaces. Other JARs are correctly listed by UnoInfo.

  4. Now, the ugliest part of the evil potion: create a Facade. Using OOoBean directly will lead to some ugly ClassLoader conflicts. I've no time to fix this, but the following works:

    public static Container buildOOoBean() {
    return (Container) Loader.getCustomLoader().
    loadClass("com.sun.star.comp.beans.OOoBean").
    newInstance();
    }

    public static void showOOoBean(Container oo) {
    Class c = oo.getClass().
    getClassLoader().
    loadClass("com.sun.star.beans.PropertyValue");
    Class ca = Array.newInstance(c, 0).getClass();
    Method m = oo.getClass().
    getMethod("loadFromURL", String.class, ca);
    m.invoke(oo, "private:factory/swriter", null);

    oo.getClass().
    getMethod("aquireSystemWindow").invoke(oo);
    }

  5. Back to the TC, I use "buildOOoBean" in ComponentOpened and "showOOoBean" AFTER the component is shown (does not work otherwise).

Yes, it is the purest evilness, but works (with some bugs):


This level of indirection is mandatory, because client module's ClassLoader is different than integration module's. If I do "OOoBean oo = new OOoBean()", the declaration and instantiation will try to use the JARs bundled. Since they don't lie on OOo install folder, the libraries will not be found. If I use "oo = (OOoBean) Loader.getCustomLoader().getClass("...").newInstance()", the "OOoBean" class from declaration will NOT be the same as the class instantiated. If you ever worked with JavaEE 4 and JSF/Struts/etc, you know that same classes from different ClassLoaders throws ClassCastException.

OOo works really well with Java, but NetBeans ClassLoader is "self-contained" (i.e. your bundled application must include EVERYTHING you use). This is a very nice architecture, but does not help if you if you need to interface with external applications.

1 comment:

Anonymous said...

Bizzarous...