WildCAT: a toolkit for context-aware applications

WildCAT is a Java toolkit/framework which can be used to build context-aware applications, of which self-adaptive applications are a special case. From the client applications point of view, it provides a simple and dynamic data-model to represent the execution context of the application, and offers a simple API for the programmers to access this information both synchronously and synchronously (pull and push). Internally, it is a framework designed to facilitate the acquisition and aggregation of contextual data and to create reusable ontologies to represent aspects of the execution context relevant to many applications. It is described in more details (but in french), in Chapter 5 of P.-C. David's PhD Thesis

WildCAT's data model

WildCAT data model

The figure on the right represents the logical model use by WildCAT to represent the execution context. Note that this UML diagram does not imply anything about the actual implementation, except for the Context class, which serves as a façade for the whole system from the clients' point of view.

The main class is called Context. It is a singleton; only one instance exists in each application, representing the whole context of the application. It provides the interfaces used by clients (see next section).

The context is made of several domains, each represented by a ContextDomain object and identified by a unique name. The purpose of context domains is to separate the different aspects of the execution context, and to allow each of these to use a custom implementation, be it for performance reason or for interoperability with existing systems. Examples of context domains include (but are not limited to):

Note that WildCAT does not actually implement any of those, but provides a framework in which these heterogeneous informations can be integrated and accessed through a common interface. Because lots of context information is common to many applications, WildCAT is designed so that context domains modelisation (ontologies) and implementation (data acquisition) can be shared and reused like software libraries.

Finally, each context domain is model as a tree of named resources, each being described by attributes (simple key, value pairs). This simple model was chosen because of its generality, familiarity and because it does not impose a complex implementation. Note that this is a logical model, as seen by clients. An actual implementation of a context domain can use a more complex model internally (for example with sharing of resources, resulting in a DAG), as long as it provides a tree-like interface.

Events

Of course, since the context being modeled is highly dynamic, so is WildCAT's data model. Each change occuring in the context model is represented by an event. The possible kinds of events are:

Adressing

WildCAT uses a syntax inspired by URIs to denote an element in the context while being indenpendent on the actual implementation. A path can denote either a resource, an attribute, or all the sub-resources or sub-attributes of a resource.

sys://storage/memory              // A specific resource
sys://storage/disks/hda#removable // A specific attribute
sys://devices/input/*             // All the (direct) sub-resources of input
sys://devices/input/mouse#*       // All the attributes of mouse

Note that a path can be syntactically valid but denote a place which does not exist.

Paths are represented by instances of the Path class, which can be created this way:

Path p = new Path("sys://storage/disks/hda#removable");

External APIs

The Context class offers two complementary interfaces to access the actual contextual data of an application.

Sychronous requests (pull mode)

The first one allow clients to discover the structure of the context and to request the immediate value of an attribute. The methods in Context corresponding to this interface are:

public class Context {
    // Discovery and synchronous requests
    public String[] getDomains() { ... }
    public Path[] getChildren(Path res) { ... }
    public Path[] getAttributes(Path res) { ... }
    public boolean exists(Path path) { ... }
    public Object resolve(Path attr) { ... }
    ...
}

The get* methods can be used to discover the current structure of the context, as known by WildCAT. exists() tests whether the element(s) denoted by its argument path actually exist at this time in the context. Finally, resolve() returns the value of the single attribute denoted by its argument. For example,

Path[] drives = context.getChildren(new Path("sys://storage/disks"));

returns the list of all disk drives currently known to WildCAT, while

Number n = (Number) context.resolve(new Path("sys://storage/memory#free"));

returns the amount of memory currently available.

Asynchronous notifications (push mode)

The second interface provided by Context follow the publish/subscribe pattern. The client first registers its interest in a kind of event, and from this point on, WildCAT will send it asynchronous notifications each time this kind of event occurs. The methods in Code corresponding to this interface are:

public class Context {
    ...
    // Asynchronous notifications (publish / subscribe)
    public long register(ContextListener listener, int eventKinds, Path path) { ... }
    public long registerExpression(ContextListener listener, int eventKinds, String expr) { ... }
    public void unregister(long regId) { ... }
}

The first method, register() takes a listener object (which will receive the event notifications), a bitmask representing the kinds of events the client is interested in (using constants defined in the EventKinds interface), and finally the path on which the events must be detected (this path can include a wildcard as its last element). For exemple, to get notified each time a new input device is plugged or unplugged:

context.register(myListener, EventKinds.RESOURCE_ADDED | EventKinds.RESOURCE_REMOVED,
                 new Path("sys://devices/input/*"));

Where myListener implements the ContextListener interface:

