Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

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.

Loading Groovy as an OSGi service

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:

Code Block
Bundle-Version: 1.7.0
Export-Package: groovy.text;version="1.7.0.beta-1-SNAPSHOT",groovy.xml;
    version="1.7.0.beta-1-SNAPSHOT",groovy.util;version="1.7.0.beta-1-SNA
    PSHOT",groovy.lang;version="1.7.0.beta-1-SNAPSHOT",groovyjarjarcom...

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:

Code Block
java -jar org.eclipse.osgi_3.4.0.v20080605-1900.jar -console

This should bring up an OSGi console prompt:

Code Block
osgi>

You can see the system status of the container at any time using the "ss" command.

Code Block
osgi> ss

   Framework is launched.

   id      State       Bundle
   0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900

Install the Groovy jar file using "install" and a file URL to your groovy-all jar:

Code Block
osgi>install file:///home/user/dev/groovy-core/target/dist/groovy-all-1.7-beta-1-SNAPSHOT.jar
   Bundle id is 10

The container will assign the bundle an identifier. Start the bundle using the "start" command:

Code Block
osgi> start 10

Verify the bundle is started using "ss":

Code Block
osgi> ss

   Framework is launched.

   id      State       Bundle
   0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
   10      ACTIVE      groovy-all_1.7.0.beta-1-SNAPSHOT

You can list all the packages the Groovy bundle provides with the "packages" command:

Code Block
osgi>packages 10
groovy.xml.streamingmarkupsupport; version="1.7.0.beta-1-SNAPSHOT"<file:///hom
e/user/dev/groovy-core/target/dist/groovy-all-1.7-beta-1-SNAPSHOT.jar [10]>gro
ovyjarjarantlr.actions.java; version="1.7.0.beta-1-SNAPSHOT"<file:///home/user
...

Writing a Groovy OSGi Service

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.

Code Block
package org.codehaus.groovy.osgi

import org.osgi.framework.BundleActivator
import org.osgi.framework.BundleContext

class Activator implements BundleActivator {

    void start(BundleContext context) {
        println "Groovy BundleActivator started"
    }

    void stop(BundleContext context) {
        println "Groovy BundleActivator stopped"
    }
}

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:

Code Block
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.0
Created-By: 10.0-b19 (Sun Microsystems Inc.)
Built-By: user
provider: org.codehaus.groovy.osgi
Bundle-ManifestVersion: 2
Bundle-Name: Groovy OSGi Example Bundle
Bundle-SymbolicName: org.codehaus.groovy.osgi.hello-groovy-bundle
Bundle-Version: 1.0.0
Bundle-Activator: org.codehaus.groovy.osgi.Activator
Bundle-Vendor: Groovy
Bundle-Localization: plugin
Import-Package: groovy.lang;version="1.7.0.beta-1-SNAPSHOT",org.codeha
us.groovy.reflection;version="1.7.0.beta-1-SNAPSHOT",org.codehaus.gro
ovy.runtime;version="1.7.0.beta-1-SNAPSHOT",org.codehaus.groovy.runti
me.callsite;version="1.7.0.beta-1-SNAPSHOT",org.w3c.dom,org.osgi.fram
ework;version="1.3.0"
Bundle-ClassPath: .

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:

Code Block
hello-bundle-imports-groovy.jar
--META-INF
----MANIFEST.MF
--org
----codehaus
------groovy
--------osgi
----------Activator.class

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:

Code Block
osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
10      ACTIVE      groovy-all_1.7.0.beta-1-SNAPSHOT

osgi> install file:///home/user/dev/groovy-core/src/examples/osgi/build/hello-bundle-imports-groovy.jar
Bundle id is 12

osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
10      ACTIVE      groovy-all_1.7.0.beta-1-SNAPSHOT
12      INSTALLED   org.codehaus.groovy.osgi.hello-groovy-bundle_1.0.0

osgi> start 12
Groovy BundleActivator started

osgi> stop 12
Groovy BundleActivator stopped

The start and stop message shows that the Groovy service was correctly started and stopped.

Including the Groovy Jar within a Bundle

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:

Code Block
hello-bundle-contains-groovy.jar
--groovy-all-1.7-beta-1-SNAPSHOT.jar
--META-INF
----MANIFEST.MF
--org
----codehaus
------groovy
--------osgi
----------Activator.class

And use this template as the MANIFEST.MF:

