Prev Next

Service Components

Components are created using Declarative Services (DS) using annotations. In Declarative Services there is a rather large number of different mechanisms at work:

  • PID
  • Component service properties
  • Default configuration
  • Component Service Properties
  • Factory configuration
  • Mandatory configuration
  • Singleton configuration
  • PIDs are Public API
  • Management Agent
  • Metatypes
  • Component Factory
  • Targets, or how to Wire Services
  • Registering a service manually

This section attempts to explain how Declarative Services should be used without getting lost.

Trees & Forest

The OSGi specifications are very cohesive and decoupled. Though this provides numerous benefits it clearly makes it hard for newcomers to see how the different parts interact. More importantly, how should you actually use them together?

The best advice is to start very simple and use defaults. This basically means the skeleton of your component is:

package com.acme;
@Component 
public class Foo implements Bar {

	@Reference
	EventAdmin eventAdmin;

	@Reference
	final List<Foo> foo = new CopyOnWriteArrayList<>();

	@interface Config {
		int port();
	}

	@Activate void activate( Config config ) {
	}

	@Deactivate void close() {
	}
}

This gives you a static component that can be be fully configured through the com.acme.Foo PID with Configuration Admin whatever Management Agent you pick.

service.pid	: com.acme.Foo
port		: 8080

If you use bndtools, use the osgi.enroute.configurer.simple.provider bundle. This allows you to specify the configuration data in JSON in a bundle in the configuration/configuration.json resource. The best practice is to create one ‘application’ project that only holds configuration data and no code. This application project pulls together the components that are used in a specific application and configures them. The OSGi enRoute templates create such a setup automatically.

If you run into a problem with this basic model (though realize that 95% of the components can get away with these defaults) that does not seem to have a simple solution then first try to see if you redefine the problem to fit the simple model. (Yes, numerous times people define the problem in terms of a specific solution and not of actual requirements. Taking a step back and trying to see what is really required can make a surprising difference.)

If that does not work, ask on a mailing list. Declarative Services are so widely used that is extremely unlikely there is no solution to your actual requirements.

The PID

The core idea is the PID: an identifier that identifies the configuration for the a component type. For example, the following component has a PID of com.acme.FooImpl:

package com.acme;

@Component
public class FooImpl { }

Default Configuration

Let’s forget Configuration Admin for a moment. If you run this component without any configuration then the component will be started since by default it can live without configuration.

Component Service Properties

If you want to create a service property to be registered (and provided to the activate method) then you can provide such a property in the @Component annotation:

@Component(
	property="foo=bar"
)
public class Foo { 
	@Activate void activate( Map<String,Object> map) {
		assert "bar".equals(map.get("foo"));
	}
}

Configuration Properties Suck

Properties suck but are often a necessary evil. They do not refactor easily, are error prone in their spelling, require messy conversions to their desired Java types, require casting, and in general look awful in your code with their YELLING UPPER CASE (assuming you turn the keys into constants). How nice would it be if you could just use type safe constructs?

Well, you can. In the previous examples we used a Map<String,Object> to get the service properties in the activate method. However, we can also replace this map with any annotation type:

@interface Config {
	String foo() default "bar";
}

@Activate void activate( Foo.Config config ) {
	assert "bar"equals( config.foo() );
}

Factory Configuration

In most cases it makes sense to be able to create multiple instances of a component. For example, you are polling a host for some information in your application. It is common that you want the final application to be able to poll different hosts and not just one. Even if you want it to be just one, it is bad practice to choose a singleton configuration if it is theoretically possible that one day you need two or more.

This is where Configuration Admin comes in. Let’s ignore how we manage Configuration Admin but assume that there are two factory configurations for the ‘com.acme.Foo’ Factory PID:

service.pid		:	com.acme.Foo-126121312-12112
service.factoryPid	:	com.acme.Foo
foo			:	one

service.pid		:	com.acme.Foo-456781232-83622
service.factoryPid	:	com.acme.Foo
foo			:	two

With this configuration the com.acme.Foo component will be instantiated twice. One component will get foo=one and the other will get foo=two

@Activate void activate( Foo.Config config) {
	String foo = config.foo();
	assert "one".equals(foo) || "two".equals(foo);
}

Singleton Configuration

There are cases where a singleton configuration is necessary. If it is logically impossible to have more than one instance then a singleton configuration should be used. However, these cases are more rare than most developers realize. For example, the Apache Felix jetty configuration is a singleton configuration but should likely have been a factory configuration because it is possible to have multiple web servers on different ports. This would have simplified the configuration and made it more flexible.

Assume that there is a singleton Configuration for the ‘com.acme.Foo’ PID:

service.pid	:	com.acme.Foo
foo		:	singleton

If we now run our system, the activate method gets called with foo=singleton

@Activate void activate( Foo.Config config) {
	assert "singleton".equals( config.foo() );
}

Mandatory Configuration

Maybe you’ve noticed, but factories now have a rather strange life cycle. If there is no configuration for the given PID, then the component gets instantiated once with the service properties defined in the @Component annotation. If you then create a factory configuration for that PID, the default component is deleted and the factory configuration is used to instantiate a new component. The consequence of this is that you cannot have zero components, there will always be at least one. Though a default instance is sometimes handy, in general it does not make sense. In that case you can make the configuration mandatory: only if the configuration is set (either singleton or factory) will the component be instantiated.

@Component(
	configurationPolicy = ConfigurationPolicy.MANDATORY
)

With a mandatory configuration the component is only instantiated when there is a Configuration for the corresponding PID. The following table shows how the configuration interacts with this policy:

			Mandatory	Optional	Ignore
no config		no instance	default		default	
singleton config	1 instance      1 instance      default
one factory config      1 instance      1 instance      default
two factory config      2 instances     2 instances     default

PIDs are Public API

PIDs are public API since external parties will be aware of them when they configure your component. For this reason it is sometimes better to create a name that can be kept constant over time even if implementations change.

@Component( name = "com.acme.foo" )
public class FooImpl { }

Management Agent

The previous section should make it clear how the instances of a component can be controlled through the configurations associated with the component’s PID. Components follow Configuration Admin and, except for some default service properties, should never ever try to configure themselves. Things are a lot simpler if components just do what they are being told by Configuration Admin!

However, that leaves the question: Who sets the Configurations for a given PID? This role is in general fulfilled by the management agent. This is not magic entity in OSGi, it is just one or more bundles that perform the role of managing the framework. One of it is roles is to configure the components. This is not a single solution. On the contrary, there are lots of viable solutions. The following lists describes a list of popular management agents:

  • FileInstall – Watches a directory and synchronizes any configuration in that directory to Configuration Admin. File Install allows you to manage singleton and factory configurations just by adding/removing/changing a file in a directory. In a cluster, a directory can be shared (even over for example Dropbox). Very convenient.
  • Web Console – Apache Felix Web Console is a Web based tool that allows you manage configurations. The Web Console is excellent during debugging and small systems because it is very interactive.
  • Configurer – The OSGi enRoute configurer reads JSON resources from active bundles and installs them as configurations. The Configurer is very suitable for production since it delivers configuration as a bundle, just as code bundles.
  • Deployment Admin – Provides a means to deliver configuration through Deployment Packages and a Resource Processor. I think Apache Ace uses this.

Of course there is always the option to write your own management agent. The Configuration Admin API is quite flexible though be aware of some of the quirks (locations!). However, postponing the decision to handle your own configuration until there is no alternative and you’ve also become quite experienced is the recommended (in)action.

Metatype

Using the annotation types significantly cleaned up our code. However, with File Install, Configurer, and Web Console we’re still having to use properties. Since properties are evil, we’d like to have a more elegant solution. This elegant solution is called metatype. It is an OSGi service that enables components to describe their configuration types. User agents can then use a metatype to automatically create a form for that configuration. Apache Felix Web Console fully supports metatypes in its web based user interface.

So how do we link our annotation type to a metatype? Though the metatypes are defined in XML there are fortunately (build) annotations for it.

@ObjectClassDefinition
@interface Config {
	@AttributeDefinition
	String foo();
}

@Designate( ocd = Config.class, factory=true )
@Component
public class Foo {}

The @ObjectClassDefinition and @AttributeDefinition can describe the ‘properties’ and provide additional information to control the user interface. The @Designate annotation links a configuration type (or as we call it: an Object Class Definition or ocd) to a component’s PID. This will create the proper XML for the Metatype service to link it all together.

Targets, or how to Wire Services

Services are linked to each other by their object classes. I.e. one component requires an instance of the Foo service type. You create that dependency with the @Reference annotation. A @Component annotated type can then register this service. However, sometimes you cannot avoid but make the wiring of the services more complicated. Sometimes you really need to ensure that a component Bar is really wired to a specific instance of Foo.

This can be done with the target field in the @Reference annotation.

@Reference( target="(foo=bar)" )
Foo foo;

A component Foo can then register its service with a number of properties under Configuration Admin control.

@Component public class FooImpl implements Foo {
}

Another component can depend on this property.

@Component public class Bar {
	@Reference( target="(foo=bar)" )
	Foo foo;
}

We can now create a configuration for FooImpl

service.pid		:	com.acme.FooImpl-131231412-15322
service.factoryPid	:	com.acme.FooImpl
foo			:	‘bar’

This will create an instance of the Foo service with a service property ‘foo=bar’. The Bar service has a dependency on this service so it will be wired to it.

Obviously this is not symmetric. We need to know the name of the service property ahead of time in the Bar service. It is therefore possible to set the target field of the @Reference annotation with configuration admin:

service.pid		:	com.acme.Bar
foo.target		:	‘(foo=bar)

Component Factory

This part barely made it in the spec and in general it is not advised to use it except when you really know what you’re doing.

In rare cases it is necessary to register a ‘service by hand’ using the standard OSGi API. Try very hard to avoid this case because if you start to register (and maybe depend) on services that you manually register you get a lot of responsibility that is normally hidden from view. For example, you have to ensure that the services you register are also unregistered.

One of the rare cases where this is necessary is when you have to wait for a condition before you can register your service. For example, you need to poll a piece of hardware before you register a service for the device. You will need to control the CPU but at that moment you cannot yet register a service. In that case you create an immediate component and register the service manually.

To register a service manually you require a BundleContext object. You can get that objectvia the activate method, just declare a Bundle Context in its arguments and it is automatically injected:

@Activate
void activate( BundleContext context) {
	this.context = context;
}

You can now register a service with the bundle context:

void register(MyService service) {
	Hashtable<String,Object> properties = new Hashtable<>();
	properties.put("foo", "bar");
	this.registration = context.registerService( MyService.class, service, properties );
}

However, you now have the responsibility to unregister this service in your deactivate. If you do not clean up this service then your component might be deactivated while your service still floats around. Your service is unmanaged. (Although when the bundle is stopped it will be cleaned up.)

@Deactivate
void deactivate() {
	if ( this.registration != null)
		this.registration.unregister();
}

If you create the service is a call back or background thread then you obviously have to handle the concurrency issues. You must ensure that there is no race condition that you register a service while the deactivate method has finished.

This text has also been added to the DS Page of OSGi enRoute


Prev Next