ECF commits to OSGi Remote Services

In previous releases, the ECF project has provided implementations of both OSGi Remote Services and OSGi Remote Services Admin. For ECF's Kepler release, the project committers have now fully tested both of these implementations against the OSGi Release 5 Test Compatibility Kit (TCK)...and we are happy to report that our Kepler release implementation is passing all tests.

Standards Compliance

TCK testing was done to assure the community that our implementation of RS/RSA is fully compatible with these two enterprise specifications (Chapters 100 and 122, repectively). Why is standards compliance helpful to our community? The primary reason is that compatibility allows SOA developers to develop and use high level remote services that are independent of the underlying network transport. Not only does this prevent commercial lock-in, but developers and commercial adopters using ECF's implementation of OSGi Remote Services can also be assured that they can:

  1. Develop/test their proprietary remote services on one implementation of RS/RSA, and if necessary...deploy/run on others
  2. If desired, use other compliant RS/RSA implementations alongside/with ECF's implementation
  3. Create remote services that are interoperable with existing/legacy services
  4. Extend or enhance ECF's open source implementation without restrictions

What are OSGi Remote Services?

OSGi Remote Services specifies a very simple idea: Start with a local OSGi service, and export it for access outside the hosting process. There are a number of excellent articles about defining and using local OSGi services...for example OSGi Services - Tutorial by Lars Vogel and OSGi Services Tutorial by Knopflerfish...so we will not describe OSGi services in detail here. However, for the remainder of this article, we will go through a hands-on example of how to create, publish, and use an example OSGi Remote Service. It should be said, however, that an important aspect of the OSGi Remote Services specification is it's tight integration with the OSGi service registry, meaning that any/all of the mechanisms described in the tutorial by Lars Vogel for example, Declarative Services...also work with Remote Services.

The Service Interface

OSGi Services are defined by 1 or more service interfaces. For example, here is a very simple interface to retrieve the current time

public interface ITimeService {
	/**
	 * Get current time.
	 * 
	 * @return Long current time in milliseconds since Jan 1, 1970. Will not
	 *         return null.
	 */
	public Long getCurrentTime();
 
}

For the consumer/user of the ITimeService, access to this interface class is all that is needed to use this remote service. For example, here is a declarative services (DS) component that uses the ITimeService to call getCurrentTime() and print out the result to stdout:

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());
	}
}

Note that

  1. The bindTimeService method is called automatically by DS
  2. This implementation simply calls getCurrentTime and shows the result

And for the ITimeService consumer...that's it for code. Everything about the remote service discovery, proxy creation, connection setup, parameter and result marshaling...anything having to do with the actual transport-level or distribution activities that make the remote call, are hidden from the consumer of this remote service. The ITimeService interface specifies the semantic contract between the ITimeService host and the consumer.

Service Implementation

Here is a trivial implementation of the ITimeService host (aka server)

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());
	}
}

As per the OSGi Remote Services specification, the way that this service implementation is exported/published is to register it as an OSGi service, and add the specification of standard service properties. For example, in the code example, here is how this remote service is exported

1: Dictionary props = new Hashtable();
2: props.put("service.exported.interfaces", "*");
3: props.put("service.exported.configs","ecf.generic.server");
4: props.put("ecf.generic.server.id","ecftcp://localhost:3288/server");
5: context.registerService(ITimeService.class, new TimeServiceImpl(), props);

