JGUIraffe and OSGi

OSGi is a framework focusing on modularity of Java applications. It becomes more and more popular, so you may want to run your JGUIraffe application in an OSGi container. This is possible, but there are some aspects to be aware of. In this chapter these topics are discussed.

Bundles

In order to deploy an application into an OSGi framework, it has to be packaged in so-called bundles - jar archives containing special meta data. From version 1.2 on, the JGUIraffe jar is a valid OSGi bundle. This is also true for the integration jars for the supported target platforms. They all contain the necessary meta data and thus can be directly deployed into your OSGi container of choice - you have to deploy the main jar plus the correct integration jar for the selected target platform.

The JGUIraffe modules have some dependencies to other libraries (details can be found on the Dependencies page). For a successful deployment into an OSGi container, all these dependencies have to be available as well. Unfortunately, not all of them support OSGi out of the box.

Newer releases of the Apache Commons components referenced by JGUIraffe already contain OSGi meta data, but some of the older releases do not. Especially the Commons Jelly jar is (at the time of this writing) not provided as an OSGi bundle. Because these dependencies have to be satisfied when installing the JGUIraffe bundle in an OSGi framework you have to obtain OSGi-enabled versions of the affected libraries. One option could be to re-package the jars as OSGi bundles yourself. Using the Apache felix maven bundle plug-in this is not too complicated. For instance, The Maven Cookbook contains a chapter about OSGi development which also covers the creation of OSGi bundles for existing jars.

Class loading

A major feature of OSGi is that it enforces a very strict class loading architecture allowing each bundle only access to classes it explicitly references in its OSGi meta data. Especially for the dependency injection framework of JGUIraffe this has a big impact because classes have to be used which are defined in client bundles unknown to JGUIraffe.

The dependency injection framework uses an object implementing the ClassLoaderProvider interface to resolve classes. When starting up, the central Application object creates and initializes such a ClassLoaderProvider object. Per default, it sets the class loader which loaded the custom application class as default class loader. For many simple applications this is exactly what you want because this is typically the class loader of the client bundle. So referenced classes can directly be obtained from this bundle. If you have a more complicated setup, you should override the initClassLoaderProvider() method of your Application class to register all involved class loaders with specific names. In your builder scripts you have to make sure that the correct class loaders are referenced.

There is one caveat: To ensure that the current ClassLoaderProvider is actually used in a builder script, always specify class references using tag attributes ending on ClassName. Do not use the simpler attributes with the Class suffix. For instance, the following fragment shows a wrong example of declaring a FileSystemManager bean from the Apache Commons VFS project (using a static factory method) in an OSGi environment:

  <di:bean name="fileSystemManager"
    beanClass="org.apache.commons.vfs2.FileSystemManager">
    <di:factory>
      <di:methodInvocation method="getManager"
        targetClass="org.apache.commons.vfs2.VFS">
      </di:methodInvocation>
    </di:factory>
  </di:bean>
  

Note the usage of the beanClass and targetClass attributes. The correct bean declaration is as follows:

  <di:bean name="fileSystemManager"
    beanClassName="org.apache.commons.vfs2.FileSystemManager">
    <di:factory>
      <di:methodInvocation method="getManager"
        targetClassName="org.apache.commons.vfs2.VFS">
      </di:methodInvocation>
    </di:factory>
  </di:bean>
  

Here the problematic attributes have been replaced by variants ending on ClassName. There are some more attributes falling into this category, e.g. parameterClassName. The reason why the attributes ending on Class cause problems in an OSGi container is that they are directly evaluated by the Jelly engine processing the builder script. This engine does not know about the ClassLoaderProvider object and thus will probably use a wrong class loader. The ClassName attributes in contrast are processed by JGUIraffe; here different class loaders can be taken into account. Note that for each ClassName attribute there is a companion attribute ending on Loader. With this attribute a special class loader can be selected. In simple applications these attributes are typically not needed; if they are missing the default class loader is used. However, if an application deals with multiple OSGi bundles (and therefore multiple class loaders), it may be necessary to specify the correct class loader explicitly. In such a case the value of the Loader attribute must match the name of a class loader as it has been registered at the ClassLoaderProvider.

Startup and shutdown

The OSGi environment also affects starting and shutdown of applications. Rather than providing a static main() method, application bundles are installed in an OSGi container and are started automatically by an OSGi-specific mechanism. One possibility is a bundle activator, i.e. a class implementing the org.osgi.framework.BundleActivator interface. The activator must be referenced from the bundle's manifest file.

Stand-alone JGUIraffe applications typically define a class derived from Application with a static main() method. The method creates an instance of this class and invokes the static startup() method inherited from Application. The bundle activator has to do the same in its start() method. Because starting up the application is an expensive operation - bean declarations have to be read, the UI is constructed, etc. - this should be done in a new thread, so that control can be passed back to the OSGi framework immediately. Also, an alternative exit handler should be set to prevent that the Java virtual machine is just terminated. Rather, the OSGi framework should be shut down in a graceful way. This can be achieved by obtaining a reference to the system bundle using the reserved bundle ID 0 and calling the stop() method on it.

The following listing shows a full example of a bundle activator implementation. It can serve as a basis for own implementations. It assumes that there is a class called Main derived from Application; this is the application class to be started.

public class Activator implements BundleActivator
{
    /** The logger. */
    private final Log log = LogFactory.getLog(getClass());

    @Override
    public void start(BundleContext context) throws Exception
    {
        log.info("Starting application bundle.");
        final Runnable exitHandler = createExitHandler(context);
        new Thread()
        {
            @Override
            public void run()
            {
                Main main = new Main();
                main.setExitHandler(exitHandler);
                try
                {
                    Main.startup(main, new String[0]);
                }
                catch (ApplicationException e)
                {
                    log.error("Could not start application!", e);
                }
            }
        }.start();
    }

    @Override
    public void stop(BundleContext context) throws Exception
    {
        log.info("Stopping application bundle.");
    }

    /**
     * Creates the exit handler for the application. This handler will shutdown
     * the OSGi framework by stopping the system bundle.
     *
     * @param context the bundle context
     * @return the exit handler
     */
    private Runnable createExitHandler(final BundleContext context)
    {
        return new Runnable()
        {
            @Override
            public void run()
            {
                Bundle sysBundle = context.getBundle(0);
                try
                {
                    sysBundle.stop();
                }
                catch (BundleException bex)
                {
                    log.error("Could not stop OSGi framework!", bex);
                }
            }
        };
    }