This website and its associated repositories, are deprecated and no longer supported by the OSGi Alliance. Please visit https://enroute.osgi.org for the latest supported version of OSGi enRoute.
This enRoute v2 archive site is kept for those who do not intend to use the latest version of OSGi enRoute. If you are new to OSGi enRoute, then please start with the latest OSGi enRoute.
In this section we create another project that provides an implementation for our
Eval API, a so called provider. In this case we call this the simple.provider
.
We will also export the api package from the
provider bundle and explain why this will be simplifying your life.
Make sure you are in the top directory:
$ cd ~/workspaces/osgi.enroute.examples.eval
A service is an object that is registered by a bundle. It is registered under an interface and a set of properties. Services are dynamic which means that we have to write code that uses services defensively. Since this a lot of work we use Declarative Services to simplify this considerably with annotations.
In the previous section we created an API for an expression evaluator. We now need a
so called provider for this service. A provider is responsible for the contract
defined in the API so that consumers can use the service. In the case of the
Eval service the consumer will call the eval(String)
method and the provider must
implement this method.
In the osgi.enroute.examples.eval
directory we create a directory called simple.provider
.
In this directory we create the POM.
osgi.enroute.examples.eval $ mkdir simple.provider
osgi.enroute.examples.eval $ cd simple.provider
simple.provider $
simple.provider $ vi pom.xml
// fill in from next section
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
The parent and packaging is the same as for the API project:
<parent>
<groupId>org.osgi</groupId>
<artifactId>osgi.enroute.examples.eval</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>
In the previous section we also learned that the last segment in the
project name defines its type; this is supported by OSGi enRoute templates. A provider
project must therefore have a name that ends in .provider
. We also like to start
the project name with the workspace, the service API name and an indication of what
kind of implementation this is. Well, this is going to be an awful simple implementation,
so the name should be: osgi.enroute.examples.eval.simple.provider
. So in the POM we should have:
<artifactId>osgi.enroute.examples.eval.simple.provider</artifactId>
<description>Eval Provider</description>
We inherit the OSGi enRoute base API (a collection of standardized and specialized service contracts to make it easy for web apps) but in this case we need the API project as dependency
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.enroute.examples.eval.api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
We now need to add an implementation class. In this case we use regular expressions as the parser to
implement a very simplistic evaluation parser in the file src/main/java/osgi/enroute/examples/eval/provider/EvalImpl.java
:
simple.provider $ mkdir -p src/main/java/osgi/enroute/examples/eval/provider
simple.provider $ vi src/main/java/osgi/enroute/examples/eval/provider/EvalImpl.java
package osgi.enroute.examples.eval.provider;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.log.LogService;
import osgi.enroute.examples.eval.api.Eval;
@Component(name = "osgi.enroute.examples.eval.provider")
public class EvalImpl implements Eval {
Pattern EXPR = Pattern.compile( "\\s*(?<left>\\d+)\\s*(?<op>\\+|-)\\s*(?<right>\\d+)\\s*");
@Reference
LogService log;
@Override
public double eval(String expression) throws Exception {
Matcher m = EXPR.matcher(expression);
if ( !m.matches()) {
log.log(LogService.LOG_WARNING, "Invalid expression " + expression);
throw new IllegalArgumentException("Invalid expression " + expression);
}
double left = Double.valueOf( m.group("left"));
double right = Double.valueOf( m.group("right"));
switch( m.group("op")) {
case "+": return left + right;
case "-": return left - right;
}
return Double.NaN;
}
}
Ok, ok, simple might still give it too much credit but we’re not here to learn parsing. At least it has (some) error handling! Notice that we can only handle trivial additions and subtractions of constants.
This class is setup as a Declarative Service (DS) µservice component because the @Component
annotation was added. If your component class implements one or more interfaces, then these
will be automatically registered as OSGi services. So in this case, we want to implement
an Eval
interface so that we’re registered as an Eval service.
We can use the Eval
interface because we added the org.osgi:osgi.enroute.examples.eval.api:1.0.0-SNAPSHOT
project
to our dependencies.
To make a proper bundle we need to have a bnd.bnd
file in the same directory as our POM. This file looks
like:
simple.provider $ vi bnd.bnd
// fill in content from next section
#
# OSGi enRoute Eval Example
#
Bundle-Description: \
Provides a simple implementation for an eval parser
Export-Package: osgi.enroute.examples.eval.api
Observant readers would have noticed that the the osgi.enroute.examples.eval.api
package is
exported but it is not part of this project. This is one of the magic, and extremely useful,
tricks of bnd. Any package listed in Export-Package or Private-Package headers will be copied
from the classpath even if not part of the project.
However, this raises the question why should the API be included in the bundle? Should the API not be a separate bundle?
The reason we export the API from the provider bundle is that it makes life a lot easier when we assemble the executable and it has no hidden cost. The primary reason it has no hidden cost is due to compatibility. A provider has virtually no backward compatibility with its API as was explained in the semantic versioning section. Any change in the API will require an update of the provider code to ensure the provider implements any additional obligations of the contract.
Therefore, separating the API from the provider has no use since a given provider will virtually always use the same API version. Carrying this version of the API causes fewer bundles and less error prone metadata.
There are OSGi experts who do not completely agree though.
In Maven-terms we’ve now defined a multi-module project, currently containing two modules : api and simple.provider.
The modules should be defined in an aggregator pom, and often this is done in the parent pom.
We should add a
simple.provider $ cd ..
osgi.enroute.examples.eval $ vi pom.xml
// fill in from next section, right before the <properties> section
<modules>
<module>api</module>
<module>simple.provider</module>
</modules>
We now have enough project information to build the bundle. As we’ve changed the parent pom, it’s best to do a clean build from there. And then we can take a look at how our module (bundle) really looks like. Make sure you’re in the top directory!
osgi.enroute.examples.eval $ mvn clean install
...
osgi.enroute.examples.eval $ cd simple.provider
simple.provider $ bnd print target/osgi.enroute.examples.eval.simple.provider-1.0.0-SNAPSHOT.jar
[MANIFEST osgi.enroute.examples.eval.provider-1.0.0-SNAPSHOT]
Bnd-LastModified 1474989660493
Build-Jdk 1.8.0_25
Built-By aqute
Bundle-Description Provides a simple implementation for an eval parser
Bundle-ManifestVersion 2
Bundle-Name osgi.enroute.examples.eval.simple.provider
Bundle-SymbolicName osgi.enroute.examples.eval.simple.provider
Bundle-Version 1.0.0.201609271521
Created-By 1.8.0_25 (Oracle Corporation)
Export-Package osgi.enroute.examples.eval.api;version="1.0.0"
Import-Package osgi.enroute.examples.eval.api;version="[1.0,1.1)"
Manifest-Version 1.0
Private-Package osgi.enroute.examples.eval.provider
Provide-Capability osgi.service;objectClass:List<String>="osgi.enroute.examples.eval.api.Eval"
Require-Capability osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Require-Capability osgi.extender;filter:="(&(osgi.extender=osgi.component)(version>=1.3.0)(!(version>=2.0.0)))",osgi.service;filter:="(objectClass=org.osgi.service.log.LogService)";effective:=active,osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Service-Component OSGI-INF/osgi.enroute.examples.eval.provider.xml
Tool Bnd-3.3.0.201609221906
[IMPEXP]
Import-Package
org.osgi.service.log {version=[1.3,2)}
osgi.enroute.examples.eval.api {version=[1.0,1.1)}
Export-Package
osgi.enroute.examples.eval.api {version=1.0.0, imported-as=[1.0,1.1)}
From this we can see the following layout of our provider bundle.
In a diagram:
The concepts of consumers and providers can be confusing, mostly because it is often confused with implementers of an interface and the clients of an interface. However, providers of a service API can both implement and/or be a client of interfaces in the service package. A provider is responsible for providing the value of the contract, and a consumer receives the value of the contract. The reason we need to distinguish between these two roles is that they have far ranging consequences for how you package and version bundles.
Lets say you buy a house from me. In this scenario you are consumer of the contract and I am the provider of the contract. These roles are, surprisingly, not symmetrical. For example, if the seller adds an extra room after the contract was signed then the buyer will not object (ok, in general, you get my point). However, if the seller removes a room the buyer is going to be upset. A consumer can expect backward compatibility but a provider is closely bound to the contract. Virtually any change in the service contract will require a provider to be updated to provide the new functions.
So a consumer is relatively distant from the contract and it often plays the role of a consumer in many different service contracts. A provider usually provides only a single service contact while being a consumer in other service contracts.
Therefore the best practice in OSGi is for a provider to include its service API codes and export it. Separating the API from the provider makes no sense since there is a 1:1 relation between provider and API, unlike the consumer that will get backward compatibility from the API. Having the API in the bundle just makes life easier. That said, do not make the mistake to place them in the same project since that would require compiling against a JAR that would also contain the implementation. Compilation should always be done against API only JARs to prevent accidentally becoming dependent on implementation code.
In this section we created a simplistic provider bundle for the Eval service API. We exported the Eval service API in this bundle but also import it.