What's being done here is that

  1. Three service properties are put into a new Hashtable
    1. service.imported.interfaces set to '*' (line 2). service.imported.interfaces is a standard service property that is required to be set for OSGi services that are to be exported by a remote services implementation.
    2. service.exported.configs set to ecf.generic.server (line 3). service.exported.configs is a standard optional service property that is used to specify the desired remote service distribution implementation. In the case of the example, ecf.generic.server identifies the ECF Generic Provider, which is one of several distribution providers that exist in ECF.
    3. ecf.generic.server.id set to ecftcp://localhost:3288/server (line 4). ecf.generic.server.id is a service property specific to the ecf.generic.server distribution provider...to specify the generic server's identity.
  2. A new TimeServiceImpl() instance is created and then registered and exported via the OSGi service registry (line 5). This is the call that actually results in the exporting of this as a remote service. If ECF remote services is available (installed and started), the synchronous registerService call will result in ECF's implementation publishing the remote service metadata necessary for consumers to discover and use this remote service. As per the specification, this is done during the call to registerService by ECF's implementation, so that when the registerService call returns, the ITimeService is accessible for remote access by consumers as described above. Note that service registration can also be done via declarative services, or by any other means that results in registration via the OSGi service registry. It's not required that BundleContext.registerService be called directly.

Running the Example in Eclipse

  1. Install ECF SDK into Eclipse 3.8 or newer
  2. Retrieve the source for examples by cloning the ECF Eclipse Foundation Git repo. The following example projects should be imported into the local workspace
    • com.mycorp.examples.timeservice
    • com.mycorp.examples.timeservice.consumer
    • com.mycorp.examples.timeservice.consumer.ds
    • com.mycorp.examples.timeservice.consumer.filediscovery
    • com.mycorp.examples.timeservice.host

