OSGi at LinkedIn: Integrating Spring DM (Part 2)

In my last post I talked about how the Spring-DM extender automatically recognizes new namespaces. In this post I'll talk about how to configure the extender itself. First let's talk a little bit about fragments (since it is the mechanism used by Spring-DM).

What is a fragment?
A fragment is a special kind of OSGi bundle. By itself a fragment does not do anything: it cannot be started (it is illegal to have an activator for a fragment). A fragment needs to be 'attached' to another bundle called the host bundle. You define a fragment bundle by adding a special header in the MANIFEST:

Fragment-Host: <Host Bundle Symbolic Name>

This is what it looks like in the Equinox console:

39	ACTIVE      org.springframework.bundle.osgi.extender_1.1.0.m2	            Fragments=5555	RESOLVED    com.linkedin.kernel-core.lispring-osgi_1.0.1.SNAPSHOT	            Master=39

55 is a fragment bundle and it attaches to its host 39. The header is defined like this:

Fragment-Host: org.springframework.bundle.osgi.extender

Note that you can have more than one fragment attached to a host (but you cannot have a fragment attached to multiple hosts).

What is the use of a fragment?

Once a fragment is attached to its host, it behaves like if it was part of it. Conceptually, the behavior is similar to what you would obtain if you were to unjar the host and the fragment, and recreate a unique bundle made up of the content of the 2 separate bundles (including manifest headers). It allows to attach behavior and resources to a bundle.

For example if you use the method

Bundle.findEntries("META-INF/", "*", false)

before attaching the fragment then you will see only the content of the

META-INF

directory in the host bundle. If you call the same method after the fragment has been attached you will get a different result with the content of the

META-INF

directory in the host and fragment! This is pretty powerful to load any kind of resources (for example, a localized properties file, etc.).

Here is another example using the fact that the host has now access to classes it didn't know about before:

  • I have a bundle which depends on nothing else but the JDK and OSGi.
  • In the activator, I instantiate a LogFactory (internal LinkedIn class) this way:
    String logFactoryClassName = System.getProperty("org.xeril.log.Log.factory.class.name");
    
    LogFactory factory = null;
    
    // a class name was provided
    if(logFactoryClassName != null)
    {
    try
    {
    Class logFactoryClass =
    Thread.currentThread().getContextClassLoader().loadClass(logFactoryClassName);
    
    factory = (LogFactory) logFactoryClass.newInstance();
    }
    catch(Throwable th)
    {
    // we display the stack trace but we don't fail
    th.printStackTrace();
    }
    }
    
    // no class name or error
    if(factory == null)
    {
    factory = new JDKLogFactory();
    }
  • Now I have a fragment bundle which depends on Log4j and has a
    Log4jLogFactory

    class. The system property gets set on the command line and if the fragment is attached, using reflection, the Log4j factory will be instantiated properly although the host does not depend on Log4j nor knows anything about

    Log4jLogFactory

    . If it is not attached I simply use the JDK logger.

Fragment Lifecycle
The lifecycle of fragments is a little bit different from other bundles. For example you cannot start a fragment. If you undeploy a fragment that is attached to a host, the fragment is gone from the list of bundles but it is still attached to the host. Same if you install a new one. You actually need to 'refresh' the host to see something happening.

Below is an example of activating a bundle with Equinox:

osgi> ss39	ACTIVE      org.springframework.bundle.osgi.extender_1.1.0.m2	            Fragments=5555	RESOLVED    com.linkedin.kernel-core.lispring-osgi_1.0.1.SNAPSHOT	            Master=39osgi> uninstall 55

osgi> ss39	ACTIVE      org.springframework.bundle.osgi.extender_1.1.0.m2	            Fragments=55

osgi> install file:/.../com.linkedin.kernel-core.lispring-osgi_1.0.1.SNAPSHOT.jar

osgi> ss39	ACTIVE      org.springframework.bundle.osgi.extender_1.1.0.m2	            Fragments=5556	INSTALLED   com.linkedin.kernel-core.lispring-osgi_1.0.1.SNAPSHOT

osgi> refresh 39osgi> ss39	ACTIVE      org.springframework.bundle.osgi.extender_1.1.0.m2	            Fragments=5656	RESOLVED    com.linkedin.kernel-core.lispring-osgi_1.0.1.SNAPSHOT	            Master=39

Spring-DM Extender
Now that you know what a fragment is, it should be easy to understand how to extend Spring-DM: you simply need to attach a fragment to the host bundle called

org.springframework.bundle.osgi.extender

. In your fragment you need to have a directory called

META-INF/spring/extender

which contains normal Spring context files. Spring-DM will automatically see those files, create an application context and instantiate the beans. It will then look for beans with some specific names to customize itself: this is described in the reference documentation.

For example, by having a bean called

applicationContextCreator

, you can extend or override entirely the way Spring-DM detects that a bundle is a Spring Powered Bundle. By default, Spring-DM looks for a header or the presence of a

META-INF/spring

directory to determine that it is a Spring Powered Bundle. You can redefine this behavior to be your own custom header or your own custom directory (or whatever other mechanism you can think of). You can also provide configuration properties like how long to wait on shutdown.

Note: As mentioned in the fragment lifecycle section above, be careful if you attach/update the fragment. You need to refresh the host which in the case of Spring-DM extender will shutdown all application contexts created previously and recreate them. My recommendation is to install both the fragment and the host, and start the host only once they are both installed so that the fragment attaches right away without having to refresh the host afterwards.