Configuring OSGi Services with Apache Web Console and Metatype

Introduction

OSGi services can be easily configured using the ConfigurationAdmin service.  If you add metadata to your services, they can be configured with a nice user interface such as the Apache Web Console.  This can be a very nice way to configure remote servers.  This tutorial will walk you through the steps to create the metadata, set up the web console, and configure your services.  Here’s an example of the end result where an OSGi service is configured with the Apache Web Console:

Metatype

Administrative tools such as the Apache Web Console can provide a nice form based user interface for configuring your OSGi services only if you provide sufficient metadata for your service properties.  Using this metadata, the Apache Web Console knows how to display, for example, a label for each property, display a text box for simple string data, and display a combo box for choosing a value from a list.  This metadata is defined using the OSGi Metatype Service.

There are two ways to provide metatype data: an XML file, or by registering a MetaTypeProvider as an OSGi service.  Specifying your metatype data in an XML file works well as long as your data is static, and you are not using declarative services.  If you need to specify dynamic metatype data such as the values for options, you must register a provider.  Metatype will not – yet – work with declarative services.  I’ll discuss this in detail in the section below on declarative services.  The metatype service allows you to specify the following datatypes for configuring your services:

  • boolean
  • byte
  • char
  • double
  • float
  • int
  • long
  • short
  • String

You can also specify a cardinality for arrays, default values, and label/value pairs for choices.

Metatype XML File

To specify your metatype data in an XML file, you must create an OSGI-INF/metatype/ directory in your bundle.  In this directory, you may place one or more XML files adhering to the metatype XML schema.  The file contains a number of object class definitions each with a number of attribute definitions.  You then specify a designate to tie an object class definition to a service PID.  Here is a simple example of a metatype XML file that allows a service to be configured with a property called choice with the possible values: normal and priority.

<?xml version="1.0" encoding="UTF-8"?>
<MetaData xmlns="http://www.osgi.org/xmlns/metatype/v1.0.0">
	<OCD description="Example Managed Service" name="Managed Service" id="ocd">
		<AD name="Example Choice Attribute" id="choice" required="true" type="String" default="normal">
			<Option label="Normal" value="normal" />
			<Option label="Priority" value="priority" />
		</AD>
	</OCD>
	<Designate pid="org.eclipselabs.metatype.examples.managedService"> <Object ocdref="ocd"/>
	</Designate>
</MetaData>

Metatype Provider

To register a metatype provider, create a class that implements MetaTypeProvider and construct instances of ObjectClassDefinition and AttributeDefinition.  The provider can be registered in your bundle activator, or as a declarative service.  The metatype specification provides interfaces for the datatypes, but not implementations.  You must provide your own implementation of the interfaces, or you may use the EMF implementation I have hosted at Eclipse Labs.  Here is the source code that specifies the same configuration metadata as the XML file above using the EMF implementation.

public class ExampleDeclarativeServiceMetatype implements MetaTypeProvider
{
	@Override
	public ObjectClassDefinition getObjectClassDefinition(String id, String locale)
	{
		ObjectClassDefinitionImpl ocd = MetatypeFactory.eINSTANCE.createObjectClassDefinitionImpl();
		ocd.setID("org.eclipselabs.metatype.examples.service");
		ocd.setName("Declarative Service");
		ocd.setDescription("Example Description");

		AttributeDefinitionImpl ad = MetatypeFactory.eINSTANCE.createAttributeDefinitionImpl();
		ad.setName("Exampel Chioce Attribute");
		ad.setDescription("Attribute Description");
		ad.setID("choice");
		ad.setType(AttributeDefinition.STRING);
		ad.setOptionLabels(new String[] {"Normal", "Priority"});
		ad.setOptionValues(new String[] {"normal", "priority"});
		ad.setDefaultValue(new String[] {"normal"});

		ocd.getAttributeDefinitions().add(ad);
		return ocd;
	}

	@Override
	public String[] getLocales()
	{
		return null;
	}
}

Now that you have seen how easy it is to specify your metatype data, lets write some services and configure them using the web console. We start by setting up our target platform.

Target Platform