Once these projects are imported into the workspace, you can examine the source code for the above. The ITimeService interface is declared in the com.mycorp.examples.timeservice bundle. The DS consumer ('TimeServiceComponent class) is in com.mycorp.examples.timeservice.consumer.ds, and the DS component metadata is in OSGI-INF/timeservicecomponent.xml. The TimeServiceImpl class and the remote service registration code are in the com.mycorp.examples.timeservice.host bundle. The com.mycorp.examples.timeservice.host.Activator.start method has the remote service host registration.

Launching the Time Service Host

Run/Debug the TimeServiceHost launch configuration to start the service host. In the Console window, you should see output similar to the following:

MyTimeService host registered 
with registration={com.mycorp.examples.timeservice.ITimeService}=
{ecf.generic.server.id=ecftcp://localhost:3288/server, 
service.exported.configs=ecf.generic.server, service.exported.interfaces=*, service.id=32}
osgi> Service Exported by RemoteServiceAdmin.  
EndpointDescription Properties={ecf.endpoint.id.ns=org.eclipse.ecf.core.identity.StringID, 
ecf.generic.server.id=ecftcp://localhost:3288/server, 
endpoint.framework.uuid=403cb17a-8bf1-0012-10cc-84fedd6ed32e, 
endpoint.id=ecftcp://localhost:3288/server, 
endpoint.package.version.com.mycorp.examples.timeservice=1.0.0, endpoint.service.id=1, 
objectClass=[Ljava.lang.String;@78d25faa, 
remote.configs.supported=[Ljava.lang.String;@57f221b6, remote.intents.supported=
[Ljava.lang.String;@598360d5, service.id=32, service.imported=true, 
service.imported.configs=[Ljava.lang.String;@57f221b6}

This means that the service host is running...with the id=ecftcp://localhost:3288/server...and that the ITimeService has been exported. It also reports the EndpointDescription...this is the OSGi Remote Services metadata that are published for discovery as specified by the OSGi Remote Service Admin specification.

Launching the TimeService Consumer

Run/Debug the TimeServiceConsumerDS launch configuration (in com.mmycorp.examples.timeservice.consumer.filediscovery/launch) to start the service consumer. The consumer should start, and the Console window should show the OSGi prompt only...i.e.

osgi>

At the OSGi prompt, to initiate the discovery of the remote service type

osgi> start com.mycorp.examples.timeservice.consumer.filediscovery

as a result, you should then see

osgi> start com.mycorp.examples.timeservice.consumer.filediscovery
osgi> Discovered ITimeService via DS
Current time is: 1374360266319

With the time value returned from the ITimeService.getCurrentTime() remote call. If you bring the host console back up, you should see output at the bottom like this

TimeServiceImpl.  Received call to getCurrentTime()

This indicates that the host did indeed get the consumer's remote call to getCurrentTime().

Implementation Details

A fair amount happened when the com.mycorp.examples.timeservice.consumer.filediscovery bundle was started. Briefly, what happened was this:

  1. The ECF Remote Services Admin implementation detected that the filediscovery bundle had an endpoint description file (EDEF file) and read that file (in the timeserviceendpointdescription.xml file)...as per the OSGi Remote Service Admin specification.
  2. The reading and parsing of the EDEF file resulted in the import of the remote service described by the EDEF meta-data, which also resulted in the creation of an ITimeService proxy, which was registered in the consumer's service registry.
  3. The registration of the ITimeService proxy resulted in the consumer's TimeServiceComponent.bindTimeService method to be called by DS, and the implementation of this method on the consumer made the call to timeService.getCurrentTime().
  4. The call to the timeService.getCurrentTime() went through the ECF Generic Provider to the TimeServiceImpl code on the service host, and when that method completed the return value was sent back to the consumer and printed out to the display.

Note to the consumer, the ITimeService instance bound to the TimeServiceComponent looks just like a local instance of an ITimeService, but it's actually a proxy constructed dynamically during import by the ECF implementation of Remote Service Admin.

Feel free to stop and restart the consumer and/or host...and if desired you may set breakpoints to debug into the example code at desired locations during this process. In the example source code there is also some tracing that's enabled that reports to stdout when the host exports the service, and when the client imports the service. The ability to listen to events created by the Remote Service Admin management agent is as specified by the Remote Service Admin chapter 122.

Note that ECF's implementation of OSGi Remote Services can/does run on multiple OSGi R5 Framework implementations...and has been tested explicitly on both Equinox and Felix.

ECF's Provider Architecture and Remote Services Customization

One of the unique aspects of ECF's implementation of RS/RSA is that it is based upon an open provider architecture. This allows adopters to customize, configure, extend, or even replace the major parts of this process. For example, with remote service discovery, it's often desirable to have dynamic, network-based discovery of remote services...so that consumers will simply be notified via the network when a remote service becomes available. ECF has multiple implementations of discovery providers, and any one of these can be used without modification of the remote service code. Here is a list of the discovery providers that ECF committers have created, and are already developed and available from ECF for use.

  1. EDEF xml-file-based discovery (used in example above)
  2. Apache Zookeeper. A popular server-based service discovery system that's part of the Apache Hadoop project.
  3. Zeroconf. A multicast-based protocol originally created by Apple for printer and other device discovery.
  4. Service-Location Protocol (SLP). An IETF service discovery standard.
  5. DNS-Service Discovery (DNS-SD). A discovery protocol based upon dynamic DNS.

As well, ECF has a full Discovery API...allowing anyone to implement a new provider for remote service discovery.

The other major subsystem used by ECF's OSGi Remote Service implementation is known as the distribution subsystem. The distribution subsystem is used to export the remote service, provide the protocol for
consumer<->host communication when making the remote call, marshal/unmarshall (serialize/deserialize) method parameters and return values on both host and consumer, and dynamically create proxies on the consumer when discovered. ECF has a Remote Services API that allows different providers to be used for the distribution mechanism for OSGi Remote Services. As with the Discovery API, the ECF Remote Service API is open and implementable by others, allowing new providers to be used that are based upon existing technologies (commercial and/or open source), or based upon new technologies. Here are the remote service providers that ECF currently distributes.

  1. ECF Generic
  2. r-OSGi
  3. Java Messaging Service (JMS)
  4. RESTlet
  5. Other REST-based Providers
  6. XMPP
  7. JavaGroups

And as with the Discovery API, other distribution providers may easily be created and used, simply by implementing the ECF Remote Service API.

Conclusion

ECF committers have created a flexible and extensible open implementation of the OSGi R5 Remote Services and Remote Service Admin specifications. With the ECF Kepler Release, we have now shown the implementation to be fully compatible with the R5 spec, through full testing with the OSGi RS/RSA Test Compatibility Kit.

About the Author
Scott Lewis

Scott Lewis
Composent.com