package context.arch.server;
import context.arch.comm.DataObject;
import context.arch.comm.DataObjects;
import context.arch.discoverer.Discoverer;
import context.arch.handler.Handler;
import context.arch.widget.Widget;
import context.arch.widget.WidgetHandle;
import context.arch.widget.WidgetHandles;
import context.arch.storage.AttributeNameValue;
import context.arch.storage.Attributes;
import context.arch.storage.Attribute;
import context.arch.subscriber.Callbacks;
import context.arch.subscriber.Callback;
import context.arch.service.Services;
import context.arch.service.InheritedService;
import context.arch.service.helper.ServiceDescriptions;
import context.arch.service.helper.ServiceDescription;
import context.arch.BaseObject;
import context.arch.MethodException;
import context.arch.InvalidMethodException;
import context.arch.util.Error;
import context.arch.subscriber.ClientSideSubscriber;
import java.util.HashMap;
import java.util.Map;
/**
* This class is the basic context server, with attributes and
* methods that should apply to all context servers.
*
* A server is a widget with added gathering and storage facilities.
* Servers are attached to people (incl. groups), places and things.
*
* Basically, a server subscribes to a set of widgets, stores and
* updates the attribute values from the widgets.
*
* A server has a "key" attribute that identifies the entity it is
* attached to. For example, a user server's key may be USERNAME. The server
* will only request and store information that pertains to a give value
* of USERNAME.
*
* TODO: currently, not well implemented; considering renaming to AggregationWidget
*
* @see context.arch.widget.Widget
*/
public abstract class Server extends Widget implements Handler {
/**
* Debug flag. Set to true to see debug messages.
*/
@SuppressWarnings("unused")
private static final boolean DEBUG = true;
/**
* The tag for the type of this object
*/
public static final String SERVER_TYPE = "server";
/**
* Default port to use for communications.
*/
public static final int DEFAULT_PORT = 6000;
/**
* The set of widgets this server is monitoring.
*/
protected WidgetHandles widgets;
/**
* The current port of the server
*/
@SuppressWarnings("unused")
private int serverPort;
/**
*
*/
private Attributes attributesCache;
/**
* What is this for??? --Brian
*/
private Map<Class<?>, Long> attributesTimes;
private Callbacks serverCallbacks = new Callbacks();
/**
* Constructor that sets up internal variables for maintaining
* the list of server attributes and callbacks, and setting up
* the Widget info.
*
* @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
* @param widgets The set of widgets this server will subscribe to
*/
public Server(String clientClass, String serverClass, int serverPort, String encoderClass,
String decoderClass, String storageClass, String id, String widgetClassName,
WidgetHandles widgets) {
super(clientClass,serverClass,serverPort,encoderClass,decoderClass,storageClass,id, widgetClassName);
this.widgets = widgets;
this.serverPort = serverPort;
serverSetup();
}
/**
* Constructor that sets up internal variables for maintaining
* the list of server attributes and callbacks, and setting up
* the Widget info. This version takes a boolean to indicate whether the
* default storage class should be used or whether not 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
* @param widgets The set of widgets this server will subscribe to
*/
public Server(String clientClass, String serverClass, int serverPort, String encoderClass,
String decoderClass, boolean storageFlag, String id, String widgetClassName,
WidgetHandles widgets) {
super(clientClass,serverClass,serverPort,encoderClass,decoderClass,storageFlag,id,widgetClassName);
this.widgets = widgets;
this.serverPort = serverPort;
serverSetup();
}
/**
* Constructor that sets up internal variables for maintaining
* the list of server attributes and callbacks. 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
* @param widgets The set of widgets this server will subscribe to
*/
public Server(int port, String id, String widgetClassName, WidgetHandles widgets) {
this(null,null,port,null,null,null,id, widgetClassName, widgets);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of server attributes and callbacks. 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
* @param storageFlag Flag to determine whether storage should be used or not
* @param widgets The set of widgets this server will subscribe to
*/
public Server(int port, String id, boolean storageFlag, String widgetClassName, WidgetHandles widgets) {
this(null,null,port,null,null,storageFlag,id,widgetClassName, widgets);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of server attributes and callbacks. It takes the
* widget id as a parameter
*
* @param id ID of the widget
* @param widgets The set of widgets this server will subscribe to
*/
public Server(String id, String widgetClassName, WidgetHandles widgets) {
this(null,null,-1,null,null,null,id, widgetClassName, widgets);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of server attributes and callbacks. It takes the
* widget id as a parameter with storage functionality set to storageFlag
*
* @param id ID of the widget
* @param widgets The set of widgets this server will subscribe to
* @param storageFlag Flag to determine whether storage should be used or not
*/
public Server(String id, boolean storageFlag, String widgetClassName, WidgetHandles widgets) {
this(null,null,-1,null,null,storageFlag,id,widgetClassName, widgets);
}
/**
* Returns the type of the object
* This method should be overridden
*
* @return String
*/
public String getType(){
return Server.SERVER_TYPE;
}
/**
* This method sets up the server for use. This includes getting the
* attributes and services information from relevant widgets.
*
* Modification made by Agathe
* @see #setAttributes()
* @see #setServices()
*/
protected void serverSetup() {
nonConstantAttributes = initAttributes();
constantAttributes = initConstantAttributes(); // added by Agathe
attributesCache = new Attributes();
// constantAttributeTypes = constantAttributes.toTypesHashtable(); // Added by agathe
for (Attribute<?> att : nonConstantAttributes.values()) {
// not sure what why we add only type and sub-attributes --Brian
AttributeNameValue<?> anv = AttributeNameValue.instance(att.getName(), att.getType());
anv.setSubAttributes(att.getSubAttributes());
attributesCache.add(anv);
}
attributesTimes = new HashMap<Class<?>, Long>();
Long long1 = new Long(0);
for (Attribute<?> a : nonConstantAttributes.values()) {
Class<?> type = a.getType();
attributesTimes.put(type, long1); // TODO: why all same time of 0L? Is this an initialization?
}
if (storage != null) {
storage.setAttributes(nonConstantAttributes);
// storage.setConstantAttributes(constantAttributes,cosntantAttributeTypes); // Added by agathe
}
services = initServices();
System.out.println("Server.serverSetup attributesCache: " + attributesCache);
}
/**
* This method sets the widgets that this server should subscribe to.
* Either the constructor should include a widgets parameter or this
* method should be called before startSubscriptions() is called
*
* @param widgets Handles of widgets to subscribe to
*/
public void setWidgets(WidgetHandles widgets) {
this.widgets = widgets;
}
/**
* This method is called to subscribe to the widgets this server
* is interested in. The reason this is not part of the constructor
* is that the individual server's conditions will not be set yet.
* This should be called after a constructor sets the widget handles
* to use or after setWidgets() has been called.
*
* @see #setCallbacks()
*/
public void startSubscriptions() {
callbacks = initCallbacks();
}
/**
* This method is called to aggregate the list of non cosntant attributes that the
* widgets relevant to this server provide.
* This should be called after a constructor sets the widget handles
* to use or after setWidgets() has been called.
*
* @return the server attributes
*/
protected Attributes initAttributes() {
// this protects us against the Widget constructor
// that calls us too early (we havent' got the widgets yet)
// it's good practice anyway
if (widgets == null) {
return null;
}
Attributes atts = new Attributes();
for (int i = 0; i < widgets.size(); i++) {
WidgetHandle handle = widgets.getWidgetHandleAt(i);
DataObject widgetAtts = getWidgetAttributes(handle.getHostName(),
handle.getPort(), handle.getId());
String error = new Error(widgetAtts).getError();
if (error != null) {
if (error.equals(Error.NO_ERROR)) {
Attributes wAtts = Attributes.fromDataObject(widgetAtts);
atts.putAll(wAtts);
}
}
}
atts.putAll(setServerAttributes());
return atts;
}
/**
* This method is called to aggregate the list of constant attributes that the
* widgets relevant to this server provide.
* This should be called after a constructor sets the widget handles
* to use or after setWidgets() has been called.
*
* @return AttributeNameValues the server constant attributes
*/
protected Attributes initConstantAttributes() {
// this protects us against the Widget constructor
// that calls us too early (we havent' got the widgets yet)
// it's good practice anyway
if (widgets == null) {
return null;
}
Attributes atts = new Attributes();
for (int i = 0; i < widgets.size (); i++) {
WidgetHandle handle = widgets.getWidgetHandleAt (i);
DataObject widgetAtts = getWidgetConstantAttributes(handle.getHostName(), handle.getPort(), handle.getId());
String error = new Error(widgetAtts).getError();
if (error != null) {
if (error.equals(Error.NO_ERROR)) {
Attributes wAtts = Attributes.fromDataObject(widgetAtts);
atts.putAll(wAtts);
}
}
}
atts.putAll(setServerConstantAttributes());
return atts;
}
/**
* This abstract method set the non constant attributes for a server - those
* that are specific to the server, and not contained in the widgets
* it subscribes to.
*
* @return Attributes The Attributes
*/
protected abstract Attributes setServerAttributes();
/**
* This abstract method set the constant attributes for a server - those
* that are specific to the server, and not contained in the widgets
* it subscribes to.
*
* @return Attributes The constant Attributes
*/
protected abstract Attributes setServerConstantAttributes();
/**
* This method is called to aggregate the list of services that the
* widgets relevant to this widget provide. This allows the server
* to act as a proxy to the individual widgets' services.
* This should be called after a constructor sets the widget handles
* to use or after setWidgets() has been called.
*
* @return the server services
*/
protected Services initServices() {
if (widgets == null) {
return null;
}
Services services = new Services();
for (int i=0; i<widgets.size(); i++) {
WidgetHandle handle = widgets.getWidgetHandleAt(i);
DataObject widgetServices = getWidgetServices(handle.getHostName(), handle.getPort(), handle.getId());
String error = new Error(widgetServices).getError();
if (error != null) {
if (error.equals(Error.NO_ERROR)) {
ServiceDescriptions wServices = new ServiceDescriptions(widgetServices);
for (int j=0; j<wServices.numServiceDescriptions(); j++) {
ServiceDescription desc = wServices.getServiceDescriptionAt(j);
services.add(new InheritedService(this,getId(),desc.getName(),desc.getFunctionDescriptions(),
handle.getHostName(),Integer.toString(handle.getPort()),handle.getId()));
}
}
}
}
services.putAll(setServerServices());
return services;
}
/**
* This abstract method set the services for a server - those
* that are specific to the server, and not contained in the widgets
* it subscribes to.
*
* @return Services specific to the server
*/
protected abstract Services setServerServices();
/**
* This method is called to subscribe to the widgets this server
* is interested in. It allows the server to act as a proxy to
* the callbacks provided by each individual widget.
* This should be called after a constructor sets the widget handles
* to use or after setWidgets() has been called.
*
* @see #setCallbacks()
*/
protected Callbacks initCallbacks() {
// this protects us against the Widget constructor
// that calls us too early (we havent' got the widgets yet)
// it's good practice anyway
if (widgets == null) {
return null;
}
Callbacks calls = new Callbacks();
// For all widgets
for (int i = 0; i < widgets.size (); i++) {
WidgetHandle handle = widgets.getWidgetHandleAt (i);
addWidgetCallbacksSubscription(handle, calls);
}
calls.addCallbacks(setServerCallbacks());
return calls;
}
/**
* This method first sends a message to the widget specified by handle to get
* the list of its callbacks, then the server subscribes to each of them. The
* subscription is added to calls
*
* @param handle The widget to subscribe to
* @param calls The subscription results are added to calls
*/
private void addWidgetCallbacksSubscription(WidgetHandle handle, Callbacks calls){
// Get all callbacks of each widget
// String tempCallbackName;
DataObject widgetCalls = getWidgetCallbacks(handle.getHostName(), handle.getPort(), handle.getId());
String error = new Error(widgetCalls).getError();
if (error != null) {
if (error.equals(Error.NO_ERROR)) {
Callbacks callbacks = new Callbacks(widgetCalls);
// Subscribe to each callback received by the widget
for (Callback callback : callbacks.values()) {
ClientSideSubscriber css = new ClientSideSubscriber(this.getId(), BaseObject.getHostName(), this.getPort (), callback.getName(), null, handle.getAttributes());
@SuppressWarnings("unused")
Error done = subscribeTo((Handler)this, handle.getId(), handle.getHostName (), handle.getPort(), css);
Callback serverCall = new Callback(css.getSubscriptionId(), callback.getAttributes());
serverCallbacks.addCallback(serverCall);
// //If the display is set up, add the subscriptionID in it so that we may unsubscribe via the interface
// if (this.gFrame != null)
// gFrame.setSubscriberToUnsubscribe (css.getSubscriptionId ());
// not using gFrame anymore; moved off from BaseObject to BaseObjectUI
calls.addCallback(callback);
}
}
}
}
/**
* This method allows to subscribe to a new widget
*
* @param handle The widget to subscribe to
* @author Agathe
*/
protected void addCallback (WidgetHandle handle){
// Add that to widgets
widgets.addWidgetHandle (handle);
Callbacks calls = new Callbacks();
// Get the callbacks and subscribe
addWidgetCallbacksSubscription (handle, calls);
// Add the calls to callbacks
callbacks.addCallbacks (calls);
//Update the discoverer
if (discoverer != null){
this.discovererUpdate ();
}
}
/**
* This abstract method set the callbacks for a server - those
* that are specific to the server, and not contained in the widgets
* it subscribes to.
*
* @return Callbacks containing callbacks specific to the server
*/
protected abstract Callbacks setServerCallbacks();
/**
* This method implements the handle method in the Handler interface.
* It handles the subscription information supplied by widgets this
* server has subscribed to.
*
* @param callbackName The name of the widget callback (on the subscriber side) triggered
* @param data DataObject containing the data for the widget callback
* @return DataObject containing any directives to the widget that created the callback
* @exception context.arch.InvalidMethodException if the callback specified isn't recognized
* @exception context.arch.MethodException if the callback specified can't be handled successfully
*/
@Override
public DataObject handleSubscriptionCallback(String callbackName, DataObject data) throws InvalidMethodException, MethodException {
if (!serverCallbacks.containsKey(callbackName)) {
throw new InvalidMethodException(Error.UNKNOWN_CALLBACK_ERROR);
}
/*
* currently, the non-constant attributes are the 2nd sub-element with the name "attributes".
* need an elegant way to fetch these instead of hard-code. Maybe, searching by "NCANVS" and fetching its value.
* ~~Kanupriya
*/
DataObject attsDataObj = data.getNthDataObject(Attributes.ATTRIBUTES, 2);
if (attsDataObj != null) {
Attributes atts = Attributes.fromDataObject(attsDataObj);
/*
* Setting the updated attributes. Similar to setNonConstantAttributes(), however there was a bug in that method.
* ~~Kanupriya Tavri
*/
nonConstantAttributes.putAll(atts);
/*
* Searching for the right callback and sending to the subscribers
*/
Attributes serverAttrs = serverCallbacks.get(callbackName).getAttributes();
for (Callback call : callbacks.values()) {
// why do we have to search by matching attributes? --Brian
if (call.getAttributes().equals(serverAttrs)) {
//call.setAttributes(attsObj);
sendToSubscribers(call.getName());
break;
}
}
storeAttributeNameValues(atts);
}
return null;
}
@Override
public DataObject handleCallback(String callbackName, DataObject data) throws InvalidMethodException, MethodException {
// TODO: what should happen here? --Brian
return null;
}
/**
* 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). Currently, it deals with QUERY and
* UPDATE_AND_QUERY in exactly the same way.
*
* @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) {
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) {
Attributes subset = attributesCache.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);
}
}
}
v.addElement(err.toDataObject());
return result;
}
/**
* This method stores the attribute name values, both in persistent storage
* and in local storage.
*
* @param atts AttributeNameValues to store
*/
public void storeAttributeNameValues(Attributes atts) {
store(atts);
long timestamp = atts.getAttributeValue(TIMESTAMP);
for (Attribute<?> a : atts.values()) {
AttributeNameValue<?> attNew = (AttributeNameValue<?>) a;
String attName = attNew.getName();
Object o = attributesTimes.get (attName);
long storedTime = -1;
if (o != null)
storedTime = ((Long)o).longValue();
else
System.out.println("ERROR: in <storeAttributeNameValues> storedTime null");
if (storedTime != -1 && storedTime <= timestamp) {
AttributeNameValue<?> attOld = (AttributeNameValue<?>) attributesCache.get(attName);
attOld.copyValue(attNew);
}
}
}
/**
* This method overloads the widget method
* It returns the server specific description
*
* @return DataObject The information common to all servers
*/
public DataObject getWidgetDataObject(){
DataObject result;
// Get the server non constant attributes
DataObject doAtt_ = setServerAttributes().toDataObject();
println("server all att = " + doAtt_.toString());
DataObjects vAtt = new DataObjects();
vAtt.addElement(doAtt_);
DataObject doAtt = new DataObject(Discoverer.SERVER_NON_CONSTANT_ATTRIBUTES, vAtt);
// Get the server constant attributes
DataObject doCstAtt_ = setServerConstantAttributes().toDataObject();
println("server att = " + doCstAtt_.toString());
DataObjects vCstAtt = new DataObjects();
vCstAtt.addElement(doCstAtt_);
DataObject doCstAtt = new DataObject(Discoverer.SERVER_CONSTANT_ATTRIBUTES, vCstAtt);
// Get the server callbacks
DataObject doCallbacks_ = setServerCallbacks().toDataObject();
DataObjects vCall = new DataObjects();
vCall.addElement(doCallbacks_);
DataObject doCallbacks = new DataObject(Discoverer.SERVER_CALLBACKS, vCall);
// Get the server services
DataObject doServices_ = setServerServices().toDataObject();
DataObjects vSer = new DataObjects();
vSer.addElement(doServices_);
DataObject doServices = new DataObject(Discoverer.SERVER_SERVICES, vSer);
DataObjects v = new DataObjects();
v.addElement(doAtt);
v.addElement(doCstAtt);
v.addElement(doCallbacks);
v.addElement(doServices);
// Get getWidgetDescription
DataObject doDescrip = getServerDescription();
if (doDescrip != null) {
for (DataObject child : doDescrip.getChildren()) {
v.addElement(child);
}
}
result = new DataObject(Discoverer.TEMP_DEST, v);
return result;
}
/**
* Returns the server description that should be overloaded
*
*/
public DataObject getServerDescription (){
return null;
}
}