Context Toolkit: Tutorial: Context Widgets

Context widgets are responsible for separating the details of sensing context from actually using it. They abstract away the details of how the context is sensed from applications and other context components that need the context. Each context widget is responsible for some small set of context that is captured from a (hardware or software) sensor. First, I'll describe the features of context widgets and then talk about how to actually use them. Also take a look at the source code documentation for the Widget class.



Features

In this section, I'll describe the basic features of a context widget. A context widget is quite similar to a GUI widget. Just like a GUI widget (e.g. a button on a form) hides the details of how the input is being captured (mouse, keyboard, pen, etc.) and simply provides information about interaction (button press, button release, etc.), a context widget hides the details of how a particular type of context is being captured or sensed from a software or hardware sensors and simply provides information about the context the sensor provides. For example, a web page that delivers current temperature or a thermometer outside your house can be used to provide temperature information. For the most part, an application does not care about the source of the temperature information or how it is being sensed. The context widget abstraction allows an application developer to simply use the temperature information without worrying about how it's being sensed (although that information is available, in the case where applications do want to know).

Context Widgets subclass from BaseObject, so they inherit the generic communications functionality from it. In addition, context widgets have the following features: description, handle polling, handle subscriptions, storage, and services.

Description
All context widgets can provide descriptions of themselves to inquiring components. The description includes the type of context that the widget is collecting, callbacks the widget supports (see subsection on subscriptions), and services the widget provides (see subsection on services).
Polling
Just like GUI widgets can be polled to determine their current state, context widgets can also be polled to determine their current state. There are two types of polls supported: a request for the last collected state and a request to update and return the current state.
Subscriptions
Along with polling, context widgets support subscriptions to changes in the context that they are monitoring. Context widget have callbacks that other components can subscribe to. When the monitored context changes, the callback is fired and the subscribing components are notified of the callback.
Storage
Besides providing current information on context via the poll and subscribe mechanisms, context widgets also collect and store the context they are responsible for. A default storage mechanism is provided, but like the communications mechanism provided in BaseObject, this is pluggable and can be easily replaced. In fact, context widgets can be instantiated without the ability to store their context, but the default is to support storage. With this storage ability, other components can request past context information from context widgets.
Services
Context widgets can also execute services for other components. For example, a widget might represent the display on a computer, providing information about the screen real estate available as a user is interacting with it. A service it might provide is to display a dialog box and collect input from the user to send back to the requesting component. Services are fairly new for context widgets, so they have not been explored that much. The goal was to support output to a similar degree that we support the collection of input. Context widgets should have the ability to affect their surroundings just as they have the ability to sense their surroundings. Imagine a light widget which can determine the amount of light in a room. One service this widget could provide is to change the lighting level of the room to a given level. Both synchronous and asynchronous services are supported.

How to Use It

In this section, I'll discuss how to use existing widgets and then how to create new widgets.
Using Existing Widgets
To use the existing context widgets in the context.arch.widget package, execute the widget as follows: "java context.arch.widget.W???" with no parameters. The parameter list for the widget should be printed out.

In general, the minimum parameter list will include a port number for the widget to receive communications on (although the widget will likely have a default), an identifier for the widget and an indication on whether storage should be turned on or off (default is on). For example, the PersonPresence widget, which provides the identity of a person that is present at a particular location, has the following parameter list: [port] < location > [storageFlag]. The location acts as the identifier, allowing multiple PersonPresence widgets to be deployed in multiple locations. The storageFlag parameter is a boolean that indicates whether the widget should have storage enabled. A true value means that the widget will be able to store context data, while a false value means the widget will have no storage ability. The [...] means the parameter is optional and <...> means the parameter is mandatory.

 An application or context component can communicate with the context widget using the BaseObject class. It can:

