Tuesday, July 6, 2010

Real Examples: OSGi Classloader Workaround

So, maybe my last post was a little too nebulous to get the gist. So I'm now going to give a solid example.

In this example we are going to use Substance Look And Feel within a JFrame that is also an OSGi component.

Here's the setup (again, I use iPojo for all my components/services within the Felix implementation of OSGi):

Here's a @Component JFrame. It's a little different than the standard main() created by Visual Editor within Eclipse or Jigloo. Instead of calling initialize() where I build all the visual components of my JFrame I call start() which is also the entry to building when I'm running within OSGi.


@Component
public class Example extends JFrame {
 public Example() {
  super();
 }

 public Example(BundleContext bundleContext) {
  super();
  context = bundleContext;
 }

 public static void main(String[] args) {

  SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    Example thisClass = new Example();
    thisClass.start();
    thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    thisClass.setVisible(true);
   }
  });
 }
...

The start() method looks pretty standard now. I really don't want to shut down Felix or anything when this component is stopped, so I set the close operation appropriately. When the component is stopped I just want to get rid of the JFrame.


 public void start() {
  try {
   UIManager
     .setLookAndFeel( "org.jvnet.substance.skin.SubstanceMistAquaLookAndFeel");
   SwingUtilities.updateComponentTreeUI(this);
  } catch (Exception e) {
   System.out.println("Substance Mist Aqua failed to initialize...");
   e.printStackTrace();
  }
  initialize();
  setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
  setVisible(true);
 }

 @Invalidate
 public void stop() {
  setVisible(false);
  dispose();
 }


Now for the secret sauce. This comes directly from my previous post. start() is now called from the @Validate function. Note that the method is called osgiValidate() instead of something like validate() as it is already used within java.awt.Container which JFrame inherits from four levels deep. We don't want to override that function.

The @Validate function gets the OsgiEnvironmentClassLoader and calls start().



 protected OsgiEnvironmentClassLoader getOsgiEnironmentClassLoader(
   BundleContext bundleContext) {
  return new OsgiEnvironmentClassLoader(bundleContext, Thread
    .currentThread().getContextClassLoader(), bundleContext
    .getBundle());
 }

 @Validate
 public void osgiValidate() {
  java.awt.EventQueue.invokeLater(new Runnable() {
   @Override
   public void run() {
    new ClassLoaderContext(getOsgiEnironmentClassLoader(context))
      .execute(new ClassLoaderContextCallback() {
       public void doAction() {
        start();
       }
      });
   }
  });

 }


In my case this produces something like this:


If you want the window decorated as well then you need to add a little static initializer:


 static {
  JFrame.setDefaultLookAndFeelDecorated(true);
  JDialog.setDefaultLookAndFeelDecorated(true);
 }

Then you get this:


As a little side note, if you wish to shutdown your entire OSGi instance from your swing UI you can use the BundleContext:


 Bundle bundle = context.getBundle(0);
 bundle.stop();

Some people may ask, "Why use this classloader work around instead of setting the org.osgi.framework.bootdelegation to include all the needed classes?" My answer... I'm not really sure that this way here is any more correct. They are both work-arounds for cases where 3rd party libraries are loading dependencies under the scenes... against what OSGi is trying to accomplish. I like this way because it feels better, not because it is better.

Tuesday, June 1, 2010

Cheating In OSGi

Note: Thanks to http://www.dynamicjava.org/ for making a whole slew of things OSGi ready, or at least OSGi usable.

From time to time I'm confronted with incompatibilities within OSGi with older libraries and methodologies. The classic example of this is using JDBC libraries directly. The standard method of loading a driver is through the class loader. This simply doesn't work within OSGi the way you think it does. Even if you use the Dynamic-ImportPackage header within your bundle description it's not unusual for your bundle not to be able to find the class.

Another example of this is with using 3rd party libraries like Oracle's optic and application client libraries against J2EE application servers. JaxB usage also has this issue.

So, here's a quick work around that I use. Note that I use the Felix OSGi implementation with iPOJO. However, this should work in any context.

First, get the BundleContext for your component. Within iPojo, it's as simple as creating a constructor where the BundleContext is passed in.


@Component
public class TestComponent {
protected BundleContext bundleContext;
  public TestComponent( BundleContext bundleContext) {
    this.bundleContext = bundleContext;
  }
}

Next I add a simple helper function.

 protected OsgiEnvironmentClassLoader getOsgiEnironmentClassLoader(BundleContext bundleContext) {
  return new OsgiEnvironmentClassLoader(
    bundleContext,
    Thread.currentThread().getContextClassLoader(),
    bundleContext.getBundle());
 }

Then, wherever I need to do something outside of the standard bundle's class loader:

new ClassLoaderContext(getOsgiEnironmentClassLoader(bundleContext)).execute(
  new ClassLoaderContextCallback() {
   public void doAction() {
    /* do something interesting here */
   }
 });