What’s New in Declarative Services 1.3?


I’d like to tell you about two really cool new features in OSGi Declarative
Services
. They’re so cool that I feel kind of guilty for blogging about them,
because some of the kudos may accrue to me even though I have done absolutely
none of the work to either specify or implement them. So bear in mind that I’m
only your humble messenger.

Configuration Property Types

As a bit of background, in Declarative Services (DS) it has always been possible write configurable
components by passing a Map into the activate method:

@Component
public class ServerComponent {

  @Activate
  public void activate(Map<String, Object> configProps) {
    String bindHost = (String) configProps.get("host");
    int port = (Integer) configProps.get("port");
    
    ServerSocket sock = new ServerSocket();
    sock.bind(new InetSocketAddress(bindHost, port))
    // ...
  }
}

There are some great things about the way DS handles configuration. First, the
component itself has no idea about the origin of the config data… it could
come from a properties file, or an XML file, or a database record, or
transmitted over the air from a cellular provider, etc. Because the component
is not coupled to the physical mechanism, we can change that mechanism easily
without updating the code of all our components.

Second, the configuration is passed all-at-once and can be dynamically changed.
Some other DI frameworks inject configuration data one field at a time
(setHost, setPort…). Where they support dynamic reconfiguration at all, they
tend to need methods to let the component know that a series of config changes
is starting and ending, so that it knows when it’s safe to reconfigure. Otherwise
the component could end up using a mixture of old and new config.

There is a problem, however. Did you notice that my code sample above had no
error handling at all? What if the host property was missing from the map?
What if the port was given as a String rather than an integer? What if it
contained invalid numeric characters? Some fields may be optional, where do we
specify the defaults? It turns out that most components have to do quite a bit
of work to convert, parse and validate their configuration. There are libraries
that encapsulate this – such as the Configurable API from bnd – but
they add a runtime dependency, which is inconvenient (though manageable in OSGi
with static linking).

Declarative Services 1.3 – part of the OSGi Release 6 compendium spec, which
was released to the public last week – improves this enormously. It’s now
possible to define a custom type for the configuration, rather than a generic
Map. For example:

@interface ServerConfig {
  String host() default "0.0.0.0";
  int port() default 8080;
  boolean enableSSL() default false;
}

@Component
public class ServerComponent {

  @Activate
  public void activate(ServerConfig cfg) {
    ServerSocket sock = new ServerSocket();
    sock.bind(new InetSocketAddress(cfg.host(), cfg.port()));
    // ...
  }

}

Now the DS runtime is doing all the work of pulling out fields, converting them
to the correct type and so on. If there are any conversion errors, or missing
fields that don’t have defaults, then DS will refuse to load our component and
will write an appropriate message into the OSGi Log.

This feature is known as “Configuration Property Types”. You may wonder why
an annotation is used rather than an interface, given that we don’t actually
use it like an annotation. There are a few reasons: in
annotations it’s trivial to provide defaults; the methods cannot have
parameters; and the return types allowed in an annotation are restricted to
types that mesh well with configuration data.

Of course this version of DS interoperates with the OSGi Metatype specifcation,
so that the administrators of our application can know what fields and types are
actually expected. The metadata is generated at build time if we just add a couple
more annotations:

@ObjectClassDefinition(name = "Server Configuration")
@interface ServerConfig {
  String host() default "0.0.0.0";
  int port() default 8080;
  boolean enableSSL() default false;
}

@Component
@Designate(ocd = ServerConfig.class)
public class ServerComponent {
  // ...
}

… and, from this definition, tools like the Apache Felix Web Console can automatically
generate an admin GUI for our component:

Prototype Scope Services

The second really cool feature is support for prototype scope in services.
The prototype scope has been available in the OSGi Core R6 specification for
about a year now, but there was a significant gap between the release of the
Core and Compendium documents. So until now we had to drop down to the
low-level OSGi API in order to use prototype scope. In DS 1.3 we can use
annotations and a more convenient runtime API instead.

Prototype scope is where a service has the ability to create multiple instances
on demand. Traditionally, most OSGi services were conceptually singletons: each
registry entry corresponded to a single back-end service instance that was
shared by all consumers. (Aside: there were additionally bundle-scoped
services, where each consumer bundle got its own instance of the service
object, but if you used a service multiple times from within the same bundle
you would still share the same instance).

This model had to be enhanced, since sometimes each consumer really needs to
have its own instance of the service. Also some consumers need to have
programmatic control over the creation and destruction of instances, in order
to fit in with an external lifecycle. An example of this would be in web
requests – some web standards (for example JAX-RS) require services to be
instantiated and then destroyed in every request. Hence the prototype scope was
added. This is an opt-in feature: both the provider and the consumer of the
service need to be aware that it is being used.

In DS 1.3, a provider can opt in to the prototype scope very easily with an
attribute on the @Component annotation:

@Component(scope = ServiceScope.PROTOTYPE)
public class MyComponent {
  // ...
}

As a consumer there are a few more choices. When we consume a service with the
@Reference annotation, we can use an attribute to request a new instance per
component:

@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
public void setFoo(Foo foo) {
  // ...
}

Using this annotation, each component that uses the Foo service will get its
own instance. However within each component, the same Foo instance will be used
repeatedly. Alternatively, the consumer can create and destroy instances
programmatically whenever it wants – this requires the consumer to use a bit
of API from DS as follows:

private ComponentServiceObjects<Foo> fooFactory;

@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
public void setFooFactory(ComponentServiceObjects<Foo> fooFactory) {
  this.fooFactory = fooFactory;
}

public void doSomething() {
  Foo myFoo = fooFactory.getService();
  try {

    // do something interesting with myFoo ...

  } finally {
    fooFactory.ungetService(myFoo);
  }
}

The use of the ComponentServiceObjects interface means we are no longer
building strict POJOs, but still this interface is easily mockable for testing
purposes.

Getting It

The specification for DS 1.3 is available now from http://www.osgi.org/Download/Release6. Felix SCR 2.0 implements the specification and can be downloaded from http://felix.apache.org/downloads.cgi. For full metatype support you will need Felix Metatype 1.1 from the same page.

To build components using the annotations you will need a development build of bnd/Bndtools 3.0. To install this, go to http://bndtools.org/installation.html and follow the instructions for installing a developer snapshot build.

Share This:
Facebooktwitterredditpinterestlinkedinmail

Leave a comment

Your email address will not be published. Required fields are marked *