When talking to a widget, or any context component, you must have the hostname, port number, and id of the component. (A feature we are working on is resource discovery, so you will only need to be able to describe features of the component you want, rather than knowing the specific hostname, port number and id.) The id for a widget is the classname without the preceding 'W', followed by '_', followed by the widget's identifier. For example, to communicate with an instance of WPersonPresence with location "home", the id is "PersonPresence_home".

Servers, interpreters and other widgets can communicate with a widget using the BaseObject class that they inherit from. Applications must either create an instance of BaseObject or inherit from BaseObject to talk to widgets. Following are examples of how to use methods in BaseObject to communicate with widgets and use their features.

Subscribing to a Widget
There are a number of methods that can be used to subscribe to a widget callback. I'll describe the most complicated. This method has the following prototype:
  public Error subscribeTo(Handler handler,
                           int port,
                           String subid,
                           String remoteHost,
                           int remotePort,
                           String remoteId,
                           String callback,
                           String url,
                           Conditions conditions,
                           Attributes attributes)
The handler parameter indicates which object is going to handle the results of a widget callback. The object that handles the results must implement the context.arch.handler.Handler interface,. The port parameter indicates the port number the subscribing component receives communications on. The subid is the id of the subscribing component. The remoteHost is the hostname/ip of the computer the widget is running on. The remotePort is the port number the widget is running on. The remoteId is the id of the widget. The callback is the name of the widget callback being subscribed to. Finally, the url is the id that will be attached to each callback message. If a handler is handling multiple callbacks (either from a single widget or multiple widgets), it can use the url to determine which callback the message is from. An error code is returned to indicate the success or reason for failure of the subscription.

 The last two parameters are optional. The attributes parameter is an instance of the Attributes class. A callback may have multiple attributes. An application may only be interested in a subset of them. Through this parameter, an application can specify the subset it is interested in.

The conditions parameter is an instance of the Conditions class. The callback may fire under more conditions than the application is interested in. For example, the PersonPresence widget callback fires whenever anyone (that can be identified) is present at a certain location. An application may only be interested in cases where a particular person is present or presence occurs between certain times. Through this parameter, an application can specify the conditions under which it will be notified of the widget's callback. It can specify multiple conditions. Currently, there is only support for a logical ANDing of the conditions. Soon, we hope to suport other logical operators such as NOT, OR, etc. For each condition, an application specifies an attribute name, an operator (=, !=, <, >, <=, >=) and a value.

 The following is an example of an application subscribing to a PersonPresence widget on the local machine, port 5555. It subscribes to the widget's UPDATE callback, choosing to get only the TIMESTAMP attributes, and asking to be notified when the UPDATE callback is fired when the USERID equals "16AC850600000044". It sets itself up as the handling object. It defines a handle method which simply grabs the time attribute and prints it out.

  BaseObject server = new BaseObject(7777); // create BaseObject running on port 7777

  Attributes subAtts = new Attributes();
  subAtts.addAttribute(WPersonPresence.USERID);
  subAtts.addAttribute(WPersonPresence.TIMESTAMP);
  Conditions subConds = new Conditions();
  subConds.addCondition (WPersonPresence.USERID,Storage.EQUAL,"16AC850600000044");
  Error error = server.subscribeTo(this, 7777, "testApp", "localhost", 5555, "PersonPresence_here",
                 WPersonPresence.UPDATE, "presenceUpdate", subConds,subAtts);
  System.out.println("Subscription with valid attributes/conditions: "+error6.getError());

  ...

  public DataObject handle(String callback, DataObject data) throws InvalidMethodException, MethodException {
    if (callback.equals("presenceUpdate")) {
      AttributeNameValues atts = new AttributeNameValues(data);
      AttributeNameValue timeAtt = atts.getAttributeNameValue(WPersonPresence.TIMESTAMP);
      String time = (String)timeAtt.getValue();
      System.out.print(time+"\n");
    }
  }
