The OSGi framework is a powerful Java tool providing component based service and dependency management. OSGi components can be remotely installed, started, stopped, updated and uninstalled without shutting down your application. Also, OSGi provides far greater dependency management than the basic Java classpath mechanism, allowing you to specify specific versions of dependencies and keep dependencies private so that other components cannot load them. One of the most notable usages of OSGi is the Eclipse Plugin Container. A good starting point for more information is the Wikipedia page and the Further Reading section of this document.
The Groovy jar files are released with correct OSGi metadata, so they can be loaded into any OSGi compliant container, such as Eclipse Equinox or Apache Felix. The metadata can be viewed by looking at the jar file's MANIFEST.MF file. For instance, your Jar manifest might contain these lines:
This declares to the OSGi container that the Jar is version 1.7.0 and provides the specified packages for import by other components.
The following examples all use the Eclipse Equinox container, which can be downloaded from the Internet or found in an Eclipse installation.
Perform the following steps to install and start the Groovy Jar in an OSGi container:
Start the OSGi container in console mode:
This should bring up an OSGi console prompt:
You can see the system status of the container at any time using the "ss" command.
Install the Groovy jar file using "install" and a file URL to your groovy-all jar:
The container will assign the bundle an identifier. Start the bundle using the "start" command:
Verify the bundle is started using "ss":
You can list all the packages the Groovy bundle provides with the "packages" command:
Once the Groovy jar is loaded into the container, writing an OSGi service that uses Groovy is as simple as creating a class that extends the framework's BundleActivator interface.
The Activator's start(BundleContext) method will be invoked when the container starts the service, and the stop(BundleContext) method will be invoked when the container stops the service.
The first step in deploying the new Groovy service is to create a jar file containing the Activator. The manifest for the Jar needs to specify the name of the new service, the version, the fully qualified path to the Activator, and which packages from the groovy-all jar bundle to import. The complete manifest for this example follows:
The Import-Package statement is important. It states all the dependencies from the Groovy-all jar which are allowed to be referenced. The Groovy-all Jar exports many, many more packages than just this... an Import-Package definition with just enough dependencies to get the println to work correctly is shown here. In a more meaningful Activator you'd want to import many more of the packages.
The complete Jar for this example has a layout as follows:
Test the new Hello-Groovy bundle by running the OSGi console and issuing the following commands, using "ss" to verify that the correct dependencies are loaded beforehand:
The start and stop message shows that the Groovy service was correctly started and stopped.
The previous example shows how to resolve an Activator's Groovy dependency from the container. The Activator can only be started after the Groovy bundle is started. An alternative is to simply include the groovy-all jar within your bundle. This eliminates the need to declare Import-Packages, but does bloat the size of the jar. Any Jar file included within your bundle has private visibility and cannot be referenced by any other bundles that happen to be running in the container.
To include the groovy-all jar within your bundle, rather than loading it from the container, create your Jar with a layout as follows:
And use this template as the MANIFEST.MF:
The Jar can now be loaded and started in the container without first loading the Groovy Jar. Verify this but running the following within the console:
Publishing a service written in Groovy requires one extra step that a Java service does not. This is because of the way Groovy makes extensive use of ClassLoaders and reflection. When registering a service with the BundleContext, you must be sure to temporarily set the current thread's ContextClassLoader to the target object's ClassLoader, and then set it back when you're done. It's actually quite easy, and this example walks you through this one detail.
In order to demonstrate registering a service, we need to create a sample service in Groovy. This is just a POGO interface and implementation. Consider the GroovyGreeter which simply prints a message to the console.
GroovyGreeter.groovy defines the interface:
And GroovyGreeterImpl.groovy defines the implementation:
Now the Activator can create an instance of GroovyGreeterImpl and register it with the container as a GroovyGreeter provider. In Java, you'd need one line to call BundleContext.registerService(String, Object, Dictionary), but in Groovy we need to change the ContextClassLoader while we do this. Here is a complete and correct Activator:
The Jar file for this bundle is similar to the first example's:
The Jar Manifest is almost the same as the first example. The change is the Export-Package statement. Since we are registering the a service org.codehaus.groovy.osgi,GroovyGreeter, we need to specify that package and version in an Export-Package statement:
Notice, also, how this bundle is importing the Groovy Jar from the container, it does not contain the Groovy Jar within itself. To verify and test this, use the OSGi console to install and start the groovy-all Jar and then install and start this Jar.
It is easy to consume an OSGi service from a Groovy Activator. This example shows how to locate and invoke the service from the previous section, but could just as easily work with a different service. It does not matter at all whether the service was written in Groovy, Java, or any other language. The implementation details of a service should be completely hidden from you by the OSGi module system. Also, while this example is written in Groovy, it is not very different from how it would look in Java.
To consume the service from the previous section, you will need an Activator that retrieves the service from the BundleContext in the start(BundleContext) method:
The service was registered by the interface name, so this Activator queries for that interface, printing out all the providers found. Notice the package and name of this Activator changed. Since the previous example exported the org.codehaus.groovy.osgi, this example needed to pick a different package to avoid conflicts. Also, the variables types in the code sample were explicitly declared to make the samplee easier to read.
To package and run this example you'll need to make a Jar with the following layout:
And the manifest needs to import the org.codehaus.groovy.osgi in the previous example:
Install and test this bundle in the OSGi console. To install this bundle you'll need the groovy-all bundle installed first:
To start the bundle, you'll need the groovy-all bundle started and the hello-groovy bundle installed:
To see the GroovyGreeter service locate and invoke the service, start the hello-groovy bundle and the restart the harness:
Exploring the OSGi console can be a valuable learning experience. Now that all the bundles are loaded, try playing with the "bundle", "headers", "services", and "packages" commands. Help is available by typing "help".
Diagnosing runtime exceptions can be a frustrating experience for the OSGi beginner and expert alike. Here are some common errors and solutions you might experience running these code samples:
ClassNotFoundException - The Java and Groovy compiler use a classpath to resolve dependencies, while the OSGi container does not. This means that code that compiles fine might still receive ClassNotFound exceptions when running in the container. Here are some steps to diagnose the issue:
Missing Constraint - This means that one of your declared Import-Package statements cannot be satisfied by the container. The error message will state exactly which dependency is missing, for instance it might say: Missing Constraint groovy.lang; version="1.7.0.beta-1-SNAPSHOT".
ClassCastException - This error occurs when retrieving services out of the BundleContext and takes the form of the mysterious error message "Cannot cast foo.bar.Class to foo.bar.Class". This means that the ClassLoader of your bundle has a different version of foo.bar.Class than the one you're retrieving.
The great part about using Groovy for OSGi is that the existing tutorials for Java and OSGi are all easily converted to Groovy.