This document describes the main principles in providing new types of device components to the RM device management tree.

Component CU Provider Interface

To plug a new component type, usually reflecting the state and capabilities of a specific resource, to a device, you have to implement a Component Control Unit Provider, represented by the com.prosyst.mprm.backend.ms.cu.spi.component.ComponentControlUnitProvider interface.

The Component Control Unit Provider SPI is supported only on a backend framework having the management server role. It is exported by the RM Control Unit Lib bundle (packages/gdm/gdmlib.jar) and is handled by the Control Unit Manager bundle (packages/gdm/cu.jar).

During development, you can also use the lib/api/gdm-api.jar JAR file for compilation and for code complete in a Java editor.

Example of a Component CU Provider

Most of the code examples in this document are taken from a Component Control Unit Provider with the following features:

  • The provider supports control units of type "my.component.cu" with friendly type name "My Component".

  • Each component control unit has state variables "ID", "name" and "number of messages", and an action for echoing a message.

  • Component control units are created with arguments "ID" and "name".


Some sections contain examples, which are extracts from a Component Control Unit Provider implementation for components children of "my.component.cu" instances. The sub component type is "my.subcomponent.cu".

The example Component Control Unit Provider is available as a part of the Control Units Demo (demo/controlunits) distributed with RM. The Component Control Unit Provider is packed in a separate bundle componentproviderdemo.jar located in demo/controlunits/bundles, and its source code available in the demo/controlunits/src under the demo.controlunits.component Java package.

Communicate with the Generic Device Manager

Like in Device Root Control Unit Providers, to be able to use the control unit storage of RM as well as to notify the system of changes in a component control unit, it is recommended that you save the ComponentSystemContext instance passed to the init method of your ComponentControlUnitProvider object.

Specially for component control units, RM provides a transactional storage to each Component Control Unit Provider, inheriting the features of ComponentSystemContext. The transactional storage is in the form of a TransactionalComponentStorage instance, obtained by calling the begin method of ComponentSystemContext. Once having accessed the transactional storage, you can commit or rollback data read/write requests to the storage.

The code below shows an implementation of the init method, which simply saves the ComponentSystemContext instance from the system, and initializes and saves the metatype for the managed component control unit type. Metatype loading is done from an XML within the provider bundle by using the com.prosyst.mprm.util.metatype.MetaTypeProviderInfo utility (refer to the "Provide Component Metadata" section below).

  import java.io.IOException;
  import java.io.InputStream;
  import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentControlUnitProvider;
  import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
  import com.prosyst.mprm.common.ManagementException;
  import com.prosyst.mprm.util.metatype.MetaTypeProviderInfo;
 
  public class MyComponentCUProvider implements ComponentControlUnitProvider {  
    ComponentSystemContext ctx;  
    static final String CU_TYPE = "my.component.cu";  
    static final String CU_VERSION = "1.0.0";  
    private MetaTypeProviderInfo metatype;
       . . .  
 
    public void init(ComponentSystemContext ctx) {
// Save the ComponentSystemContext for later use  
    this.ctx = ctx;    
   // Load the metatype from an XML    
    try {
      if (metatype == null) {
        InputStream xml = MyComponentCUProvider.class.getResourceAsStream("/cu.xml");
        if (xml != null) {
          metatype = new MetaTypeProviderInfo(xml, true);
// Save persistently the metatype
          ctx.addMetadataRecord(CU_VERSION, metatype);
        }
      }
    } catch (ManagementException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }  
  }      
     . . .
}

Provide a Factory for Device Components

In detail, the RM Component Control Unit Provider SPI uses the concept of per-device component factories, which are responsible for handling the component control units within the scope of a single device.

The device type a component type is associated with is indicated by means of a service property when registering the Component Control Unit Provider as a service. See the link from the same topic "Register the Component CU Provider as a Service" section.

A device-specific factory instance should implement the com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory interface and the Component Control Unit Provider should provide it to RM when the provider's getDeviceFactory method is called.

The following example returns device factories, saved in a factory cache. The factories are instances of a class, called MyDeviceFactory, whose methods are further available in the next listings on implementing a device factory.

import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentControlUnitProvider;      
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
import com.prosyst.mprm.common.ManagementException;
 
public class MyComponentCUProvider implements ComponentControlUnitProvider {
       . . .        
public DeviceControlUnitFactory getDeviceFactory(String deviceType, String deviceId)
                                  throws ManagementException {
    MyDeviceFactory fact = new MyDeviceFactory(deviceType, deviceId, ctx);    
    return fact;
  }
        . . .
}