The handle method has two parameters: a string that corresponds to the subscription url and a DataObject that contains the callback data. The url, "presenceUpdate",  is used by the method to determine which callback is returning. The DataObject is converted to an AttributeNameValues object and the object containing the "time" attribute is queried and the result printed out.
        Subscription Log
When another component subscribes to a widget, a subscription log file is automatically created (in the directory from which the widget is executed). The name of the log file is the <widget_id>-subscription.log. The log contains information about the current subscribers to a widget. If a widget is shutdown and restarted, it uses the log file to remember what components are subscribed to it. In this way, when a widget is restarted, the components that are subscribed to it do not have to be restarted to have their subscriptions known to the widget. In general, this is a good thing. However, in the following case, when a widget is shutdown and the subscribing component is also shutdown, and then the widget is restarted, the widget will assume that the component in question is still expecting callback information and will send it, causing an exception to be thrown because the component no longer exists. Just beware of this side effect. Ideally, when a subscribing component was being shut down, it would unsubscribe from any widgets it was subscribed to.
Unsubscribing From a Widget
To unsubscribe from a widget, use the unsubscribeFrom method with the following prototype:
  public Error unsubscribeFrom(Handler handler,
                               String remoteHost,
                               int remotePort,
                               String remoteId,
                               Subscriber subscriber)
The parameters are the same as in a subscribe message, except for the subscriber parameter. The Subscriber class encompasses the subid, port, callback, url, conditions, and attributes. The widget verifies that the unsubscribe info matches an existing subscription. If everything is okay, it returns Error.NO_ERROR. Otherwise, it returns a relevant error.

 An example of an unsubscription follows:

  Subscriber sub = new Subscriber("testApp",server.getHostAddress(), 7777, WPersonPresence.UPDATE, "presenceUpdate", subConds, subAtts);
  Error unsubError = server.unsubscribeFrom (this,"localhost",5555,"PersonPresence_here",sub);
  System.out.println(unsubError.getError());
Polling a Widget
To get the latest data that a widget has, a component can poll the widget. To poll a widget, you can use the pollWidget method with the following prototype:
  public DataObject pollWidget(String widgetHost,
                               int widgetPort,
                               String widgetId,
                               Attributes attributes)
The component specifies which attributes it's interested in (and there is a version of this method that does not take an Attributes parameter, which means that all the attributes of the widget will be returned). Example code showing how to poll a widget follows:
  Attributes pollAtts = new Attributes();
  pollAtts.addAttribute(WPersonPresence.USERID);
  DataObject poll = server.pollWidget("localhost",5555,"PersonPresence_here", pollAtts);
  String pollError = new Error(poll).getError();
  AttributeNameValues atts = null;
  if (pollError.equals(Error.NO_ERROR)) {
    atts = new AttributeNameValues(poll);
  }
  System.out.println("error = "+pollError+", attributes = "+atts);
The result of pollWidget is a DataObject. This one contains an Error object and an AtributeNameValues object. First, the DataObject is converted to an Error object. This pulls the error information out of the DataObject. Then, if there is no error, it is converted to a AttributeNameValues object, which contains the actual context data. It is in the form of a set of attribute name-value pairs.
Updating and Polling a Widget
To get the latest data from a widget, a component can update and poll the widget. This forces the widget, if possible, to get new data from its sensor and return it to the requesting component. To update and poll a widget, use the updateAndPollWidget method with the following prototype:
  public DataObject updateAndPollWidget(String widgetHost,
                                        int widgetPort,
                                        String widgetId,
                                        Attributes attributes)
The component specifies which attributes it's interested in. The example code to update and poll a widget is the same as the previous example, except that pollWidget is replaced with updateAndPollWidget.
Put Data in a Widget
Sometimes, it is necessary for a component to update the data in a widget. This method should be used with great care. Other than checking that the attributes match the attributes of the widget and that the widget id is correct, there is no "security". Any component with this information can put data into the widget. To put data into a widget, use the putDataInWidget method with the following prototype:
  public DataObject putDataInWidget(String widgetHost,
                                    int widgetPort,
                                    String widgetId,
                                    String callback,
                                    AttributeNameValues attributes)
