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.