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"

(Consumer: Discover and Use the Service)
(Starting the Consumer)
Line 148: Line 148:
 
This indicates that the service was registered and exported by ECF's remote service implementation.
 
This indicates that the service was registered and exported by ECF's remote service implementation.
  
==Starting the Consumer==
+
==Starting the Consumer to Execute the Remote Service==
  
 
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:
 
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:

Revision as of 14:38, 6 December 2013

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.

This tutorial will show how to

  1. define and implement a simple OSGi service
  2. expose that service for remote access via ECF's implementation of the OSGi Remote Services standard

Define the Service

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.

For example, here's a simple time service:


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


The purpose of this service is to provide the current time...in the form of a long value specifying the number of milliseconds since 1970. Of 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 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.

Here is a complete bundle with only the ITimeService class declared. Note that the package containing this class is exported via the manifest:

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

Note also the version information associated with the package.

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() {
		return new Long(System.currentTimeMillis());
	}
 
}

This implementation simply returns the local system's current time.

Service Host: Register the Service

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 Vogella's OSGi Services Tutorial. For this tutorial we will register our TimeServiceImpl via java code:

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

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:

Dictionary<String, String> props = new Hashtable<String, String>();
props.put("service.exported.interfaces", "*");
props.put("service.exported.configs","ecf.generic.server");
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props);  // NOTE use of props rather than null

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:

props.put("service.exported.interfaces","*");

// OSGi Standard Property - indicates which of the interfaces of the service will be exported. '*' means 'all'.

props.put("service.exported.configs","ecf.generic.server");

// 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":

props.put("ecf.generic.server.id","ecftcp://localhost:3288/server");

With the ECF RS implementation present in this 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 "ecf.generic.server"
  2. 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 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 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:

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. The only other thing to create is the markup necessary for declarative services to call bindTimeService when the ITimeService proxy is discovered.

<?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">
   <implementation class="com.mycorp.examples.timeservice.consumer.ds.TimeServiceComponent"/>
   <reference bind="bindTimeService" cardinality="1..1" interface="com.mycorp.examples.timeservice.ITimeService" name="ITimeService" policy="dynamic"/>
</scr:component>

The complete project with this consumer (TimeServiceComponent class, DS markup, and MANIFEST) is available here. Note that the Eclipse service component editor will construct much of the DS service component xml that is given above.

With the three bundles defined...i.e. the service interface bundle (com.mycorp.examples.timeservice), the host bundle (com.mycorp.examples.timeservice.host), and the consumer bundle (com.mycorp.examples.timeservice.consumer.ds), along with the ECF RS implementation, we can run this example.

Starting the Host

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 Consumer to Execute the Remote Service

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 then after a few seconds (for the Zeroconf discovery protocol to operate) the service will be discovered, proxy created and injected into the TimeServiceComponent, resulting in console output:

osgi> Discovered ITimeService via DS
Current time is: 1386354647672

As well as host console output:

TimeServiceImpl.  Received call to getCurrentTime()

This constitutes a successful test of the time service.

What Happened

Back to the top