OSGi Compatibility

Oxygen is in many ways similar to the OSGi framework and should not be too difficult for those familiar with OSGi to pick up. Oxygen differs from OSGi in several ways, these are now discussed.

Components vs. Bundles

In Oxygen the single distributable unit of application embodied by a JAR file is known as a "component" - this is for all intents and purposes the same as a "Bundle" in OSGi.

In OSGi dynamic components are controlled (started & stopped) by the framework using an activator. This is an instance of a class that implements BundleActivator interface and has a public no-argument constructor. The class that performs this function is indicated to the OSGi framework by the Bundle-Activator manifest entry.

In Oxygen dynamic components employ the same model of an activator class, only the class implements the ComponentActivator interface. The class name is resolved by the Oxygen framework from the Component-Activator manifest entry. This choice of different names is deliberate - to allow your component to operate in both OSGi frameworks and Oxygen frameworks.

Lastly, unlike BundleActivator - ComponentActivator does not supply a "context object" to the start and stop methods. In Oxygen the singleton context classes are discovered statically using a Factory object. ServiceManager is available from ServiceManagerFactory, and ComponentManager is available from ComponentManagerFactory.

Class Loading

In OSGi class loading between bundles is controlled by entries in the manifest file. The Import-Package and Export-Package entries of the manifest define (statically) the names of packages that are to be imported and exported by different bundles. Our experience was that this is a major nightmare for developing larger more rich applications - the problem of maintaining these inter-JAR dependencies grows quickly with the number of JARs in the system. It seems the reason for this manifest entry is only to prevent sharing "private" classes in a bundle with other bundles and to resolve dependent bundles statically.

Oxygen does not place any constraints on class loading above conventional Java. Preventing classes being exposed to the outside world can be done by not making those classes public. But to add some compatibility with OSGi we provide a manifest attribute Export-Exclude, this is a comma-separated list of the packages and/or classes of this JAR which will not be shared with other components. All other packages in the JAR will be shared with all other components. 

Here are a few important notes on export exclusion and component class loading:

Service Consumption and Release

In OSGi a service consuming bundle is required to access the services it uses from a BundleContext using the getServiceReference(String) method. If no services are registered this method returns nothing, so it is more common to attach a ServiceListener with a (LDAP style) filter that matches the service sought by the client application using the addServiceListener method. Once this listener gets a call back from the framework for a matched service, the actual service implementation object is acquired via the getService(ServiceReference) method. The OSGi framework tracks a count of service users, so when finished the service must be released by calling ungetService(ServiceReference).

Oxygen still uses a ServiceReference class to represent a service registered with the ServiceManager. However, this service reference makes the service object available directly to client code via the getServiceObject() method. Also the reference does not require client code to "unget" the service when the service is no longer being used - you can treat the object as an ordinary Java reference.

The major difference in service acquisition is that oxygen service consumers implement the ServiceConsumer interface. And register a ServiceConsumer for a ServiceInterest with the ServiceManager- a ServiceInterest is an object that contains a service identifier (String - normally fully qualified name of service interface) and an optional set of ServiceAttributes (describing desired properties of the service). The consumer will then be called back for each service that is found or lost that matches the ServiceInterest, this includes an intial call to the serviceFound method for each matching service already registered with the manager before the consumer was registered. In this way the consumer will be notified of all "matching" services. Similarly when services are unregistered - or when the consumer is unregistered, the serviceLost() method is called for each matching service the consumer can no longer use. It is normally safe to use the service one last time during the serviceLost event, but once this method completes and returns to the ServiceManager the component should no longer use the service object.

It is possible to get a list of registered services in a service consumer by calling the getServiceReference(s)() methods of ServiceManager. However this is generally a bad idea - since services can come and go at any time, those consuming a service normally must know when a service is unregistered, or wait until a service is registered (if none presently are) before continuing. The ServiceConsumer model fits most scenarios better than "manually" acquiring services.