Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Tutorial: Building your first OSGi Remote Service"

(Service Host: Register the Service)
m (Added link to relate article regards the OSGi Remote µServices)
 
(54 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 +
<!-- This CSS wraps code blocks in pretty boxes -->
 +
<css>
 +
.mw-code {
 +
  background-color: #fafafa;
 +
  padding: 20px;
 +
  border-color: #ddd;
 +
  border-width: 3px;
 +
  border-style: solid;
 +
  border-radius: 5px;
 +
  margin-left: 10px;
 +
  margin-right: 10px;
 +
  overflow-x: auto;
 +
}
 +
 +
.mw-headline {
 +
  font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
 +
  color: rgb(51, 51, 51);
 +
}
 +
 +
p {
 +
  font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
 +
  text-align: justify;
 +
}
 +
 +
h1 {
 +
  border-bottom-color: rgb(238, 238, 238);
 +
  font-weight: bold;
 +
  padding-bottom: 17px;
 +
  font-size: 30px;
 +
  line-height: 36px;
 +
}
 +
 +
h2 {
 +
  border-bottom-color: rgb(238, 238, 238);
 +
  padding-bottom: 12px;
 +
  font-weight: bold;
 +
  font-size: 24px;
 +
  line-height: 36px;
 +
}
 +
 +
h3 {
 +
  border-bottom-width: 1px;
 +
  border-bottom-style: solid;
 +
  border-bottom-color: rgb(238, 238, 238);
 +
  padding-bottom: 8px;
 +
  font-size: 18px;
 +
  line-height: 27px;
 +
}
 +
 +
h4 {
 +
  font-size: 14px;
 +
}
 +
 +
h5 {
 +
  font-size: 12px;
 +
}
 +
 +
h6 {
 +
  font-size: 11px;
 +
  color: #EBEBEB;
 +
  text-transform: uppercase;
 +
}
 +
 +
a {
 +
  color: rgb(0, 105, 214);
 +
  font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
 +
}
 +
 +
a:visited {
 +
  color: rgb(0, 105, 214);
 +
}
 +
</css>
 +
 
==Introduction==
 
==Introduction==
  
The OSGi Framework provides a very simple model to expose services within a local runtime.  Also available, however, is a specification of Remote Services...services that are exported by a distribution provider to allow remote access.  The ECF project implements a specification-compliant distribution provider.
+
OSGi Services provides a simple standardized model to expose services within a single runtime.  There is now also a specification for OSGi Remote Services (Chapter 100 in the [http://www.osgi.org/Specifications/HomePage Companion (cmpn) and Enterprise specifications]).   OSGi Remote Services are exported to provide access to out-of-process clients.
  
This tutorial will show how to  
+
The OSGi Remote Services specification is unique in being completely transport and protocol independent.  This allows those defined, implemented, tested, and consumed without committing to a particular implementation mechanism or distribution protocol.
  
#define and implement a simple OSGi service
+
When discussing Remote Services, it's useful to distinguish between two collaborating roles:  the '''service host''' implements the Remote Service, and exports it for access and use by a remote '''service consumer'''.  The reason these are not referred to as '''server''' and '''client''' is because the Remote Services model is symmetric, allowing any framework to be either a host, consumer, or both.  Depending upon the transport, in practice the service host can/will often be a server, and the consumer a client, but the host and consumer roles are defined with respect to using the service rather than the transport.
#expose that service for remote access via ECF's implementation of the OSGi Remote Services standard
+
  
==Define the Service==
+
This tutorial will guide the reader through
  
The key to building a system with low coupling and high cohesion is to create clear and coherent boundaries between different parts of your system.  Central to this is defining simple, clear interfaces between subsystems...to allow to interact, but only in clearly defined ways. 
+
#Defining and implementing an OSGi remote service
 +
#Exposing that service for remote access via OSGi Remote Services standard, using the implementation provided by the Eclipse Communication Project (ECF)
  
For example, here's a simple time service:
+
==Common to Host and Consumer: Create the Service Interface==
  
 +
The key to building a system with low coupling and high cohesion is to create clear boundaries.  Central to this is defining simple, clear services (aka service interfaces)...to allow the subsystems to interact as needed, but only in clearly defined ways. 
 +
 +
For example, here's a simple time service:
  
 
<source lang="java">
 
<source lang="java">
Line 25: Line 101:
 
</source>
 
</source>
  
 +
The semantics of this service is simply to provide the current time...in the form of a long value specifying the number of milliseconds since 1970 returned from a call to '''getCurrentTime()'''.  Of course, declarations of much more complex services are possible, with multiple methods, multiple arguments for each method, and complex types as arguments and return values.  In short, any java interface can be used.  OSGi services also allows the use of multiple service interfaces.
  
The purpose of this service is to provide the current time...in the form of a long value specifying the number of milliseconds since 1970Of course, declarations of much more complex services (and remote services) are possible, with multiple methods, multiple arguments for each method, and complex types as arguments and return values.  In essence, any semantics that can be represented as a java interface can be used as the service interface.
+
With OSGi services it's a best practice to put the service interface (and any class referred to by the service interface) in a distinct bundleThis creates a clear, modular, framework-enforced separation between the service API, the service implementation, and any code that uses the service.  For Remote Services such separation and low-coupling is particularly important, since the host implementation is in a separate process from the service client/consumers...making it highly desirable to minimize the coupling and clear interface between distributed components.
  
With OSGi Services Interfaces (and Remote Services) it's a best practice to put the service interface (and any referred to by the service interface) in a distinct bundle.  This creates a clear, modular separation between the service interface API and both the service implementation and any code that uses the service.  For Remote Services this separation is particularly important, since the client/consumer of the remote service only references the service interface...the implementation doesn't even need to be present in the framework.
+
[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice Here is a complete source for the bundle with the ITimeService interface class]The package containing this class is exported via the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice/META-INF/MANIFEST.MF manifest] so that the Host and Consumer bundles (below) can refer to the service interface in their own code.
 
+
[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice Here] is a complete bundle with only the ITimeService class declaredNote that the package containing this class is exported via the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice/META-INF/MANIFEST.MF manifest]:
+
  
 
<source lang="java">
 
<source lang="java">
Line 36: Line 111:
 
</source>
 
</source>
  
Note also the version information associated with the package.
+
Note also the version information associated with the package...OSGi Remote Services supports versioning of service interfaces.  Being able to version service interfaces, and have the underlying framework automatically restrict the service binding is a critical feature for maintaining APIs.
  
 
==Service Host: Implement the Service==
 
==Service Host: Implement the Service==
  
Next, it's necessary to create a service 'host' implementing the ITimeService interface.   
+
Next, it's necessary to create a service 'host' implementing the '''ITimeService''' interface.   
  
 
<source lang="java">
 
<source lang="java">
Line 50: Line 125:
  
 
public Long getCurrentTime() {
 
public Long getCurrentTime() {
 +
System.out.println("TimeServiceImpl.  Received call to getCurrentTime()");
 
return new Long(System.currentTimeMillis());
 
return new Long(System.currentTimeMillis());
 
}
 
}
 
 
}
 
}
 
</source>
 
</source>
  
This implementation simply returns the local system's current time.
+
This implementation simply returns the local system's current time. To refer to the '''ITimeService''' service the host bundle manifest must import the '''com.mycorp.examples.timeservice''' package from the bundle defined above by adding a line like this to the host bundle manifest:
  
==Service Host: Register the Service==
+
<source lang="java">
 +
Import-Package: com.mycorp.examples.timeservice;version="1.0.0"
 +
</source>
  
In the OSGi Service Model it's necessary to '''register''' the service with the OSGi service registry.  There are multiple ways of doing this...via java code, or declaratively via Declarative Services (DS).  For details about various approaches to register an OSGi Service see [http://www.vogella.com/articles/OSGiServices/article.html Vogella's OSGi Services Tutorial].  For this tutorial we will register our TimeServiceImpl via java code:
+
==Service Host: Register and Export the Service==
 +
 
 +
In the OSGi Service model it's necessary to '''register''' the service with the OSGi service registry.  There are multiple ways of registering a service...programmatically java code, declaratively via Declarative Services (DS).  For introductions about various approaches to register an OSGi Service see [http://www.vogella.com/articles/OSGiServices/article.html Vogella's OSGi Services Tutorial].  For this tutorial we will register our TimeServiceImpl via a single line of java code:
  
 
<source lang="java">
 
<source lang="java">
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(),null);
+
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(),props);
 
</source>
 
</source>
  
With a normal/local OSGi Service this is all that's necessary to register the service.  For a Remote Service, a little more is required to trigger the remote services provider:
+
With a local OSGi Service the above line of java is all that's necessary to register the service.  For a Remote Service, only a little more is required to trigger the distribution provider to export the service:
  
 
<source lang="java">
 
<source lang="java">
 
Dictionary<String, String> props = new Hashtable<String, String>();
 
Dictionary<String, String> props = new Hashtable<String, String>();
 +
// OSGi Standard Property - indicates which of the interfaces of the service will be exported.  '*' means 'all'.
 
props.put("service.exported.interfaces", "*");
 
props.put("service.exported.interfaces", "*");
 +
// OSGi Standard Property (optional) - indicates which provider config(s) will be used to export the service
 +
// (If not explicitly given here, the provider is free to choose a default configuration for the service)
 
props.put("service.exported.configs","ecf.generic.server");
 
props.put("service.exported.configs","ecf.generic.server");
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props); // NOTE use of props rather than null
+
// Register a new TimeServiceImpl with the above props
 +
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props);  
 
</source>
 
</source>
  
With an OSGi RS distribution provider present, adding these service properties will trigger the export of the service.  Here's an explanation of each of the service props:
+
With the ECF RS implementation present in the host runtime, adding these service properties will result in the following occurring before the registerService call returns:
  
<source lang="java">
+
#The TimeServiceImpl will be exported via the distribution provider identified via config..i.e. "ecf.generic.server"
props.put("service.exported.interfaces","*");
+
#The exported service will be published for discovery, using any installed and available network discovery providers
</source> 
+
// OSGi Standard Property - indicates which of the interfaces of the service will be exported.  '*' means 'all'.
+
  
<source lang="java">
+
The full source for the host bundle, with the host implementation and registration code can be found in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.host com.mycorp.examples.timeservice.host project].
props.put("service.exported.configs","ecf.generic.server");
+
</source> 
+
// OSGi Standard Property - indicates which provider config(s) will be used to export the service
+
 
+
Optionally, the following property can be given to specify the hostname, port, or path for the "ecf.generic.server":
+
 
+
<source lang="java">
+
props.put("ecf.generic.server.id","ecftcp://localhost:3288/server");
+
</source> 
+
  
With the ECF RS implementation present in this runtime, adding these service properties will result in the following occurring before the registerService call returns:
+
Note that there are no references to additional OSGi classes...or ECF classes...but rather only classes exposed by the service interface (in this case '''ITimeService''').  For all of the remoting information, standardized service properties are used to qualify the '''ITimeService''' API as a remote service, and to select and configure a remote services provider.  This provides a great deal of flexibility to the '''ITimeService''' implementer, and allows the dynamic selection and use of a variety of distribution providers...from ECF or not...and multiple configurations.
  
#The TimeServiceImpl will be exported via the distribution provider identified via config "ecf.generic.server"
+
==Service Consumer:  Discover and Use the Service==
#The exported service will be published for discovery, using any installed and available discovery providers
+
 
+
The full source for the host bundle, with the implementation and registration code can be found [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.host here].
+
 
+
Note that in this code there are no references to additional OSGi classes...or ECF classes...but rather only standardized service properties are used to qualify the ITimeService implementation as a remote service, and to select and configure a remote services provider.  This provides a great deal of flexibility to the ITimeService implementer, and allows the dynamic selection and use of a variety of distribution providers (ECF provided or not), and/or multiple configurations.
+
 
+
==Consumer:  Discover and Use the Service==
+
  
 
For consumers to use a remote service they must first discover the service.  With OSGi Remote Services this discovery can be accomplished several ways.  The easiest and most automatic is to use a network discovery protocol, such that when the remote service is exported by a host and published via a network discovery protocol (as described above), a consumer can then automatically discover the remote service over the network.
 
For consumers to use a remote service they must first discover the service.  With OSGi Remote Services this discovery can be accomplished several ways.  The easiest and most automatic is to use a network discovery protocol, such that when the remote service is exported by a host and published via a network discovery protocol (as described above), a consumer can then automatically discover the remote service over the network.
  
For this tutorial, we'll assume that ECF's Zeroconf implementation is present, meaning that if the remote service host and consumer are on the same LAN, the consumer will automatically discover the remote service via the Zeroconf network discovery protocol. Using Zeroconf for network discovery, one can simply use OSGi Declarative Services to inject the service into client code and then call the timeService.getCurrentTime() method:
+
For this tutorial, we'll assume that one ECF's supported LAN-based network discovery providers is present, meaning that if the remote service host and consumer are on the same LAN, the consumer will automatically discover the remote service via the network discovery protocol (e.g. Zeroconf).  With network discovery, the consumer can use OSGi Declarative Services to automatically bind/inject the service into client code:
  
 
<source lang="java">
 
<source lang="java">
Line 123: Line 188:
 
</source>
 
</source>
  
OSGi Declarative Services will automatically inject the ITimeService proxy by calling the bindTimeService method when the remote service is discovered.  The only other thing to create is the markup necessary for declarative services to call bindTimeService when the ITimeService proxy is discovered.
+
OSGi Declarative Services will automatically inject the '''ITimeService''' proxy by calling the bindTimeService method when the remote service is discovered.  With the code above, the '''ITimeService.getCurrentTime()''' method will be called only when the binding occurs.
  
<source lang="xml">
+
As with the host, to refer to the '''ITimeService''' the consumer must also import the '''com.mycorp.examples.timeservice''' package by importing the package via it's manifest:
<?xml version="1.0" encoding="UTF-8"?>
+
 
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" enabled="true" name="com.mycorp.examples.timeservice.consumer.ds">
+
<source lang="java">
  <implementation class="com.mycorp.examples.timeservice.consumer.ds.TimeServiceComponent"/>
+
Import-Package: com.mycorp.examples.timeservice;version="1.0.0"
  <reference bind="bindTimeService" cardinality="1..1" interface="com.mycorp.examples.timeservice.ITimeService" name="ITimeService" policy="dynamic"/>
+
</scr:component>
+
 
</source>
 
</source>
  
The complete project with this consumer (TimeServiceComponent class, DS markup, and MANIFEST) is available [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds  here].
+
The only other requirement for the consumer is to have standard DS markup so that DS can know to call '''bindTimeService''' when the '''ITimeService''' proxy is registered by the consumer's distribution provider.  The required DS markup is available in [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds/OSGI-INF/timeservicecomponent.xml this xml file].
 +
 
 +
The complete project for this consumer (TimeServiceComponent class, DS markup, and manifest.mf) [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds is available here]. 
 +
 
 +
With the three bundles defined...i.e.
 +
 
 +
#[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.async the service interface bundle]
 +
#[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.host the host bundle]
 +
#[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds.async the consumer bundle]
 +
 
 +
along with the ECF remote service implementation, we may now run the '''ITimeService''' host implementation. The consumer can then be run...to discovery, inject, and to use the '''ITimeService'''.
 +
 
 +
==Starting the Service Host==
 +
 
 +
The TimeService Host may now be started in either Apache Karaf or within Eclipse.
 +
 
 +
===Starting the Service Host in Apache Karaf===
 +
 
 +
To run the TimeService host in Apache Karaf you must first [[EIG:Install_TimeService_Tutorial_into_Apache_Karaf | install the TimeService Tutorial bundles]].  
 +
 
 +
'''ECF RS Example Timeservice Host''' (com.mycorp.examples.timerservice.host)
 +
 
 +
This will produce output in the command shell (System.out) similar to the following, indicating that the service has been exported by RemoteServiceAdmin and the MyTimeService registered.
 +
 
 +
<pre>
 +
karaf@root()> Service Exported by RemoteServiceAdmin.  EndpointDescription Properties=...
 +
...debug output from verboseRemoteServiceAdmin...
 +
MyTimeService host registered with registration=org.apache.felix.framework.ServiceRegistrationImpl@7a627ff2
 +
</pre>
 +
 
 +
For reference, this output is produced by the TimeService Examples Host activator class  '''com.mycorp.examples.timeservice.host.Activator'''
 +
 
 +
The output above indicates that the TimeService has been exported and is ready for remote consumer access.
 +
 
 +
===Starting the Service Host in Eclipse===
 +
 
 +
In the com.mycorp.examples.timeservice.host project, in the launch directory is a launch config named TimeServiceHost.launch.  In Eclipse, you can launch the host by right-clicking on this launch config and choosing '''Run As -> TimeServiceHost'''.  In the console output, you should see something like this
 +
 
 +
<pre>
 +
osgi> MyTimeService host registered with registration={com.mycorp.examples.timeservice.ITimeService}=...
 +
Service Exported by RemoteServiceAdmin.  EndpointDescription Properties=...
 +
</pre>
 +
 
 +
This indicates that the service was registered and exported by ECF's remote service implementation.
 +
 
 +
==Starting the Service Consumer==
 +
 
 +
In the com.mycorp.examples.timeservice.consumer.ds project, in the launch directory is a launch config named TimeServiceConsumer DS.launch.  In Eclipse, you may launch the consumer by right-clicking on this launch config and choosing '''Run As -> TimeServiceConsumer DS'''.  In the console output, you should immediately see:
 +
 
 +
<pre>
 +
osgi>
 +
</pre>
 +
 
 +
But after a few seconds (for the network discovery protocol to operate) the service will be discovered, the proxy created via the ECF distribution provider, and the proxy injected (via DS) into the TimeServiceComponent via the call to '''bindTimeService''', resulting in the following console output:
 +
 
 +
<pre>
 +
osgi> Discovered ITimeService via DS
 +
Current time is: 1386354647672
 +
</pre>
 +
 
 +
As well as host console output resulting from the remote call to '''TimeServiceImpl.getCurrentTime()''':
 +
 
 +
<pre>
 +
TimeServiceImpl.  Received call to getCurrentTime()
 +
</pre>
 +
 
 +
This constitutes a successful test of the remote time service.
 +
 
 +
==Background and Related Articles==
 +
 
 +
[[Getting Started with ECF's OSGi Remote Services Implementation]]
 +
 
 +
[[OSGi Remote Services and ECF]]
 +
 
 +
[[Asynchronous Proxies for Remote Services]]
 +
 
 +
[[File-based Discovery | Static File-based Discovery of Remote Service Endpoints]]
 +
 
 +
[[EIG:Download|Download ECF Remote Services/RSA Implementation]]
 +
 
 +
[[EIG:Add to Target Platform|How to Add Remote Services/RSA to Your Target Platform]]
 +
 
 +
[https://techblog.smc.it/en/2020-07-31/cosa-sono-osgi-remote-services What are OSGi Remote µServices - OSGi Framework as an alternative to Microservices]

Latest revision as of 13:57, 14 August 2020


Introduction

OSGi Services provides a simple standardized model to expose services within a single runtime. There is now also a specification for OSGi Remote Services (Chapter 100 in the Companion (cmpn) and Enterprise specifications). OSGi Remote Services are exported to provide access to out-of-process clients.

The OSGi Remote Services specification is unique in being completely transport and protocol independent. This allows those defined, implemented, tested, and consumed without committing to a particular implementation mechanism or distribution protocol.

When discussing Remote Services, it's useful to distinguish between two collaborating roles: the service host implements the Remote Service, and exports it for access and use by a remote service consumer. The reason these are not referred to as server and client is because the Remote Services model is symmetric, allowing any framework to be either a host, consumer, or both. Depending upon the transport, in practice the service host can/will often be a server, and the consumer a client, but the host and consumer roles are defined with respect to using the service rather than the transport.

This tutorial will guide the reader through

  1. Defining and implementing an OSGi remote service
  2. Exposing that service for remote access via OSGi Remote Services standard, using the implementation provided by the Eclipse Communication Project (ECF)

Common to Host and Consumer: Create the Service Interface

The key to building a system with low coupling and high cohesion is to create clear boundaries. Central to this is defining simple, clear services (aka service interfaces)...to allow the subsystems to interact as needed, but only in clearly defined ways.

For example, here's a simple time service:

package com.mycorp.examples.timeservice;
 
public interface ITimeService {
 
	public Long getCurrentTime();
 
}

The semantics of this service is simply to provide the current time...in the form of a long value specifying the number of milliseconds since 1970 returned from a call to getCurrentTime(). Of course, declarations of much more complex services are possible, with multiple methods, multiple arguments for each method, and complex types as arguments and return values. In short, any java interface can be used. OSGi services also allows the use of multiple service interfaces.

With OSGi services it's a best practice to put the service interface (and any class referred to by the service interface) in a distinct bundle. This creates a clear, modular, framework-enforced separation between the service API, the service implementation, and any code that uses the service. For Remote Services such separation and low-coupling is particularly important, since the host implementation is in a separate process from the service client/consumers...making it highly desirable to minimize the coupling and clear interface between distributed components.

Here is a complete source for the bundle with the ITimeService interface class. The package containing this class is exported via the manifest so that the Host and Consumer bundles (below) can refer to the service interface in their own code.

Export-Package: com.mycorp.examples.timeservice;version="1.0.0"

Note also the version information associated with the package...OSGi Remote Services supports versioning of service interfaces. Being able to version service interfaces, and have the underlying framework automatically restrict the service binding is a critical feature for maintaining APIs.

Service Host: Implement the Service

Next, it's necessary to create a service 'host' implementing the ITimeService interface.

package com.mycorp.examples.timeservice.host;
 
import com.mycorp.examples.timeservice.ITimeService;
 
public class TimeServiceImpl implements ITimeService {
 
	public Long getCurrentTime() {
		System.out.println("TimeServiceImpl.  Received call to getCurrentTime()");
		return new Long(System.currentTimeMillis());
	}
}

This implementation simply returns the local system's current time. To refer to the ITimeService service the host bundle manifest must import the com.mycorp.examples.timeservice package from the bundle defined above by adding a line like this to the host bundle manifest:

Import-Package: com.mycorp.examples.timeservice;version="1.0.0"

Service Host: Register and Export the Service

In the OSGi Service model it's necessary to register the service with the OSGi service registry. There are multiple ways of registering a service...programmatically java code, declaratively via Declarative Services (DS). For introductions about various approaches to register an OSGi Service see Vogella's OSGi Services Tutorial. For this tutorial we will register our TimeServiceImpl via a single line of java code:

bundleContext.registerService(ITimeService.class, new TimeServiceImpl(),props);

With a local OSGi Service the above line of java is all that's necessary to register the service. For a Remote Service, only a little more is required to trigger the distribution provider to export the service:

Dictionary<String, String> props = new Hashtable<String, String>();
// OSGi Standard Property - indicates which of the interfaces of the service will be exported.  '*' means 'all'.
props.put("service.exported.interfaces", "*");
// OSGi Standard Property (optional) - indicates which provider config(s) will be used to export the service
// (If not explicitly given here, the provider is free to choose a default configuration for the service)
props.put("service.exported.configs","ecf.generic.server");
// Register a new TimeServiceImpl with the above props
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props);

With the ECF RS implementation present in the host runtime, adding these service properties will result in the following occurring before the registerService call returns:

  1. The TimeServiceImpl will be exported via the distribution provider identified via config..i.e. "ecf.generic.server"
  2. The exported service will be published for discovery, using any installed and available network discovery providers

The full source for the host bundle, with the host implementation and registration code can be found in the com.mycorp.examples.timeservice.host project.

Note that there are no references to additional OSGi classes...or ECF classes...but rather only classes exposed by the service interface (in this case ITimeService). For all of the remoting information, standardized service properties are used to qualify the ITimeService API as a remote service, and to select and configure a remote services provider. This provides a great deal of flexibility to the ITimeService implementer, and allows the dynamic selection and use of a variety of distribution providers...from ECF or not...and multiple configurations.

Service Consumer: Discover and Use the Service

For consumers to use a remote service they must first discover the service. With OSGi Remote Services this discovery can be accomplished several ways. The easiest and most automatic is to use a network discovery protocol, such that when the remote service is exported by a host and published via a network discovery protocol (as described above), a consumer can then automatically discover the remote service over the network.

For this tutorial, we'll assume that one ECF's supported LAN-based network discovery providers is present, meaning that if the remote service host and consumer are on the same LAN, the consumer will automatically discover the remote service via the network discovery protocol (e.g. Zeroconf). With network discovery, the consumer can use OSGi Declarative Services to automatically bind/inject the service into client code:

package com.mycorp.examples.timeservice.consumer.ds;
 
import com.mycorp.examples.timeservice.ITimeService;
 
public class TimeServiceComponent {
 
	void bindTimeService(ITimeService timeService) {
		System.out.println("Discovered ITimeService via DS");
		// Call the service and print out result!
		System.out.println("Current time is: " + timeService.getCurrentTime());  // Call the ITimeService remote service
	}
}

OSGi Declarative Services will automatically inject the ITimeService proxy by calling the bindTimeService method when the remote service is discovered. With the code above, the ITimeService.getCurrentTime() method will be called only when the binding occurs.

As with the host, to refer to the ITimeService the consumer must also import the com.mycorp.examples.timeservice package by importing the package via it's manifest:

Import-Package: com.mycorp.examples.timeservice;version="1.0.0"

The only other requirement for the consumer is to have standard DS markup so that DS can know to call bindTimeService when the ITimeService proxy is registered by the consumer's distribution provider. The required DS markup is available in this xml file.

The complete project for this consumer (TimeServiceComponent class, DS markup, and manifest.mf) is available here.

With the three bundles defined...i.e.

  1. the service interface bundle
  2. the host bundle
  3. the consumer bundle

along with the ECF remote service implementation, we may now run the ITimeService host implementation. The consumer can then be run...to discovery, inject, and to use the ITimeService.

Starting the Service Host

The TimeService Host may now be started in either Apache Karaf or within Eclipse.

Starting the Service Host in Apache Karaf

To run the TimeService host in Apache Karaf you must first install the TimeService Tutorial bundles.

ECF RS Example Timeservice Host (com.mycorp.examples.timerservice.host)

This will produce output in the command shell (System.out) similar to the following, indicating that the service has been exported by RemoteServiceAdmin and the MyTimeService registered.

karaf@root()> Service Exported by RemoteServiceAdmin.  EndpointDescription Properties=...
...debug output from verboseRemoteServiceAdmin...
MyTimeService host registered with registration=org.apache.felix.framework.ServiceRegistrationImpl@7a627ff2

For reference, this output is produced by the TimeService Examples Host activator class com.mycorp.examples.timeservice.host.Activator

The output above indicates that the TimeService has been exported and is ready for remote consumer access.

Starting the Service Host in Eclipse

In the com.mycorp.examples.timeservice.host project, in the launch directory is a launch config named TimeServiceHost.launch. In Eclipse, you can launch the host by right-clicking on this launch config and choosing Run As -> TimeServiceHost. In the console output, you should see something like this

osgi> MyTimeService host registered with registration={com.mycorp.examples.timeservice.ITimeService}=...
Service Exported by RemoteServiceAdmin.  EndpointDescription Properties=...

This indicates that the service was registered and exported by ECF's remote service implementation.

Starting the Service Consumer

In the com.mycorp.examples.timeservice.consumer.ds project, in the launch directory is a launch config named TimeServiceConsumer DS.launch. In Eclipse, you may launch the consumer by right-clicking on this launch config and choosing Run As -> TimeServiceConsumer DS. In the console output, you should immediately see:

osgi>

But after a few seconds (for the network discovery protocol to operate) the service will be discovered, the proxy created via the ECF distribution provider, and the proxy injected (via DS) into the TimeServiceComponent via the call to bindTimeService, resulting in the following console output:

osgi> Discovered ITimeService via DS
Current time is: 1386354647672

As well as host console output resulting from the remote call to TimeServiceImpl.getCurrentTime():

TimeServiceImpl.  Received call to getCurrentTime()

This constitutes a successful test of the remote time service.

Background and Related Articles

Getting Started with ECF's OSGi Remote Services Implementation

OSGi Remote Services and ECF

Asynchronous Proxies for Remote Services

Static File-based Discovery of Remote Service Endpoints

Download ECF Remote Services/RSA Implementation

How to Add Remote Services/RSA to Your Target Platform

What are OSGi Remote µServices - OSGi Framework as an alternative to Microservices

Back to the top