Implementing a device factory is shown below:

import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
      
public class MyDeviceFactory implements DeviceControlUnitFactory {
  String deviceType;
  String deviceId;
  private ComponentSystemContext ctx;  
 
MyDeviceFactory(String deviceType, String deviceId, ComponentSystemContext ctx) {
    this.deviceType = deviceType;
    this.deviceId = deviceId;
    this.ctx = ctx;        
   }
   . . .  
}

A device factory can provide the following features:

  • Create/destroy components on request from a management application through the Generic Device Manager

  • Provide the current state of a component and synchronize this state if required

  • Define parent/child relations with other components

  • Handle queries for the value of a specific state variable

  • Handle invocation of actions

  • Find a specific control unit(s)

Create a Component Control Unit

Creating a component control unit is similar to creating a device control unit. If the provider is going to support user-initiated registration of resources, it should implement the createControlUnit method of the device-specific DeviceControlUnitFactory. If it is not possible to create a component at the moment of request, for example the device is offline or it does not have capacity for new resources, you should indicate an error to the ProviderResult argument.

Optionally, you can benefit from the RM persistent storage to avoid data losses on system restarts. Save the ControlUnitState in the Control Unit Database by calling the saveControlUnitState method of the ComponentSystemContext instance, previously provided by RM in the call to the init method (see "Communicate with the Generic Device Manager" section below)) of the ComponentControlUnitProvider.

If your component control unit is a child of another component unit, consider the following guidelines:

  • Make sure you have defined the parent control unit type in the metatype (see "Provide Component Metadata" section below).

  • To get the ID of the parent unit define it as an argument of the constructor in the control unit metatype so that a management application, such as the console, can pass it to the provider through the Generic Device Manager.

  • In addition, it is recommended that you add a hierarchy record at creating the unit. This is done by using addHierarchyRecord of the ComponentSystemContext instance.


To get automatically the parent ID when using the console, define the parent ID as a constructor argument in the metatype and within the AttributeDefinition of the argument add a <key> tag with name attribute "argument.parent" and value "parent.id"


<attribute modifier="in">
  <name>Parent ID</name>
  <id>parentId</id>
  <description/>
  <type>&string;</type>
  <cardinality>0</cardinality>
  <key name="argument.parent" value="parent.id"/>
</attribute>


In addition, you can also use a <key> tag with name "invisible" and value="true", which will make the console hide the parent ID argument from the user when creating a component control unit.

Finally, to be able to interact efficiently with the RM device management subsystem, it is recommended to notify the system of the newly-created component control unit by calling the controlUnitEvent method of the ComponentSystemContext instance.

The code below implements the createControlUnit method to register new components. It illustrates creating a new component control unit with ID and name as constructor arguments. Next, the unit's state is saved in the Control Unit Database and an event is fired to the RM system. The component state is implemented as a MyComponent object from the code after it.

import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
import com.prosyst.mprm.admin.devices.event.ControlUnitEvent;      
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
import com.prosyst.mprm.common.ManagementException;      
 
public class MyDeviceFactory implements DeviceControlUnitFactory {
  ComponentSystemContext ctx;
     . . .
  public void createControlUnit(String constructor, Object arguments,
                                ProviderResult result) throws ManagementException {
    if (!(arguments instanceof Object[]) || ((Object[]) arguments).length != 2) {
      result.setResult(null, new Exception("Arguments are not valid!"));
    }
    Object[] arguments1 = (Object[]) arguments;
    String id = (String) arguments1[0];
    String name = (String) arguments1[1];
    // Create a new component state and save it
    MyComponent state = new MyComponent(deviceType, deviceId);
    state.setID(id);
    state.setName(name);
    state.setNumberOfMsgs(0);
    ctx.saveControlUnitState(state);
    // Fire an event about a new component control unit
    ControlUnitEvent createEvent = new ControlUnitEvent(ControlUnitEvent.CONTROL_UNIT_ADDED,
                                                        MyComponentCUProvider.CU_TYPE,
                                                        id, deviceType, deviceId);
    ctx.controlUnitEvent(createEvent);
    // Return the state of the new component to RM
    result.setResult(id, null);
  }
     . . .
}


This is the example for implementing a control unit state of a component:

import java.util.Vector;
 
import com.prosyst.mprm.admin.devices.ControlUnitID;        
import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;
    
public class MyComponent implements ControlUnitState {
 
  private String name = "";
  static final String NAME_VAR_ID = "name";
  private String id = "";
  static final String ID_VAR_ID = "id";
  private int numberOfMsgs = 0;
  static final String COUNT_VAR_ID = "msgCount";
  private String deviceId;
  private String deviceType;
  private Vector stateInterface;
 
  MyComponent(deviceType, deviceId) {
    this.deviceType = deviceType;
    this.deviceId = deviceId;
  }
 
  // Methods inherited from ControlUnitState
  public ControlUnitID getControlUnitID() {
    return new ControlUnitID(MyComponentCUProvider.CU_TYPE, id, deviceType, deviceId);
  }
 
  public String[] getStateInterface() {
    String[] result = new String[stateInterface.size()];
    stateInterface.copyInto(result);
    return result;
  }
 
  public Object getStateVariable(String stateVarId) {
    if (stateVarId.equals(COUNT_VAR_ID)) {
      return new Integer(numberOfMsgs);
    } else if (stateVarId.equals(ID_VAR_ID)) {
      return id;
    } else if (stateVarId.equals(NAME_VAR_ID)) {
      return name;
    }
    return null;
  }
 
  // Collects the state variable IDs of the component when some of them is set
  private void addInStateInterface(String sv) {
    if (stateInterface == null) {
      stateInterface = new Vector();
    }
    if (!stateInterface.contains(sv)) {
      stateInterface.addElement(sv);
    }
  }
 
  // Setter methods for the component's state
  void setNumberOfMsgs(int numberOfMsgs) {
    this.numberOfMsgs = numberOfMsgs;
    addInStateInterface(COUNT_VAR_ID);
  }
 
  void setID(String id) {
    this.id = id;
    addInStateInterface(ID_VAR_ID);
  }
 
  void setName(String name) {
    this.name = name;
    addInStateInterface(NAME_VAR_ID);
  }
}


The code below illustrates creating a component, which has a parent control unit:

import com.prosyst.mprm.admin.devices.ControlUnitID;
import com.prosyst.mprm.admin.devices.event.ControlUnitEvent;
import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;
import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory
import com.prosyst.mprm.common.ManagementException;
      
public class MySubDeviceFactory implements DeviceControlUnitFactory {
  ComponentSystemContext ctx;
         . . .
  public void createControlUnit(String constructorId, Object arguments,
                                final ProviderResult result) throws ManagementException {      
 
    Object[] arguments1 = (Object[]) arguments;
    // Get the ID of the parent component as the first constructor argument
    final String parentId = (String) arguments1[0];
    final String id = (String) arguments1[1];
 
    // Create a state for the new component
    ControlUnitState cuState = new ControlUnitState() {
      public ControlUnitID getControlUnitID() {
        return new ControlUnitID(MySubComponentProvider.CU_TYPE, id, deviceType, deviceId);
      }
 
      public String[] getStateInterface() {
        return new String[]{"id"};
      }
 
      public Object getStateVariable(String sVarId) {
        return id;
      }
    };
    // Save persistently the state of the new component
    ctx.saveControlUnitState(cuState);
    // Save the relation parent component - child component
    ctx.addHierarchyRecord(deviceType, deviceId, PARENT_CU_TYPE, parentId, id);
    // Fire an event about the newly-created component control unit
    ctx.controlUnitEvent(new ControlUnitEvent(ControlUnitEvent.CONTROL_UNIT_ADDED,
                                              MySubComponentProvider.CU_TYPE, id, deviceType, deviceId));
    // Return the component ID to RM
    result.setResult(id, null);
    }
         . . .
}


Destroy a Component Control Unit

The destroyControlUnit method is called when a request to delete a component has been made through the Generic Device Manager. The Component Control Unit Provider is expected to remove the component from the RM storage, to remove added hierarchy records and to fire an event. Finally, set the control unit ID as a string to the ProviderResult object.

Below is provided a simple example of the steps to take when deleting a component control unit. It implements the destroyControlUnit method to delete components from RM.

import com.prosyst.mprm.admin.devices.ControlUnitID;
import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;
import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;      
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
import com.prosyst.mprm.common.ManagementException;
            