public interface ContextListener {
    void attributeAdded(Path attr, long timeStamp);
    void attributeRemoved(Path attr, long timeStamp);
    void attributeChanged(Path attr, Object oldValue, Object newValue, long timeStamp);
    void resourceAdded(Path res, long timeStamp);
    void resourceRemoved(Path res, long timeStamp);
    void expressionValueChanged(long regId, Object oldValue, Object newValue, long timeStamp);
    void conditionOccured(long regId, long timeStamp);
}

the most convenient way being to subclass ContextListenerAdapter and override only the required methods:

class InputDevicesListener extends ContextListenerAdapter {
    public void resourceAdded(Path res, long timeStamp) {
        System.out.println("New input device: " + res);
    }
    public void resourceremoved(Path res, long timeStamp) {
        System.out.println("Input device unplugged: " + res);
    }
}

The next method, registerExpression(), is used only in combination with the EXPRESSION_CHANGED and CONDITION_OCCURED event kinds (and the corresponding methods in ContextListener). This allows clients to register to more complex events. For example, if a client only wants to be notified when the room temperature goes beyond 30°C, it can use the following code:

context.registerExpression(aListener, EventKinds.CONDITION_OCCURED,
                           "geo://location/room#temperature > 30");

instead of using a simpler ATTRIBUTE_CHANGED event kind, and being notified a many non-intersting events.

An EXPRESSION_CHANGED event occurs each time the value of the monitored expression changes. If the expression depends on multiple paths, WildCAT will automatically recompute its value each time one of these paths changes. CONDITION_OCCURED can be seen as a special case of EXPRESSION_CHANGED for boolean expressions where only the transation from false to true are considered.

The expressions usable with registerExpression() can use paths, constant numbers and strings, comparison operators (=, !=, <, >, <=, >=), arithmetic operations (+, -, *, /, with the usual precedence rules), boolean operators (not, and, or), and finally function invocation (function(arg1, arg2...)). WildCAT provides some predefined functions working on numbers and strings, but new functions can be added easily to extend the vocabulary of synthetic expression (this feature is probably the best way to extend the power of WildCAT without changing its external interface).

Both register() and registerExpression() return a numeric identifier corresponding to this particular subscription. This number should be kept by the client, as it must be provided to unregister() in order to disable an event subscription.

Internal design

The following UML diagram represents the internal structure of WildCAT.

Class diagram of WildCAT internals

The framework can be decomposed in several different parts:

The client interface (in green on the diagram).
This correspond to what has be described in the previous section, and is the only part of WildCAT seen by standard client applications.
The extension interface (in blue on the diagram).

The ContextDomain interface is the main extension point of WildCAT as a framework. New implementations of the notion of context domain can be provided if the default one is not suitable to a particular domain, for example for performance reasons or to create a bridge with an existing system.

public interface ContextDomain {
    void initialize(Context ctx);
    String getName();
    boolean exists(Path path);
    Object resolve(Path attr);
    Path[] getAttributes(Path res);
    Path[] getChildren(Path res);
    void register(ContextListener listener, int eventKinds, Path path);
    void unregister(ContextListener listener, int eventKinds, Path path);
    void update(Path path, Path cause);
}
The default, generic implementation (in grey on the diagram).
WildCAT provides a default implementation of ContextDomain in the StandardDomain class. This implementation follows directly the logical model, with classes for Resources and Attributes. The creation of the initial structure of the domain and its updating is managed by the DomainUpdater, which creates and removes resources and attributes according to the specification provided by an XMLTemplate (see below). Finally, the raw, unstructured data acquisition is delegated to the last part of the WildCAT framework.
The data acquisition sub-framework (part of the default implementation, in grey on the diagram).

This sub-framework is centered around the notion of sensor, which represent the Java objects responsible for the acquisition of raw data. The actual methods used to get the data can be very varied, requiring communication with OS layers or even directly with hardware; WildCAT does not offer any direct help here. However, WildCAT provides the SensorManager class to organize all the sensors. Each sensor is identified by a name, and can be either started or stopped. When it is started, a sensor will send new samples to the SensorManager asynchronously to the sensor manager, which gathers all the samples it receives from every sensor and make them available to its clients.

WildCAT distinguishes two kinds of sensors. Active sensors implement directly the Sensor interface and run in their own thread. They are responsible to send new samples to the sensor manager when it is appropriate.

public interface Sensor {
    String getName();
    void setListener(SamplesListener listener);
    void start();
    void stop();
    boolean isStarted();
}

Passive sensors are created by associating a Sampler and a Schedule to create a SamplingSensor. The sensor manager then uses a daemon to invoke the sampler regularly according to its scheduling policy. Samplers are very easy to implement, requiring ony one method to be implemented:

public interface Sampler {
    SampleSet sample();
}

When invoked, this sample() method should return the raw data corresponding to the current state of the part of the context it is responsible to observe.

