Writing about technology, traveling, politics and more

Wednesday, January 28, 2009

Developing your first context sensor plug-in - Tutorial 8

This tutorial builds on the foundations defined in the previous one, which described the use of the MUSIC Context Model, in order to describe the process needed to develop a Context Plug-in. These tutorials are part of a series which aims to first introduce OSGi and declarative services and then the MUSIC Context System.

What are the Context Plug-ins?

In Tutorial 6, we introduced the MUSIC Context System architecture and argued that one of its main advantages is the fact that it allows to develop context producers and context consumers independently. Furthermore, as the sources and the sinks are independent, it is possible to reuse the same plug-ins (i.e., sources) to satisfy the needs of multiple context clients (i.e., sinks).

Furthermore, as the plug-ins define their provided (and potentially required) context types in a well-defined, Ontology-backed manner, it is quite possible that the same plug-ins are reused by multiple developers for the construction of various applications and even, sometimes, across various platforms. On the other hand, again as the context clients express their needs in the same well-defined, ontology-backed manner, it is quite possible that the same clients accept context input from various plug-ins, depending on the availability (and resource consumption profile) of the latter.

Finally, this architecture has the additional benefits of allowing automatic resolution of plug-ins (only those which have no needs for context input or those of which their input is provided are resolved)
A more scientific discussion of the nature of the context plug-ins, along with a description of the intelligent resolution and activation mechanism are described in [1].

So, the answer to the question of the header, in a few words, is the following: "Context plug-ins are custom OSGi bundles which are designed to produce context data, perhaps by combining lower-level, more elementary context types or simply by wrapping hardware or OS-based sensors."

Anatomy of a Context Plug-in

As discussed already, Context Plug-ins are first and foremost OSGi bundles. Furthermore, these bundles have the special characteristic that they are discoverable via Declarative Services and they also implement the IContextPlugin interface. Most importantly, this interface specifies two custom life-cycle operations, activate and deactivate, which are automatically controlled by the middleware.

In principle a context plug-in, like all other OSGi components adhering to the Declarative Services standard, consists of the following parts:
  1. A bundle Manifest descriptor, defining its name, the imported and exported Java packages of the bundle and finally a reference to the service descriptor XML file.
  2. The service descriptor XML file that specifies the service provided by the plug-in (fixed to IContextPlugin) and the properties characterizing the context types provided and required by the plug-in.
  3. The actual code implementing the Context Plug-in. In its simplest form, this code implements the IContextPlugin interface (possibly by extending one of the abstract implementations available). In practice, the code needs to respond to life-cycle commands as instructed by the middleware with the purpose of starting (and stopping) the generation of context data (encapsulated in context events).
This structure is illustrated in the following class diagram:

Structure of the memory context plug-in and utilization of core classes

In this class diagram, we illustrate the three parts comprising a typical context plug-in measuring the memory use in the device. Notably, this plug-in extends an AbstractContextPlugin class provided by the core context middleware, and which automates a portion of the required functionality. The exact implementation of the memory context plugin are discussed in more detail further on.

An important detail is the extended life-cycle of the context plug-in components. In particular, while active the context plug-ins can transition among three possible states:
  • C_INSTALLED: In this state, the plug-in is simply installed meaning that it has not been resolved. To be resolved, a context plug-in must either have no context types required or it must require context types provided by already resolved plug-ins.
  • C_RESOLVED: In this state, the context plug-in has resolved its dependencies and can be started at any time. The only reason for a plug-in to be resolved but not started is when there is no current need for its provided context type, in which case the plug-ins stays inactive to avoid unnecessary resource consumption.
  • C_ACTIVE: This is the third and final possible state for the plug-ins, which indicates that it is resolved and active. In this state, and only in this, the plug-ins are active and generate context events.
Notably, these states are triply nested in each other: C_INSTALLED contains C_RESOLVED which itself contains C_ACTIVE. This is also illustrated in the following state diagram:

Context Plug-ins: Extended OSGi Component Lifecycle

While transitioning into (or out of) the C_ACTIVE state, the activate (or deactivate) method of the corresponding plug-in is invoked. To illustrate all these concepts, we here describe the development of a memory context plug-in step-by-step.

Developing the Memory Context Plugin