public class MyDeviceFactory implements DeviceControlUnitFactory {
  String deviceType;
  String deviceId;
  private ComponentSystemContext ctx;
       . . .
  public void destroyControlUnit(String cuId, ProviderResult result)
              throws ManagementException {  
    // Delete the state of the component control unit
    ctx.deleteControlUnitState(new ControlUnitID(MyComponentCUProvider.CU_TYPE, cuId,
                                                 deviceType, deviceId));
    // Fire an event about destroying the component control unit
    ControlUnitEvent destroyEvent = new ControlUnitEvent(ControlUnitEvent.CONTROL_UNIT_REMOVED,
                                                         MyComponentCUProvider.CU_TYPE, cuId,
                                                         deviceType, deviceId);
    ctx.controlUnitEvent(destroyEvent);
    // Indicate that the operation is complete
    result.setResult(null, null);
  }
     . . .
}

List All Managed Control Units

The listControlUnits method should list all component control units existing in the scope of the device. The result should be in the form of a String[] object and should be set to the ProviderResult method argument.

The following example implements the listControlUnits methods by simply calling the getControlUnits method, implemented in the code above. Providing null for the parent type and ID will make the getControlUnits method return all components in the device's scope.

import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
import com.prosyst.mprm.common.ManagementException;
            
public class MyDeviceFactory implements DeviceControlUnitFactory {
        . . .          
  public void listControlUnits(ProviderResult result) throws ManagementException {
    getControlUnits(null, null, result);  
  }
        . . .
}  

Provide the Proper Component State

When a request from a management application comes for getting the actual state of a device component through the Generic Device Manager, the getControlUnit method of the device factory is called. The provider should return a ControlUnitState instance reflecting the state of the device component.

The code below contains a simple implementation of the getControlUnitState method, which retrieves the state from the RM Control Unit Database. The provider is implemented to save each state variable change, i.e. each state change, in the RM storage.

import com.prosyst.mprm.admin.devices.ControlUnitID;
import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;
import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;    
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;    
 
import com.prosyst.mprm.common.ManagementException;
 
public class MyDeviceFactory implements DeviceControlUnitFactory {
  String deviceType;      
  String deviceId;      
  private ComponentSystemContext ctx;
        . . .
  public void getControlUnitState(String cuId, ProviderResult result)
              throws ManagementException {
    // Get the component's state from the RM database
    ControlUnitState cu = ctx.retrieveControlUnitState(new ControlUnitID(MyComponentCUProvider.CU_TYPE,
                                                                         cuId, deviceType, deviceId));
    // Return the component's state to RM
    result.setResult(cu, null);
  }
        . . .
}

Define Hierarchy Relations

Device components that a Component Control Unit Provider implements may be defined as children of another component control units.

It is not needed to specify a relation for the ID and type of the parent device in an hierarchy relation. This is done by means of a service property on provider service registration.

The factory should provide the following information regarding parent-child relations:

  • Provide the IDs of all control units, children of a specific control unit, managed by the provider. This feature can be implemented in the getControlUnits method of the device factory. You can simply call RM to get the child control units of the specified parent.

    • For components which are directly attached to their parent devices, such as "my.component.cu", and do not have parent components, implement the getControlUnits methods by simply listing available control units from the RM storage. For getting such "root" components the system will pass null in the provider getControlUnits method as parent type and ID arguments.

    • For components which can be roots as well as subcomponents, support hierarchy records for the root case (the system will call the getControlUnits method with null parent arguments) as well as for the "sub" case (the system will call the getControlUnits method with non-null parent arguments).

      import com.prosyst.mprm.admin.devices.ControlUnitID;
      import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
      import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
      import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;    
      import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;    
      import com.prosyst.mprm.common.ManagementException;
              
      public class MySubDeviceFactory implements DeviceControlUnitFactory {
       
        private static final String PARENT_CU_TYPE = "my.component.cu";
        private String deviceId;
        private String deviceType;
        private ComponentSystemContext ctx;    
           . . .    
        public void getControlUnits(String parentCUType, String parentCUId,
          ProviderResult result) throws ManagementException {
          // Get the children of the specified parent from the RM storage.
          String[] ids = ctx.retrieveSubControlUnits(new ControlUnitID(parentCUType,
                                                                       parentCUId, deviceType,
                                                                       deviceId),
                                                     MySubComponentProvider.CU_TYPE);
          // Return the child IDs to RM
          result.setResult(ids, null);
        }
           . . .
      }



  • Provide the IDs of the control units of a particular type, parents of a specific control unit. This feature can be implemented in the getParents method of the device factory. You can simply call the ComponentSystemContext to retrieve the parents of the specified component.

    import com.prosyst.mprm.admin.devices.ControlUnitID;
    import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
    import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
    import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
    import com.prosyst.mprm.common.ManagementException;
     
    public class MySubDeviceFactory implements DeviceControlUnitFactory {
       . . .
      public void getParents(String childCUId, String parentCUType, ProviderResult result)
                  throws ManagementException {
        // Get the parents of the specified component from RM.
        String[] parentIds = ctx.retrieveParentControlUnits(new ControlUnitID(MySubComponentProvider.CU_TYPE,
                                                                             childCUId, deviceType, deviceId),
                                                            parentCUType);
       // Return the parent IDs to RM
        result.setResult(parentIds, null);
      }
    . . .
    }