In order to make it easier to integrate WildCAT with existing systems, WildCAT provides the XMLCommandSensor class. This class implements the Sampler interface by invoking an external program (using Runtime.getRuntime().exec()). The output of the command should be a valid XML document representing a SampleSet object, which is then parsed an returned by the XMLCommandSensor. As an example, here is a simple Ruby program which can be used by XMLCommandSensor:

require 'wildcat'
class KernelVersionSensor < Sampler
  def sample
    uname = IO.read('/proc/version').chomp
    version = uname.scanf('Linux version %d.%d.%d');
    return { 'uname' => uname,
             'major-version' => version[0],
             'minor-version' => version[1],
             'patch-level' => version[2],
             'version' => version.join('.') }
  end
end
print KernelVersionSensor.new.sense

The output of this program looks like this:

<?xml version="1.0"?>
<sample-set timestamp="2005-07-19T09:15:36">
  <sample name="uname" type="string">Linux version 2.6.10-5-k7 ...</sample>
  <sample name="patch-level" type="integer">10</sample>
  <sample name="minor-version" type="integer">6</sample>
  <sample name="version" type="string">2.6.10</sample>
  <sample name="major-version" type="integer">2</sample>
</sample-set>
      

and can be understood by XMLCommandSensor.

Finally, the SensorFactory class helps creating new sensors directly from XML specifications in the format used by the default context domain implementation (see below).

The default context domain implementation

The default implementation of ContextDomain is generic and can be configured through an XML file. The structure of this file can be seen as a template for the structure of the context domain being modeled. Here is a commented example of such a file.

<?xml version='1.0' encoding='ISO-8859-15'?>
<context-domain name="sys">
  <resource name="load">
    <sensor name="load" class="org.obasco.wildcat.sensors.LoadSensor">
      <schedule><periodic period="10000"/></schedule>
    </sensor>
  </resource>

The main XML element is context-domain and indicates the name of the domain being defined. Inside this element we find a resource element, also with a name. The resource is associated with a sensor defined using the name of the Java class implementing it, and an embedded scheduling specific. In this case, the load passive sensor will sampled every 10 seconds by WildCAT to get updated load values. The samples returned by the sensor are automatically mapped as dynamic attributes of the load resource.

  <resource name="storage">
    <resource name="disks">
      <sensor name="hda" class="org.obasco.wildcat.sensors.HardDriveSensor">
        <schedule><on-create/></schedule>
        <configuration>
          <device>/dev/hda</device>
          <mode>static</mode>
        </configuration>
      </sensor>
    </resource>
  </resource>

The next part of the file defines abstract resources named storage and disks to serve as categories and organize other sub-resources. The specification of resource hda, representing the first hard drive in the host system, shows how to pass configuration information to sensor classes. Such a mechanism is required because a given context can have many resources of the same kind (for example hard drives), and each must be observed by a particular instance of the sensor class. The configuration XML element, if present inside a sensor specification, is passed to the constructor of the sensor class (here HardDriveSensor) to configure this particular instance. The actual value passed to the constructor is a parsed XML element (currently using the JDOM API), and the only constraint is that it is a well-formed XML fragment. Every configurable sensor class can define its own "configuration file" format and is responsible for its interpretation. In the example, the configuration fragment tells the HardDriveSensor object to observe the device named /dev/hda and to consider it a static device (i.e. non-removable).

  <resource name="network">
    <attribute name="nb_interfaces">count(*)</attributes>
    <resource name="eth0">
      <sensor name="nic" class="org.obasco.wildcat.sensors.NICSensor">
        <schedule><periodic period="1000"/></schedule>
        <configuration><device>eth0</device</configuration>
      </sensor>
      <attribute name="error_rate">#dropped_packets / #received_packets</attribute>
    </resource>
  </resource>
</context-domain>

The last part of the file deals with the network card. It uses attribute elements to define synthetic attributes (as opposed to the primitive ones corresponding to raw data coming from sensors). The value of these attributes is defined by a expression (using the same simple language as the one used with the registerExpression() method) which can reference any part of the context, including elements of other domains. WildCAT tracks the dependencies between these synthetic attributes (using the DependencyManager internal class in the architecture diagram) and updates their values automatically. From the point of view of the client programs, these attributes are not different from the others.

The current implementation of WildCAT does not support the definition of dynamic context domains using the default implementation, although such a feature is planned. See Chapter 5 of P.-C. David's PhD Thesis, Section 5.5.4, page 89, for a description of the expected design.


Publications

Although it us usable independently, WildCAT was developed as a part of a bigger system named SAFRAN (Self-Adaptive FRActal compoNents). The following publications describe SAFRAN as a whole, including WildCAT.

Older publications on previous related work available at http://pcdavid.net/research/.