Registering a service declaratively

Registering services imperatively in the start method of an Activator class is one way of installing services in an OSGi framework. However, it requires that the bundle be started, which in turn requires that either the bundle is started automatically or has classes (such as API classes) accessed by default. Both approaches will mean that additional code will have to be run to bring the system into the desired state.

An alternative is to use one of the declarative service approaches, which represents the service definition in an external file. These are processed using an extender pattern, which looks out for bundles with a given file or files and then instantiates the service from this definition. It combines the declarative nature of the extension registry with the flexibility of OSGi services.

There are two providers of declarative service support. Both achieve a similar result but use slightly different configuration files and approaches. They are Declarative Services and Blueprint.

Declarative Services

Declarative Services (DS) was the original declarative implementation to instantiate services in a declarative fashion during OSGi runtime. Both Equinox and Felix have DS modules, and it is a required part of the Eclipse 4 runtime, so it can be trivially expected to be present. In the OSGi specification, it is referred to as the Services Component Runtime (SCR), which is why the associated package names use org.osgi.service.component.

The DS bundle needs to be started before it can process bundles; as a result, it is typically started early on in the start-up process. It listens to bundles being installed and then looks for a specific header in the META-INF/MANIFEST.MF file:

Service-Component: OSGI-INF/*.xml

If the DS bundle finds this header, it looks for files contained in the bundle itself, matching the file pattern specified. This is a comma-separated list, and can use a single wildcard * character (which will match filenames but not directories).

The service document is then loaded and parsed, and used to instantiate and register services with the OSGi runtime environment. The XML document uses namespaces to represent the component, using http://www.osgi.org/xmlns/scr/v1.2.0. Different versions of SCR use different endings; v1.0.0 is defined as the first version, with v1.1.0 the second. The current version (as of the writing of this book) is v1.2.0, and the next version (which is in development at the time of writing this book) will use the suffix v1.3.0.

Each service document defines a single service, which has an implementation class as well as an identifier. The service can be registered under one or more interfaces as well as optional properties.

This can be used to replace the custom code in the FeedActivator class created previously:

public class FeedsActivator implements BundleActivator {
  public void start(BundleContext context) throws Exception {
    // context.registerService(IFeedParser.class,
    //  new RSSFeedParser(), priority(1));
    // context.registerService(IFeedParser.class,
    //  new MockFeedParser(), priority(-1));
    // context.registerService(IFeedParser.class,
    //  new AtomFeedParser(), priority(2));
    bundleContext = context;
  }
  … 
}

If the application is run now, the feeds won't be parsed. To register these as OSGi services, create a file called OSGI-INF/atomfeedparser.xml:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
 name="AtomFeedParser">
 <implementation
  class="com.packtpub.e4.advanced.feeds.internal.AtomFeedParser"/>
  <service>
   <provide
    interface="com.packtpub.e4.advanced.feeds.IFeedParser"/>
  </service>
  <property name="service.ranking" type="Integer" value="2"/>
</scr:component>

Tip

Don't forget to tell Eclipse to consider this part of the build by adding OSGI-INF/ to the build.properties file in the bin.includes property.

As long as a Declarative Services provider is installed in the application and started, the service will be created on demand.

Tip

In future (Eclipse Mars and above), client bundles will be able to express a dependency on a Declarative Services provider by adding:

Require-Capability:
 osgi.extender;osgi.extender="osgi.component"

This is being planned to be added to version 1.3.0 of the Declarative Services specification, which is scheduled to be released in March 2015; visit http://www.osgi.org/Specifications/ for more details.

Properties and Declarative Services

Declarative Services can also be used to register properties with a service when it is registered. These properties can be sourced either from the services XML file, or an external properties file.

To add the service.ranking property to the registered service, add the following code to the services document:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
 name="AtomFeedParser">
  … 
  <property name="service.ranking" type="Integer" value="2"/>
</scr:component>

Now, when the application is restarted, the services console command will show that the service.ranking property is associated with the feed service:

osgi> services | grep IFeed
{com.packtpub.e4.advanced.feeds.IFeedParser}=
 {service.ranking=2,
  component.name=AtomFeedParser,
  component.id=0,
  service.id=37}

Tip

If the property isn't listed, add a -clean argument to the Eclipse runtime console; sometimes the files are cached and Plug-in Development Environment (PDE) doesn't always notice when files are changed.

The property types can be one of the following:

  • String (default)
  • Long
  • Double
  • Float
  • Integer
  • Byte
  • Character
  • Boolean
  • Short

Additionally, arrays of elements can be specified by placing them in the body of the element instead of as an attribute:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
 name="AtomFeedParser">
  … 
  <property name="compass.point" type="String">
    NORTH
    EAST
    SOUTH
    WEST
  </property>
</scr:component>

Service references in Declarative Services

Along with hardcoded values, it is also possible to set up references to services in DS. A service has the bind and unbind methods, which are called when a service becomes available or becomes inactive.

These can be mandatory or optional; if the dependency is mandatory, then the service is not instantiated until its dependencies are available. If they are optional, the service can come up and be assigned later. They can also be single- or multi-valued. These are encoded in the relationship cardinality:

  • 0..1: This service is optional with either zero or one instance needed
  • 1..1: This service is mandatory with exactly one instance needed (default)
  • 0..n: This service is optional and may have zero or more instances
  • 1..n: This service is mandatory and may have one or more instances

This can be used to inject a LogService instance into the component. Modify the AtomFeedParser class to accept an instance of LogService by adding the setLog and unsetLog methods:

private LogService log;
public void setLog(LogService log) {
  this.log = log;
}
public void unsetLog(LogService log) {
  this.log = null;
}

The following code can be used to report on the success of feed parsing, or to log errors if they occur:

public List<FeedItem> parseFeed(Feed feed) {
  try {
    List<FeedItem> feedItems = new ArrayList<FeedItem>();
    // parse feed items
    if(log != null) {
      log.log(LogService.LOG_INFO, feedItems.size() +
       " atom feed items parsed from " + feed.getUrl());
    }
    return feedItems;
  } catch (Exception e) {
    if (log != null) {
      log.log(LogService.LOG_WARNING, "Problem parsing feed "+e);
    }
    return null;
  }
}

To configure DS to provide a log service, the following code must be added to the atomfeedparser.xml file:

<scr:component name="AtomFeedParser"
 xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
 … 
 <reference interface="org.osgi.service.log.LogService"
  cardinality="0..1" name="log" 
  bind="setLog" unbind="unsetLog"/>
</scr:component>

This tells DS that the log service is optional (so it will bring the feed parser service up before a LogService is available) and setLog(log) will be called when it is available. DS also provides an unbind method that can be used to remove the service if it becomes inactive. The instance is provided for both the setLog and unsetLog methods, which may look strange, but when setting multiple elements, the methods are typically called addXxxListener and removeXxxListener, where having a value is more appropriate.

Multiple components and debugging Declarative Services

Although the example so far has only contained a single component, it is possible to have multiple components defined in a single XML file. An XML parent can be defined with multiple scr namespaced children; in fact, all elements outside the scr namespace are ignored, so it is possible to embed an XHTML document with an scr namespaced element inside, and still have it picked up by Declarative Services:

<xhtml>
  <h1>Example HTML file with SCR elements</h1>
  <h2>Component One</h2>
  <scr:component name="One" xmlns:scr="http://...">
    …
  </scr:component>
  <h2>Component Two</h2>
  <scr:component name="Two" xmlns:scr="http://...">
    …
  </scr:component>
</xhtml>

Note that many developers will use a one-to-one mapping between service components and the corresponding XML files; it is rare to see a single XML file with multiple service components. It is recommended to only put one component per XML file for ease of maintenance.

Tip

When using DS inside Equinox, using -Dequinox.ds.print=true can give additional diagnostic information on the state of the Declarative Services, including highlighting which services that are waiting. For Felix, specifying -Dds.showtrace=true can increase logging, and so can -Dds.loglevel=4.

Dynamic service annotations

Although XML allows flexibility, it has fallen out of fashion in the Java community in favor of Java annotations. Version 1.2 of the OSGi DS specification provides annotations that can be used to mark the code such that a build time processor can create the service component XML files automatically.

Tip

Note that the standard OSGi annotations are not read at runtime by the service but only build-time tools such as maven-scr-plugin. As a result, they should be optionally imported, since they aren't needed at runtime or with the compile scope if using a Maven-based build.

To use the annotations, add the following as an Import-Package for the bundle in the MANIFEST.MF file:

Import-Package:
 org.osgi.service.component.annotations;
 version="1.2.0";
 resolution:=optional

The @Component annotation can now be added to the individual classes that should be represented as services. Add the following to RSSFeedParser:

@Component(name="RSSFeedParser",
 service={IFeedParser.class},
 property={"service.ranking:Integer=1"})
public class RSSFeedParser implements
 IFeedParser, IExecutableExtension {
  … 
}

Tip

There are also Felix annotations (org.apache.felix.scr.annotations) that predate the standard OSGi ones. Both the Felix annotations and the OSGi core annotations are available in Maven Central.

Processing annotations at Maven build time

If using Maven Tycho to build bundles, it is possible to add a Maven plug-in to generate service XML files from the components. (See the book's GitHub repository for an example if unfamiliar with Maven Tycho.)

Tip

Maven Tycho is covered in more detail in chapter 10 of Eclipse 4 Plug-in Development by Example Beginner's Guide, Packt Publishing, as well as on the Tycho home page at http://www.eclipse.org/tycho/.

To configure the maven-scr-plugin for a build, first add the following dependency to the pom.xml file:

<dependencies>
  <dependency>
    <groupId>org.apache.felix</groupId>
    <artifactId>org.apache.felix.scr.ds-annotations</artifactId>
    <version>1.2.0</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

This dependency provides both the org.osgi.service.component.annotations classes as well as the processing engine necessary to generate the components. Note that even if other dependencies are given (say, osgi.enterprise or equinox.ds), this isn't sufficient on its own to generate the service.xml files.

Next, the plug-in needs to be added to the pom.xml file:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.felix</groupId>
      <artifactId>maven-scr-plugin</artifactId>
      <version>1.15.0</version>
      <configuration>...</configuration>
      <executions>...</executions>
    </plugin>
  </plugins>
  <sourceDirectory>src</sourceDirectory>
</build>

The sourceDirectory needs to be specified to match the value of the source attribute of the build.properties file (which is used by eclipse-plugin instead of sourceDirectory); otherwise, the maven-scr-plugin cannot find the source files.

The plug-in needs to be configured specifically for eclipse-plugin projects. Firstly, the supported projects default to jar and bundle for maven-scr-plugin, so it needs to be given additional configuration to permit processing eclipse-plugin projects.

Secondly, the service files are written to target/scr-plugin-generated/ by default. Although this will work, it makes for more difficult debugging in Eclipse. Instead, maven-scr-plugin can be configured to write it to the project root, which will place the service files under OSGI-INF. This permits the code to be tested and exported in Eclipse using the standard build tools:

<configuration>
  <supportedProjectTypes>
    <supportedProjectType>eclipse-plugin</supportedProjectType>
  </supportedProjectTypes>
  <outputDirectory>${basedir}</outputDirectory>
</configuration>

Finally, to hook it in with the standard build process, add the following configuration to the build:

<executions>
  <execution>
    <id>generate-scr</id>
    <goals>
      <goal>scr</goal>
    </goals>
  </execution>
</executions>

When the package is built, the service descriptor XML file will be automatically regenerated based on the annotations. The filename is derived from the service name.

Blueprint

Although Declarative Services has been present in OSGi since the 4.0 release, a new specification called Blueprint was made available in the 4.2 release. This provides the same kind of capabilities as Declarative Services, in that dependencies between services can be defined externally to the source code. However, the format of the corresponding XML file is slightly different and there is some difference in behavior.

The Blueprint service can be installed through a couple of implementations: Gemini or Aries.

Installing Gemini Blueprint

The Blueprint service is provided through a number of bundles as follows:

  • gemini-blueprint-core-1.0.2.RELEASE.jar
  • gemini-blueprint-extender-1.0.2.RELEASE.jar
  • gemini-blueprint-io-1.0.2.RELEASE.jar

However, these bundles also need the following Spring dependencies. Since Spring doesn't contain OSGi metadata (since version 3), the bundles must be acquired through the now defunct SpringSource EBR at http://ebr.springsource.com:

  • com.springsource.org.aopalliance-1.0.0.jar
  • org.springframework.aop-3.2.5.RELEASE.jar
  • org.springframework.beans-3.2.5.RELEASE.jar
  • org.springframework.context-3.2.5.RELEASE.jar
  • org.springframework.core-3.2.5.RELEASE.jar
  • org.springframework.expression-3.2.5.RELEASE.jar

The Gemini Blueprint implementation suffers from being built on Spring thereby dragging in the Spring dependencies as well. This is because the Gemini implementation can handle both the native OSGi Blueprint service as well as Spring bundle contexts.

For existing Spring-based applications that have migrated to OSGi, Gemini Blueprint may provide an easy transition path. For greenfield or non-Spring applications, using Aries may allow a reduced set of dependencies.

Note

The gemini-blueprint-extender bundle must be started in order to automatically register Blueprint services.

Installing Aries Blueprint

The Aries Blueprint service only needs three bundles: the Blueprint bundle and the Aries proxy, which in turn needs the Aries util bundle:

  • org.apache.aries.blueprint-1.1.0.jar
  • org.apache.aries.proxy-1.0.1.jar
  • org.apache.aries.util-1.0.1.jar

In addition, the Aries code requires an implementation of SLF4J, which can be acquired from the following:

  • slf4j-api-1.7.5.jar
  • slf4j-simple-1.7.5.jar

Once installed, and the org.apache.aries.blueprint and org.apache.aries.proxy bundles are started, other bundles in the framework will have their Blueprint services automatically registered.

Using the Blueprint service

Blueprint files are stored under OSGI-INF/blueprint/ by default, and end in a .xml extension. These follow a Spring-inspired format to represent the services and beans.

To add a Blueprint file for the MockFeedParser, create a file mockfeedparser.xml in the OSGI-INF/blueprint/ directory. The contents should be as follows:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
  <service interface="com.packtpub.e4.advanced.feeds.IFeedParser"
   activation="eager">
    <service-properties>
      <entry key="other.property">
        <value type="java.lang.Integer">314</value>
      </entry>
    </service-properties>
    <bean
 class="com.packtpub.e4.advanced.feeds.internal.MockFeedParser"/>
  </service>
</blueprint>

Now create a run configuration for an Eclipse application or an OSGi framework, and ensure that either the Gemini bundles or Aries bundles are installed and started.

Tip

By default, Eclipse will set the DS bundle to start automatically when creating a new configuration, but it does not do this for Blueprint bundles. If the Blueprint bundle is not started, then Blueprint services will also not be started.

The GitHub project for this book contains a copy of the required libraries for the bundles, which can be seen at https://github.com/alblue/com.packtpub.e4.advanced/.

Passing properties in Blueprint

To pass properties to the service when it is registered, the Blueprint XML can have a service-properties element added. Like the DS specification, this allows entries to be added in key/value pairs.

Modify the mockfeedparser.xml file to add a service-properties element:

<service interface="com.packtpub.e4.advanced.feeds.IFeedParser"
 activation="eager">
  <service-properties>
    <entry key="other.property">
      <value type="java.lang.Integer">314</value>
    </entry>
  </service-properties>
  …
</service>

For single value types, the element can be specified as a single value. If an array of elements is required, it must be wrapped in an <array> element:

  <service-properties>
    <entry key="other.property">
      <array>
        <value type="java.lang.Integer">314</value>
        <value type="java.lang.Integer">271</value>
      </array>
    </entry>
  </service-properties>

It is possible to define non-standard objects as property types by specifying a different class name. At runtime, introspection is used to find an appropriate single argument constructor and the value in the XML file is passed in.

Tip

Note that service properties are generally meant to be portable between different runtimes, and in some cases, exported over a network. It is recommended that the values be serializable and, where possible, use a String representation instead of a parsed object representation for portability.

Although it might seem possible to use this to register service.ranking, the following does not work:

 <service-properties>
    <entry key="service.ranking">       <!-- does not work -->
      <value type="java.lang.Integer">-1</value>
    </entry>
  </service-properties>

This happens because the Blueprint specification defines an alternate key for the service ranking, which is used to override the value given. If Blueprint services need to specify a ranking, then the following code must be used instead:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
  <service interface="com.packtpub.e4.advanced.feeds.IFeedParser"
   activation="eager" ranking="-1">
    … 
  </service>
</blueprint>

Finally, although a single interface is the most common approach, it is possible to register multiple interfaces against a single service. The implementation class must implement these interfaces; otherwise, an error is logged by the Blueprint extender:

<service ranking="-1" activation="eager">
  <interfaces>
    <value>com.packtpub.e4.advanced.feeds.IFeedParser</value>
    <value>java.lang.Cloneable</value>
  </interfaces>
  … 
</service>

Bean references and properties

Blueprint provides an easy means to name the created beans and relate them to other beans. Each bean may have an ID associated with it, which can then be used by a ref in another bean or by name.

Properties can be set on a bean by using the <property> element inside the bean's constructor. These are used to invoke JavaBean style setters on the instantiated object itself. For example, if the MockFeedParser had a method setNumberOfItems(int) to configure the number of returned values, the value could be set in the Blueprint XML file as follows:

<bean
 class="com.packtpub.e4.advanced.feeds.internal.MockFeedParser">
  <property name="numberOfItems" value="5"/>
</bean>

The property syntax also supports dotted property names; so if the MockFeedParser had a config property, and that config object had a value property, then the name could be specified as config.value.

References to other properties can be specified in the beans. A reference can be defined either through an ID defined in the same bundle, or from an OSGi service reference elsewhere. To set a LogService on MockFeedParser, the following code can be used:

private LogService log;
public void setLog(LogService log) {
  this.log = log;
}

This can be used when setting the number of items in the previous feed:

public void setNumberOfItems(int numberOfItems) {
  this.numberOfItems = numberOfItems;
  if (log != null) {
    log.log(LogService.LOG_INFO, "Setting number of items to "
      + numberOfItems);
  }
}

Finally, to get Blueprint to provide LogService to the bean, it needs to be acquired as a reference, as shown:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
  <reference id="logService"
   interface="org.osgi.service.log.LogService" />
  <service ranking="-1" activation="eager">
  …
    <bean
  class="com.packtpub.e4.advanced.feeds.internal.MockFeedParser">
      <property name="log" ref="logService"/>
      <property name="numberOfItems" value="5"/>
    </bean>
  </service>
</blueprint>

Comparison of Blueprint and DS

Both Blueprint and Declarative Services allow one or more services to be registered declaratively and instantiated on demand when requested. They are advantageous because they can defer the creation of the service until it is first needed, which in turn means that the bundle does not have to start until as late as possible.

In runtimes with large numbers of bundles, this reduced start-up time can result in faster overall start-up of the application. This is used in many of the enterprise Java services to provide functionality that may not all be needed at first.

There are a couple of significant differences that are worth knowing when comparing the two services.

Firstly, DS will create services and unbind them dynamically, so throughout the lifetime of a bundle's use, the services may come and go (in other words, they may be null). Code must be defensive when consuming services in case they are no longer present.

Blueprint, on the other hand, creates proxy objects that remain the same for the lifetime of the object. If a LogService instance is requested, a dynamic LogService proxy class is created and injected into the class. This instance will stay with the class for its entire life, even if log services come and go over time.

For Java code that isn't adapted to using OSGi services, particularly their dynamism, the ability to have a non-null placeholder object that can be passed to other objects provides an easy way to migrate from a non-OSGi solution to a full OSGi-based system. On the other hand, the proxy object is set to block until a service becomes available, so clients attempting to log a message may inadvertently block until either a real LogService instance is found or a timeout error is thrown.

There are many more configuration properties available for Blueprint services, including complex expressions and the ability to wire and connect up objects through injection. This additional flexibility can introduce additional problems when developing and testing a Blueprint solution; since the XML file can be complex, it's possible for the file to not be valid. Unfortunately, if a bundle's XML file is invalid, then no services will be registered; however, proxy classes can still be registered in client bundles that will silently hang when called.

Finally, the Blueprint extender will scan every bundle installed, looking for files in OSGI-INF/blueprint/*.xml. As a result, frameworks that use Blueprint may notice a slightly delayed start-up time.

Tip

If migrating a Spring-based application to an OSGi runtime, then Blueprint may provide an easy way forward. If you are creating a dynamic OSGi application without prior Spring involvement, use Declarative Services instead.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset