Miles To Code Before I Sleep

June 20, 2009

Remote declarative OSGi services

Filed under: Eclipse — bryanhunt @ 5:47 pm

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

June 10, 2009

Modeling SWT with EMF

Filed under: Eclipse — bryanhunt @ 12:19 am

My EMF Workflow Model is starting to take shape.  One of the next features I’d like to add is to make it easier to create and configure a workflow.  I was thinking it would be nice if the configuration editor was configurable without having to write a lot (if any) code.

I started playing around with the idea of modeling SWT with EMF, and it actually seems to work nicely.  I’ve modeled about half of the SWT widgets and all of the layouts in EMF.  This screencast shows an Eclipse view in which the contents are created from an EMF model, then I change the model and refresh the view to reflect the updated model.  The next step will be to figure out how to model databindings and connect them to a EMF Workflow Model.

Blog at WordPress.com.