Support State Variable Query

In the cases where a management application is interested only in the value of a particular component property, represented as a state variable, the Generic Device Manager will call the queryStateVariable method of the device-specific factory. The provider should return the value of the variable, obeying the correct value type, in the passed ProviderResult object.

The following example contains an implementation of the queryStateVariable method, which gets the state of the component from the RM Control Unit Database and calls the state's getStateVariable method to get the value of the requested state variable. The provider is implemented to save each state change in the RM database.

import com.prosyst.mprm.admin.devices.ControlUnitID;
import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;
import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
import com.prosyst.mprm.common.ManagementException;
 
public class MyDeviceFactory implements DeviceControlUnitFactory {
       . . .    
  public void queryStateVariable(String cuId, String sVarId, ProviderResult result)
              throws ManagementException {
    // Get the component's state from the RM database.
    ControlUnitState cu =
      (ControlUnitState) ctx.retrieveControlUnitState(new ControlUnitID(MyComponentProvider.CU_TYPE,
                                                                        cuId,
                                                                        deviceType,
                                                                        deviceId));
    if (cu != null) {
      // Get the state variable's value and return it to RM
      result.setResult(cu.getStateVariable(sVarId), null);
    } else {
      result.setResult(null, new Exception("Component does not exist!"));
    }
  }
       . . .
}

Synchronize RM with the Actual Component State

The Generic Device Manager can force synchronization of a component's state in two cases:

  • When a management application wants to synchronize only the state of a specific component control unit. The Generic Device Manager will call the synchronizeState method of your device factory. In return, the provider should contact the device and get the required information. The ProviderResult should contain a ControlUnitState with the synchronized device properties.

  • When a management application wants to synchronize the state of the entire device including all its components. The Generic Device will call the synchronizeDevice method of the device factory. The factory is expected to update all component states from the device and return synchronized information on later requests. Set null as result to the ProviderResult argument.

Support Component Searching

If your provider will support looking for control units on part of a management application, that is, the provider will support one or more finder actions, implement the findControlUnits method. When the search operation is complete, set as a result in the ProviderResult argument the string IDs of the components satisfying the search criteria.

Support Action Invocation

From the Generic Device Manager

When a management application invokes an action on a component control unit by using the Generic Device Manager (see System-Wide Device Management), the RM system locates the proper Component Control Unit Provider and invokes the provider's invokeAction(String controlUnitId, String actionId, Object arguments, ProviderResult result) method. Hence, if the control units managed by your provider support some actions, in the body of this method implement the operations for sending the corresponding command to the device. If an action returns some output, set it to the passed ProviderResult instance. If the action does not have an output, set null as result in ProviderResult.

The example below supports action invocation from the Device Manager. It implements the invokeAction method, dedicated to calls from the Generic Device Manager, adding support of the echo action – the action increments the value of the "number of messages" state variable of the target component control unit, saves the change in the RM storage, fires a change event and returns the echoed message to RM.

import org.osgi.service.metatype.MetaTypeProvider;
import com.prosyst.mprm.admin.devices.ControlUnitID;
import com.prosyst.mprm.admin.devices.event.StateVariableEvent;      
import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;
import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
import com.prosyst.mprm.common.ManagementException;
 
public class MyDeviceFactory implements DeviceControlUnitFactory {        
  String deviceType;
  String deviceId;
  private ComponentSystemContext ctx;
         . . .
  public void invokeAction(String cuId, String actionId, Object arguments, ProviderResult result) {
    try {
      // Prepare an object to accept the echoed message
      StringBuffer output = new StringBuffer();
      // Echo  the message
      invokeAction0(cuId, actionId, arguments, output);
      // Return the echoed message to the system
      result.setResult(output.toString(), null);
    } catch (Exception e) {
      result.setResult(null, e);
    }
  }
 
