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 (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>
As long as a Declarative Services provider is installed in the application and started, the service will be created on demand.
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.
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}
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>
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 needed1..1
: This service is mandatory with exactly one instance needed (default)0..n
: This service is optional and may have zero or more instances1..n
: This service is mandatory and may have one or more instancesThis 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.
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.
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.
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 { … }
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.)
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.
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.
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.
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.
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.
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/.
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.
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>
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>
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.