Code Block
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.0
Created-By: 10.0-b19 (Sun Microsystems Inc.)
Built-By: user
provider: org.codehaus.groovy.osgi
Bundle-ManifestVersion: 2
Bundle-Name: Groovy OSGi Example Bundle
Bundle-SymbolicName: org.codehaus.groovy.osgi.hello-groovy-bundle
Bundle-Version: 1.0.0
Bundle-Activator: org.codehaus.groovy.osgi.Activator
Bundle-Vendor: Groovy
Bundle-Localization: plugin
Import-Package: org.w3c.dom,org.osgi.framework;version="1.3.0"
Bundle-ClassPath: .,groovy-all-1.7-beta-1-SNAPSHOT.jar

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:

Code Block
osgi> install file:///home/user/dev/groovy-core/src/examples/osgi/build/hello-bundle-contains-groovy.jar
Bundle id is 14
osgi> ss

Framework is launched.
id      State       Bundle

0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
14      INSTALLED   org.codehaus.groovy.osgi.hello-groovy-bundle_1.0.0

osgi> start 14
Groovy BundleActivator started

Publishing a Service Written in Groovy

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:

Code Block
package org.codehaus.groovy.osgi

interface GroovyGreeter {
    void sayHello()
}

And GroovyGreeterImpl.groovy defines the implementation:

Code Block
package org.codehaus.groovy.osgi

class GroovyGreeterImpl implements GroovyGreeter {
    void sayHello() {
        println "Hello from the Groovy Greeter!"
    }
}

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:

Code Block
package org.codehaus.groovy.osgi
import org.osgi.framework.BundleActivator
import org.osgi.framework.BundleContext
import org.osgi.framework.ServiceRegistration

class Activator implements BundleActivator {
    ServiceRegistration registration
    void start(BundleContext context) {
        ClassLoader originalClassLoader = Thread.currentThread().contextClassLoader
        try {
            Thread.currentThread().contextClassLoader = getClass().classLoader
            GroovyGreeter myService = new GroovyGreeterImpl()
            registration = context.registerService(GroovyGreeter.class.getName(), myService, null)
        }
        finally {
            Thread.currentThread().contextClassLoader = originalClassLoader
        }
    }
    void stop(BundleContext context) {
        registration.unregister()
    }
}

The Jar file for this bundle is similar to the first example's:

Code Block
hello-bundle-imports-groovy.jar
--META-INF
----MANIFEST.MF
--org
----codehaus
------groovy
--------osgi
----------Activator.class
----------GroovyGreeter.class
----------GroovyGreeterImpl.class

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:

Code Block
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.0
Created-By: 10.0-b19 (Sun Microsystems Inc.)
Built-By: user
provider: org.codehaus.groovy.osgi
Bundle-ManifestVersion: 2
Bundle-Name: Groovy OSGi Example Bundle
Bundle-SymbolicName: org.codehaus.groovy.osgi.hello-groovy-bundle
Bundle-Version: 1.0.0
Bundle-Activator: org.codehaus.groovy.osgi.Activator
Bundle-Vendor: Groovy
Bundle-Localization: plugin
Import-Package: groovy.lang;version="1.7.0.beta-1-SNAPSHOT",org.codeha
us.groovy.reflection;version="1.7.0.beta-1-SNAPSHOT",org.codehaus.gro
ovy.runtime;version="1.7.0.beta-1-SNAPSHOT",org.codehaus.groovy.runti
me.callsite;version="1.7.0.beta-1-SNAPSHOT",org.w3c.dom,org.osgi.fram
ework;version="1.3.0"
Export-Package: org.codehaus.groovy.osgi;version="1.0.0"
Bundle-ClassPath: .

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.

Consuming a Service from Groovy

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:

Code Block
package org.codehaus.groovy.osgi.harness

import org.osgi.framework.BundleActivator
import org.osgi.framework.BundleContext
import org.osgi.framework.ServiceRegistration
import org.osgi.framework.ServiceReference
import org.codehaus.groovy.osgi.GroovyGreeter

class HarnessActivator implements BundleActivator {

    void start(BundleContext context) {
        String serviceName = GroovyGreeter.class.name
        ServiceReference[] references = context.getAllServiceReferences(serviceName, null)

        println "${ references ? references.size() : 0 } GroovyGreeter services found."

        references?.each { ServiceReference ref ->
            Object serviceHandle = context.getService(ref)
            GroovyGreeter service = serviceHandle
            service.sayHello()
        }
    }

    void stop(BundleContext context) {
    }
}

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:

Code Block
hello-groovy-test-harness.jar
--META-INF
----MANIFEST.MF
--org
----codehaus
------groovy
--------osgi
----------harness
------------HarnessActivator.class
------------HarnessActivator$_start_closure1.class

And the manifest needs to import the org.codehaus.groovy.osgi in the previous example:

Code Block
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.0
Created-By: 10.0-b19 (Sun Microsystems Inc.)
Built-By: user
provider: org.codehaus.groovy.osgi.harness
Bundle-ManifestVersion: 2
Bundle-Name: Groovy OSGi Test Harness
Bundle-SymbolicName: org.codehaus.groovy.osgi.harness.hello-groovy-tes
 t-harness