The component specifies which callback it is putting data in the widget for. In the AttributeNameValues object, the component specifies the data to put into the widget. Each AttributeNameValue consists of an attribute name, value and a type.

 Example code for putting data in a widget follows:

  AttributeNameValues atts = new AttributeNameValues();
  atts.addAttributeNameValue(WPersonPresence.TIMESTAMP,new Long(999999),Attribute.LONG);
  atts.addAttributeNameValue(WPersonPresence.USERID,"234AB3F");
  DataObject put = server.putDataInWidget("localhost",5555,"PersonPresence_here",
                   WPersonPresence.UPDATE, atts);
  String putError = new Error(put).getError();
  System.out.println("result of putData is: "+putError);
This example put timestamp and userid information into the PersonPresence widget. The result is a DataObject that simply contains an Error object.
Retrieve a Widget's Callbacks
A component can query a widget for its list of callbacks. To do so, use the getWidgetCallbacks method with the following prototype:
  public DataObject getWidgetCallbacks(String widgetHost,
                                       int widgetPort,
                                       String widgetId)
Example of how to use this follows:
  DataObject callbacks = server.getWidgetCallbacks("localhost",5555,"PersonPresence_here");
  String callbacksError = new Error(callbacks).getError();
  Callbacks calls = null;
  if (callbacksError.equals(Error.NO_ERROR)) {
    calls = new Callbacks(callbacks);
  }
  System.out.println("error = "+callbacksError+", callbacks = "+calls);
The result is a DataObject that contains an Error object and a Callbacks object. The Callbacks object contains a list of Callback objects. Each Callback object contains the name of the callback and the attributes corresponding to that callback.
Retrieve a Widget's Attributes
A component can query a widget for its list of attributes. To do so, use the getWidgetAttributes method with the following prototype:
  public DataObject getWidgetAttributes(String widgetHost,
                                       int widgetPort,
                                       String widgetId)
Example of how to use this follows:
  DataObject attributes = server.getWidgetAttributes("localhost",5555,"PersonPresence_here");
  String attributesError = new Error(attributes).getError();
  Attributes atts = null;
  if (attributesError.equals(Error.NO_ERROR)) {
    atts = new Attributes(attributes);
  }
  System.out.println("error = "+attributesError+", attributes = "+atts);
The result is a DataObject that contains an Error object and an Attributes object. The Attributes object contains a list of Attribute objects. Each Attribute object contains the name of an attribute that the widget has.
Retrieve a Widget's Services
A component can query a widget for its list of services. To do so, use the getWidgetServices method with the following prototype:
  public DataObject getWidgetServices(String widgetHost,
                                       int widgetPort,
                                       String widgetId)
Example code showing how to use this follows:
  DataObject services = server.getWidgetServices("localhost",5555,"PersonPresence_here");
  String servicesError = new Error(services).getError();
  Services servs = null;
  if (servicesError.equals(Error.NO_ERROR)) {
    servs = new Services(services);
  }
  System.out.println("error = "+servicesError+", services = "+servs);
The result is a DataObject that contains an Error object and an Services object. The Services object contains a list of Service objects. Each Service object contains a service name and a FunctionDescriptions object. There is a FunctionDescription for each function in the service.
Retrieve a Widget's Context History
Every widget stores a history of the context it has sensed (caveat: only if a storage mechanism has been set up for this widget). A component can retrieve this history information from a widget. To do so, use the retrieveDataFrom method with the following prototype:
  public DataObject retrieveDataFrom(String remoteHost,
                                     int remotePort,
                                     String remoteId,
                                     Retrieval retrieval)
In the Retrieval parameter, the component specifies the AttributeFunctions and Conditions that it wants data for.

 Example code showing how to use this follows:

  Attributes atts = new Attributes();
  atts.addAttribute(WPersonPresence.USERID);
  Conditions conds = new Conditions();
  Conditions subConds = new Conditions();
  subConds.addCondition (WPersonPresence.TIMESTAMP,Storage.GREATER_THAN,99999999);
  Retrieval retrieval = new Retrieval(atts,conds);
  DataObject retrieve = server.retrieveDataFrom("localhost",5555,"PersonPresence_here",retrieval);
  String retrieveError = new Error(retrieve).getError();


  RetrievalResults retrieveData = null;
  if (retrieveError.equals(Error.NO_ERROR)) {
    retrieveData = new RetrievalResults(retrieve);
  }
  System.out.println("error = "+retrieveError4+", retrieval4 = "+retrieveData4);
The result of the request is a DataObject. It contains an Error object and a RetrievalResults object. The latter object is just a vector of AttributeNameValues objects that match the request. If there is no storage mechanism set up for this context widget, the RetrievalResults object will be null and the Error object will be set appropriately.
Execute a Widget's Service
A component can remotely execute the services (synchronous and asynchronous) of a widget. For synchronous services, use the executeSynchronousWidgetService method with the following prototype:
  public DataObject executeSynchronousWidgetService(String remoteHost,                                       
                                      int remotePort,
                                      String remoteId,
                                      String service,
                                      String function,
                                      AttributeNameValues input)
The service is the name of the service to execute. The function specifies the service function, in case a service has multiple functions. The AttributeNameValues object is used to specify the input data to the service.

 Example code showing how to use this function follows:

  AttributeNameValues atts = new AttributeNameValues();
  atts.addAttributeNameValue(WPersonPresence.USERID,"AAAA");
  atts.addAttributeNameValue(WPersonPresence.TIMESTAMP,new Long(999999),Attribute.LONG);
  DataObject service = server.executeSynchronousWidgetService("localhost",5555,"PersonPresence_here","service_name","function_name",atts);
  String serviceError = new Error(service).getError();
  System.out.println("error = "+serviceError);
Currently, the DataObject that is returned contains only an Error object. However, there is no reason why it could not contain other objects.

 For asynchronous services, use the executeAsynchronousWidgetService method with the following prototype:

  public DataObject executeAsynchronousWidgetService(
                        AsyncServiceHandler handler,
                        String serviceHost,
                        int servicePort,
                        String serviceId,
                        String service,
                        String function,
                        AttributeNameValues input,
                        String requestTag)
The differences from the synchronous service are the use of a handler and a requestTag. The handler is the object that will handle the results of the asynchronous service request. This object must implement the context.arch.handler.AsyncServiceHandler interface. The requestTag acts like the url parameter in a subscribeTo method. In the case of a handler handling multiple asynchronous services, the requestTag is used to indicate which service has returned.

 Example code showing how to use this function follows:

  AttributeNameValues atts = new AttributeNameValues();
  atts.addAttributeNameValue(WPersonPresence.USERID,"AAAA");
  atts.addAttributeNameValue(WPersonPresence.TIMESTAMP,new Long(999999),Attribute.LONG);
  DataObject service = server.executeAsynchronousWidgetService(this,"localhost",5555,"PersonPresence_here","service_name","function_name",atts,"myService");
  String serviceError = new Error(service).getError();
  System.out.println("error = "+serviceError);

  ...

  public DataObject asynchronousServiceHandle(String requestTag, DataObject data) throws InvalidMethodException, MethodException {
    if (requestTag.equals("myService")) {
      String serviceError = new Error(data).getError();
      System.out.println("error = "+serviceError);
      // possibly do something here with data
    }
    ...
  }
