Besides extending RM with a custom device type, a Device Root Control Unit Provider or a helper application related to it can dynamically provide specific capabilities of the device.
Overview
Device capabilities are defined in JSR 124, "J2EE Client Provisioning" and represent key-value pairs providing information about the features of a device, related to its ability to run specific applications. Device capabilities are generally represented as javax.provisioning.Capabilities (JSR 124). The specific capabilities, returned in the Capabilities's getCapability method, should return a List of String objects.
There are two ways to export device capabilities:
By saving them persistently – Such capabilities are saved as platform properties in the Platform Profiles Database (see the Software Repository documentation for more information about platform properties and profiles). This approach is suitable for capabilities which do not change too often.
By providing capabilities on request from the Device Manager – Such capabilities usually have dynamic nature with their values frequently changing. Dynamic capabilities are usually kept in the runtime memory and may be lost if the cache is overflown.
The APIs for providing capabilities are supported on a backend framework having the management server role. The provider API 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). In addition, you need the JSR 124 bundle from the RM System package (packages/foundation/jsr124.jar) as its exports the javax.provisioning API of JSR 124.
During development, for compilation and for code complete in a Java editor you can also use the JAR files lib/api/gdm-api.jar and lib/api/foundation-api.jar.
Providing Persistent Capabilities
Persistent capabilities are provided usually by a Device Root Control Unit Provider along with the other data related to managed devices. Capabilities are saved by using the DeviceRootSystemContext instance allocated for the provider. Call the updateCapabilities method by providing the following arguments:
cuId– The ID of the device whose capabilities you are savingcapabilities– The capabilities of the devicetype– A number indicating the nature of the capabilities. RM supports the following predefined capabilities types regarding persistent capabilities:1 – The capabilities are exported by the Device Root Control Unit Provider
2 – The capabilities are defined manually by the user from a GUI application such as the console.
You can retrieve all types of capabilities for a device by using the following methods:
getCapabilities– Gets capabilities of a specific type. Here besides "1" and "2" as capabilities type, you can use other types such as "4", which stands for dynamic capabilities. In addition, you can also use a type wildcard(*)filter of the format[prefix]*,*[suffix],[suffix]*[prefix]or*.getAllCapabilities– Gets all capabilities of a device.
Providing Dynamic Capabilities
Dynamic capabilities are exported by a Dynamic Capabilities Provider. In case the device communicates its features with the provider, capabilities may change in accordance with the specifics of the used communication protocol. For example, when using OMA DS or OMA DM capabilities the provider has to wait until the client connects to RM in order to retrieve and update the managed capabilities.
Implement a Dynamic Capabilities Provider
To provide a Dynamic Capabilities Provider to RM, implement the com.prosyst.mprm.backend.ms.cu.spi.root.DynamicCapabilitiesProvider interface.
The provider has two main tasks:
Return device capabilities on request – A Dynamic Capabilities Provider returns a device's capabilities to RM by implementing the
getDynamicCapabilitiesmethod.To reduce data kept in memory, developers can store dynamic capabilities in a special central cache designed for a great number of registered devices.
Usually each device's dynamic capabilities are kept in the runtime memory in a map-like manner. In the case of many devices of the relevant type, the capability provider will have to store a lot of maps, even for devices with identical capabilities. This will lead to high memory consumption.
The memory optimization, offered by the RM dynamic capabilities cache, consists of keeping a common
java.util.Mapfor all devices with the same capabilities. Capabilities are internally represented as identified capabilities, which are designed as objects with simple structure. Capability providers are expected to keep an identified capabilities instance for each device and call it to return contained capabilities on demand. In particular, all identified capabilities instances for devices with identical capabilities point to the same Map. As a result, the overall consumed memory is taken by a few Maps (containing the actual data) and many identified capabilities objects (referencing the Maps).The cache is available through the backend Profile Service
(com.prosyst.mprm.backend.platform.ProfileService).To add a dynamic capability to the cache, call the properaddDynamicIdentifiedCapabilitiesmethod.Register and notify of capability changes - A Dynamic Capabilities Provider can sent events to interested
com.prosyst.mprm.backend.ms.cu.spi.root.CapabilitiesChangeListeners– usually modules of the RM system. The RM will call theaddCapabilitiesChangeListenermethod to register a capabilities change listener and theremoveCapabilitiesChangeListenermethod to unregister it.This release of the GDM package does not receive and process event about capabilities changes as the Generic Device Manager directly calls the responsible Dynamic Capabilities Provider when needed instead of listening for events. The interface for exchanging capabilities change events is preserved mainly for future versions of the Generic Device Manager in which capabilities will be cached.
The code below implements a Dynamic Capabilities Provider: it extends the example Device Root Control Unit Provider introduced in the Extending RM with a New Device Type guide with a Dynamic Capabilities Provider. The capabilities provider simply handles a capability, called "IS ON", equal to the value of the "on" state variable of the device root control unit. The provider keeps a cache with javax.provisioning.Capabilities objects associated with a specific device ID. When the "on" variable changes in result to the "turnOn" or "turnOff" action, the provider updates the capabilities cache. The next time, a management application requires the capabilities of the device, the new Capabilities will be returned.
import java.util.HashSet;import java.util.Hashtable;import java.util.List;import java.util.Set;import java.util.Vector;import javax.provisioning.Capabilities;import com.prosyst.mprm.admin.devices.ControlUnitID;import com.prosyst.mprm.admin.devices.event.ControlUnitEvent;import com.prosyst.mprm.backend.ms.commands.spi.InterpretationResult;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.root.CapabilitiesChangeListener;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext;import com.prosyst.mprm.backend.ms.cu.spi.root.DynamicCapabilitiesProvider;import com.prosyst.mprm.common.ManagementException;public class MyDeviceCUProvider implements DeviceRootControlUnitProvider, DynamicCapabilitiesProvider { private static final String IS_ON = "IS ON"; DeviceRootSystemContext ctx = null; Vector capabListeners; Hashtable capabilities; . . . // Method inherited from DeviceRootControlUnitProvider for serving action requests //through the Operation Manager public void invokeAction(String cuId, String actionId, Object args, final InterpretationResult result) throws Exception { try { // Calling a common method for action execution invokeAction0(cuId, actionId); result.setSuccess(null); } catch(ManagementException e){ result.setError(e.getMessage()); e.printStackTrace(); } } // Method inherited from DeviceRootControlUnitProvider for serving action requests // through the Generic Device Manager public void invokeAction(String cuId, String actionId, Object args, ProviderResult result) { try { invokeAction0(cuId, actionId); } catch (ManagementException e) { result.setResult(null, e); } result.setResult(null, null); } // Helper method which handles a device's state on action invocation private void invokeAction0(String cuId, String actionId) throws ManagementException { ControlUnitID targetCuId = new ControlUnitID(CU_TYPE, cuId); ControlUnitState cuState = (ControlUnitState) ctx.retrieveControlUnitState(targetCuId); if (cuState == null) { throw new ManagementException("[MyDeviceCUProvider] Device does not exist!"); } else { // Changing the "on" state of the device as a result of turnOn or turnOff MyDevice newState = new MyDevice(metatype, targetCuId, this); if (actionId.equals(MyDevice.ON_ACTION_ID)) { newState.setOn(true); ctx.saveControlUnitState(newState); // Updating the dynamic capabilities of the device capabilities.put(targetCuId, createCapabilities(targetCuId)); // Firing an event about the capabilities change fireCapabilityEvent(targetCuId); } else if (actionId.equals(MyDevice.OFF_ACTION_ID)) { newState.setOn(false); ctx.saveControlUnitState(newState); capabilities.put(targetCuId, createCapabilities(targetCuId)); fireCapabilityEvent(targetCuId); } else { throw new ManagementException("[MyDeviceCUProvider] Unsupported action!"); } } } // Method inherited from DeviceRootControlUnitProvider for unregistering a device from RM public void destroyControlUnit(String cuId, ProviderResult result) throws ManagementException { ControlUnitID controlUnitID = new ControlUnitID(CU_TYPE, cuId); . . . ctx.deleteControlUnitState(controlUnitID); . . . // Delete the capabilities of the device and fire an event capabilities.remove(controlUnitID); fireCapabilityEvent(controlUnitID); result.setResult(cuId, null);} // Method inherited from DynamicCapabilitiesProvider // for registering a capabilities change listener public void addCapabilitiesChangeListener(CapabilitiesChangeListener listener) { if (capabListeners == null) { capabListeners = new Vector(); } if (!capabListeners.contains(listener)) { capabListeners.addElement(listener); } } // Method inherited from DynamicCapabilitiesProvider // for returning the dynamic capabilities handled by the provider public Capabilities getDynamicCapabilities(ControlUnitID cuId) throws ManagementException {// If the capabilities cache contains capabilities for this device, return them.// Otherwise, create a new Capabilities object and populate it with the "IS ON" capability if (capabilities.containsKey(cuId)) { return (Capabilities) capabilities.get(cuId); } else { Capabilities deviceCapabs = createCapabilities(cuId); capabilities.put(cuId, deviceCapabs); fireCapabilityEvent(cuId); return deviceCapabs; } }// Creates a Capabilities object with a single capability called // "IS ON", set to the value of the "on" device state variable private Capabilities createCapabilities(final ControlUnitID cuId) throws ManagementException { return new Capabilities() { public List getCapability(String capabilityName) { ControlUnitState state = null; try { state = ctx.retrieveControlUnitState(cuId); } catch (ManagementException e) { e.printStackTrace(); } if (state == null) { return null; } Boolean isOn = (Boolean) state.getStateVariable(MyDevice.ON_VAR_ID); Vector list = new Vector(); list.add(isOn.toString()); return list; } public Set getCapabilityNames() { HashSet set = new HashSet(); set.add(IS_ON); return set; } }; } // Method inherited from DynamicCapabilitiesProvider for // unregistering a listener for capability events public void removeCapabilitiesChangeListener(CapabilitiesChangeListener listener) { if (capabListeners != null) { capabListeners.removeElement(listener); }} // Traverses all added listeners and notifies them // that there is a change in a device's capabilities private void fireCapabilityEvent(ControlUnitID cuId) { if (capabListeners != null) { synchronized (capabListeners) { for (int i = 0, len = capabListeners.size(); i < len; i++ ) { ((CapabilitiesChangeListener) capabListeners.elementAt(i)).capabilitiesChanged(cuId); } } } } . . .}Register the Provider as a Service
Having implemented the Dynamic Capabilities Provider interface, register it as a service on a backend framework with role "management server" with service property "mbs.control.type" (org.mbs.services.cu.ControlConstants.TYPE) holding the device type string that the Dynamic Capabilities Provider handles capabilities of.
The code registers a Dynamic Capabilities Provider a service on the backend.
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.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DynamicCapabilitiesProvider;public class MyCUProviderActivator implements BundleActivator { ServiceRegistration sReg = null; private MyDeviceCUProvider provider; ServiceRegistration capabReg = null; public void start(BundleContext bc) throws Exception { provider = new MyDeviceCUProvider(bc); Hashtable props = new Hashtable(); props.put(ControlConstants.TYPE, MyDeviceCUProvider.CU_TYPE); props.put(ControlConstants.VERSION, MyDeviceCUProvider.CU_VERSION); sReg = bc.registerService(DeviceRootControlUnitProvider.class.getName(), provider, props); // Registering a Dynamic Capabilities Provider service // for devices of type "my.device.cu" Hashtable capabsProps = new Hashtable(1); capabsProps.put(ControlConstants.TYPE, MyDeviceCUProvider.CU_TYPE); capabReg = bc.registerService(DynamicCapabilitiesProvider.class.getName(), provider, capabsProps); } public void stop(BundleContext bc) throws Exception { if (sReg != null) { sReg.unregister(); sReg = null; } if (capabReg != null) { capabReg.unregister(); capabReg = null; } provider = null; }}