OSGi Declarative Services news in Eclipse Oxygen

10 minute read

With this blog post I want to share my excitement about the OSGi DS related news that are coming with Eclipse Oxygen. I want to use this blog post to inform about the new features and also the changes you will face with these. With Oxygen M6 you can already have a look at those features and also provide feedback if you find any issues.

Note:
You don’t have to be a committer or contribute code to be part of an Open Source Community. Also testing new features and providing feedback is a very welcome contribution to a project. So feel free to participate in making the Eclipse Oxygen release even better than the previous releases!

DS 1.3 with Felix SCR

The Equinox team decided to drop Equinox DS (stuck with DS 1.2) and replace it with Felix SCR (Bug 501950). This brings DS 1.3 to Eclipse which was the last missing piece in the OSGi R6 compendium in Equinox.

It was already possible to exchange Equinox DS with Felix SCR with Neon, but now you don’t need to replace it yourself, it is directly part of Equinox. There are some important things to notice though, which I will list here:

Felix SCR bundle from Orbit

The Felix SCR bundle included in Equinox/Eclipse is not equal to the Felix SCR bundle from Apache. The Apache bundle imports and exports the org.osgi packages it requires, e.g. the component related interfaces like ComponentContext, ComponentFactory or ComponentServiceObjects. It also contains the Promises API used by Felix SCR, which was not available in Equinox before. The Felix SCR bundle in Orbit does not contain these packages. They are provided by other Equinox bundles, which are now required to use DS with Equinox.

Note:
If you are interested in some more information about the reasons for the changes to the Orbit Felix SCR bundle, have a look at Bug 496559 where Thomas Watson explained the reasons very nicely.

The bundles needed for DS in Equinox are now as follows:

  • org.apache.felix.scr
    The Declarative Services implementation.
  • org.eclipse.osgi.services
    Contains the required OSGi service interfaces.
  • org.eclipse.osgi.util
    Contains the Promises API and implementation required by Felix SCR.
  • org.eclipse.equinox.ds
    (optional) Wrapper bundle to start Felix SCR and provide backwards compatibility.

Adding the Promises API (see OSGi R6 Compendium Specification chapter 705) in Equinox is also a very nice, but worth its own blog post. So I will not go into more details here. The more interesting thing is that org.eclipse.equinox.ds is still available and in some scenarios required. It does not contain a DS implementation anymore. It is used as a wrapper bundle to start Felix SCR and provide backwards compatibility. The main reasons are:

  1. Auto-starting DS
    The Equinox startup policy is to start bundles only if a class is accessed from them, or if it is configured for auto-starting. As the SCR needs to be automatically started but actually no one really accesses a class from it, every Eclipse application that makes use of Declarative Services configured the auto-start of org.eclipse.equinox.ds in the Product Configuration. If that bundle would be simply replaced, every Eclipse based product would need to modify the Product Configuration.
  2. Behavioral Compatibility
    Equinox DS and Felix SCR behave differently in some cases. For example Felix SCR deactivates and destroys a component once the last consumer, that references the component instance, is done with it. Equinox DS on the other hand keeps the instance (I explained that in my Control OSGi DS Component Instances blog post). As p2 and probably also other implementations rely on the Equinox behavior that components are not deactivated and destroyed immediately, the property
ds.delayed.keepInstances=true

is set automatically by org.eclipse.equinox.ds.

