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.
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:
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.
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.
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());
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.
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.
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.
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.
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.
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.
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.
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.
When you want to create a new widget, there are a set of steps that you must take:
/** * Name of widget */ public static final String CLASSNAME = "PersonPresence";
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.
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; }
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.
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.
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.
/** * 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); }
The following classes are interesting if you want to provide your own storage mechanism:
Back to the BaseObject
section.
Forward to the Context
Servers section.
Up to the Context
Components section.