Usage
Let's look at how to use this implementation. When developing an OSGi bundle that uses and possibly registers services, there are two classes in particular we need to implement. The first one is the bundle activator which controls the life-cycle of the bundle. The second one is the actual service implementation.
When using the dependency manager, your bundle activator is a subclass of DependencyActivatorBase. The following paragraphs will explain how to specify a service and its dependencies. Subsequently, some more advanced scenarios will be covered that involve listening to dependency and service state changes and interacting with the OSGi framework from within your service implementation.
Registering a service
Registering a service is done using a custom implementation of the bundle activator, DependencyActivatorBase, which requires you to implement two methods: init and destroy.
Each bundle can specify zero or more services in the init method. Services can optionally have an interface and properties with which they're registered into the service registry. Registering a service involves creating a new service and adding it to the dependency manager. You then specify the interface as well as the implementation, as shown in the example.
public HttpActivator extends DependencyActivatorBase { public void init(BundleContext ctx, DependencyManager manager) throws Exception { Properties props = new Properties(); props.put("port", "8080"); manager.add(createService() .setInterface(WebService.class.getName(), props) .setImplementation(WebServiceImpl.class)); } public void destroy(BundleContext ctx, DependencyManager manager) throws Exception { // cleaned up automatically } }
There are a couple of alternatives that need some clarification. First of all, you might not have a public interface at all, so the setInterface call is optional. Like in OSGi itself, you can also specify multiple service interfaces in an array of strings. For the implementation you have two choices. You can either specify the class of the implementation, in which case this class needs to have a default, no-args constructor and the dependency manager will use lazy instantiation or you can specify an instance you've already created. More on the life-cycle of the service implementation can be found in the next paragraph.
Specifying service dependencies
Dependencies can either be required or optional. Required dependencies need to be resolved before the service can even become active. Optional dependencies can appear and disappear while the service is active. The example demonstrates how both are specified in the activator.
manager.add(createService() .setInterface(WebService.class.getName(), null) .setImplementation(WebServiceImpl.class) .add(createServiceDependency() .setService(ConfigurationAdmin.class, null) .setRequired(true)) .add(createServiceDependency() .setService(LogService.class, null) .setRequired(false))
Implementing the service
The service implementation is pretty straightforward. Basically any class that implements the service interface can be used here. Two aspects of the implementation deserve some attention though: the life-cycle model and the service dependencies.
Depending on how the service was specified in the activator it is either instantiated as soon as all required dependencies become available or it was already instantiated when the activator started. The service then goes through a number of states, where each transition includes the invocation of a method on the service implementation. The default names of these methods are init, start, stop and destroy but these can be altered. As a developer, all you need to do is specify any of these methods in your service implementation and they will be invoked.
public class WebServiceImpl implements WebService { public void init() { } public void start() { } public void stop() { } public void destroy() { } }
Upon activation, the service implementation should initialize its internal state in the init() method and establish connections with other services in the start() method.
The dependency manager will automatically fill in any references to required dependencies that are specified as attributes. The same goes for optional dependencies if they are available. If not, those will be implemented by a null object TODO-ref. In short, this allows you to simply use these interfaces as if they were always available. A good example of this is the LogService. If it's available, we want to use it for logging. If not, we want to simply ignore log messages. Normally, you'd need to check a reference to this service for null before you can use it. By using a null object, this is not necessary anymore.
public class WebServiceImpl implements WebService { private volatile ConfigurationAdmin configAdminSvc; private volatile LogService logSvc; public void loadSettings() { logSvc.log(LogService.LOG_INFO, "Loading settings."); // do stuff here } }
Listening to service dependencies
Optionally, a service can define callbacks for each dependency. These callbacks are invoked whenever a new dependency is discovered or an existing one is disappearing. They allow you to track these dependencies. This can be very useful if you, for example, want to implement the white board pattern.
manager.add(createService() .setImplementation(DeviceListener.class) .add(createServiceDependency() .setService(Device.class, null) .setCallbacks("added", "removed") .setRequired(false));
public class DeviceListener { public void added(ServiceRegistration reg, Object service) { } public void removed(ServiceRegistration reg, Object service) { } }
Listening to the service state
If you're interested in the state of a service, you can register a service state listener with the service of interest. It will be notified whenever the service is starting, started, stopping and stopped.
Service s = createService() .setInterface(WebService.class.getName(), null) .setImplementation(WebServiceImpl.class); s.setStateListener(new ServiceStateListener() { public void starting(Service s) { } public void started(Service s) { } public void stopping(Service s) { } public void stopped(Service s) { } }); manager.add(s);
Interacting with OSGi
Although interaction of the service implementation with the OSGi framework has nothing to do with dependency managemement, the dependency manager does offer support for communicating with the framework.
Normally, your service implementation does not have to contain any OSGi specific classes or code. As shown before, even references to other services can be used without dealing with OSGi specifics. Sometimes, this is an advantage, since the service implementation can easily be reused in a different service oriented framework. However, there are times when you do want to access, for example, the BundleContext because you want to interact with the framework.
For these cases, you can specify one or two members to get direct access to:
- the BundleContext that provides you with an interface to the OSGi framework;
- the ServiceRegistration of the service that was registered, provided you have registered a public service interface.
public class WebServiceImpl implements WebService { private BundleContext ctx; private ServiceRegistration svcReg; // }
Dynamic dependencies
Most of the time, the dependency definitions are static. You define them when the bundle starts and they stay the same throughout the life-cycle of the bundle. However, in some cases you might want to add dependencies to a service afterwards. For example, you might want to start tracking a service with specific properties. To do this, simply add the dependency in the same way as you would have done in the init() method.