Considering these changes it is also possible to remove org.eclipse.equinox.ds from an Eclipse Product Configuration and solely rely on org.apache.felix.scr. You just need to ensure org.apache.felix.scr is automatically started and ds.delayed.keepInstances is set to true (e.g. required when using p2 as described in Bug 510673.

DS Console Commands

If you want to inspect services via console, you need to know the new commands, as the old commands are not available anymore:

Equinox DS Felix SCR Description
list/ls [bundle-id] scr:list [bundle-id] List all components.
component|comp <comp-id> scr:info <comp-id> Print all component information.
enable|en <comp-id> scr:enable <comp-name> Enable a component.
disable|dis <comp-id> scr:disable <comp-name> Disable a component.
enableAll|enAll [bundle-id] - Enable all components.
disableAll|disAll [bundle-id] - Disable all components.

Despite some different command names and the fact that the short versions are not supported, you should notice the following:

  • The scope (scr:) is probably not needed in Equinox because there are by default no multiple commands with the same name. So only the command names after the colon can be used.
  • There are no equivalent commands to enable or disable all components at once.
  • To enable or disable a component you need to specify the name of the component, not the id that is shown by calling list before.

DS 1.3 Annotations Support in PDE

With Eclipse Neon the DS Annotations Support was added to PDE. Now Peter Nehrer (Twitter: @pnehrer) has contributed the support for DS 1.3 annotations. In the Preferences you will notice that you can specify which DS specification version you want to use. By default it is set to 1.3. The main idea is that it is possible to configure that only DS 1.2 annotations should be used in case you still need to develop on that specification level (e.g. for applications that run on Eclipse Neon).

The Preferences page also has another new setting “Add DS Annotations to classpath”, which is enabled by default. That setting will automatically add the necessary library to the classpath. While this is nice if you only implement a plain OSGi application, this will cause issues in case of Eclipse RCP applications that are build using Tycho. The JAR that is added to the classpath is located in the IDE, so the headless Tycho build is not aware of it! For Eclipse RCP development I therefore suggest to disable that setting and add org.osgi.service.component.annotations as an optional dependency to the Import-Package header as described in my Getting Started tutorial. At least if the bundles should be build with Tycho.

As a quick overview, with DS 1.3 the following modifications to the annotations are available:

  • Life cycle methods accept Component Property Types as parameter
  • Introduction of the Field Strategy which means @Reference can be used for field injection
  • Event methods can get the ComponentServiceObjects parameter type for PROTOTYPE scoped references, and there are multiple parameter type options for these methods
  • @Component#configurationPid multiple configuration PID values can be set and the value “$” can be used as placeholder for the name of the component
  • @Component#servicefactory deprecated and replaced by scope
  • @Component#reference specify Lookup Strategy references
  • @Component#scope specify the service scope of the component
  • @Reference#bind specify the name of the bind event method of a reference
  • @Reference#field name of the field, typically not specified manually
  • @Reference#fieldOption specify how field values should be managed
  • @Reference#scope specify the reference scope

Note:
For further information have a look at my previous blog posts where I explained these options in comparison to DS 1.2.

Although already in a very good shape, the DS 1.3 annotations are not finished 100% as of now. I already uncovered the following missing pieces:

  • Missing Require-Capability header in MANIFEST.MF (Bug 513216)
  • Missing Provide-Capability header in MANIFEST.MF (Bug 490063)
  • False error when using bind/updated/unbind parameter on field references (Bug 513462)

IMHO it would be also nice if the necessary p2.inf files are automatically created/updated to support p2 Capability Advice configurations, which is necessary because p2 still does not support OSGi capabilities.

As stated at the beginning, you could help with the implementation by testing and giving feedback on this implementation. It would be very helpful to have more people testing this, to have a stable implementation in the Oxygen release.

Thanks to Peter for adding that long waiting feature to PDE!

@Service Annotation for Eclipse RCP

Also for RCP development there are some news with regards to OSGi services. The @Service annotation, created by Tom Schindl for the e(fx)clipse project, has been ported to the Eclipse Platform (introduced here).

When using the default Eclipse 4 injection mechanisms, the injection of OSGi services is limited to a unary cardinality. Given an OSGi service of type StringInverter (see my previous tutorials) the injection can be done like this:

public class SamplePart {

	@Inject
	StringInverter inverter;

	@PostConstruct
	public void postConstruct(Composite parent) { ... } 
}
public class SamplePart {

	@Inject
	@Optional
	StringInverter inverter;

	@PostConstruct
	public void postConstruct(Composite parent) { ... } 
}

This means:

  • Only a single service instance can get injected.
  • If the cardinality is MANDATORY (no @Optional), a service instance needs to be available, otherwise the injection fails with an exception.
  • If the cardinality is OPTIONAL (@Inject AND @Optional) and no service is available at creation time, a new service will get injected when it becomes available.

This behavior is similar to the DYNAMIC GREEDY policy for OSGi DS service references. But the default injection mechanism for OSGi services has several issues that are reported in Bug 413287.

  • If a service is injected and a new service becomes available, the new service will be injected, regardless of his service ranking. So even if the new service has a lower ranking it will be injected. Compared with the OSGi service specification this is incorrect as the service with the highest ranking should be used, or, if the ranking is equal, the service that was registered first .
  • If a service is injected and it becomes unavailable, there is no injection of a service with a lower service ranking. Instead null will be injected, even if a valid service is still available.
  • If a service implements multiple service interfaces, only the first service key is reset.
  • If a service instance should be created per bundle or per requestor by using either a service factory or scope, there will be only one instance for every request, because the service is always requested via BundleContext of one of the platform bundles.

Note:
I was able to provide a fix for the first three points. The last issue in the list regarding scoped services can not be solved for the default injection mechanism.

The @Service annotation was introduced to solve all these issues and additionally support the multiple cardinality (only MULTIPLE, not AT_LEAST_ONE).

To use it simply add @Service additionally to @Inject:

public class SamplePart {

	@Inject
	@Service
	StringInverter inverter;

	@PostConstruct
	public void postConstruct(Composite parent) { ... } 
}

The above snippet is similar to the Field Strategy in OSGi DS. To get something similar to the Event Strategy you would use method injection like in the following snippet:

public class SamplePart {

	StringInverter inverter;

	@Inject
	public void setInverter(@Service StringInverter inverter) { 
		this.inverter = inverter; 
	} 

	@PostConstruct
	public void postConstruct(Composite parent) { ... } 
}

With using the @Service annotation on a unary reference, you get a behavior similar to the DYNAMIC GREEDY policy for OSGi DS service references, which is actually the same as with the default injection mechanism after my fix is applied. Additionally the usage of a service factory or scoped services is supported by using the @Service annotation, as the BundleContext of the requestor is used to retrieve the service.

Note: While writing this blog post there is an issue with the OPTIONAL cardinality in case no service is available at creation time. If a new service becomes available, it is not injected automatically. I created Bug 513563 for this and provided a fix for both, the Eclipse Platform and e(fx)clipse.

One interesting feature of the @Service annotation is the support of the MULTIPLE cardinality. This way it is possible to get all OSGi services of a specific type injected, in the same order as in the OSGi service registry. For this simply use the injection on a list of the desired service type.

public class SamplePart {

	@Inject
	@Service
	List<StringInverter> inverter;

	@PostConstruct
	public void postConstruct(Composite parent) { ... } 
}

Another nice feature (and also pretty new for e(fx)clipse) is the filter support. Tom introduced this here. e(fx)clipse supports static as well as dynamic filters that can change at runtime. Because of dependency issues only the support for static filters was ported to the Eclipse Platform. Via filterExpression type element it is possible to specify an LDAP filter to constrain the set of services that should be injected. This is similar to the target type element of OSGi DS service references.

public class SamplePart {

	// only get services injected that have specified the 
	// value "online" for the component property "connection" 

	@Inject
	@Service(filterExpression="(connection=online)")
	List<StringInverter> inverter;

	@PostConstruct
	public void postConstruct(Composite parent) { ... } 
}

With the @Service annotation the Eclipse injection for OSGi services aligns better with OSGi DS. And with the introduction of DS 1.3 to Equinox the usage of OSGi services for Eclipse RCP applications should become even more a common pattern than it was before with using the Equinox only Extension Points.

For me the news on OSGi DS in the Eclipse Platform are the most interesting ones in the Oxygen release. But of course not the only ones. So I encourage everyone to try out the newest Oxygen milestone releases to get the best out of it for everyone!

Updated: