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:
- Create a new plug-in project com.example.database.admin
- 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:
- Create a new Component Definition
- Set File name: admin.xml
- Set name: com.example.database.admin
- 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.
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.
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.
- Create a new Plug-in Project
- 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:
- Create a new OSGi Framework launch configuration
- Click Deselect All
- Uncheck Include optional dependencies when computing required bundles
- Uncheck Add new workspace bundles to this launch configuratin automatically
- Check com.example.database
- Check com.example.database.admin
- Check com.example.services.init
- Check ch.ethz.iks.r_osgi.remote
- Check org.eclipse.ecf.discovery
- Check org.eclipse.ecf.osgi.services
- Check org.eclipse.ecf.osgi.services.discovery
- Check org.eclipse.ecf.osgi.services.distribution
- Check org.eclipse.ecf.provider
- Check org.eclipse.ecf.provider.jmdns
- Check org.eclipse.ecf.provider.r_osgi
- Check org.eclipse.ecf.remoteservice
- Check org.eclipse.equinox.cm
- Check org.eclipse.equinox.ds
- Click Add Required Bundles
- Set the Start Level of org.eclipse.equinox.ds to 5
- Set Auto-Start to true on org.eclipse.ecf.osgi.*
- Set Auto-Start to true on org.eclipse.ecf.provider.jmdns
- Set Auto-Start to true on org.eclipse.equinox.cm
- 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:
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.
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.
- Create a new plug-in project com.example.database.client
- Check Activate this plug-in when one of its classes is loaded
- Create a new folder OSGI-INF
- Create a new Component Definition
- Create a new ClientAdmin class
- Declare a dependency on IDatabaseAdminService
Don’t forget to specify the bind and unbind functions for the referenced service.
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:
- Clone the Server launch configuration
- Uncheck com.example.database
- Check com.example.database.client
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.
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
Pingback: Remote Declarative OSGi Services with ECF « The OSGi Look
Pingback: travelling and not arriving
Pingback: Remote Declarative Services « EclipseSource Blog
Excuse me , but should client create container? If client does not create container, service is discovered, but proxy isn’t registered and service isn’t called.
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.
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.
For those picking this up … there’s been a few changes in ECF 3.6; the latest wiki page is here:
http://wiki.eclipse.org/Getting_Started_with_ECF%27s_OSGi_Remote_Services_Implementation
Hey, was just browsing on the internet looking for some information and came
across your site. I am impressed by the information that you have on this
site.really i like your post…
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
It’s really a nice and helpful piece of info. I’m glad that you just shared this useful info with us. Please keep us informed like this. Thanks for sharing.