Bundle-Version: 1.0.0
Bundle-Activator: org.codehaus.groovy.osgi.harness.HarnessActivator
Bundle-Vendor: Groovy
Bundle-Localization: plugin
Import-Package: org.codehaus.groovy.runtime.typehandling;version="1.0.
 0",org.codehaus.groovy.osgi;version="1.0.0",groovy.lang;version="1.7.
 0.beta-1-SNAPSHOT",org.codehaus.groovy.reflection;version="1.7.0.beta
 -1-SNAPSHOT",org.codehaus.groovy.runtime;version="1.7.0.beta-1-SNAPSH
 OT",org.codehaus.groovy.runtime.callsite;version="1.7.0.beta-1-SNAPSH
 OT",org.w3c.dom,org.osgi.framework;version="1.3.0"
Bundle-ClassPath: .

Install and test this bundle in the OSGi console. To install this bundle you'll need the groovy-all bundle installed first:

Code Block
osgi> install file:///home/user/dev/groovy-core/src/examples/osgi/../../../target/dist/groovy-all-1.7-beta-1-SNAPSHOT.jar
Bundle id is 6

osgi>  install  file:///home/user/dev/groovy-core/src/examples/osgi/build/hello-groovy-test-harness.jar
Bundle id is 7

osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
6       INSTALLED   groovy-all_1.7.0.beta-1-SNAPSHOT
7       INSTALLED   org.codehaus.groovy.osgi.harness.hello-groovy-test-harness_1.0.0

To start the bundle, you'll need the groovy-all bundle started and the hello-groovy bundle installed:

Code Block
osgi> install file:///home/user/dev/groovy-core/src/examples/osgi/build/hello-bundle-imports-groovy.jar
Bundle id is 8

osgi> start 6

osgi> start 7
0 GroovyGreeter services found.

To see the GroovyGreeter service locate and invoke the service, start the hello-groovy bundle and the restart the harness:

Code Block
osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
6       ACTIVE      groovy-all_1.7.0.beta-1-SNAPSHOT
7       ACTIVE      org.codehaus.groovy.osgi.harness.hello-groovy-test-harness_1.0.0
8       RESOLVED    org.codehaus.groovy.osgi.hello-groovy-bundle_1.0.0

osgi> start 8
Groovy BundleActivator started

osgi> stop 7

osgi> start 7
1 GroovyGreeter services found.
Hello from the Groovy Greeter!

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".

Common Errors 

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:

  • Find out which class is missing but reading the exception stack trace
  • Find out which Jar contains the missing class
  • Does that Jar's Manifest contain an Export-Package statement for the class' package? If not, then the Jar was built incorrectly and you need to add the package to the Export-Package statement of the Jar.
  • Does your Jar's Manifest contain an Import-Package statement for the imported package? If not, then your Jar was built incorrectly and you need to add the package to the Import-Package statement of your Jar.
  • Does the Export-Package version number match your Import-Package version number? If not, then update your manifest to import the correct version, or update the missing class' jar to export the correct version?
  • Is the missing class' bundle correctly installed and started in the container? The results of an "ss" command must show the Jar as "Active" or else it won't be resolved. Use the "install" and "start" commands to start the jar correctly.

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".

  •  Is the missing constraint's bundle correctly installed and started in the container? The results of an "ss" command must show the Jar as "Active" or else it won't be resolved. Use the "install" and "start" commands to start the jar correctly.

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.

  • Look at how the service's Activator is adding foo.bar.Class to the BundleContext. If the class is implemented in Groovy then you must add the service using the ClassLoader code in the Publishing a Service Written in Groovy of this document.
  • Look at how your Activator is resolving the reference to the class. If your bundle already defines foo.bar.Class and you're trying to retrieve a foo.bar.Class from a different bundle, then the versions of the classes won't match. Declare the type as an interface that is imported the same way between both bundles to resolve this issue.

Further Reading

The great part about using Groovy for OSGi is that the existing tutorials for Java and OSGi are all easily converted to Groovy.

  • JavaWorld.com has a good three part introduction to Java and OSGi. Part 1 details the basics os a Hello World service. Part 2 describes the Spring DM product and adding Spring to OSGi. And Part 3 covers several web deployment scenarios and issues. Part 2 describes the Spring DM product and adding Spring to OSGi. And Part 3 covers several web deployment scenarios and issues.
  • TheServerSide.com also has an OSGi for Beginners article.
  • Hamlet D'Arcy has a Groovy and OSGi on the Desktop tutorial on his blog.
  • Groovy and Sling (using OSGi): Apache Sling doco, blog