Setting up your target platform is really easy with Eclipse and PDE.  Before you configure Eclipse, you need to download the Apache Web Console (Full) bundle since they do not build P2 repositories.  As of this writing, the latest version of the web console is 3.1.0.

  1. Start up Eclipse (I’m using Helios) with a new workspace.
  2. Select Preferences -> Plug-in Development -> TargetPlatform
  3. Click Add…
  4. Select Nothing: Start with an empty target definition
  5. Click Next
  6. Name: Equinox
  7. In the Locations tab, click Add…
  8. Choose Software Site and click Next
  9. Work with: Helios – http://download.eclipse.org/releases/helios
  10. Expand EclipseRT Target Platform Components
  11. Check
    1. EMF – Eclipse Modeling Framework SDK
    2. Equinox Target Components
  12. Click Finish
  13. Click Add…
  14. Choose Software Site and click Next
  15. Work with: http://metatype-emf-model.eclipselabs.org.codespot.com/hg/site/org.eclipselabs.metatype.site/
  16. Check Metatype EMF Model
  17. Click Finish
  18. Click Add…
  19. Choose Directory and click Next
  20. Location: is the path to the directory containing the web console bundle
  21. Click Finish

Your New Target Definition should look like:

Click Finish, check the target definition to make it active, and click OK.  Your target platform is now ready for you to start writing code.

Managed Services

There are two interfaces that can be registered to make your service configurable by ConfigurationAdmin: ManagedService and ManagedServiceFactory.  Use these interfaces for non-factory and factory services respectively.  Let’s create an Eclipse plug-in project with a ManagedService and ManagedServiceFactory.  Here is the code for the ManagedService:

package org.eclipselabs.metatype.examples;

import java.util.Dictionary;
import java.util.Enumeration;

import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;

public class ExampleManagedService implements ManagedService
{
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public void updated(Dictionary properties) throws ConfigurationException
	{
		System.out.println("Updating configuration properties for ExampleManagedService");

		if (properties != null)
		{
			Enumeration<String> keys = properties.keys();

			while (keys.hasMoreElements())
			{
				String key = keys.nextElement();
				System.out.println(key + " : " + properties.get(key));
			}
		}
	}
}

The code for the ManagedServiceFactory is almost identical:

package org.eclipselabs.metatype.examples;

import java.util.Dictionary;
import java.util.Enumeration;

import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;

public class ExampleManagedServiceFactory implements ManagedServiceFactory
{
	@Override
	public String getName()
	{
		return "ManagedServiceFactoryExample";
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void updated(String pid, Dictionary properties) throws ConfigurationException
	{
		System.out.println("Updating configuration properties for ExampleManagedServiceFactory " + pid);

		if (properties != null)
		{
			Enumeration<String> keys = properties.keys();

			while (keys.hasMoreElements())
			{
				String key = keys.nextElement();
				System.out.println(key + " : " + properties.get(key));
			}
		}
	}

	@Override
	public void deleted(String pid)
	{
		System.out.println("ExampleManagedServiceFactory deleted(" + pid + ")");
	}
}

Create a bundle activator to register the two services:

package org.eclipselabs.metatype.examples;

import java.util.Hashtable;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.cm.ManagedServiceFactory;

public class Activator implements BundleActivator
{
	private static BundleContext context;

	static BundleContext getContext()
	{
		return context;
	}

	public void start(BundleContext bundleContext) throws Exception
	{
		Activator.context = bundleContext;

		Hashtable<String, String> serviceProperties = new Hashtable<String, String>();
		serviceProperties.put(Constants.SERVICE_PID, "org.eclipselabs.metatype.examples.managedService");
		context.registerService(ManagedService.class.getName(), new ExampleManagedService(), serviceProperties);

		Hashtable<String, String> serviceFactoryProperties = new Hashtable<String, String>();
		serviceFactoryProperties.put(Constants.SERVICE_PID, "org.eclipselabs.metatype.examples.managedServiceFactory");
		context.registerService(ManagedServiceFactory.class.getName(), new ExampleManagedServiceFactory(), serviceFactoryProperties);
	}