The DataObject returned from the method invocation returns only an Error object. The DataObject received by the asynchronousServiceHandle method also currently contains an Error object. However, there is no reason why it could not contain other objects. The asynchronousServiceHandle method acts much like the handle method for subscriptions.
Creating New Widgets
The easiest way to create a new widget, is to use an existing widget from the context.arch.widget package as a starting point. You can also look at the basic Widget class documentation.

 When you want to create a new widget, there are a set of steps that you must take:

The WPersonPresence class code will be referred to throughout this subsection. The code is available here.
Setting the CLASSNAME constant
The id of a widget is set to the type of widget concatenated with the widget's identifier. Specifically, it is set to CLASSNAME+"_"+identifier or CLASSNAME+SPACER+identifier. It allows other components to easily identify the widget. Example code for the WPersonPresence class follows:
  /**
   * Name of widget
   */
  public static final String CLASSNAME = "PersonPresence";
Creating the Widget's Constructor
The constructor must call the Widget class' constructor, usually using this constructor that specifies the widget port number and id.

  public Widget(int port, String id)

The id should be set to CLASSNAME+SPACER+identifier. The constructor should set the version number using BaseObject's setVersion method. Finally, it can do anything else it needs or wants to do for dealing with its sensor.

 Example code from the WPersonPresence class follows:

  /**
   * Constructor that creates the widget at the given location and 
   * monitors communications on the given port and
   * creates an instance of the IButton position generator.  It also
   * sets the id of this widget to the given id and sets storage
   * functionality to storageFlag
   *  
   * @param location Location the widget is "monitoring"
   * @param port Port to run the widget on
   * @param id Widget id
   * @param storageFlag Flag to indicate whether or not to enable storage functionality
   *
   * @see context.arch.generator.PositionIButton
   */
  public WPersonPresence (String location, int port, String id, boolean storageFlag) {
    super(port,id,storageFlag);
    setVersion(VERSION_NUMBER);
    this.location = location;
    ibutton = new PositionIButton (this,location);
  }
In this case, the constructor was called with the id already set to CLASSNAME+SPACER+location.

Widget has a number of constructors. I'll describe the most generic. The other constructors are simplifications that eventually call this constructor. The generic constructor is here.

  public Widget(String clientClass,
                String serverClass,
                int serverPort,
                String encoderClass,
                String decoderClass,
                String storageClass,
                String id)

All of the parameters are similar to BaseObject's constructor, except for the storageClass parameter. Widget has a pluggable storage mechanism. The default storage mechanism is to use JDBC (Java DataBase Connectivity) with the MySQL database. When a widget is instantiated for the first time, it creates a table in the database to store its context. To use a different storage mechanism, simply provide the name of the class that implements the new mechanism, specified by the context.arch.storage.Storage interface. If the value null is provided for this clas, the default JDBC/MySQL implementation is used.

If storage is not desired at all, another generic constructor should be used.

  public Widget(String clientClass,
                String serverClass,
                int serverPort,
                String encoderClass,
                String decoderClass,
                boolean storageFlag,
                String id)

This is similar to the previous constructor, except rather than a storageClass being used, a storageFlag parameter is used. If the flag is true, the default storage mechanism will be enabled. If the flag is false, the storage mechanism is turned off and the widget will not store any data.

Specifying the Widget's Attributes
The attributes of the widget must be specified using the Widget class' setAttributes abstract method. A widget must implement this abstract method by simply returning an Attributes object that contains the attributes of the widget.

 Example code from the WPersonPresence class follows:

  /**
   * This method implements the abstract method Widget.setAttributes().
   * It defines the attributes for the widget as:
   *    TIMESTAMP, USERID, and LOCATION 
   *
   * @return the Attributes used by this widget
   */
  protected Attributes setAttributes() {
    Attributes atts = new Attributes();
    atts.addAttribute(TIMESTAMP,Attribute.LONG);
    atts.addAttribute(USERID);
    atts.addAttribute(LOCATION);
    return atts;
  }
Specifying the Widget's Callbacks
The callbacks of the widget must be specified using the Widget class' setCallbacks abstract method. A widget must implement this abstract method by simply returning a Callbacks object that contains the callbacks of the widget.

 Example code from the WPersonPresence class follows:

  /**
   * This method implements the abstract method Widget.setCallbacks().
   * It defines the callbacks for the widget as:
   *    UPDATE with the attributes TIMESTAMP, USERID, LOCATION
   *
   * @return the Callbacks used by this widget
   */
  protected Callbacks setCallbacks() {
    Callbacks calls = new Callbacks();
    calls.addCallback(UPDATE,setAttributes());
    return calls;

  }
This implementation uses the setAttributes method previously shown.
Specifying the Widget's Services
The services of the widget must be specified using the Widget class' setServices abstract method. A widget must implement this abstract method by simply returning a Services object that contains the callbacks of the widget.

 Example code from the WPersonPresence class follows:

  /**
   * This method implements the abstract method Widget.setServices().
   * It currently has no services and returns an empty Services object.
   *
   * @return the Services provided by this widget
   */
  protected Services setServices() {
    return new Services();
  }
The WPersonPresence class has no services. The following is from the WDisplay class which does specify a service.
  /**
   * This method implements the abstract method Widget.setServices().
   * It has a single service: DISPLAY_CHOICES
   *
   * @return the Services provided by this widget
   * @see DisplayChoiceService
   */
  protected Services setServices() {
    Services services = new Services();
    services.addService(new DisplayChoiceService(this));
    return services;
  }
This uses an existing service class called DisplayChoicesService. This service displays choices to a user and returns the selected choice to the component that requested the service be executed.
Creating a device driver
Not much that I can say here. Most likely, the sensor will come with some sort of device driver. The key is that the widget must be notified when data changes, and possibly allow the sensor to be polled. A widget designer can choose their own method for implementing this.

 The WPersonPresence class created an object device driver called PositionIButton (see the last line of the constructor above, where it was created). This object is set up to call the notify method in WPersonPresence when data changes. The polling method described in the next subsection uses this object to poll the sensor as well.

Connecting the Device Driver: Notifying and Polling
When a sensor's data changes, the widget must be notified so that it can take the proper steps: storing the data and sending it to interested subscribers. Example code from the WPersonPresence class follows:
  /**
   * Called by the generator class when a significant event has
   * occurred.  It creates a DataObject, sends it to its subscribers and
   * stores the data.
   *
   * @param event Name of the event that has occurred
   * @param data Object containing relevant event data
   * @see context.arch.widget.Widget#sendToSubscribers(String, AttributeNameValues)
   * @see context.arch.widget.Widget#store(AttributeNameValues)
   */
  public void notify(String event, Object data) {
    AttributeNameValues atts = IButtonData2Attributes((IButtonData)data);
    if (atts != null) {      
      if (subscribers.numSubscribers() > 0) {
        sendToSubscribers(event, atts);
      }
      store(atts);
    }
  }
The notify method is empty in the Widget class and can be over-ridden by any widget. It is just one way that a device driver could notify a widget of changes to the data. The important parts are that the widget, when notified calls the sendToSubscribers method with the correct callback and attributes for sending the changed data to subscribers, and calls the store method with the attributes.

 A widget should support the ability to be polled, if applicable. It must override the queryGenerator method from the Widget class. This method should use the device driver to poll the sensor and return the data in the form of an AttributeNameValues object. Example code from the WPersonPresence class follows:

  /**
   * This method returns an AttributeNameValues object with the latest iButton data.
   *
   * @return AttributeNameValues containing the latest data
   */
  protected AttributeNameValues queryGenerator() {
    IButtonData result = ibutton.pollData();
    if (result == null) {
      return null;
    }
    if (result.getId() == null) {
      return null;
    }
    return IButtonData2Attributes(result);
  }

Useful Classes

