package context.arch.widget;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import context.arch.BaseObject;
import context.arch.comm.DataObject;
import context.arch.comm.DataObjects;
import context.arch.comm.RequestObject;
import context.arch.comm.clients.DiscovererClient;
import context.arch.comm.clients.IndependentCommunication;
import context.arch.comm.language.EncodeException;
import context.arch.comm.language.InvalidEncoderException;
import context.arch.discoverer.ComponentDescription;
import context.arch.discoverer.Discoverer;
import context.arch.discoverer.lease.Lease;
import context.arch.discoverer.query.AbstractQueryItem;
import context.arch.logging.ComponentUpdateLogger;
import context.arch.logging.LoggingException;
import context.arch.logging.WidgetRegistrationLogger;
import context.arch.service.Service;
import context.arch.service.Services;
import context.arch.service.helper.FunctionDescription;
import context.arch.service.helper.FunctionDescriptions;
import context.arch.service.helper.ServiceDescription;
import context.arch.service.helper.ServiceInput;
import context.arch.storage.Attribute;
import context.arch.storage.AttributeNameValue;
import context.arch.storage.AttributeNameValuePin;
import context.arch.storage.Attributes;
import context.arch.storage.Conditions;
import context.arch.storage.Retrieval;
import context.arch.storage.RetrievalResults;
import context.arch.storage.StorageObject;
import context.arch.subscriber.AbstractSubscriber;
import context.arch.subscriber.Callback;
import context.arch.subscriber.Callbacks;
import context.arch.subscriber.Subscriber;
import context.arch.subscriber.Subscribers;
import context.arch.util.Constants;
import context.arch.util.Error;
;
/**
* This class is the basic context widget, with attributes and
* methods that should apply to all context widgets.
*
* Modified for generics.
* @param <D> represents the WidgetData used to encapsulate the widget data.
* TODO: since data is being transmitted using an encapsulation, should we also use that to store the data?
*
* @see context.arch.BaseObject
* @author Anind
* @author Brian Y. Lim
*/
public abstract class Widget extends BaseObject {
private static final Logger LOGGER = Logger.getLogger(Widget.class.getName());
static {LOGGER.setLevel(Level.INFO);} // this should be set in a configuration file
/** Debug flag. Set to true to see debug messages. */
public static boolean DEBUG = false;
/** Tag for the class file being used by the widget. */
public static final String CLASS = "class";
/** Tag for the type of this object. */
public static final String WIDGET_TYPE = "widget";
/** Dummy version number. Subclasses should override this value. */
public String VERSION_NUMBER = "UNDEFINED";
/** Default port for widgets to use */
public static final int DEFAULT_PORT = 5000;
/** Tag for version number. */
public static final String VERSION = "version";
/** Attribute tag for the timestamp of widget data */
public static final String TIMESTAMP = "timestamp";
/**
* DataObject protocol tag to indicate the widget should return the latest stored data
*/
public static final String QUERY = "query";
/**
* DataObject protocol tag to indicate the reply to a QUERY message
*/
public static final String QUERY_REPLY = "queryReply";
/**
* DataObject protocol tag to indicate the widget should get the latest data from the generator and return them
*/
public static final String UPDATE_AND_QUERY = "updateAndQuery";
/**
* DataObject protocol tag to indicate the reply to an UPDATE_AND_QUERY message
*/
public static final String UPDATE_AND_QUERY_REPLY = "updateAndQueryReply";
/**
* DataObject protocol tag to indicate the widget should return its list of attributes
*/
public static final String QUERY_ATTRIBUTES = "queryAttributes";
/**
* DataObject protocol tag to indicate the widget should return its list of attributes
*/
public static final String QUERY_CONSTANT_ATTRIBUTES = "queryConstantAttributes";
/**
* DataObject protocol tag to indicate the reply to a QUERY_ATTRIBUTES message
*/
public static final String QUERY_ATTRIBUTES_REPLY = "queryAttributesReply";
/**
* DataObject protocol tag to indicate the reply to a QUERY_CONSTANT_ATTRIBUTES message
*/
public static final String QUERY_CONSTANT_ATTRIBUTES_REPLY = "queryConstantAttributesReply";
/**
* DataObject protocol tag to indicate the widget should return its list of callbacks
*/
public static final String QUERY_CALLBACKS = "queryCallbacks";
/**
* DataObject protocol tag to indicate the reply to a QUERY_CALLBACKS message
*/
public static final String QUERY_CALLBACKS_REPLY = "queryCallbacksReply";
/**
* DataObject protocol tag to indicate the widget should return its list of services
*/
public static final String QUERY_SERVICES = "queryServices";
/**
* DataObject protocol tag to indicate the reply to a QUERY_SERVICES message
*/
public static final String QUERY_SERVICES_REPLY = "queryServicesReply";
/**
* DataObject protocol tag to indicate the widget should return its version number
*/
public static final String QUERY_VERSION = "queryVersion";
/**
* DataObject protocol tag to indicate the reply to a QUERY_VERSION message
*/
public static final String QUERY_VERSION_REPLY = "queryVersionReply";
/**
* DataObject protocol tag to indicate the widget should accept the given data
*/
public static final String PUT_DATA = "putData";
/**
* DataObject protocol tag to indicate the reply to a PUT_DATA message
*/
public static final String PUT_DATA_REPLY = "putDataReply";
/**
* DataObject protocol tag to indicate an update is being sent
*/
public static final String CALLBACK_UPDATE = "update";
/**
* Constant for the widget spacer
*/
public static final String SPACER = Constants.SPACER;
protected Attributes nonConstantAttributes;
protected Attributes constantAttributes;
protected Callbacks callbacks;
protected Services services;
protected long CurrentOffset;
/**Object to handle subscriptions to context data
* @see context.arch.subscriber.Subscribers
* @see context.arch.subscriber.Subscriber
*/
public Subscribers subscribers;
/**Object to keep track of storage
* @see context.arch.storage.StorageObject
*/
public StorageObject storage;
/**
* Constructor that sets up internal variables for maintaining
* the list of widget attributes, callbacks, and services and setting up
* the BaseObject info.
*
* TO COMPLETE : for storage use
*
* @param clientClass Class to use for client communications
* @param serverClass Class to use for server communications
* @param serverPort Port to use for server communications
* @param encoderClass Class to use for communications encoding
* @param decoderClass Class to use for communications decoding
* @param storageClass Class to use for storage
* @param id String to use for widget id and persistent storage
* @see context.arch.storage.StorageObject
*/
public Widget(String clientClass, String serverClass, int serverPort, String encoderClass,
String decoderClass, String storageClass, String id, String widgetClassName) {
super(clientClass, serverClass, serverPort, encoderClass, decoderClass);
initFields();
setId(id);
// setWidgetClassName(widgetClassName);
// init(id);
// initFull();
}
/**
* Constructor that sets up internal variables for maintaining
* the list of widget attributes, callbacks, and services and setting up
* the BaseObject info. This version takes a boolean to indicate whether the
* default storage class should be used or whether no storage should be
* provided.
*
* @param clientClass Class to use for client communications
* @param serverClass Class to use for server communications
* @param serverPort Port to use for server communications
* @param encoderClass Class to use for communications encoding
* @param decoderClass Class to use for communications decoding
* @param storageFlag Flag to determine whether storage should be used or not
* @param id String to use for widget id and persistent storage
* @see context.arch.storage.StorageObject
*/
public Widget(String clientClass, String serverClass, int serverPort, String encoderClass,
String decoderClass, boolean storageFlag, String id, String widgetClassName) {
super(clientClass,serverClass,serverPort,encoderClass,decoderClass);
initFields();
setId(id);
// setWidgetClassName(widgetClassName);
// init(id); // call this only with start()
// initFull();
// TODO: not ready --Brian
if (storageFlag) {
storage = new StorageObject();
}
}
/**
* Constructor that sets up internal variables for maintaining
* the list of widget attributes, callbacks and services. It takes a port
* number as a parameter to indicate which port to listen for
* messages/connections, the id to use for the widget, and a flag to indicate
* whether storage functionality should be turned on or off.
*
* @param port Port to listen to for incoming messages
* @param id Widget id
* @param storageFlag Boolean flag to indicate whether storage should be turned on
*/
public Widget(int port, String id, String widgetClassName, boolean storageFlag) {
this(null, null, port, null, null, storageFlag, id, widgetClassName);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of widget attributes, callbacks and services. It takes a port
* number as a parameter to indicate which port to listen for
* messages/connections.
*
* @param port Port to listen to for incoming messages
* @param id Widget id
*/
public Widget(int port, String id, String widgetClassName) {
this(null, null, port, null, null, null, id, widgetClassName);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of widget attributes, callbacks and services. It takes
* the id to use for the widget, and a flag to indicate
* whether storage functionality should be turned on or off.
*
* @param id Widget id
* @param storageFlag Boolean flag to indicate whether storage should be turned on
*/
public Widget(String id, boolean storageFlag, String widgetClassName) {
this(null, null, -1, null, null, storageFlag, id, widgetClassName);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of widget attributes, callbacks and services. It takes the
* widget id as a parameter
*
* @param id ID of the widget
*/
public Widget(String id, String widgetClassName) {
//this(null,null,-1,null,null,null,id, widgetClassName);
/*
* The original constructor would create a CommunicationsObject with the DEFAULT_PORT of 5555.
* This is not scalable if we have multiple widgets.
* So this has been changed to use findFreePort()
*/
this(null, null, findFreePort(), null, null, null, id, widgetClassName);
}
/**
* Convenience method to contain all initializations of lists of attributes, callbacks, services
*/
protected void initFields() {
this.nonConstantAttributes = new Attributes();
this.constantAttributes = new Attributes();
this.callbacks = new Callbacks();
this.services = new Services();
}
/**
* Encapsulation of init processes
*
* Don't put this in the constructor, because it is overridable.
* http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/
* http://efreedom.com/Question/1-3342784/Using-Abstract-Init-Function-Abstract-Classs-Constructor
*/
protected void initFull() {
// add timestamp attribute for all attributes
addAttribute(Attribute.instance(TIMESTAMP, Long.class));
init(); // may be implemented by subclass widget
setCallbacks(initCallbacks());
setSubscribers();
getNewOffset();
}
/* ----------------------------------------------------------------
* Methods that subclasses should use to populate widget with
* attributes, services, callbacks.
* ---------------------------------------------------------------- */
/**
* Subclasses implement this to do initialization work like adding
* non-constant and constant attributes. This is similar to the
* init() function in Java applets. Attributes can be added using
* #addAttribute(Attribute) and its variants.
*/
protected void init() {
// adds and does nothing by default
}
/**
* Add non-constant attribute to the Widget.
* @param att
*/
protected void addAttribute(Attribute<?> att) {
addAttribute(att, false);
}
/**
* Add constant or non-constant attribute to the Widget.
* @param att Attribute to add
* @param constant if true, then add attribute as constant, otherwise as non-constant
*/
protected void addAttribute(Attribute<?> att, boolean constant) {
if (constant) {
constantAttributes.add(att);
}
else {
nonConstantAttributes.add(att);
}
}
/**
* Add a Service that can be executed remotely on this Widget.
* @param service
* @see Service
*/
public void addService(Service service) {
services.add(service);
}
/* ---------------------------------------------------------------- */
public int getPort() {
return communications.getServerPort();
}
/**
* Must call this method to start the Widget.
* TODO this procedure is needed because {@link #initFull()} depends on some instance variables
* that may be set only after a widget subclass is constructed.
* @param register If null, then doesn't find discoverer; otherwise, it would find the discoverer and
* would register if true
*/
@Override
public void start(final Boolean register) {
initFull();
if (register != null) {
// // put into thread
// final String threadName = this.getId();
// new Thread(threadName) {
// @Override
// public void run() {
findDiscoverer(register);
// System.out.println(threadName + " started (port = " + Widget.this.getPort() + ")");
// }
// }.start();
}
LOGGER.info(getId() + " started (port = " + this.getPort() + ")");
}
@Override
public String getType() {
return Widget.WIDGET_TYPE;
}
/**
* Method to find the discoverer. inserts the widget registration entry into the log
* Refactored to make it protected, so it should not be called externally. --Brian
* @see context.arch.BaseObject#findDiscoverer(boolean, context.arch.discoverer.lease.Lease, boolean)
*/
@Override
protected Error findDiscoverer(boolean registration, Lease registrationLease, boolean automaticRenewal) {
WidgetRegistrationLogger WRL= WidgetRegistrationLogger.getWRLInstance();
try{
WRL.insertWidgetRegistrationEntry(this.getId(), this.constantAttributes, this.nonConstantAttributes, this.callbacks, this.services);
}catch(LoggingException e){
System.out.println(e.toString());
}
return super.findDiscoverer(registration, registrationLease, automaticRenewal);
}
/**
* Sets the current state of *some* set of NonConstantAttributes in the widget.
* If subscribers or storage is enabled, we send the attributes out to
* subscribers.
*
* Note: does not enforce strong type checking. If an arbitrary attribute is
* set, this widget will 'acquire' that attribute. It will probably not be
* sent as a callback, however.
*
* @param atts
*/
public void addNonConstantAttributes(Attributes atts) {
// adds or replaces
nonConstantAttributes.putAll(atts);
}
/**
* Sets the callbacks for the widget
*/
protected Callbacks initCallbacks() {
Callbacks calls = new Callbacks();
Attributes atts = new Attributes();
atts.putAll(new Attributes(nonConstantAttributes)); // make copy, so that callbacks don't point to changed data
atts.putAll(new Attributes(constantAttributes));
// add a callback corresponding to the UPDATE tag
calls.addCallback(CALLBACK_UPDATE, atts);
return calls;
}
/**
* Sets the attributes; may add if an attribute is not already contained.
* @param atts
*/
protected void setNonConstantAttributes(Attributes atts) {
nonConstantAttributes.putAll(atts);
}
protected void setConstantAttributes(Attributes atts) {
constantAttributes.putAll(atts);
}
protected void setCallbacks(Callbacks calls) {
callbacks.putAll(calls);
}
protected void setServices(Services svcs) {
services.putAll(svcs);
}
/**
* Returns the attribute value with the given name
*
* @param name Name of the attribute to get
*/
protected Class<?> getAttributeType(String name) {
return nonConstantAttributes.get(name).getType();
}
/**
* Checks if the given attribute is an attribute of this widget.
*
* @param name Name of the attribute to check
*/
protected boolean isNonConstantAttribute(String name) {
return nonConstantAttributes.containsName(name);
}
protected boolean isConstantAttribute(String name) {
return constantAttributes.containsName(name);
}
/**
* Checks if the given callback is a callback of this widget
*
* @param name Name of the callback to check
* @return boolean True if name is a known callback name
*/
protected boolean isCallback(String name) {
return callbacks.containsKey(name);
}
/**
* Call this to update widget with new data.
* It will also notify listeners of this change.
* This is called in memory, when the caller has a direct memory reference to this widget.
* @param data that encapsulates some or all of the widget attributes
* @see #updateData(Attributes)
*/
public void updateData(WidgetData data) {
updateData(data.toAttributes());
}
/**
* Method of updating several attributes of the Widget.
* It will ignore attributes that do not match what the widget contains.
* @param attrs
*/
public void updateData(Attributes attrs) {
notify(Widget.CALLBACK_UPDATE, attrs);
// System.out.println(this.getClass().getSimpleName() + ".updateData(Attributes attrs: " + attrs + ")");
}
/**
* Convenience method of directly updating an attribute of the Widget.
* It also adds an attribute TIMESTAMP specifying the current time if that was not set.
* @param attName
* @param value
*/
@SuppressWarnings("serial")
public <T extends Comparable<? super T>> void updateData(final String attName, final T value) {
Attributes attrs = new Attributes() {{
if (!containsName(Widget.TIMESTAMP)) { // set time if not already set
add(AttributeNameValue.instance(Widget.TIMESTAMP, System.currentTimeMillis()));
}
add(AttributeNameValue.instance(attName, value));
}};
updateData(attrs);
}
/**
* This actually notifies subscribers, and
* also stores the attribute values
* TODO: consider refactoring the name of the method
* @param callbackName
* @param attrs
*/
protected void notify(String callbackName, Attributes attrs) {
// new RuntimeException("widget.notify attrs = " + attrs).printStackTrace();
// System.out.println("widget.notify attrs = " + attrs);
if (attrs == null) { return; }
// comment becouse this method override not update widget in memory
//setNonConstantAttributes(attrs); // update widget in memory
memory(attrs);
store(attrs); // update widget in storage
sendToSubscribers(callbackName); // notify subscribers
// This is very useful for debugging, but it is very verbose, especially for widgets with many attributes
// System.out.println(getClassName() + ".notify attrs: " + attrs);
}
/**
* <p>
* Convenience class to wrap Attributes as an encapsulation to pass around to different components.
* Subclasses can have their own instance fields to clarify which attributes are valid.
* Nested class to support transmission of widget data.
* </p>
* <p>
* If only using Attributes, and not specifying specific attributes as fields in code, then just use {@link Attributes} instead
* </p>
*/
public static class WidgetData {
private String widgetName; // name of widget that this data is associated to
private Attributes atts;
/**
* Sets timestamp to default: current time
*/
public WidgetData(String widgetName) {
this(widgetName, System.currentTimeMillis());
}
public WidgetData(String widgetName, long timestamp) {
this.widgetName = widgetName;
atts = new Attributes();
atts.add(new AttributeNameValue<Long>(TIMESTAMP, timestamp));
}
public WidgetData(Class<? extends Widget> widgetClass, long timestamp) {
this(widgetClass.getName(), timestamp);
}
@SuppressWarnings("unchecked")
public <T extends Comparable<? super T>> T getAttributeValue(String name, Class<T> type) {
T value = ((AttributeNameValue<T>) atts.get(name)).getValue();
return value;
}
public <T extends Comparable<? super T>> void setAttributeValue(String name, T value) {
atts.add(new AttributeNameValue<T>(name, value));
}
public <T extends Comparable<? super T>> void setAttributeValuePin(String name, T value, Integer pin, String pinType) {
atts.add(new AttributeNameValuePin<T>(name, value, pin, pinType));
}
public Attributes toAttributes() {
// may want to add more things to att
return atts;
}
/**
* Convenience method to get ComponentDescription w/o using the discovery mechanism.
* Should be overridden by subclasses to consider constant attributes. TODO
*/
public ComponentDescription toWidgetState() {
ComponentDescription widgetState = new ComponentDescription();
// widgetState.id = this.getClass().getName(); // need this or it would be invalidated before classification
// actually, don't need to set anymore --Brian
for (Attribute<?> att : this.toAttributes().values()) {
widgetState.addNonConstantAttribute(att);
}
return widgetState;
}
public String toString() {
return widgetName + " data: " + atts;
}
}
/**
* This method is called when a remote component sends an UPDATE_AND_QUERY message.
* It calls the widget's queryGenerator method to get the latest generator info,
* and then stores it.
*/
protected void updateWidgetInformation() {
Attributes atts = queryGenerator();
if (atts != null) {
if (storage != null) {
storage.store(atts);
}
}
}
// /**
// * This abstract method is called when the widget wants to get the latest generator
// * info.
// *
// * @return AttributeNameValues containing the latest generator information
// */
// protected abstract Attributes queryGenerator();
/**
* This method is called when the widget wants to get the latest generator info.
* Default returns an empty AttributeNameValues object; widget cannot be polled.
* @return empty AttributeNameValues
*
* TODO: this functionality has been superceded by Generators that subclass Enactors
*/
protected Attributes queryGenerator() {
return new Attributes();
}
/**
* This is an empty method that should be overridden by objects
* that subclass from this class. It is called when another component
* tries to run a method on the widget, but it's not a query.
*
* @param data DataObject containing the data for the method
* @param error String containing the incoming error value
* @return DataObject containing the method results
*/
protected DataObject runWidgetMethod(DataObject data, String error) {
@SuppressWarnings("unused")
String name = data.getName();
Error err = new Error(error);
if (err.getError() == null) {
err.setError(Error.UNKNOWN_METHOD_ERROR);
}
DataObjects v = new DataObjects();
v.addElement(err.toDataObject());
return new DataObject(data.getName(),v);
}
/**
* This method is meant to handle any internal methods that the baseObject doesn't
* handle. In particular, this method handles the common details for query requests,
* update and query requests, and version requests that each widget should provide.
* If the method is not one of these queries, then it calls runWidgetMethod which each widget
* should provide.
*
* @param data DataObject containing the method to run and parameters
* @return DataObject containing the results of running the method
* @see #QUERY
* @see #QUERY_VERSION
* @see #UPDATE_AND_QUERY
*/
public DataObject runUserMethod(DataObject data) {
debugprintln(DEBUG, "\nWidget runUserMethod " + data.getName ());
DataObject widget = data.getDataObject(ID);
String error = null;
if (widget == null) {
error = Error.INVALID_ID_ERROR;
}
else {
String queryId = widget.getValue();
if (!queryId.equals(getId())) {
error = Error.INVALID_ID_ERROR;
}
}
String methodType = data.getName();
if (methodType.equals(UPDATE_AND_QUERY)) {
return queryWidget(data,true,error);
}
else if (methodType.equals(QUERY)) {
return queryWidget(data,false,error);
}
else if (methodType.equals(QUERY_ATTRIBUTES)) {
return queryAttributes(data,error);
}
else if (methodType.equals(QUERY_CONSTANT_ATTRIBUTES)) {
return queryConstantAttributes(data,error);
}
else if (methodType.equals(QUERY_CALLBACKS)) {
return queryCallbacks(data,error);
}
else if (methodType.equals(QUERY_SERVICES)) {
return queryServices(data,error);
}
else if (methodType.equals(Subscriber.ADD_SUBSCRIBER)) {
return addSubscriber(data,error);
}
else if (methodType.equals(Subscriber.REMOVE_SUBSCRIBER)) {
return removeSubscriber(data,error);
}
else if (methodType.equals(StorageObject.RETRIEVE_DATA)) {
return retrieveData(data,error);
}
else if (methodType.equals(PUT_DATA)) {
Error err = putData(data, error);
DataObjects v = new DataObjects();
v.addElement(err.toDataObject());
return new DataObject(PUT_DATA_REPLY, v);
}
else if (methodType.equals(Service.SERVICE_REQUEST)) {
return executeService(data,error);
}
else {
return runWidgetMethod(data,error);
}
}
/**
* This method puts context data in a widget. It is expected
* that widgets will get data from a generator. But for some
* widgets, the generator will not use the context toolkit directly,
* but may use a web CGI script, for example. For this case, the
* widget provides this method to collect the data and makes it available
* to subscribers and for retrieval.
*
* @param data DataObject containing the context data to write
* @param error String containing the incoming error value
* @return DataObject containing the results of writing the data
*/
protected Error putData(DataObject data, String error) {
Error err = new Error(error);
if (err.getError() == null) { return err; }
Attributes atts = Attributes.fromDataObject(data);
// empty data
if (atts == null || atts.isEmpty()) { return err.setError(Error.INVALID_DATA_ERROR); }
DataObject callbackObj = data.getDataObject(Subscriber.CALLBACK_NAME);
String callbackName = null;
if (callbackObj != null) { callbackName = callbackObj.getValue(); }
if (callbackName != null) {
Callback callback = callbacks.get(callbackName);
if (callback == null) { return err.setError(Error.INVALID_CALLBACK_ERROR); }
Attributes callAtts = callback.getAttributes();
// check if atts contains all atttributes in callAtts
for (String name : callAtts.keySet()) {
if (!atts.containsKey(name)) {
return err.setError(Error.INVALID_ATTRIBUTE_ERROR);
}
}
setNonConstantAttributes(atts);
sendToSubscribers(callbackName);
store(atts);
return err.setError(Error.NO_ERROR);
}
// nevertheless, if it can handle attributes, then process
else if (canHandle(atts)) {
store(atts);
return err.setError(Error.NO_ERROR);
}
else {
return err.setError(Error.INVALID_ATTRIBUTE_ERROR);
}
}
/**
* This method queries the callbacks of a widget.
*
* @param query DataObject containing the query
* @param error String containing the incoming error value
* @return DataObject containing the results of the query
*/
protected DataObject queryCallbacks(DataObject query, String error) {
DataObjects v = new DataObjects();
Error err = new Error(error);
if (err.getError() == null) {
if (callbacks == null || callbacks.isEmpty()) {
err.setError(Error.EMPTY_RESULT_ERROR);
}
else {
v.addElement(callbacks.toDataObject());
err.setError(Error.NO_ERROR);
}
}
v.addElement(err.toDataObject());
return new DataObject(QUERY_CALLBACKS_REPLY, v);
}
/**
* This method queries the attributes of a widget.
*
* @param query DataObject containing the query
* @param error String containing the incoming error value
* @return DataObject containing the results of the query
*/
protected DataObject queryAttributes(DataObject query, String error) {
DataObjects v = new DataObjects();
Error err = new Error(error);
if (err.getError() == null) {
if (nonConstantAttributes == null || nonConstantAttributes.isEmpty()) {
err.setError(Error.EMPTY_RESULT_ERROR);
}
else {
err.setError(Error.NO_ERROR);
v.addElement(nonConstantAttributes.toDataObject());
}
}
v.addElement(err.toDataObject());
return new DataObject(QUERY_ATTRIBUTES_REPLY, v);
}
/**
* This method queries the constant attributes of a widget.
*
* @param query DataObject containing the query
* @param error String containing the incoming error value
* @return DataObject containing the results of the query
*
* @author Agathe
*/
protected DataObject queryConstantAttributes(DataObject query, String error) {
DataObjects v = new DataObjects();
Error err = new Error(error);
if (err.getError() == null) {
if (constantAttributes == null) {
err.setError(Error.EMPTY_RESULT_ERROR);
}
else {
err.setError(Error.NO_ERROR);
v.addElement(constantAttributes.toDataObject());
}
}
v.addElement(err.toDataObject());
return new DataObject(QUERY_CONSTANT_ATTRIBUTES_REPLY, v);
}
/**
* This method queries the services of a widget.
*
* @param query DataObject containing the query
* @param error String containing the incoming error value
* @return DataObject containing the results of the query
*/
protected DataObject queryServices(DataObject query, String error) {
DataObjects v = new DataObjects();
Error err = new Error(error);
if (err.getError() == null) {
err.setError(Error.NO_ERROR);
v.addElement(services.toDataObject());
}
v.addElement(err.toDataObject());
return new DataObject(QUERY_SERVICES_REPLY, v);
}
/**
* This method runs a query on a widget, asking for either it's latest
* acquired data (QUERY) or asking for the widget to acquire and return
* new data (UPDATE_AND_QUERY)
*
* @param query DataObject containing the query request
* @param update Whether or not to acquire new data
* @param error String containing the incoming error value
* @return DataObject containing the reply to the query
*/
protected DataObject queryWidget(DataObject query, boolean update, String error) {
debugprintln(DEBUG, "Widget queryWidget query:"+query.toString() + "\nerror:"+ error);
// println("Widget.queryWidget query:"+query.toString() + "\nerror:"+ error);
DataObject result = null;
DataObjects v = new DataObjects();
if (update) {
result = new DataObject(UPDATE_AND_QUERY_REPLY, v);
}
else {
result = new DataObject(QUERY_REPLY, v);
}
Attributes atts = Attributes.fromDataObject(query);
Error err = new Error(error);
if (err.getError() == null) {
if (atts == null) {
err.setError(Error.MISSING_PARAMETER_ERROR);
}
else if (!canHandle(atts)) {
err.setError(Error.INVALID_ATTRIBUTE_ERROR);
}
}
if (err.getError() != null) {
v.addElement(err.toDataObject());
return result;
}
// update widget with data in Attributes
if (atts != null) { // TODO shouldn't this also check for whether to update?
updateData(atts);
if (storage != null) {
if (update) {
updateWidgetInformation();
}
storage.flushStorage();
Attributes values = storage.retrieveLastAttributes();
if (values != null) {
Attributes subset = values.getSubset(atts);
if (subset.isEmpty()) {
err.setError(Error.INVALID_DATA_ERROR);
}
else {
v.addElement(subset.toDataObject());
if (subset.size() >= atts.size()) {
err.setError(Error.NO_ERROR);
}
else {
err.setError(Error.INCOMPLETE_DATA_ERROR);
}
}
}
else {
err.setError(Error.INVALID_DATA_ERROR);
}
}
}
v.addElement(err.toDataObject());
debugprintln (DEBUG, "Widget queryWidget return:"+result.toString());
return result;
}
/**
* This method checks the list of attributes to ensure
* that the widget contains these attributes.
*
* @param attributes Attributes object containing attributes to check
* @return whether the list of attributes is valid
*/
protected boolean canHandle(Attributes atts) {
purgeConstantAttributes(atts);
for (Attribute<?> att : atts.values()) {
String name = att.getName();
if (!isNonConstantAttribute(name)) { // not a modifiable attribute
return false;
}
}
return true;
}
protected void purgeConstantAttributes(Attributes atts) {
for (Iterator<Attribute<?>> it = atts.values().iterator(); it.hasNext();) {
Attribute<?> a = it.next();
if (isConstantAttribute(a.getName())) {
it.remove();
}
}
}
/**
* This method attempts to execute a widget service.
*
* @param request DataObject containing the service request
* @param error String containing the incoming error value
* @return DataObject containing the results of the service request
*/
protected DataObject executeService(DataObject request, String error) {
DataObjects v = new DataObjects();
Error err = new Error(error);
DataObject result;
if (err.getError() == null) {
ServiceInput si = new ServiceInput(request);
if (!services.hasService(si.getServiceName())) {
err.setError(Error.UNKNOWN_SERVICE_ERROR);
}
else {
Service service = services.getService(si.getServiceName());
FunctionDescriptions fds = service.getFunctionDescriptions();
if (!fds.hasFunctionDescription(si.getFunctionName())) {
err.setError(Error.UNKNOWN_FUNCTION_ERROR);
}
else {
String synchronicity = request.getDataObject(FunctionDescription.FUNCTION_SYNCHRONICITY).getValue();
FunctionDescription fd = fds.getFunctionDescription(si.getFunctionName());
if (!fd.getSynchronicity().equals(synchronicity)) {
err.setError(Error.INVALID_TIMING_ERROR);
}
else {
result = service.execute(si);
err.setError(Error.NO_ERROR);
// make sure result is meaningful, then add
if (result != null && result.getName() != null) {
v.add(result);
}
}
}
}
}
v.addElement(err.toDataObject());
return new DataObject(Service.SERVICE_REQUEST_REPLY, v);
}
public Attributes getNonConstantAttributes() {
return new Attributes(nonConstantAttributes); // duplicate so that it is not accidentally overridden
}
public <T extends Comparable<? super T>> T getNonConstantAttributeValue(String attName) {
return nonConstantAttributes.getAttributeValue(attName);
}
public Attributes getConstantAttributes() {
return new Attributes(constantAttributes); // duplicate so that it is not accidentally overridden
}
/**
* This method should be called to send data to subscribers when a context
* widget's callback is triggered. It sends data only to those subscribers
* that have subscribed to the specified callback.
*
* @author Agathe, to use independentCommunication
*
*
* @param callbackTag Context widget callback that was triggered
* @param atts AttributeNameValues to send to subscribers
* @param data DataObject version of atts
* @see BaseObject#userRequest(DataObject, String, String, int)
* @see context.arch.subscriber.Subscribers
*/
protected void sendToSubscribers(String callbackName) {
if (!callbacks.containsKey(callbackName)) { return; }
if (subscribers.isEmpty()) { return; }
// new RuntimeException("widget.sendToSubscribers attributes = " + nonConstantAttributes).printStackTrace();
//ADDED FOR EXPLANATIONS
ArrayList<ComponentDescription> logSubscribers = new ArrayList<ComponentDescription>();
debugprintln(DEBUG, "\n\nWidget <sendToSubscribers> callback=" + callbackName);
Callback callback = callbacks.get(callbackName);
// For each subscriber, see if the subscriber is interested
debugprintln(DEBUG, "widget <sendToSubs> nb subs? " + subscribers.size());
//println("widget <sendToSubs> subs? " + subscribers);
for (AbstractSubscriber asub : subscribers.values()) {
Subscriber sub = (Subscriber) asub;
DataObject result = null; // callback reply
// Check if the subscriber wants this callback
debugprintln(DEBUG, "Widget <sendToSubs> test callback=" + callbackName + " ?? equal to sub call=" + sub.getSubscriptionCallback ());
if (callbackName.equals(sub.getSubscriptionCallback())) {
// Checks if the subscriber has specified conditions
if (dataValid(callback, sub.getCondition())) {
debugprintln(DEBUG, "Widget <sendToSubscribers> datavalid TRUE");
//println("widget callback = " + callback);
Attributes callAtts = new Attributes(callback.getAttributes());
Attributes subAtts = nonConstantAttributes.getSubset(callAtts);//.getSubset(sub.getAttributes());
Attributes constSubAtts = constantAttributes.getSubset(callAtts);//.getSubset(sub.getAttributes());
//println("widget nonConstantAttributes = " + nonConstantAttributes);
//println("widget callAtts = " + callAtts);
//println("widget sub = " + sub);
//println("widget sub.getAttributes() = " + sub.getAttributes());
//println("widget subAtts = " + subAtts);
//only process if we have attribute to return
if (subAtts.isEmpty() && constSubAtts.isEmpty()) { continue; }
DataObject subId = new DataObject(Subscriber.SUBSCRIBER_ID, sub.getSubscriptionId());
DataObjects v = new DataObjects();
v.addElement(subId);
DataObject compDescription = buildCallbackComponentDescription(subAtts, constSubAtts);
//println("widget compDescription = " + compDescription + "\n");
v.addElement(compDescription);
DataObject send = new DataObject(Subscriber.SUBSCRIPTION_CALLBACK, v);
// Agathe: change to use independentUserRequest
try {
result = null;
// Create the independent comm object
IndependentCommunication comm = new IndependentCommunication(
new RequestObject(send, Subscriber.SUBSCRIPTION_CALLBACK,
sub.getSubscriberHostName(),
sub.getSubscriberPort()));
// Store the sub object to remove it if it does not exist anymore
comm.setObjectToStore(sub);
// Store some reference for this communication
comm.setSenderClassId(Widget.WIDGET_TYPE + Subscriber.SUBSCRIPTION_CALLBACK);
// Send the notification
independentUserRequest(comm);
} catch (EncodeException ee) {
System.out.println("Widget sendToSubscribers EncodeException: "+ee);
} catch (InvalidEncoderException iee) {
System.out.println("Widget sendToSubscribers InvalidEncoderException: "+iee);
}
//ADDED FOR LOGGING:
ComponentDescription logCompDescription = new ComponentDescription();
logCompDescription.id = sub.getBaseObjectId();
logCompDescription.setConstantAttributes(constSubAtts);
logCompDescription.setNonConstantAttributes(subAtts);
sub.resetErrors();
// we pass the result on for processing
// TODO: pass it on only if it's not an error message?
try {
processCallbackReply(result, sub);
//ADDED FOR EXPLANATIONS
logSubscribers.add(logCompDescription);
} catch (Exception e) {
System.out.println ("Widget sendToSubscribers Exception during processCallbackReply: "+e);
}
}
else {
debugprintln(DEBUG, "Widget <sendToSubscribers> datavalid FALSE");
}
}
}
//ADDED FOR EXPLANATIONS
ComponentUpdateLogger CUL = ComponentUpdateLogger.getCULInstance();
try{
CUL.insertComponentUpdateEntry(this.getId(), callbackName, logSubscribers);
} catch (LoggingException e) {
e.printStackTrace();
}
}
/**
* constructs an abbreviated ComponentDescription containing only the necessary information
* for the callback.
* @param nonConstantAtts
* @param constantAtts
*/
private DataObject buildCallbackComponentDescription(Attributes nonConstantAtts, Attributes constantAtts) {
DataObjects cdv = new DataObjects();
cdv.addElement(new DataObject(Discoverer.ID, getId()));
DataObjects v = new DataObjects();
v.addElement(constantAtts.toDataObject());
cdv.addElement(new DataObject(Discoverer.CONSTANT_ATTRIBUTE_NAME_VALUES, v));
v = new DataObjects();
v.addElement(nonConstantAtts.toDataObject());
cdv.addElement(new DataObject(Discoverer.NON_CONSTANT_ATTRIBUTE_NAME_VALUES, v));
return new DataObject(Discoverer.REGISTERER, cdv);
}
/**
* This private method checks that the given data falls within the given conditions.
*
* @param atts AttributeNameValues containing data to validate
* @param conditions Conditions to validate against
* @return whether the data falls within the given conditions
*/
private boolean dataValid(Callback callback, AbstractQueryItem<?,?> condition) {
if (condition == null) { return true; }
Boolean match = condition.match(getComponentDescription());
if (match == null) { return false; }
else { return match; }
}
/**
* This method should be overridden to process the results of subscription callbacks.
*
* @param result DataObject containing the result
* @param sub Subscriber that returned this reply
*/
protected void processCallbackReply (DataObject result, Subscriber sub) {
}
/**
* This method adds a subscriber to this object. It calls
* Subscribers.addSubscriber() if it can add the subscriber. It returns a
* DataObject containing the reply information, including any error information.
*
* Agathe: change the Subscriber calls to use AbstractSubscriber
*
* @param sub DataObject containing the subscription information
* @param error String containing the incoming error value
* @return DataObject with the reply to the subscription request
* @see context.arch.subscriber.Subscribers#addSubscriber(String,String,int,String,String,Conditions,Attributes)
*/
public DataObject addSubscriber(DataObject sub, String error) {
DataObjects v = new DataObjects();
Error err = new Error(error);
debugprintln(DEBUG, "Widget <addSubscriber> " + sub);
if (err.getError() == null) {
// Subscriber subscriber = (Subscriber) AbstractSubscriber.fromDataObject(sub); // seemed obsolete --Brian
Subscriber subscriber = new Subscriber(sub);
debugprintln(DEBUG, "Widget <addSubscriber> has created sub=" + subscriber);
debugprintln(DEBUG, "\nWidget <addSubscriber> Subscription callback=" + subscriber.getSubscriptionCallback());
//debugprintln("Widget <addSubscriber> Callbacks =" + this.callbacks);
// Test if this widget may handle the specified attributes and conditions
//TODO: add this validation code back in after query system updated --alann
// else if (!canHandle(subscriber.getAttributes(),subscriber.getCondition()) {
// debugprintln(DEBUG, "Widget <addSubscriber> cannot handle att");
// err.setError(Error.INVALID_ATTRIBUTE_ERROR);
// }
// Test if this widget may handle the specified callback
if (!isCallback(subscriber.getSubscriptionCallback())) {
debugprintln(DEBUG, "Widget <addSubscriber> doesn't know the callback");
err.setError(Error.INVALID_CALLBACK_ERROR);
}
// Add the subscriber
else {
debugprintln(DEBUG, "Widget <addSubscriber> has added it");
subscribers.add(subscriber);
debugprintln(DEBUG, "Widget <addSubscriber> The sub is now " + subscriber);
v.addElement(new DataObject(AbstractSubscriber.SUBSCRIBER_ID, subscriber.getSubscriptionId()));
err.setError(Error.NO_ERROR);
}
}
// Send an update to the discoverer
if (discoverer != null)
discovererUpdate ();
v.addElement(err.toDataObject());
debugprintln(DEBUG, "Widget <addSubscriber> data to send back " + v);
return new DataObject(Subscriber.SUBSCRIPTION_REPLY, v);
}
/**
* This method removes a subscriber to this object. It calls
* Subscribers.removeSubscriber() if it can remove the subscriber. It returns a
* DataObject containing the reply information, including any error information.
*
* @param sub DataObject containing the subscription information
* @param error String containing the incoming error value
* @return DataObject with the reply to the subscription request
* @see context.arch.subscriber.Subscribers#removeSubscriber(String, String, String, String, String)
*/
public DataObject removeSubscriber(DataObject sub, String error) {
DataObjects v = new DataObjects();
Error err = new Error(error);
if (err.getError() == null) {
DataObject dobj = sub.getDataObject(AbstractSubscriber.SUBSCRIBER_ID);
if (dobj != null) {
String subId = dobj.getValue();
if (subId != null) {
boolean done = subscribers.removeSubscriber(subId);
if (!done) {
err.setError(Error.UNKNOWN_SUBSCRIBER_ERROR);
}
else {
v.addElement(new DataObject(Subscriber.SUBSCRIBER_ID, subId));
err.setError(Error.NO_ERROR);
}
}
}
}
// Send an update to the discoverer - start a thread for that
DiscovererClient discoClient = new DiscovererClient(this, Discoverer.DISCOVERER_UPDATE,
getSubscribersDescription(), Discoverer.UPDATE_REPLACE_TYPE);
discoClient.start();
v.addElement(err.toDataObject());
return new DataObject(Subscriber.SUBSCRIPTION_REPLY, v);
}
/**
* This method retrieves data from the widget's storage. It returns a
* DataObject containing the retrieved data information, including any error information.
*
* @param data DataObject containing the subscription information
* @param error String containing the incoming error value
* @return DataObject with the reply to the subscription request
*/
protected DataObject retrieveData(DataObject data, String error) {
debugprintln (DEBUG, "Widget retrieveData data :"+data + "\nerror:" + error);
DataObjects v = new DataObjects();
Error err = new Error(error);
if (err.getError() == null) {
Retrieval retrieval = new Retrieval(data);
if (storage == null) {
err.setError(Error.EMPTY_RESULT_ERROR);
}
else {
RetrievalResults results = storage.retrieveAttributes(retrieval);
if (results == null) {
err.setError(Error.INVALID_REQUEST_ERROR);
}
else if (results.size() == 0) {
err.setError(Error.EMPTY_RESULT_ERROR);
}
else {
err.setError(Error.NO_ERROR);
}
if (results != null) {
v.addElement(results.toDataObject());
}
}
}
v.addElement(err.toDataObject());
debugprintln(DEBUG, "Widget retrieve data return:"+v.toString());
return new DataObject(StorageObject.RETRIEVE_DATA_REPLY, v);
}
/**
* This stub method stores the data in the given DataObject
*
* @param data Data to store
* @see context.arch.storage.StorageObject#store(DataObject)
*/
protected void store(DataObject data) {
debugprintln(DEBUG, "Widget <store (DO)>");
if (storage != null) {
storage.store(data);
}
}
/**
* This stub method stores the data in the given AttributeNameValues object
*
* @param data Data to store
* @see context.arch.storage.StorageObject#store(AttributeNameValues)
*/
protected void store(Attributes data) {
debugprintln(DEBUG, "Widget <store(ANVS)>");
if (storage != null) {
storage.store(data);
}
}
/**
* This method creates a thread that retrieves a global time clock and determines
* the offset between the local clock and the global clock. It checks this
*
* @return the offset between the global and local clocks
* @see context.arch.widget.OffsetThread
*/
protected void getNewOffset() {
OffsetThread offset = new OffsetThread();
CurrentOffset = offset.getCurrentOffset();
offset = new OffsetThread(120);
}
/**
* This method retrieves the offset between the local clock and a global clock
* with no delay.
*
* @return the offset between the global and local clocks
* @see context.arch.widget.OffsetThread
*/
protected long getNewOffsetNoDelay() {
OffsetThread offset = new OffsetThread();
return offset.getCurrentOffset();
}
/**
* This method returns the current time to use as a timestamp
*
* @return the current time, corrected using a global clock offset
*/
protected Long getCurrentTime() {
long temp = new Date().getTime();
return new Long(temp + CurrentOffset);
}
/**
* This method builds the widget description which contains the constant and
* non constant attributes, the callbacks, the services, the subscribers
* This method overloads the BaseObject's getUserDescription method
*
* @return DataObject The description of the widget
* @see #getWidgetDataObject()
* @author Agathe
*/
public DataObject getUserDataObject() {
// TO complete !!!!!
DataObject result;
// Get the non constant attributes
DataObject doAtt_ = nonConstantAttributes.toDataObject();
DataObjects vAtt = new DataObjects();
vAtt.addElement(doAtt_);
DataObject doAtt = new DataObject(Discoverer.NON_CONSTANT_ATTRIBUTE_NAME_VALUES, vAtt);
// Get the constant attributes
DataObject doCstAtt_ = constantAttributes.toDataObject();
DataObjects vCstAtt = new DataObjects();
vCstAtt.addElement(doCstAtt_);
DataObject doCstAtt = new DataObject(Discoverer.CONSTANT_ATTRIBUTE_NAME_VALUES, vCstAtt);
// Get the callbacks
DataObject doCallbacks_ = callbacks.toDataObject();
DataObjects vCall_ = doCallbacks_.getChildren();
DataObjects vCall = new DataObjects();
if ( ! vCall_.isEmpty()) {
Enumeration<DataObject> eCall_ = vCall_.elements();
DataObject element;
while ( eCall_.hasMoreElements()){
element = (DataObject) eCall_.nextElement();
vCall.addElement(element.getDataObject(Callback.CALLBACK_NAME));
}
}
DataObject doCallbacks = new DataObject(Discoverer.WIDGET_CALLBACKS, vCall);
// Get the services
DataObject doServices_ = services.toDataObject();
DataObjects vSer_ = doServices_.getChildren();
DataObjects vSer = new DataObjects();
if ( ! vSer_.isEmpty()) {
Enumeration<DataObject> eSer_ = vSer_.elements();
DataObject element;
while ( eSer_.hasMoreElements()){
element = (DataObject) eSer_.nextElement();
vSer.addElement(element.getDataObject(ServiceDescription.SERVICE_NAME));
}
}
DataObject doServices = new DataObject(Discoverer.WIDGET_SERVICES, vSer);
//Get the subscribers
DataObject doSubs = getSubscribersDescription();
DataObjects v = new DataObjects();
v.addElement(doAtt);
v.addElement(doCstAtt);
v.addElement(doCallbacks);
v.addElement(doServices);
v.addElement(doSubs);
// Get getWidgetDescription
DataObject doDescrip = getWidgetDataObject();
if (doDescrip != null) {
for (DataObject temp : doDescrip.getChildren()){
v.addElement(temp);
}
}
result = new DataObject(Discoverer.TEMP_DEST, v);
return result;
}
/**
* This method returns a DataObject containig the list of subscribers
*
* @return DataObject The list of subscribers
* @author Agathe
*/
public DataObject getSubscribersDescription(){
// Get the subscribers
DataObjects subs = new DataObjects();
// need to lock subscribers, as some other thread may modify it meanwhile
synchronized (subscribers) {
for (AbstractSubscriber sub : subscribers.values()) {
subs.addElement(new DataObject(AbstractSubscriber.SUBSCRIBER_ID, sub.getBaseObjectId()));
}
}
return new DataObject(Subscribers.SUBSCRIBERS, subs);
}
/**
* This method returns the desciption specific to a widget.
* By default, it returns the type of the object that is 'WIDGET' type
* This method should be overridden to add more details if necessary.
*
* @return DataObject The DataObject containing the description of the widget
* @see #getUserDataObject()
* @author Agathe
*/
public DataObject getWidgetDataObject() {
return null;
}
/**
* This method is called when the widget is restarted. This method restarts
* the subscriptions, that is, the widget creates the subscribers that are
* described in its logfile. To each identified subscriber, the widget sends a
* PING message to check their liveliness. The URL sent is WIDGET+SUBSCRIBERS+PING.
* This PING is done through an independent connection (a thread handles the
* communication), so the result of the PING is got in the widget.handleIndependentReply.
*
* @author Agathe
*/
protected void setSubscribers(){
debugprintln(DEBUG, "\n\nWidget <setSubscribers> ");
// Get the subscribers retrieved from the log file
Subscribers notCheckedSubs = new Subscribers(this, this.getId());
// Check them to be sure they are still alive
int numSubsToCheck = 0;
for (AbstractSubscriber temp : notCheckedSubs.values()) {
subscribers = notCheckedSubs;
debugprintln(DEBUG, "widget <setSubs> send a PING for " + temp);
IndependentCommunication indComm =
new IndependentCommunication(
new RequestObject(null, null, temp.getSubscriberHostName (),temp.getSubscriberPort (), temp.getSubscriptionId ()),
true);
indComm.setObjectToStore (temp);
indComm.setSenderClassId (Widget.WIDGET_TYPE+Subscribers.SUBSCRIBERS+BaseObject.PING);
pingComponent(indComm);
numSubsToCheck++;
}
subscribers = notCheckedSubs;
debugprintln(DEBUG, "End setSubscriber # subs to check= " + numSubsToCheck);
}
/**
* This method overrides the handleIndependentReply defined in the BaseObject
* class.
*
* This method handles the reply to : <ul>
* <li>PING messages sent to subscriber to check out if they are still alive.
* This test is done when the widget is restarted and restarts the subscriptions
* based on its logfile. The subscribers are checked
* The url is WIDGET+SUBSCRIBERS+PING
* <li> Notification messages sent to the subscribers. If the communication failed
* (due to connection errors) the subscriber is removed from the widget and the
* widget updates the discoverer
* The url is WIDGET+SUBSCRIPTION_CALLBACK
*
* </ul>
* If the url is not recognized, the message is sent to the
* BaseObject.handlIndependentReply
*
* @param independentCommunication The object sent back by the thread
* @author Agathe
*/
public void handleIndependentReply(IndependentCommunication independentCommunication){
debugprintln(DEBUG, "Widget <handleIndependentReply>");
// Reply from the subscribers that are checked with a PING : WIDGET+SUBSCRIBERS+PING
if (independentCommunication != null){
String senderId= independentCommunication.getSenderClassId ();
// The reply of a message sent to ping a subscriber from the widget
if (senderId != null && senderId.equals(Widget.WIDGET_TYPE+Subscribers.SUBSCRIBERS+BaseObject.PING)) {
independentCommunication.decodeReply (this);
DataObject replyContent = independentCommunication.getDecodedReply ();
debugprintln(DEBUG, "\nWidget <handleIndependentReply> Reply=" + replyContent + " - exceptions " + independentCommunication.getExceptions ());
if (independentCommunication.getRequest ().getUrl ().equals (BaseObject.PING)){
if ( ! independentCommunication.getExceptions ().isEmpty () // There are exceptions
|| replyContent == null) {
debugprintln(DEBUG, "Widget <handleIndependentReply> removes subscriber");
subscribers.removeSubscriber ((AbstractSubscriber)independentCommunication.getObjectToStore ());
this.discovererUpdate ();
}
}
}
// The reply comes from a subscription notification
else if (senderId != null && senderId.equals(Widget.WIDGET_TYPE+Subscriber.SUBSCRIPTION_CALLBACK)){
if ( ! independentCommunication.getExceptions ().isEmpty ()){
// If there are exception, remove the subscriber corresponding to that notification
Subscriber sub = (Subscriber) independentCommunication.getObjectToStore ();
debugprintln (DEBUG, "IndependentCommunication ERROR - remove the subscriber=" + sub);
subscribers.removeSubscriber (sub);
this.discovererUpdate ();
}
}
// Else, asks to the super class
else{
super.handleIndependentReply (independentCommunication);
}
}
}
/** This method overrides the BaseObject setId(String) method so that
* the baseobject id specified in the subscribers object be also updated.
* @param id The id of this ctk component
*/
public void setId(String id) {
super.setId(id);
if (this.subscribers != null) {
this.subscribers.setBaseObjectId(id);
}
}
/* --------------------------------------------------------------------------------
* Shut down code
* -------------------------------------------------------------------------------- */
public void initShutdownHook() {
ShutdownHook shutdownHook = new ShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
public void shutdown() {
System.out.println(this.getId() + " shutting down");
}
class ShutdownHook extends Thread {
public void run() {
shutdown();
}
}
/**
* By default, this returns null.
* Subclasses should implement it to return relevant definitions for each attribute.
* @param attributeTag
* @return
*/
public static String getAttributeDefinition(String attributeTag) {
return null;
}
/**
* This method update attributes in memory
*
* @param data Data to update in memory
* @see context.arch.storage.StorageObject#store(AttributeNameValues)
*/
protected void memory(Attributes data) {
for (Attribute<?> current : data.values()) {
Attribute<?> target = nonConstantAttributes.get(current.getName());
if (target instanceof AttributeNameValuePin<?>) {
nonConstantAttributes.put(current.getName(), ((AttributeNameValuePin<?>) target).cloneWithNewValue(((AttributeNameValue<?>) current).getValue()));
} else if (target instanceof AttributeNameValue<?>) {
nonConstantAttributes.put(current.getName(), ((AttributeNameValue<?>) target).cloneWithNewValue(((AttributeNameValue<?>) current).getValue()));
} else {
nonConstantAttributes.put(current.getName(), current);
}
}
}
}