Remote declarative OSGi services

My last post on OSGi services showed how you can dynamically configure OSGi services using the ConfigurationAdmin service.  I used an example where I configured a service to create a JDBC connection.  JDBC connections are often made on a server, and it would be nice to be able to do your configuration remotely.  This post will show you how you can use RFC 119 to make remote OSGi service calls to configure the JDBC connection on a server.

There are two implementations of RFC 119: Apache CXF, and ECF.  I’m using ECF for this example.  This example also uses Bonjour (Zeroconf) for service discovery.  This means that the client and server must be running on the same local subnet for the client to discover the services advertised by the server.  ECF also supports SLP for service discovery, but like Bonjour is limited to the local subnet.  ECF allows you to plug in your own discovery provider, and I have done just that to support discovery across a WAN.  Look for a future blog post describing how to create your own ECF discovery provider.

Service Interface

Lets start by creating our remote service interface:

  1. Create a new plug-in project com.example.database.admin
  2. Create a new interface IDatabaseAdminService
package com.example.database.admin;

import java.util.Dictionary;

public interface IDatabaseAdminService
{
	public void createConnection(Dictionary<String, String> connectionConfig);
}

Service Implementation

Create an implementation of IDatabaseAdminService. The implementation creates a Configuration using the ConfigurationAdmin service.

package com.example.database;

import java.io.IOException;
import java.util.Dictionary;

import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;

import com.example.database.admin.IDatabaseAdminService;