Similar to developing a normal OSGi bundle component, the first two steps are to define the Manifest file and the service descriptor. In the case of the memory context plug-in, the Manifest is defined as follows:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Memory sensor plug-in for the MUSIC Context System
Bundle-SymbolicName: MemorySensor
Bundle-Version: 1.0.0
Bundle-ClassPath: .
Import-Package: org.istmusic.mw.context.plugins,
org.istmusic.mw.context.events,
org.istmusic.mw.context.model.api,
org.istmusic.mw.context.model.impl,
org.istmusic.mw.context.util.scheduler,
cy.ac.ucy.cs.osgi.tutorial7.memory_model
Service-Component: OSGI-INF/MemorySensor.xml


As you can see, this Manifest is identical to any other OSGi bundle-describing Manifest file. It does not export any packages at all, but it imports a few. Notably, the set of imported packages cannot be typically defined a priori. First, the actual sensor plugin must be coded, and then any packages, beyond the standard ones, which are needed in the code must be added to the "Import-Package" declaration. As you have probably noticed already, this sensor imports the memory_model package that was defined in the previous tutorial. The reason for this is that we will leverage that model for encoding the sensed context information. Finally, the Manifest file points to the service descriptor of the plugin (in this case the "MemorySensor.xml"), which is described in the following.

<?xml version="1.0"?>

<component name="MemorySensor" immediate="true">

<implementation class="cy.ac.ucy.cs.osgi.tutorial8.MemorySensor"/>

<service>
<provide interface="org.istmusic.mw.context.plugins.IContextPlugin"/>
</service>

<property name="PROVIDED_CONTEXT_TYPES" value="memory"/>
<property name="PROVIDED_CONTEXT_TYPE_ENTITY[memory]" value="#concepts.entities.device|this"/>
<property name="PROVIDED_CONTEXT_TYPE_SCOPE[memory]" value="#concepts.scopes.resources.memory"/>

</component>

Again, the service descriptor is similar to those used in the development of typical OSGi components, as it was described in the second tutorial. The "implementation" element defines the absolute path of the implementing class (which must be a sub-type of IContextPlugin). By default, all context plug-ins must export the IContextPlugin service, which is achieved with the "service" XML element as shown above. In addition to these however, context plug-ins also define a number of properties. These properties describe the provided and the required context types of the plug-in. In this example, only provided types are defined but in other cases (i.e., when developing a context reasoner plug-in), required context types can also be defined.

The format of the properties is as follows:
  • PROVIDED_CONTEXT_TYPES: This property contains a space-separated list of names denoting what context types are provided by the plug-in. For example, a possible assignment could be "p1 p2", where the "p1" and the "p2" types are defined as provided. For each provided type defined in this property, an entity and a scope must also be defined, using the following two parameters.
  • PROVIDED_CONTEXT_TYPE_ENTITY[p]: This property must be defined for each context type "p" that has been specified in the provided context types property. The value of this property should be a string corresponding to the actual value of the entity. For example, in the case of the "memory" context type, the entity is the device itself (i.e., "#concepts.entities.device|this").
  • PROVIDED_CONTEXT_TYPE_SCOPE[p]: Similar to the entity property, thus one must be defined for each context type "p" that has been specified in the provided context types property. The value of this property should be a string corresponding to the actual value of the scope. For example, in the case of the "memory" context type, the scope is the memory resource (i.e., "#concepts.scopes.resources.memory").
It should be noted that besides these three properties, the following three are also possible:
  • REQUIRED_CONTEXT_TYPES: This property contains a space-separated list of names denoting what context types are required by the plug-in. For example, a possible assignment could be "r1 r2", where the "r1" and the "r2" types are defined as required ones. For each required context type defined in this property, an entity and a scope must also be defined, using the following two parameters.
  • REQUIRED_CONTEXT_TYPE_ENTITY[r]: This property must be defined for each context type "r" that has been specified in the required context types property. The value of this property should be a string corresponding to the actual value of the entity.
  • REQUIRED_CONTEXT_TYPE_SCOPE[r]: Similar to the entity property, thus one must be defined for each context type "r" that has been specified in the required context types property. The value of this property should be a string corresponding to the actual value of its scope.
Required context types are communicated to the context plug-in automatically (by the middleware), as it will be described later on.

Finally, to finish with the development of the context plug-in, we define its actual implementation in code. As mentioned already, the implementing class must be a sub-type of IContextPlugin. In this regard, the context plugin can either implement the IContextPlugin interface directly, or it can extend the AbstractContextPlugin class, as illustrated in the context plugins class diagram. The code of the plugin is listed in the following:

package cy.ac.ucy.cs.osgi.tutorial8;

import org.istmusic.mw.context.plugins.AbstractContextPlugin;
import org.istmusic.mw.context.util.scheduler.RecurringEvent;
import org.istmusic.mw.context.util.scheduler.Scheduler;
import cy.ac.ucy.cs.osgi.tutorial7.memory_model.MemoryContextElement;

import java.util.logging.Logger;

public class MemorySensor
extends AbstractContextPlugin
implements Runnable
{
private final Logger logger = Logger.getLogger(MemorySensor.class.getCanonicalName());

public static final String MEMORY_PLUGIN_ID = "Memory plugin";

public static final long MIN_WAIT_TIME = 500L; // 0.5 sec

public static final long INTERVAL = MemoryContextElement.MILLISECONDS_BEFORE_EXPIRY / 2;

private final RecurringEvent recurringEvent = new RecurringEvent(this, MIN_WAIT_TIME, INTERVAL);

private final double totalMemory;

public MemorySensor()
{
super(MEMORY_PLUGIN_ID);

logger.info("MemorySensor: constructed");
this.totalMemory = Runtime.getRuntime().totalMemory();
}

private final Scheduler scheduler = Scheduler.getInstance();

/** Automatically invoked by the context middleware when the plugin must be activated. */
public void activate()
{
logger.info("c_activating: " + this);
scheduler.scheduleEvent(recurringEvent);
}

/** Automatically invoked by the context middleware when the plugin must be deactivated */
public void deactivate()
{
logger.info("c_deactivating: " + this);
scheduler.removeRecurringEvent(recurringEvent);
}

public void run()
{
// acquire memory measurements
final long availableMemory = Runtime.getRuntime().freeMemory();
final double memoryLoad = 1d - (availableMemory / totalMemory);

// generate memory context element
MemoryContextElement memoryContextElement
= new MemoryContextElement(MEMORY_PLUGIN_ID, availableMemory, memoryLoad);

// fire context change event
logger.info("MemorySensor: firing event: " + memoryContextElement);
fireContextChangedEvent(this, memoryContextElement);
}

public String toString()
{
// produce a human-readable string
return MEMORY_PLUGIN_ID;
}
}
The first thing to note here is the fact that the class uses the "memory_mode package defined in the previous tutorial. The second important observation is that the memory sensor plugin extends the AbstractContextPlugin class. With this, we do not need to implement each and every method defined in the IContextPlugin interface, but rather simply implement the abstract methods defined in the abstract class and override any methods we need to change the functionality for. This is actually a popular idiom used extensively even in the J2SE libraries themselves (e.g., in the "javax.swing" package).

The constructor is quite straight-forward: we simply delegate the call to the super-constructor with the plug-in's ID as argument. Furthermore, in the constructor we initiate the "totalMemory" constant using a core JVM command.

The interesting part of the plug-in lies in the implementation of the activate and deactivate methods as well as to the implementation of the run method (of the "java.lang.Runnable" interface). The activate (and deactivate) method does a simple thing: it schedules (or unschedules) a recurring event. In this case, we use a custom scheduler which allows for the definition of one-time or recurring events. As shown in this example, the RecurringEvent is constructed by passing it three arguments:
  • A class implementing the Runnable interface: this is the code that will be invoked each time the recurring event is fired. In this case, the MemorySensor class itself is defined to implement the IContextListener interface, so the passed argument is itself (i.e., "this").
  • Minimum wait time: This long arithmetic value indicates the number of milliseconds the scheduler must wait before it fires an event for the first time. In this example this number is set to 500 (i.e., half a second).
  • Interval: The interval is also a long arithmetic value indicating the number of milliseconds which the scheduler waits before it repeats an invocation. In this example, the interval is set to 10000 (or 10 seconds) which means that the scheduler will invoke the runnable listener once every 10 seconds).
When scheduling the recurring event with the scheduler ("activate" method), in principle you register for asynchronous invocation of the run method. When you remove the recurring event, then you unregister form the asynchronous invocations. This mechanism is quite similar to using Threads, but it uses a Thread pool instead which can be quite beneficial in case you need to deal with resource constrained, mobile devices.

Finally, we examine the run method. This is where the actual context sensing takes place. In the first place, we collect our needed data. In this case we measure the available memory using the freeMemory core call of Java. Then we compute the memory load as a function of the available and total memory.

In the next step, we construct an instance of the MemoryContextElement (defined in the previous tutorial) by using the sensed data as input.