  // Helper method for echoing a message
  private void invokeAction0(String cuId, String actionId, Object arguments,
                             StringBuffer output) throws Exception {
    if (actionId.equals("printMsg")) {
      ControlUnitState stateDb = ctx.retrieveControlUnitState(new ControlUnitID(MyComponentCUProvider.CU_TYPE, cuId,
                                                                                deviceType, deviceId));
      if (arguments instanceof String) {
        // Increment the number of echoed messages
        int numberOfMsgs = incrementNumberOfMessages(stateDb);
        // Prepare the echoed message
        String outputMsg = "Message processed : ";
        output.append(outputMsg);
        output.append((String) arguments);
        // Update the component's state with the changed number of messages
        MyComponent state = new MyComponent(deviceType, deviceId);
        state.setID(cuId);              
        // Not setting the whole CU state, but only the changed variable
        state.setNumberOfMsgs(numberOfMsgs);
        ctx.saveControlUnitState(state);
        // Fire an event that the number of messages has been changed
        ctx.stateVariableChanged(new StateVariableEvent(
               MyComponentCUProvider.CU_TYPE, cuId, deviceType,
                  deviceId, MyComponent.COUNT_VAR_ID, new Integer(numberOfMsgs)));
      } else {
        throw new Exception("Invalid argument type");
      }
    } else {
      throw new Exception("Action is not supported!");
    }
  }
  
  // Helper method for incrementing the number of echoed messages
  int incrementNumberOfMessages(ControlUnitState stateDb) {
    // Get the number of messages echoed so far
    int numberOfMsgs = ((Integer)stateDb.getStateVariable(MyComponent.COUNT_VAR_ID)).intValue();
    // Increase the number of messages
    numberOfMsgs++;
    return numberOfMsgs;
  }
           . . .
}  


Provide Component Metadata

Implement a Metatype Provider

To describe the interface of the provided component control unit type to RM and management applications, the Component Control Unit Provider should return a Metatype Provider (org.osgi.service.metatype.MetaTypeProvider) in its getMetaType method.

The Metatype Provider should export metadata with AttributeDefinitions for state variables and extended ObjectClassDefinitions for actions.

Basically, there are two ways for providing a component Metatype Provider:

  • By implementing all required interfaces from the OSGi Metatype API (org.osgi.service.metatype) and from the (org.mbs.services.metatype) Bosch Digital Metatype Extension API.

  • By writing a metadata XML following the Bosch Digital defined DTD and converting it to a Metatype Provider through the com.prosyst.mprm.util.metatype.MetaTypeProviderInfo utility (available in the System Package).


The code below returns the MetaTypeProvider instance generated from the metadata XML in the example following this one through the MetaTypeProviderInfo utility.

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
 
import org.osgi.service.metatype.MetaTypeProvider;  
import com.prosyst.mprm.util.metatype.MetaTypeProviderInfo;
import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentControlUnitProvider;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
import com.prosyst.mprm.common.ManagementException;      
 
public class MyComponentCUProvider implements ComponentControlUnitProvider {
  static final String CU_VERSION = "1.0.0";
  static final String CU_TYPE = "my.component.cu";
  private ComponentSystemContext ctx;
       . . .
  public void getMetaType(String cuVersion, ProviderResult result)
              throws ManagementException {  
    // Get the metatype from the RM storage and return it to RM
    MetaTypeProvider metatype = ctx.getMetadataRecord(CU_TYPE, CU_VERSION);
    if (mtp == null) {
      mtp = loadMetatype();
    }
    result.setResult(metatype, null);
  }
 
  private MetatypeProvider loadMetatype() throws ManagementException {
    try {
      URL mtpURL = bc.getBundle().getResource("component.xml");
      InputStream in = mtpURL.openStream();
      MetaTypeProviderInfo mtp = new MetaTypeProviderInfo(in);
      cuSystemContext.addMetadataRecord(CU_VERSION, mtp);
      return mtp;
    } catch (Exception e) {
      throw new ManagementException(e);
    }
  }
      . . .
}


Here is the code providing a metadata XML for a component control unit type.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE metatype-provider SYSTEM "metatype.dtd">
<metatype-provider>
  <objectclass>
    <locale>en</locale>
    <name>My Component</name>
    <id>my.component.cu</id>
    <description/>
    <attribute modifier="req" load="true">
      <name>Component ID</name>
      <id>id</id>
      <description/>
      <type>&string;</type>
      <cardinality>0</cardinality>
    </attribute>
    <attribute modifier="req">
      <name>Component Display Name</name>
      <id>name</id>
      <description/>
      <type>&string;</type>
      <cardinality>0</cardinality>
    </attribute>
    <attribute modifier="req">
      <name>Number of Printed Message</name>
      <id>msgCount</id>
      <description/>
      <type>&int;</type>
      <cardinality>0</cardinality>
      <value>
        <scalar>0</scalar>
      </value>
    </attribute>
    <objectclass>
      <locale>en</locale>
      <name>Create My Component</name>
      <id>$create.</id>
      <description/>
      <attribute modifier="in">
        <name>ID</name>
        <id>id</id>
        <description/>
        <type>&string;</type>
        <cardinality>0</cardinality>
      </attribute>      <attribute modifier="in">
        <name>Name</name>
        <id>name</id>
        <description/>
        <type>&string;</type>
        <cardinality>0</cardinality>
      </attribute>    </objectclass>    <objectclass>
      <locale>en</locale>
      <name>Delete Component</name>
      <id>$destroy</id>
      <description/>
    </objectclass>
    <objectclass>
      <locale>en</locale>
      <name>Print Test Message</name>
      <id>printMsg</id>
      <description/>
      <attribute modifier="in">
        <name>Test Message</name>
        <id>inMsg</id>
        <description/>
        <type>&string;</type>
        <cardinality>0</cardinality>
      </attribute>
      <attribute modifier="out">
        <name>Output Message</name>
        <id>outMsg</id>
        <description/>
        <type>&string;</type>
        <cardinality>0</cardinality>
      </attribute>
    </objectclass>   </objectclass>
</metatype-provider>


Define of a Parent

To define one or more parent types of a component, include an AttributeDefinition within the root ObjectClassDefinition for attribute with ID mbs.control.parent.type, type String or String[] and default value equal to the parent type(s).

The code below contains an XML AttributeDefinition of two parent types - "my.component1.cu" and "my.component2.cu".

<attribute modifier="req">
  <name>ParentType</name>
  <id>mbs.control.parent.type</id>
  <description>Parent Types</description>
  <type>&string;</type>
  <cardinality>2</cardinality>
  <value>
    <array>
      <scalar>my.component1.cu</scalar>
      <scalar>my.component2.cu</scalar>
    </array>
  </value>
</attribute>

Inherit a Superior Metatype

If a component control unit type is designed to inherit another type, that is, acquire all attributes of its "superior", the control unit metatype should contain an attribute with ID "super", type String and default value indicating the super type ID. If you want to exclude one or more actions of a super type, redefine the action with an "!" in front of the ID string.

The following example contains an XML AttributeDefinition of a super metatype called "my.generic.cu".

<attribute modifier="req">
  <name>Super Type</name>
  <id>super</id>
  <description>Super Type</description>
  <type>&string;</type>
  <cardinality>1</cardinality>
  <value>
    <array>
      <scalar>my.generic.cu</scalar>
    </array>
  </value>
</attribute>

Provide Icons for the Components

To provide icons that will represent each component of the same type and its actions, you need to implement the getIcon method. Depending on the passed object class ID and on the component control unit version, you can provide different icons.

Basically, you can retrieve icons from a file or from the system-provided control unit storage. To handle icons in the CU storage, use the addIcon, getIcon and deleteIcons methods of the provider's ComponentSystemContext.

The code below returns a single icon common to the component type and to all its actions. For a more detailed example on providing icons in a Control Unit Provider, check the example in the "Support Action Invocation" > "From the Generic Device Manager" subsections of the Extending RM with a New Device Type guide.

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.osgi.framework.BundleContext;
import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentControlUnitProvider;
      
public class MyComponentCUProvider implements ComponentControlUnitProvider {
  private BundleContext bc;
       . . .    
  public void getIcon(String cuVersion, String ocdId, int size, ProviderResult result)
              throws ManagementException {
    // Get the icon's location in the provider's bundle JAR
    URL iconURL = bc.getBundle().getEntry("/component.png");
    InputStream iconStream = null;
    try {
      iconStream = iconURL.openStream();
    } catch (IOException e) {
      result.setResult(null, e);
    }
    // Return the stream to the icon to RM
    result.setResult(iconStream, null);
  }
       . . .
}


Destroy Control Units on Device Remove

As component control units are associated with a parent device, when a management application removes the device from RM, the system requests all involved Component Control Unit Providers to clean all related components. In this case, the system will calls the deviceRemoved method of your Component Control Unit Provider. The conventional actions to take are delete all control unit states, delete all parent-child relations within the scope of the deleted device and fire an event about each component's removal.

It is responsibility of the provider to clean all related component data from the database on removing a device. In this case, RM does not check if the database contains records associated with the device being unregistered – the system expects the provider to do so and take the proper actions.

The couple if listings below contain an example of how to clear all components attached to a specific device. The deviceRemoved method of the example Component Control Unit Provider gets the factory for the device being removed and calls a specific method of the factory, which deletes the states of the components attached to the device and fires proper events:

import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentControlUnitProvider;
        
public class MyComponentCUProvider implements ComponentControlUnitProvider {
 
       . . .    
  public void deviceRemoved(String deviceType, String deviceId) {
    MyDeviceFactory fact = new MyDeviceFactory(deviceType, deviceId, ctx);    fact.destroyAll();
  }
      . . .
}

Clearing all related components on device remove is shown in the following example:

import com.prosyst.mprm.admin.devices.ControlUnitID;
import com.prosyst.mprm.admin.devices.event.ControlUnitEvent;
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentSystemContext;
import com.prosyst.mprm.backend.ms.cu.spi.component.DeviceControlUnitFactory;
 
public class MyDeviceFactory implements DeviceControlUnitFactory {
  ComponentSystemContext ctx;
  String deviceType;
  String deviceId;
       . . .    
  public void destroyAll() {
    try {
      // Get all component in the scope of the device from the RM storage
      String[] ids = ctx.getControlUnits(deviceType, deviceId, MyComponentCUProvider.CU_TYPE);
      // Delete the state of each component and fire an event
      for (int i = 0; i < ids.length; i++ ) {
        ctx.deleteControlUnitState(new ControlUnitID(MyComponentCUProvider.CU_TYPE,
                                                     ids[i], deviceType, deviceId));
        ControlUnitEvent destroyEvent = new ControlUnitEvent(ControlUnitEvent.CONTROL_UNIT_REMOVED,
                                                             MyComponentCUProvider.CU_TYPE, ids[i],
                                                             deviceType, deviceId);
        ctx.controlUnitEvent(destroyEvent);
      }
    } catch (ManagementException e) {
      e.printStackTrace();
    }
  }
       . . .
}

Register the Component CU Provider as a Service

After implementing the ComponentControlUnitProvider interface, you need to register it as an OSGi-compliant service on the RM backend hosts with the management server role. The service should have the following properties:

  • "mbs.control.type" (org.mbs.services.cu.ControlConstants.TYPE) – Shows the type of components this Component Control Unit Provider service handles.

  • "mprm.cu.device.type" (ComponentControlUnitProvider.DEVICE_TYPE) – Shows the type of devices that the component control unit type is valid for.

  • "mbs.control.version" (org.mbs.services.cu.ControlConstants.VERSION) – Optional. Indicates the supported component type version in case versioning is supported.


The example belowis a bundle activator, which registers and unregisters the example Component Control Unit Provider as a service on a backend framework. The registration properties indicate that the provider handles components of type "my.component.cu" and version "1.0" for devices of type "my.device.cu" (the Extending RM with a New Device Type guide contains the code of the "my.device.cu" provider).

import java.util.Hashtable;
 
import org.mbs.services.cu.ControlConstants;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
 
import com.prosyst.mprm.backend.ms.cu.spi.component.ComponentControlUnitProvider;
    
public class MyComponentCUActivator implements BundleActivator {
          
  ServiceRegistration sReg;
 
  public void start(BundleContext bc) throws Exception {
    Hashtable props = new Hashtable();
    props.put(ComponentControlUnitProvider.DEVICE_TYPE, "my.device.cu");
    props.put(ControlConstants.TYPE, MyComponentCUProvider.CU_TYPE);
    props.put(ControlConstants.VERSION, MyComponentCUProvider.CU_VERSION);
    MyComponentCUProvider provider = new MyComponentCUProvider(bc);
    sReg = bc.registerService(ComponentControlUnitProvider.class.getName(), provider, props);
  }
 
  public void stop(BundleContext bc) throws Exception {
    if (sReg != null) {
      sReg.unregister();
      sReg = null;
    }
  }
}


References