public class DatabaseAdminService implements IDatabaseAdminService
{
	public synchronized void createConnection(Dictionary<String, String> connectionConfig)
	{
		try
		{
			Configuration config = null;

			if (configAdmin != null)
				config = configAdmin.createFactoryConfiguration("com.example.database.connection", null);

			if (config != null)
				config.update(connectionConfig);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	protected synchronized void bind(ConfigurationAdmin configAdmin)
	{
		this.configAdmin = configAdmin;
	}

	protected synchronized void unbind(ConfigurationAdmin configAdmin)
	{
		if (this.configAdmin == configAdmin)
			this.configAdmin = null;
	}

	private ConfigurationAdmin configAdmin;
}

Declare the Admin Service

Now that we have a service, let’s declare it:

  1. Create a new Component Definition
  2. Set File name: admin.xml
  3. Set name: com.example.database.admin
  4. Set Class: com.example.database.DatabaseAdminService

Using the Component Definition editor, we need to set a service property osgi.remote.interfaces=*.  This property tells the RFC 119 implementation to make all declared interfaces available remotely.

Component Overview

Next, on the services tab, add a dependency on the ConfigurationAdmin service, and declare the IDatabaseAdminService.  Don’t for get to set the bind and unbind functions on the referenced ConfigurationAdmin service.

Component Services

Initialize the ECF Container

ECF does much more than implement RFC 119, so in order to use distributed services, you must create an instance of the ecf.r_osgi.peer container.  This should be done in its own bundle since it is necessary on both the client and server.

  1. Create a new Plug-in Project
  2. Create an Activator for the bundle
package com.example.services.init;

import org.eclipse.ecf.core.ContainerCreateException;
import org.eclipse.ecf.core.IContainer;
import org.eclipse.ecf.core.IContainerManager;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

/**
 * The activator class controls the plug-in life cycle
 */
public class Activator implements BundleActivator
{
	public void start(final BundleContext context) throws Exception
	{
		containerManagerTracker = new ServiceTracker(context, IContainerManager.class.getName(), new ServiceTrackerCustomizer()
		{
			public void removedService(ServiceReference reference, Object service)
			{
				if (container != null)
					container.disconnect();

				container = null;
			}

			public void modifiedService(ServiceReference reference, Object service)
			{}

			public Object addingService(ServiceReference reference)
			{
				try
				{
					IContainerManager containerManager = (IContainerManager) context.getService(reference);
					container = containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
					return containerManager;
				}
				catch (ContainerCreateException e)
				{
					e.printStackTrace();
					return null;
				}
			}
		});

		containerManagerTracker.open();
	}

	public void stop(BundleContext context) throws Exception
	{
		containerManagerTracker.close();

		if (container != null)
			container.dispose();
	}

	private ServiceTracker containerManagerTracker;
	private IContainer container;
}

Launch Server

You can launch the server now, or wait until you have written the client code.  To launch the server:

  1. Create a new OSGi Framework launch configuration
  2. Click Deselect All
  3. Uncheck Include optional dependencies when computing required bundles
  4. Uncheck Add new workspace bundles to this launch configuratin automatically
  5. Check com.example.database
  6. Check com.example.database.admin
  7. Check com.example.services.init
  8. Check ch.ethz.iks.r_osgi.remote
  9. Check org.eclipse.ecf.discovery
  10. Check org.eclipse.ecf.osgi.services
  11. Check org.eclipse.ecf.osgi.services.discovery
  12. Check org.eclipse.ecf.osgi.services.distribution
  13. Check org.eclipse.ecf.provider
  14. Check org.eclipse.ecf.provider.jmdns
  15. Check org.eclipse.ecf.provider.r_osgi
  16. Check org.eclipse.ecf.remoteservice
  17. Check org.eclipse.equinox.cm
  18. Check org.eclipse.equinox.ds
  19. Click Add Required Bundles
  20. Set the Start Level of org.eclipse.equinox.ds to 5
  21. Set Auto-Start to true on org.eclipse.ecf.osgi.*
  22. Set Auto-Start to true on org.eclipse.ecf.provider.jmdns
  23. Set Auto-Start to true on org.eclipse.equinox.cm
  24. Set Auto-Start to true on org.eclipse.equinox.ds

You normally should not set the start level of org.eclipse.equinox.ds.  However, there is a start order dependency bug in ECF that requires your services to be registered after the ecf.r_osgi.peer container, and a bug in declarative services that requires DS to start after CM, so the easiest workaround is to set the start level of DS to 5 with a default start level of 4.  Your launch configuration should look something like:

Launch Config

After you launch the server, issue the command services (objectClass=com.example.database.admin.IDatabaseService).  You should see the service listed twice.  Note that there are not really two instances of the service running.  One is a proxy created by ECF to handle the remote calls.

Registered Services

Client

Now we need to create the client code that will call the remote service.  To illustrate the power of RFC 119, let’s create the client code as a declared service itself that dynamically binds to the remote service.

  1. Create a new plug-in project com.example.database.client
  2. Check Activate this plug-in when one of its classes is loaded
  3. Create a new folder OSGI-INF
  4. Create a new Component Definition
  5. Create a new ClientAdmin class
  6. Declare a dependency on IDatabaseAdminService

Client Component

Don’t forget to specify the bind and unbind functions for the referenced service.

Client Referenced Services

Here is the AdminClient:

package com.example.database.client;

import java.util.Hashtable;

import org.osgi.service.component.ComponentContext;

import com.example.database.admin.IDatabaseAdminService;

public class AdminClient
{
	protected synchronized void activate(ComponentContext context)
	{
		if(adminService == null)
			return;

		Hashtable<String, String> properties = new Hashtable<String, String>();
		properties.put("database.id", "example");
		properties.put("database", "junit");
		properties.put("user", "");
		properties.put("password", "");
		properties.put("create", "create");

		adminService.createConnection(properties);
		System.out.println("Database connection created");
	}

	protected synchronized void bind(IDatabaseAdminService adminService)
	{
		this.adminService = adminService;
	}

	protected synchronized void unbind(IDatabaseAdminService adminService)
	{
		if(this.adminService == adminService)
			this.adminService = null;
	}

	private IDatabaseAdminService adminService;
}

Launch Client

We can now launch the client:

  1. Clone the Server launch configuration
  2. Uncheck com.example.database
  3. Check com.example.database.client

Client Launch

After launching the client and seeing “Database connection created” print to the console, switch over to the server console and type: services (objectClass=com.example.database.IDatabaseConnectionService) and you will see that the service was configured by the client and started.

Service Configured

What has happened here?  When the client started, ECF discovered the service advertised by the server and registered a proxy as if it were a local OSGi service.  The declarative services bundle saw the service and recognized that it was required by the client component.  The service proxy was dynamically bound to the client component by calling bind(IDatabaseAdminService).  The client service was then activated by the declarative services bundle by calling activate(ComponentContext).  The client then created a configuration and called IDatabaseService.createConnection(Dictionary<String, String>) which made a remote call to the service running on the server.  The server side service created a Configuration using the ConfigurationAdmin service.  The configuration of the service then caused the declarative services bundle on the server to create an instance of the IDatabaseConnectionService.  Pretty cool!

Update: fixed a cut/paste problem with the source code.

Update 8/23/09: There is a bug in ECF 3.0 that prevents more than one remote service from being discovered.  You may still use ECF 3.0, but will need to check out org.eclipse.ecf.provider.r_osgi from CVS to pick up the fix.  This fix will be released in ECF 3.1

11 thoughts on “Remote declarative OSGi services

  1. Pingback: Remote Declarative OSGi Services with ECF « The OSGi Look

  2. Pingback: travelling and not arriving

  3. Pingback: Remote Declarative Services « EclipseSource Blog

    • I’m assuming you are referring to the r_osgi.peer container. Yes, both the server and client must create an instance of this container. This is stated so in the section “Initialize the ECF Container”. Also, if you look at the “Launch Client” section, you will see that the launch configuration for the client includes the bundle “com.example.services.init” which is the bundle that creates an instance of the r_osgi.peer container.

  4. Bryan,
    For the above example I want to use jgroups, instead of r_orgi container, but while deploying it shows service registered. but as per your example it should show 2 instance (second proxy one) but in my case its showing only one, and my client is not showing any registered service (jgroups client). can please guide me where I am wrong

    • Having two instances of the service registered may be specific to R-OSGi. I’m not really familiar with jgroups, and you might get a better answer on the ECF mail list at eclipse.org.

  5. i really impressed by your article.am currently working on the implementation of osgi remote services .i want remote service implementing on felix framework.on felix framework already there are other applications like sip etc.i want remote serivce as one of it.am not worried currently about felix framwork.but i want to implement osgi rmote service implementation can u please help how to implement technically ,logically using the java code?
    i have tried the above your example but at server side near ECF container am geeting this error please resolve this
    package com.example.services.init;

    container = containerManager.getContainerFactory().createContainer(“ecf.r_osgi.peer”);

    catch (ContainerCreateException e)

    {

    e.printStackTrace();

    error:configure the build path
    same error is coming at four places..how and what to configure?
    i already installed ecf software
    please kindly answer all my question

    thanks
    addanki

Leave a reply to tutorial services Cancel reply