This is a list of useful classes, that you'll be using when you deal with context widgets, and some classes that are useful for implementing your own storage mechanism.
Attributes
The Attributes class is really a collection of individual Attribute objects. An Attribute represents a type of context, containing the name of the context and the data type of the context (float, int, string, etc). Attributes are used to describe context components, and to specify what context a component is interested in (in the case of a poll/subscribe/retrieval). Attributes can be nested (think of a record with sub-records) by setting the data type to be a STRUCT. More information on using Attributes in this way will hopefully be available in a future incarnation of this tutorial.
AttributeNameValues
The AttributeNameValues class is really a collection of individual AttributeNameValue objects. An AttributeNameValue is similar to an Attribute object, except that it contains a value for the piece of context it represents. AttributeNameValue objects are used to pass context data between components.
Conditions
The Conditions class is really a collection of individual Condition objects. A Condition is used to specify what data a component is interested in, in the case of a poll/subscribe/retrieval. It is meant to help a component more easily get the data it is interested in. Each Condition has the name of an attribute, a comparison operator, and a value. For example, an application may only want to know about temperature changes when the temperature is above 20 degrees. It would set a condition on its subscription, where the attribute name was "temperature", operator was ">", and value was "20". Conditions are a collection of AND'ed Condition statements.
Subscriber
The Subscriber class is a container for information about the subscriber to a component. It contains the information required to identify the subscriber and subscription in question. This includes the name of the callback, the attributes asked for, any conditions on the subscription, the name the subscriber gave to identify the subscription, and the hostname, port and id of the subscribing component.
Callbacks
The Callbacks class is really a collection of individual Callback objects. Each Callback object contains the name of the callback and the attributes corresponding to that callback.
Services
The Services class is really a collection of individual Service objects. Each Service object contains a service name and a FunctionDescriptions object that describes what the service does.
FunctionDescriptions
The FunctionDescriptions class is really a collection of individual FunctionDescription objects. Each FunctionDescription object contains the name of the function, a textual description of the function, and whether the function is synchronous or asynchronous.
Retrieval
The Retrieval class allows a component to specify what context it wants to retrieve from a widget. It contains an AttributeFunctions object and a Conditions object. The AttributeFunctions object contains the set of attributes and corresponding functions to attach to each. The Conditions object contains the set of conditions the context should meet to be returned to the requesting component.
AttributeFunctions
The AttributeFunctions class is really a collection of individual AttributeFunction objects. An AttributeFunction object is basically the same as an Attribute object, except that it has a function parameter. The function parameter allows a component to be more specific about the context it wants to retrieve. Example functions are MAX, MIN, COUNT, AVERAGE, SUM, etc. When a component wants to know when the last time anyone entered a room after 5 pm, it sets up an AttributeFunction object as follows: name is TIMESTAMP, datatype is long (timestamps are longs), and function as MAX. It would also set a Condition object for TIMESTAMP > 5 pm.
RetrievalResults
The RetrievalResults class is really a collection of individual AttributeNameValues objects. It is used for passing the results of a data retrieval back to a requesting component. Each AttributeNameValues object corresponds to a single record (with each AttributeNameValue object within it corresponding to a single attribute within the record), and the RetrievalResults corresponds to all the matching records.

The following classes are interesting if you want to provide your own storage mechanism:

StorageObject
The StorageObject object is responsible for storing context information so other components can request it at a later time. It is through this object that users are allowed to specify their own storage mechanism, if the default JDBC/MySQL combination is not desired. This object uses another object, a Storage object.
Storage
The Storage object is really an interface that indicates what methods a storage mechanism must implement.
VectorStorage
The VectorStorage object implements the Storage interface using a combination of JDBC and the MySQL database..


Back to the Table of Contents.

Back to the BaseObject section.
Forward to the Context Servers section.
Up to the Context Components section.



Context Toolkit Home
Last Modified: Feburary 24, 2000
Comments to: anind@cc.gatech.edu