Finally, using the generated context element, we fire an event by using the overriden fireContextChangedEvent method. The only two arguments are the source (i.e., the plugin itself) and the context element.

Packaging and deploying the Memory Context Plugin

As usual, we package the code of the plug-in in a JAR file which contains the Manifest, the service descriptor XML file and the Memory Sensor class. A ready JAR file with the source code and an ANT build script is readily available here.

To deploy it, follow the steps indicated in the following screen:

osgi> install file:MemorySensor.jar
Bundle id is 9

osgi> bundle 9
file:MemorySensor.jar [9]
Id=9, Status=ACTIVE Data Root=C:\eclipse\configuration\org.eclipse.osgi\bundles\9\data
Registered Services
{org.istmusic.mw.context.plugins.IContextPlugin}={PROVIDED_CONTEXT_TYPE_SCOPE[memory]=#concepts.scopes.resources.memory, component.name=MemorySensor, PROVIDED_CONTEXT_TYPE_ENTITY[memory]=#concepts.entities.device|this, component.id=6, PROVIDED_CONTEXT_TYPES=memory, service.id=31}
No services in use.
No exported packages
Imported packages
org.istmusic.mw.context.plugins; version="0.0.0"<file:music-context-0.2.1.0-SNAPSHOT.jar [7]>
org.istmusic.mw.context.util.scheduler; version="0.0.0"<file:music-context-0.2.1.0-SNAPSHOT.jar [7]>
org.osgi.service.component; version="1.0.0"<file:plugins\org.eclipse.osgi.services_3.1.200.v20071203.jar [3]>
cy.ac.ucy.cs.osgi.tutorial7.memory_model; version="0.0.0"<file:MemoryModel.jar [8]>
No fragment bundles
Named class space
MemorySensor; bundle-version="1.0.0"[provided]
No required bundles

osgi> start 9

osgi> Jan 29, 2009 10:05:45 AM cy.ac.ucy.cs.osgi.tutorial8.MemorySensor <init>
INFO: MemorySensor: constructed
Jan 29, 2009 10:05:45 AM org.istmusic.mw.context.manager.ContextManager installPlugin
INFO: CM:Installing: Memory plugin

Notably, the bundle exports (i.e., registers) the IContextPlugin service. Also, when started (i.e., when the plug-in moves in to the ACTIVE state), the middleware detects it and automatically binds to it moving it to the C_INSTALLED state (shown in the extended plugins state diagram shown above). At this point, the plug-in is not started for a simple reason: There is no context listener actively requiring the memory context type, and thus the middleware keeps the plug-in inactive albeit resolved.

Developing context reasoner plugins

Besides the AbstractContextPlugin, the developer can also extend the AbstractContextReasonerPlugin class. As shown in the context plugin class diagram, the AbstractContextReasonerPlugin class extends the AbstractContextPlugin class. In addition to the facilities provided by the latter, the former also allows for receiving and handling context events. In this regard, the corresponding plugin must implement the "contextChanged" method (inherited from the IContextListener interface), in which it should listen to events of its required context types (as defined with "REQUIRED_*" properties in the service descriptor) and react based on them. The development of a context reasoner plug-in is the object of the second homework.

Homework 8.1: Develop a context plug-in to monitor the location. The actual coordinates reported can be simulated (i.e., fixed or random). You can use the location model developed in homework 7.1 of the last tutorial. You are actually encouraged to do so.

Homework 8.2: Develop a context reasoner plug-in that will use the location context type (i.e., as generated from the plug-in of the previous homework) to produce the weather context type. The actual value will be computed as a function of the latitude as follows: "temperature = 5 + (90 - |latitude|) / 1.5". I.e., the temperature is reported as -5°C at the poles and 65°C at the equator (which is of course an exaggeration even with the most pessimistic global warming predictions). The humidity should be constantly reported as 10%. You can use the location and weather models developed in homework 7.1 of the last tutorial. You are actually encouraged to do so.

References

[1]. Nearchos Paspallis, Romain Rouvoy, Paolo Barone, George A. Papadopoulos, Frank Eliassen, Alessandro Mamelli, A Pluggable and Reconfigurable Architecture for a Context-aware Enabling Middleware System, 10th International Symposium on Distributed Objects, Middleware, and Applications (DOA'08), Monterrey, Mexico, Nov 10 - 12, 2008, Springer Verlag LNCS 5331, pp. 553-570

No comments: