ProSyst Metatype Extension

The ProSyst Metatype API supplies the Metatype Bundle with additional characteristics of metadata. It extends the OSGi Metatype Specification from the OSGi Service Platform Specification, Release 3.

Contents:


Features of the ProSyst Metatype API

The ProSyst Metatype API, mainly in the org.mbs.services.metatype Java package, enables developers to define:


Types of Metadata

The Metatype Bundle is capable of managing metadata of different types. Usually, a metadata type represents some kind of logical entity.

Each type is associated with a Metadata Plugin service, indicating the supported metadata type through a service registration property. Initially, there is a Metadata Plugin for plain and factory bundle configurations (see the Config Bundle description) and for control units (see the Control Unit Admin Bundle description).

The Metatype Bundle processes only metatypes for whose metadata type there is a Metadata Plugin currently registered in the OSGi framework.

See the "How to Add a New Metadata Type" section for more information on how to introduce new metadata types into the Metatype Bundle.


How to Create a Metatype

Object classes and their attributes can be described:

Implement a Metatype Provider

According to the OSGi Metatype Specification, metadata is exported via a org.osgi.service.metatype.MetaTypeProvider. The org.osgi.service.metatype.ObjectClassDefinition and org.osgi.service.metatype.AttributeDefinition instances of a Metatype Provider contain sufficient information to automatically build rudimentary user interfaces. They can also be used to augment dedicated interfaces with accurate validations.

The mechanism, implemented in the Metatype Bundle, for mapping management operations to metatypes is based on the availability of Metatype Providers as services in the OSGi framework. To register a new metadata, accessible through the Metatype API, a bundle should register its Metatype Provider as a service in the OSGi framework under the org.osgi.service.metatype.MetaTypeProvider interface.

Metadata type and version can be indicated by the values of the "metadata.type" (MetypeProviderExtern.METADATA_TYPE) and "metadata.version" (MetypeProviderExtern.METADATA_VERSION) registration properties, respectively.

If no type and version are indicated, the Metadata Manager assumes that the corresponding Metatype Provider is designed for the Configuration Admin service (see the Config Bundle description for more information about Configuration Admin metadata). This is done for backward compatibility and for handling the metatyping, defined in the OSGi Configuration Admin Specification.

To take advantage of the ProSyst Metatype API's features, you have to implement the following interfaces, extending the OSGi Metatype API:

Interface to Implement Additional Functionality
MetypeProviderExtern

Create metadata of a specified type and version, which can also contain configuration objects.

As the MetatypeProviderExtern interface extends com.prosyst.util.io.Externalizable, you can serialize the metadata of the Metatype Provider and make it available to remote applications over PMP or another communication mechanism.

The MetatypeProviderExtern implementation should be registered as a service in the OSGi framework.

ObjectClassDefinitionEx

Provide:

  • Nested ObjectClassDefinitions.
  • ObjectClassDefinition key/value modifiers (e.g. the ProSyst-defined load modifier indicating if the object class should be loaded by the corresponding management application)
  • Means to search an attribute by its ID.
  • Means to filter input (INPUT) and output (OUTPUT) attributes besides optional and required.
  • Define a hierarchy of object classes (SUPER).

ObjectClassDefinitionEx instances should be returned by the getObjectClassDefinition method of the corresponding MetaTypeProvider.

AttributeDefinitionEx

Provide:

  • Dictionaries as attributes.
  • Attribute key/value modifiers.
  • More fine-grained constraints on allowed attribute values.

AttributeDefinitionEx instances should be supplied by the corresponding ObjectClassDefinition.

Tip: Implementation of programming code to provide metadata seems to be a time- and effort-consuming process, so we suggest using the more convenient solution for metadata definition based on XML files.

Write Metadata XMLs

Each bundle can supply XML resource files with metatype information and specify these files in the bundle's manifest.

Let us try to explain the advantages of using XML description of a bundle's metatype information instead of programming interfaces:

  1. First of all, an XML file can be read and parsed before bundle startup. A bundle resource can be changed when the bundle is still in INSTALLED state for example, and when the bundle starts, the resource can receive its new state immediately.
  2. An XML can be easily edited - it is not necessary to change programming code, recompile it and rebuild the bundle. XML resources take less place than a set of class files, holding the same information.

If the XML files are correct, the Metadata Manager will parse them and persistently store the metadata in the database of the DB Bundle. On request for particular metadata, the Metadata Manager will produce a Metatype Provider for the specified PID and populate the provider with serialized data from the database. When the bundle, providing the XML metadata is updated or removed, the Metadata Manager takes care to reflect the change in its storage.

Metadata XML DTD defined by ProSyst

A metadata XML should use the following Data Type Definition (DTD):

DTD elements Description
<!ELEMENT metatype-provider (objectclass+, configobject*)> Describes a Metatype Provider entry. The metatype-provider element consists of one or more objectclasses and zero or more configobjects.
ObjectClassDefinition
<!ELEMENT objectclass (locale*, name, id, description, icon*, attribute*, attribute-ref*, objectclass*)> objectclass contains information about an ObjectClassDefinition as defined in the OSGi Metatype Specification, part of the OSGi Service Platform Specification Release 3. It is obligatory to include in it "name", "id", "description" and at least one "attribute" element. The "icon" and "locale" elements are optional.
<!ATTLIST objectclass
load CDATA #IMPLIED>
An attribute of the objectclass element. If it is set to true, the Metadata Manager will construct a configuration from the objectclass's data.
objectclass Elements
<!ELEMENT locale (#PCDATA)> The locale the ObjectClassDefinition is designed for. It affects the language used in the descriptions and names.

Note: Currently, the locale element is NOT read.

<!ELEMENT name (#PCDATA)> The name of the ObjectClassDefinition or AttributeDefinition, which is displayed by the administration applications.
<!ELEMENT id (#PCDATA)> As termed in the OSGi Metatype Specification, this is the ID of the ObjectClassDefinition or AttributeDefinition.
<!ELEMENT description (#PCDATA)> A short text, describing resource, again used in the administration applications.
<!ELEMENT icon (#PCDATA)> Resource's icon, used in the administration applications. You can have more than one icon, or have no icon at all. The properties of this tag are explained below. The location of the icon may be a resource within the bundle JAR, or a user-defined URL.
<!ATTLIST icon
size CDATA #REQUIRED>
Image resource taken from bundle's JAR file or an URL. Icon's size of 16 means 16x16 pixels. See this example:
<icon size="16">/http16x16.ico</icon>
<!ELEMENT attribute (name?, id, description?, type, cardinality?, key*, value?, (selected-pairs)?)> Metadata for an AttributeDefinition as defined in OSGi Metatype Specification. The symbols after an attribute define the number of attributes:
  • "*" means 0 or more of this attribute
  • "+" means 1 or more of this attribute
  • "?" means 0 or 1 of this attribute
  • the lack of any symbol means that only 1 attribute can be used
<!ATTLIST attribute
modifier CDATA #REQUIRED
load CDATA #IMPLIED>
  • The modifier attribute indicates if the AttributeDefinition is optional (if modifier's value is opt) or required (if modifier's value is req), and if it is used for input data (if modifier's value is in) or output data (if modifier's value is out).
  • The load attribute defines if an optional attribute should be initially loaded or not. By default, the property is accepted as true.
<!ELEMENT cardinality (#PCDATA)> The maximum size of an array or a vector. Positive value indicates that the attribute's value is an array, negative - that this is a vector, and 0 - that this is a single value attribute.
<!ELEMENT key EMPTY> Additional attribute modifier. You can use zero or more keys, i.e. modifiers. See key's attributes explained below.
<!ATTLIST key
name CDATA #REQUIRED
value CDATA #REQUIRED>

Required name and value of the key element.

<!ELEMENT selected-pairs ((scalar, scalar)+)> Values defined as a list of options to choose from. The first scalar is the option name and the second one - the value.
<!ELEMENT type (#PCDATA)> This element specifies the type of the property. It must be one of the entity elements below.
The following elements show the possible types for a property, according to the specification
<!ENTITY int "int"> Specifies the type of an integer property.
<!ENTITY byte "byte"> Specifies the type of a byte property.
<!ENTITY boolean "boolean"> Specifies the type of a boolean property.
<!ENTITY string "string"> Specifies the type of a string property.
<!ENTITY long "long"> Specifies the type of a long property.
<!ENTITY short "short"> Specifies the type of a short property.
<!ENTITY char "char"> Specifies the type of a char property.
<!ENTITY double "double"> Specifies the type of a double property.
<!ENTITY float "float"> Specifies the type of a float property.
 <!ENTITY dictionary "dictionary">  Specifies the type of a dictionary property.
   
<!ELEMENT value (scalar | array|(property+))> The attribute's default value. Can be scalar - single value, array - an array or a vector depending on the specified attribute cardinality, or property - a dictionary.
<!ELEMENT array (scalar+)> The set of scalars (single values) of an array or vector.
<!ELEMENT property (type, scalar?)> The property of a dictionary. It contains the nested type and scalar elements, which respectively indicates the type and the value of the property.
<!ATTLIST property
key CDATA #REQUIRED>
The property name.
<!ELEMENT scalar (#PCDATA)> The default value of the property, or of the array or vector element. It must be of the same type, as indicated in the type tag.
<!ELEMENT attribute-ref EMPTY> An attribute reference can be used in nested object classes to point to an attribute already defined into an ancestor object class.
<!ATTLIST attribute-ref
refid IDREF #REQUIRED
modifier CDATA #REQUIRED
load CDATA #IMPLIED>
Configuration Object Definition
<!ELEMENT configobject (id, (light-attribute*))>

configobject keeps data of a particular object class instance (a configuration object), generated using a parent "factory" ObjectClassDefinition. If nothing is defined in the configobject tag, it is assumed to have the same attributes and values as objectclass. New values for the required attributes may be defined and optional ones may be included or excluded depending on the values in the load flags.

There should be as many configobjects as is the number of required initially-generated object class instances.

<!ELEMENT light-attribute (id, value?)> An attribute, inherited from the ObjectClassDefinition, with specific value.
<!ATTLIST light-attribute
modifier CDATA #REQUIRED
load CDATA #IMPLIED>
See the description of the attribute tag attributes.

Metadata Manifest Header

A bundle should specify each of its metadata XMLs via a separate manifest header with the following syntax:

header := metadata-type “:” metatype-providers
metatype-providers := metatype-provider (“,” metatype-provider)*
metatype-provider := “xml=” xml-resource-path “;pid=” service-pid “;name=” display-name “;version=” metadata-version

where:

Definition of Object Class Hierarchies

The Metadata Manager is capable of handling hierarchies of not-nested object classes. It is the descendant's responsibility to indicate its superior by including in its ObjectClassDefinition the super attribute with value the ID of the super object class. As a result, the descendant inherits all attributes of the superior. If you want to exclude one or more ObjectClassDefinitions of a super object class you can specify this in the following way: !<ID_to_exclude>.

How to Update Stored Metadata

Although a bundle with changed metadata of a certain type and PID is updated, the Metadata Manager will not update the changes in its storage and provide them to management applications unless the metadata version is incremented.

To retain backward compatibility with bundles based on older versions of the Configuration Admin, the Metadata Manager supports updating an XML-defined metatype by declaring a new PID. On bundle update the Metadata Manager checks the metatype PID in the bundle's manifest - if the old PID is not present, the manager deletes related data from its storage and saves a new storage entry associated with the new PID.


How to Manage Available Metadata

Management of registered metadata is handled by the Metadata Manager, which other applications can call to retrieve metadata of interest. As some of the Metadata Manager's functionality is intended for internal use by the Configuration Admin , this document section focuses mainly on the options useful to all applications within the OSGi framework.

Accessing Metadata

The Metadata Manager allows you to:

Subscribe for Metadata Changes

The Metadata Manager is capable of notifying applications about added, removed or changed XML-formatted metadata. Interested applications should implement an org.mbs.services.metatype.MetaDataListener and registers it in the Metadata Manager through the addMetaDataListener method.

Tip: For metadata, exported by Metatype Provider services, you may use the eventing mechanism of the OSGi framework to receive notifications about metadata changes - for example, by registering a service listener for Metatype Providers.


How to Add a New Metadata Type

Adding a new type of metadata resource can be achieved by implementing a org.mbs.services.metatype.MetaDataPlugin and registering it as a service in the OSGi framework with registration property MetaDataPlugin.METADATA indicating the new metadata type(s) defined as String or String[] types.

When a bundle holding metadata of specific type is installed or updated, the Metadata Manager asks the associated Metadata Plugin whether to process the metadata or not by calling the plugin's process method. For example, this method may return false for intensively used metadata as its processing may significantly load the system and slow down its performance.


Metadata Example

The following example illustrates the usage of the ProSyst Metatype support, and in particular of the Metadata Manager service. The example consists of the several parts:

Management Metadata Plugin

The ServerMetadataPlugin class combines the functionality of a Metadata Plugin - it registers an org.mbs.services.metatype.MetaDataPlugin, and of a metadata management application - it listens for changed XML-defined metadata and for Metatype Provider services with registration property "metadata.type" equal to "Server". On detecting changes, the ServerMedataPlugin class checks the "isServer" and "port" attributes and logs the result.

import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;
import org.mbs.services.metatype.MetaDataListener;
import org.mbs.services.metatype.MetaDataManager;
import org.mbs.services.metatype.MetaDataPlugin;
import org.mbs.services.metatype.MetaTypeProviderExtern;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.MetaTypeProvider;
import org.osgi.service.metatype.ObjectClassDefinition;

import com.prosyst.util.ref.Log;

public class ServerMetadataPlugin
implements MetaDataPlugin, BundleActivator, ServiceListener, MetaDataListener {

// Represents the "Server" metadata type
public static final String METATYPE = "Server";

// Represent managed attributes
private static final String SERVER_ATTRIBUTE_NAME = "isServer";
private static final String PORT_ATTRIBUTE_NAME = "port";

ServiceRegistration sReg = null;

ServiceReference mdataMngrRef = null;
MetaDataManager mdataMngr = null;

Log log = null;

// Method inherited from MetaDataPlugin
public boolean processed(String type) {
return true;
}

// Methods inherited from BundleActivator
public void start(BundleContext bc) throws Exception {
// Initializing the ProSyst log reference utility
log = new Log(bc);
log.setPrintOnConsole(true);
// Registering Metadata Plugin for the "Server" type
Hashtable sProps = new Hashtable();
sProps.put(METADATA, METATYPE); sReg = bc.registerService(MetaDataPlugin.class.getName(), this, sProps); // Getting the Metadata Manager
mdataMngrRef = bc.getServiceReference(MetaDataManager.class.getName());
if (mdataMngrRef != null) {
mdataMngr = (MetaDataManager) bc.getService(mdataMngrRef);
// Adding a listener for XML-formatted metadata changes
mdataMngr.addMetaDataListener(this, new String[] { METATYPE });
}
// Checking and registering a listener for Metatype Providers
ServiceReference[] mdataProviders =
bc.getServiceReferences(MetaTypeProvider.class.getName(),
"(metadata.type=" + METATYPE + ")"); boolean iterateProviders = false; if (mdataProviders != null) { iterateProviders = true; } for (int i = 0; iterateProviders && (i < mdataProviders.length); i++) { if (mdataProviders[i].getProperty(Constants.SERVICE_PID) != null) { // Processing available Metatype Providers
processMetadata((String) mdataProviders[i].getProperty(Constants.SERVICE_PID));
}
}
bc.addServiceListener(
this,
"(&(" + Constants.OBJECTCLASS + "=" + MetaTypeProvider.class.getName() +
")(metadata.type=" + METATYPE + "))");
}

public void stop(BundleContext bc) throws Exception {
if (sReg != null) {
sReg.unregister();
}
mdataMngr.removeMetaDataListener(this);
}

// Method inherited from ServiceListener
public void serviceChanged(ServiceEvent serviceEvent) {
int eventType = serviceEvent.getType();
// Processing new and changed Metatype Providers
if ((eventType == ServiceEvent.REGISTERED) || (eventType == ServiceEvent.MODIFIED)) {
ServiceReference mtpRef = serviceEvent.getServiceReference();
String servicePid = (String) mtpRef.getProperty(Constants.SERVICE_PID);
try {
if (servicePid != null) {
processMetadata(servicePid);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

// Checks if the metadata refers to a server or to a client and prints the result
// through the ProSyst log utility
private void processMetadata(String servicePid) throws IOException {
String header = "Entity with metatype ID " + servicePid;
AttributeDefinition adServer = null;
AttributeDefinition adPort = null;
MetaTypeProviderExtern mtp = mdataMngr.getMetaTypeProvider(servicePid, true);
ObjectClassDefinition ocd = mtp.getObjectClassDefinition(servicePid, null);
AttributeDefinition[] attrDefs = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
for (int i = 0; i < attrDefs.length; i++) {
if ((attrDefs[i].getID()).equals(SERVER_ATTRIBUTE_NAME)) {
adServer = attrDefs[i];
}
if ((attrDefs[i].getID()).equals(PORT_ATTRIBUTE_NAME)) {
adPort = attrDefs[i];
}
}
if ((adPort != null) && (adServer != null)) {
if ((adServer.getDefaultValue()[0]).equals("true")) {
log.info(header + " is a server listening on port " + adPort.getDefaultValue()[0]);
} else {
log.info(header + " is a client using port " +
adPort.getDefaultValue()[0] + " for connection");
} } }
// Method inherited from MetaDataListener
public void metaDataChanged(int event, long bundleId,
String type, String pid,
String version, int sourceType) {
try {
// Processing new or changed XML metadata
if ((event == ADDED) || (event == UPDATED)) {
processMetadata(pid); } } catch (Exception e) { e.printStackTrace(); }
}
}
Listing 1: An example Metadata Plugin.

XML-Based Metadata

This chapter contains an example for defining metadata in an XML file. Shown XML metadata is of the "Server" type and indicates that this is a server application.

XML File

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE metatype-provider SYSTEM "D:\metatype\metatype.dtd">
<metatype-provider>
<objectclass load="true">
  <name>Server Metadata</name>
  <id>serverapp</id>
  <description>Meta data to test the "Server" metatype</description>
  <attribute modifier="req">
    <name>Server</name>
    <id>isServer</id>
    <description>Shows if the entity is a server</description>
    <type>&boolean;</type>
    <value>
      <scalar>true</scalar>
    </value>
  </attribute>
  <attribute modifier="req">
    <name>Port</name>
    <id>port</id>
    <description>Indicates the port the entity will use</description>
    <type>&int;</type>
    <value>
      <scalar>25</scalar>
    </value>
  </attribute>
</objectclass>
</metatype-provider>
Listing 2: An example metadata XML.

Manifest Header

Server: xml=config.xml; pid=serverapp; name=Server Metadata; version=1.0.0

MetaTypeProvider Implementation

As previously discussed, besides in an XML file, you can define metadata as a Metatype Provider service in the OSGi framework. Following is an example provider for the "Server" type, which defines a client application.

import java.util.Hashtable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;

import org.osgi.service.metatype.MetaTypeProvider;
import org.osgi.service.metatype.ObjectClassDefinition;

public class ServerMetaTypeProvider implements BundleActivator, MetaTypeProvider {
private OCD ocd = null; private AD [] attrDefinitions = null;
protected static String PID = "clientapp";
private ServiceRegistration sReg = null;
// Methods inherited from BundleActivator
public void start(BundleContext bc) throws Exception {
// Creating an ObjectClassDefinition attrDefinitions = new AD[2];
attrDefinitions[0] = new AD("isServer", AttributeDefinition.BOOLEAN, 0);
attrDefinitions[0].setDefaultValue(new String[] { "false" });
attrDefinitions[1] = new AD("port", AttributeDefinition.INTEGER, 0);
attrDefinitions[1].setDefaultValue(new String[] { "5983" });
ocd = new OCD(PID, attrDefinitions);

// Registering the Metatype Provider

Hashtable regProps = new Hashtable();
regProps.put(Constants.SERVICE_PID, PID);
regProps.put("metadata.type", "Server");
regProps.put("metadata.version", "1.0.0");
sReg = bc.registerService(MetaTypeProvider.class.getName(), this, regProps); ocd.setName("My Example Metatype");
}

public void stop(BundleContext bc) throws Exception {
if (sReg != null) {
sReg.unregister();
}
ocd = null;
}

// Methods inherited from MetaTypeProvider
public String[] getLocales() {
return null;
}

public ObjectClassDefinition getObjectClassDefinition(String pid, String locale) {
return ocd;
}
}
Listing 3.1: An example Metatype Provider.

import java.io.IOException;
import java.io.InputStream;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.ObjectClassDefinition;
public class OCD implements ObjectClassDefinition {
  private String description;
private String name;
// Represents the attributes of this object class
AttributeDefinition[] requiredADs = new AttributeDefinition[2]; private String id;
  protected OCD(String id, AttributeDefinition[] requiredADs) {
this.id = id;
this.requiredADs = requiredADs;
}
  public AttributeDefinition[] getAttributeDefinitions(int filter) {
if (filter == ObjectClassDefinition.OPTIONAL) {
return null;
}
return requiredADs;
}
  public String getDescription() {
return description;
}
  public InputStream getIcon(int arg0) throws IOException {
return null;
}
  public String getID() {
return id;
}
  public String getName() {
return name;
}
  protected void setName(String name) {
this.name = name;
}
  protected void setDescription(String description) {
this.description = description;
}
}
Listing 3.2: An example ObjectClassDefinition.

import org.osgi.service.metatype.AttributeDefinition;     

public class AD implements AttributeDefinition {
private String id;
private int type = 0;
private int cardinality = -1;
private String name;
private String description;
private String[] defValue;
private String[] optionalLabels;
private String[] optionalValues;

protected AD(String id, int type, int cardinality) {
this.id = id;
this.type = type;
this.cardinality = cardinality;
}

public int getCardinality() {
return cardinality;
}

public String[] getDefaultValue() {
return defValue;
}

public String getDescription() {
return description;
}

public String getID() {
return id;
}
public String getName() { return name; }
public String[] getOptionLabels() {
return optionalLabels;
}

public String[] getOptionValues() {
return optionalValues;
}

public int getType() {
return type;
}

public String validate(String arg0) {
return null;
}

protected void setOptionLabels(String[] labels) {
optionalLabels = labels;
}

protected void setOptionValues(String[] values) {
optionalValues = values;
}

protected void setDescription(String description) {
this.description = description;
}
protected void setName(String name) { this.name = name; }
protected void setDefaultValue(String[] defValue) { this.defValue = defValue; } }
Listing 3.3: An example AttributeDefinition.


References


OSGi Metatype Service