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

Tutorial: Building your first Asynchronous OSGi Remote Service

Revision as of 16:41, 14 January 2014 by Slewis.composent.com (Talk | contribs) (Introduction)


Introduction

In a previous tutorial, we went through how to define, implement, and execute a simple OSGi Remote Service using ECF's Remote Service implementation. By definition, Remote Services require communication between processes/frameworks since local OSGi services are based upon synchronous invocation of a service method (i.e. the calling thread blocks while the service method is being invoked, and only continues after the service method returns). With Remote Services, this means that the calling thread can block as the remote method invocation is sent over a network, handled by the remote service, and then the response (method return value) is returned over the network. As well, the remote method invocation may fail outright...because of a network partition, or the remote service host failure. The handling of such failures may block the calling thread for a significant length of time.

To support asynchronous (non-blocking) remote service invocation, ECF's implementation of OSGi Remote Services ECF has created Asynchronous Remote Services. This provides developers of Remote Services the option to expose non-blocking/asynchronous remote method invocation to consumers of their remote service, without any additional programming. This tutorial will step through the process of defining and using an asynchronous remote service.

Define the Asynchronous Service

In the | previous tutorial a simple time service was defined:

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

As described above, however, when the consumer calls/invokes the getCurrentTime() method, it's possible...without implementation error...for this method to block for a relatively long time, and/or throw a runtime exception because the communication between the consumer and the host failed.


The purpose 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 as the service interface. OSGi also allows the use of multiple interfaces exposed by a single implementation.

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. This minimizes the coupling between subsystems. 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 clarify the interface between systems.

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 to compatible versions is a very useful feature for enhancing and maintaining APIs over time.

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 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(),null);

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.

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 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 network discovery protocol to operate) the service will be discovered, the proxy created via the ECF distribution provier, and the proxy injected into the TimeServiceComponent via the call to bindTimeService, resulting in console output:

osgi> Discovered ITimeService via DS
Current time is: 1386354647672

As well as host console output resulting from the 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

Back to the top