WildCAT is a Java toolkit/framework which can be used to build context-aware applications. 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 aggregation of contextual data by integrating existing monitoring technologies into a single common model.
Numerous monitoring solutions already exist in many domains (network and grid, performance, application-level, environment, etc.). Each represent real expertise in a specialized domain, but these solutions can not be easily combined to provide an overall picture, as they are generally ad hoc and heterogeneous. Building context-aware applications and autonomic control loops requires a systemic view of the target application and its environment, which covers domains currently targeted by different solutions. In order to build such a systemic view without re-implementing already existing solutions, one must be able to integrate to (called "providers" from now on) in a uniform way. In a sense, what is needed is a monitoring middleware, able to provide a uniform -- and hopefuly simple -- interface to a set of heterogeneous monitoring solutions.
WildCAT provides such a middleware platform, and is built around the following requirements:
WildCAT is designed to take different kinds of users into account, each of which has particular needs which influence the system's design:
WildCAT represents the execution context using a purposedly simple data model consisting in a tree of resources, each described by a set of attributes. The top-level, root resource is anonymous and can contain only sub-resources (no attributes). Every other resource is identified by a name, unique among all the sub-resources of a given parent resource, and can contain sub-resources and attributes. Attributes are named values attached to a resource they describe, and as for resources their names is unique among siblings. However, resources and attributes names are in different namespaces, so it is possible for a resource to have both a sub-resource and an attribute with the same name.
This model does not impose any particular semantics on the structure of resources and attributes. Depending on how the model is used, "r2 is a sub-resource of r1" can mean "r2 depends on r1", "r2 is a part of r1", "r2 is an aspect of r1", etc. WildCAT only provides a base mechanism, and does not impose a particular policy on how it should be used. For example, most applications map attributes to properties of the corresponding resource. This works if properties are atomic, but if one wants a more sophisticated modeling of properties (including for example the unit of measure, its precision, etc.), one would model properties as resources with different attributes for different aspects of the properties.
WildCAT uses a syntax inspired by Unix file names to denote elements in the context while being indenpendent on the actual implementation. A path can denote either a resource, an attribute, or a set of resources or attributes matching a particular pattern.
/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 /cluster/nodes/*/load#cpu // The processing loads of all the nodes in a cluster
Note that a path can be syntactically valid but denote a location which does not exist.
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, which can be observed (other kinds of events can also be observed, but they do not strictly correspond to primitive changes in the model). The possible kinds of events are:
RESOURCE_ADDED
and RESOURCE_REMOVED
, fired when resources
appear or disappear in the context.ATTRIBUTE_ADDED
and ATTRIBUTE_REMOVED
, fired when attributes
appear or disappear in a resource.ATTRIBUTE_CHANGED
, fired when the value
of an attribute changes.The user-level API, which is used by the client programmer, is
designed to be simple and easy to learn. All the interactions of
the programmer with WildCAT happen through an instance of
the Context
class (multiple instances can be
created inside a single application).
The first set of methods implement synhronous requests (pull mode) and allow clients to discover the structure of the context and to request the immediate value of an attribute:
Collection<Path> lookup(Path query) { ... } Collection<Path> lookup(String query) { ... } Object lookupAttribute(Path attr) { ... } Object lookupAttribute(String attr) { ... }
The main methods are lookup(Path)
and lookupAttribute(Path)
, but both provide
variants taking strings instead of pre-parsed Path
s
for convenience.
lookup(Path)
takes an absolute path as a
parameter, which can be either definite (i.e. representing a
specific location like /geo/location#latitude
) or a
pattern (i.e. representing of set of potential locations,
like /sys/devices/*
). It returns the paths of all
the elements currently in the context (resources and attributes)
which match the query. This method is very generic and
covers several use cases:
// Testing whether an element exists in the context boolean exists = !context.lookup("/sys/devices/keyboard").isEmpty(); // Finding all the sub-resources Collection<Path> devices = context.lookup("/sys/devices/*"); // Finding all the attributes of a resource Collection<Path> mouseAttrs = context.lookup("/sys/devices/mouse#*"); // Finding a specific attribute on a set of resources Collection<Path> vendors = context.lookup("/sys/devices/*#vendor"); // Finding all the siblings of a resource // See the Path interface for other methods to manipulate paths Collection<Path> siblings = context.lookup(aPath.getParent().append("*"));
The second method, lookupAttribute(Path)
takes an
absolute path (either as a string or a real Path
object) which must designate a definite attribute (i.e. not a
pattern), and returns the current value of the attribute in
question, or null
if it does not exist.
Double load = (Double) context.lookupAttribute("/sys/cpu/load#load_1min");
The second set of methods provided by Context
enable a client to be notified asynchronously when some events
of interest occur (push mode). These methods 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 a corresponding
event occurs. The methods in Context
corresponding
to this interface are:
Object register(EnumSet<EventKind> evtKinds, Path path, ContextListener listener) { ... } Object registerExpression(EnumSet<EventKind> evtKinds, String expression, ContextListener listener) { ... } void unregister(Object cookie) { ... }
The first method, register()
takes a "bitmask"
representing the kinds of events the client is interested in
(using the EventKind
enumeration), the path on
which the events must be detected (this path can be a pattern),
and finally a listener object (which will receive the event
notifications). For exemple, to get notified each time a new
input device is plugged or unplugged:
context.register(EnumSet.of(RESOURCE_ADDED, RESOURCE_REMOVED), Context.createPath("/sys/devices/input/*")), myListener);
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(Object cookie, Object oldValue, Object newValue, long timeStamp); void conditionOccured(Object cookie, long timeStamp); }
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(EnumSet.of(CONDITION_OCCURED), "/geo/location/room#temperature > 30", aListener);
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
an opaque identifier ("cookie") corresponding to this particular
subscription. This object should be kept by the client, as it
must be provided to unregister()
in order to
disable an event subscription, and is also used when sending
notifications to listeners.
When first created, an instance of Context
does
not contain any resource or attribute. Before it can be used by
a client as described in the previous section, it must be
configured. This configuration can be done by the client
himself, but need not be: pre-configured contexts could be made
available to users, tailored to particular kinds of applications
(i.e. desktop applications, application servers, administration
consoles...).
In analogy with Unix filesystems, where different filesystems
implementations can be integrated in a single hierarchy,
configuring WildCAT is done by mounting (and
unmounting) context providers at specific locations. A
context provider is simply a Java object which implements
the ContextProvider
interface (see that interface's
documentation for more details). As a framework, WildCAT is only
concerned with the integration of such providers, and not on
their actual implementation or configuration: each specific
implementation provides its own configuration mechanism.
Context ctx = new Context(); MyProvider p1 = new MyProvider(); // configure p1 ctx.mount("/some/location", p1); // change p1's configuration SomeOtherProvider p2 = new SomeOtherProvider("configuration.file"); ctx.mount("/another/location", p2);
However, WildCAT provides nonetheless two flexible and
configurable implementations of ContextProvider
:
BasicContextProvider
simply implements the
data model of resources and attributes as a tree of Java
objects in memory. It does not provide any data by itself
but is fully controllable by program: its API enable the
creation/deletion of resources and attributes, and the
modification of attributes value. It is designed to serve
as a possible starting point for other
implementations.
DynamicContextProvider
extends BasicContextProvider
with two features:
sensors
to
resources. Sensors are objects which monitor some part
of the context on request and return there measures as
a set of named values, which are then used to update
the resource's attributes. When a sensor is attached,
a time period is supplied.
The DynamicContextProvider
will
automatically request new values from the sensor
according to this period and update the resource's
attributes.WildCAT distinguishes two kinds of extensions: adapters and providers.
Adapters are simple wrappers for existing sensors, to make them
usable by DynamicContextProvider
. There are used
when the existing monitoring technology to integrate has a
simple, non-structured data model. For example, an adapter
exists for LeWYS probes (not included in the default
distribution), as these probes provide their data using a simple
model and are independent of each others.
Providers are more complex: they provide a full implementation
of the ContextProvider
interface (maybe using one
of the existing implementation as a basis). They are used when
the technology to intergrate already provides a structured data
model
(e.g. HAL).
The default WildCAT distribution does not include any adapter
or provider (except for an
example FileSystemProvider
) in ordre to keep its
dependencies minimal. Several already exist or are in the work
however, and will be distributed separately as extensions
when they mature.