	public void stop(BundleContext bundleContext) throws Exception
	{
		Activator.context = null;
	}
}

Now, we need the metatype data for the two services. In your plug-in project, create the directory OSGI-INF/metatype/ and in that directory, create the following two files:

managed_service.xml

<?xml version="1.0" encoding="UTF-8"?>
<MetaData xmlns="http://www.osgi.org/xmlns/metatype/v1.0.0">
	<OCD description="Example Managed Service" name="Managed Service" id="ocd">
		<AD name="Example Choice Attribute" id="choice" required="true" type="String" default="normal">
			<Option label="Normal" value="normal" />
			<Option label="Priority" value="priority" />
		</AD>
	</OCD>
	<Designate pid="org.eclipselabs.metatype.examples.managedService"> <Object ocdref="ocd"/>
	</Designate>
</MetaData>

managed_service_factory.xml

<?xml version="1.0" encoding="UTF-8"?>
<MetaData xmlns="http://www.osgi.org/xmlns/metatype/v1.0.0">
	<OCD description="Example Managed Service" name="Managed Service Factory" id="ocd">
		<AD name="Example Choice Attribute" id="choice" required="true" type="String" default="normal">
			<Option label="Normal" value="normal" />
			<Option label="Priority" value="priority" />
		</AD>
	</OCD>
	<Designate factoryPid="org.eclipselabs.metatype.examples.managedServiceFactory" pid="org.eclipselabs.metatype.examples.managedServiceFactory" bundle="*">
		<Object ocdref="ocd"/>
	</Designate>
</MetaData>

Note that for the factory case, you must specify both the pid and factoryPid in the metatype XML.

Run the Examples

Let’s set up a launch configuration and run the examples you’ve created so far.

  1. Select Run -> Run Configurations…
  2. Select OSGi Framework
  3. Click New launch configuration on the toolbar
  4. In the Bundles tab, click Deselect All
  5. Uncheck Include optional dependencies when computing required bundles
  6. Check your examples bundle
  7. Check the following bundles from the Target Platform
    1. org.eclipse.equinox.metatype
    2. org.eclipselabs.metatype
    3. org.eclipse.equinox.cm
    4. org.ecliplse.equinox.http.jetty
    5. org.mortbay.jetty.server
    6. org.mortbay.jetty.util
    7. org.apache.felix.webconsole
  8. Click Add Required Bundles
  9. On the Arguments tab, add the VM arguments: -Dorg.osgi.service.http.port=8080

Your launch configuration should look like:

Click Run and after the application starts, open your web browser on the URL: http://localhost:8080/system/console/configMgr.  When prompted for login, use admin for both the id and password.  You should now see the Apache Felix Web Console with your services listed for configuration.  Your services will probably appear twice, and if you click to edit them you will see different results.  One will display an empty text box, and the other will display a proper form with a combo allowing you to select between Normal and Priority.  Ignore the one with the empty text box – I assume it’s a bug in the web console.  For your managed service, you can click the edit button to change it’s configuration.  For your managed service factory, you can add and remove configurations.

Try changing the the setting on your managed service from Normal to Priority.  You should see the following in the Console view:

Declarative Services

As I mentioned above, the metatype service does not – yet – work with declarative services.  With some help from Tom Watson, I proposed a change to the OSGi specification to allow the metatype service to work with declarative services. Here are the important parts of the proposal:

I would propose changing the MetaType spec to allow for MetaTypeProvider
services to be registered without a ManagedService or ManagedServiceFactory,
and with the following two service properties: metatype.target.pid, and
metatype.factory. The value of metatype.target.pid would be set to the pid of
the declarative service for which the meta type applies, and the
metatype.factory would be set to boolean true if the pid is a factory pid, and
false otherwise.

The meta type service would be changed to include a service tracker for
MetaTypeProvider and the existence of the two properties defined above. The
meta type service would continue to look for ManagedService and
ManagedServiceFactory and simply add any MetaTypeProvider services with those
properties to the list.

Tom suggested that we use metatype.provider.pid and metatype.provider.type = “normal” | “factory” instead of metatype.target.pid and metatype.factory. The net of this is that you can register a metatype provider with the metatype.provider.pid and metatype.provider.type properties set, and you will get metatype data for declarative services.  This proposal appears to be moving forward into the OSGi specification.

I have provided a patch to the Equinox metatype implementation at https://bugs.eclipse.org/bugs/show_bug.cgi?id=311128 with these proposed changes.  With this patch, you can experiment with metatype and declarative services.  Note that this work is not final and still subject to change until it is in the actual OSGi specification.  To try this out, you first need the Equinox metatype source from CVS.

  1. Switch to the CVS Repository Exploring perspective
  2. Click Add CVS Repository in the toolbar
  3. Host: dev.eclipse.org
  4. Repository path: /cvsroot/rt
  5. User: anonymous
  6. Connection type: pserver
  7. Click Finish
  8. In the CVS Repositories view, expand:
    1. /cvsroot/rt
    2. HEAD
    3. org.eclipse.equinox
    4. compendium
    5. bundles
  9. Select org.eclipse.equinox.metatype, right-click and choose Check Out

You should now have the metatype source in your workspace ready to be patched.  To apply my patch:

  1. Switch back to the Plug-in Development perspective
  2. Right-click on the org.eclipse.equinox.metatype project
  3. Choose Team -> Apply Patch…
  4. Choose URL https://bugs.eclipse.org/bugs/attachment.cgi?id=166704
  5. Click Finish

Let’s create a declarative service and try this out.  You need to create a new bundle for your declarative services.  If you add your declarative service metatype provider to a bundle that contains metatype XML files, the registered metatype providers are ignored.  This might be a bug in the metatype implementation.

Here is the code for the declarative service:

package org.eclipselabs.metatype.examples.ds;

import java.util.Dictionary;
import java.util.Enumeration;

import org.osgi.service.component.ComponentContext;

public class ExampleDeclarativeService
{
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected void activate(ComponentContext context)
	{
		System.out.println("Activating ExampleDeclarativeService");

		Dictionary properties = context.getProperties();

		if (properties != null)
		{
			Enumeration<String> keys = properties.keys();

			while (keys.hasMoreElements())
			{
				String key = keys.nextElement();
				System.out.println(key + " : " + properties.get(key));
			}
		}
	}

	protected void deactivate()
	{
		System.out.println("Deactivating ExampleDeclarativeService");
	}
}

Here is the code for the declarative service metatype:

package org.eclipselabs.metatype.examples.ds;

import org.eclipselabs.metatype.AttributeDefinitionImpl;
import org.eclipselabs.metatype.MetatypeFactory;
import org.eclipselabs.metatype.ObjectClassDefinitionImpl;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.MetaTypeProvider;
import org.osgi.service.metatype.ObjectClassDefinition;

public class ExampleDeclarativeServiceMetatype implements MetaTypeProvider
{
	@Override
	public ObjectClassDefinition getObjectClassDefinition(String id, String locale)
	{
		ObjectClassDefinitionImpl ocd = MetatypeFactory.eINSTANCE.createObjectClassDefinitionImpl();
		ocd.setID("org.eclipselabs.metatype.examples.service");
		ocd.setName("Declarative Service");
		ocd.setDescription("Example Description");

		AttributeDefinitionImpl ad = MetatypeFactory.eINSTANCE.createAttributeDefinitionImpl();
		ad.setName("Exampel Chioce Attribute");
		ad.setDescription("Attribute Description");
		ad.setID("choice");
		ad.setType(AttributeDefinition.STRING);
		ad.setOptionLabels(new String[] {"Normal", "Priority"});
		ad.setOptionValues(new String[] {"normal", "priority"});
		ad.setDefaultValue(new String[] {"normal"});

		ocd.getAttributeDefinitions().add(ad);
		return ocd;
	}

	@Override
	public String[] getLocales()
	{
		return null;
	}
}

Here is the code for the declarative service factory metatype:

package org.eclipselabs.metatype.examples.ds;

import java.util.UUID;

import org.eclipselabs.metatype.AttributeDefinitionImpl;
import org.eclipselabs.metatype.MetatypeFactory;
import org.eclipselabs.metatype.ObjectClassDefinitionImpl;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.MetaTypeProvider;
import org.osgi.service.metatype.ObjectClassDefinition;

public class ExampleDeclarativeServiceFactoryMetatype implements MetaTypeProvider
{
	@Override
	public ObjectClassDefinition getObjectClassDefinition(String id, String locale)
	{
		ObjectClassDefinitionImpl ocd = MetatypeFactory.eINSTANCE.createObjectClassDefinitionImpl();
		ocd.setID("org.eclipselabs.metatype.examples.service.factory");
		ocd.setName("Declarative Service Factory");
		ocd.setDescription("Example Description");

		AttributeDefinitionImpl ad = MetatypeFactory.eINSTANCE.createAttributeDefinitionImpl();
		ad.setName("Exampel Chioce Attribute");
		ad.setDescription("Attribute Description");
		ad.setID("choice");
		UUID normal = UUID.randomUUID();
		UUID priority = UUID.randomUUID();
		ad.setType(AttributeDefinition.STRING);
		ad.setOptionLabels(new String[] {"Normal", "Priority"});
		ad.setOptionValues(new String[] {normal.toString(), priority.toString()});
		ad.setDefaultValue(new String[] {normal.toString()});

		ocd.getAttributeDefinitions().add(ad);
		return ocd;
	}

	@Override
	public String[] getLocales()
	{
		return null;
	}
}

The metatype providers can be registered as declarative services. When you register them, you must set the metatype.provider.pid and metatype.provider.type properties. First, register the declarative service as a factory and non-factory configurable service.

Here is the non-factory registration:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.metatype.examples.service">
   <implementation class="org.eclipselabs.metatype.examples.ds.ExampleDeclarativeService"/>
</scr:component>

Here is the factory configurable service:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="require" immediate="true" name="org.eclipselabs.metatype.examples.service.factory">
   <implementation class="org.eclipselabs.metatype.examples.ds.ExampleDeclarativeService"/>
</scr:component>

Here is the metatype provider registration:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.metatype.examples.service.metatype">
   <implementation class="org.eclipselabs.metatype.examples.ds.ExampleDeclarativeServiceMetatype"/>
   <service>
      <provide interface="org.osgi.service.metatype.MetaTypeProvider"/>
   </service>
   <property name="metatype.provider.pid" type="String" value="org.eclipselabs.metatype.examples.service"/>
   <property name="metatype.provider.type" type="String" value="normal"/>
</scr:component>

Here is the metatype factory provider registration:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.metatype.examples.service.factory.metatype">
   <implementation class="org.eclipselabs.metatype.examples.ds.ExampleDeclarativeServiceFactoryMetatype"/>
   <service>
      <provide interface="org.osgi.service.metatype.MetaTypeProvider"/>
   </service>
   <property name="metatype.provider.pid" type="String" value="org.eclipselabs.metatype.examples.service.factory"/>
   <property name="metatype.provider.type" type="String" value="factory"/>
</scr:component>

That’s all the code we need, so let’s modify our launch configuration and try it out.

Run the Examples

Bring up the launch configuration you created to run the first example.

  1. Add your new bundle containing your declarative services
  2. Add the org.eclipse.equinox.metatype bundle from your Workspace
  3. Add the org.eclilpse.equinox.ds bundle
  4. Remove the org.eclipse.equinox.metatype bundle from the Target Platform
  5. Click Add Required Bundles

Your launch configuration should look like:

Click Run and point your web browser to http://localhost:8080/system/console/configMgr.  You should see your declarative services in addition to your managed services.  Try creating a configuration for your factory service and you will see it display the value of the UUID that was dynamically chosen for a configuration value.  This is where the real power of this technology shines – the ability to programatically create values for declarative service configuration and have those values available as choices on an administrative UI.

If you don’t feel like setting up the bundles and typing in the code, you can get the example source code from Eclipse Labs.

References

  1. OSGi Specifications
  2. Metatype API
  3. OSGi Metatype Service
  4. Configuration Admin API
  5. Dynamically configured declarative OSGi services
  6. Configuration Admin Service specification explained by Example
  7. How to use Config Admin
  8. OSGi as a Web Application Server
  9. Eclipse Labs
Advertisements

3 thoughts on “Configuring OSGi Services with Apache Web Console and Metatype

  1. Nice article!
    What I am missing in the MetaType information is some kind of grouping mechanism. Without it the UI is just cluttered with input/select fields.
    Also I miss a non-web based solution for the ConfigAdmin. I started to work on one a while ago but never finished it.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s