/*
* Discoverer.java
*
* Created on 22 mars 2001, 10:40
*/
package context.arch.discoverer;
import java.util.ArrayList;
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.IndependentCommunication;
import context.arch.comm.language.EncodeException;
import context.arch.comm.language.InvalidEncoderException;
import context.arch.discoverer.lease.Lease;
import context.arch.discoverer.query.AbstractQueryItem;
import context.arch.service.Services;
import context.arch.storage.Attribute;
import context.arch.storage.Attributes;
import context.arch.subscriber.AbstractSubscriber;
import context.arch.subscriber.Callback;
import context.arch.subscriber.Callbacks;
import context.arch.subscriber.DiscovererSubscriber;
import context.arch.util.Error;
import context.arch.widget.Widget;
/**
* This class allows to add a discovery system to the context toolkit.
* <p>The discoverer is found by
* context components thanks to multicast communications.
* The discoverer allow them to register to it (to send their complete description),
* to query to it (to retrieve the description of components that
* fit several characteristics), to subscribe to notification (the subscriber wants to be notified when the
* discoverer registers a new component that fit some characteristics), to lease it (when a component
* registers to the discoverer, it registers for a given period that is identified by a lease), to unregister
* (when the component lease ends or when the components wants).
*
*<P>When a context component is created, it does not know the other components. And it can
* subscribes to another component only if it knows the component's name, port and hostname.
* The discoverer registers all context components when they are created, it stores for each of them
* their description. So that a component is able to do a request to the discoverer to get one or more
* context components that fit some characteristics (for example : I want all widgets that has information
* about Anind in the CRB)
*
* @see context.arch.discoverer.SearchEngine
* @see context.arch.discoverer.ComponentDescription
* @see context.arch.discoverer.DiscovererDescription
* @see edu.cmu.intelligibility.query.query.Query
* @see context.arch.discoverer.lease.Lease
* @see context.arch.discoverer.lease.LeasesWatcher
*
* @author Agathe
* @author Brian Y. Lim
*
*/
public final class Discoverer extends Widget {
private static final Logger LOGGER = Logger.getLogger(Discoverer.class.getName());
static {LOGGER.setLevel(Level.WARNING);} // this should be set in a configuration file
/**
* Debug flag. Set to true to see debug messages.
*/
public static final boolean DEBUG = false;
/**
* Default port for discoverer to use
*/
public static final int DEFAULT_PORT = 6000;
/**
* Classname tag
*/
public static final String CLASSNAME = "Discoverer";
/**
* Message tag used by components to send a multicast message to lookup a discoverer
*/
public static final String LOOKUP_DISCOVERER = "lookupDiscoverer";
/**
* Tag used in LOOKUP_DISCOVERER to contain the description of the caller component
*/
public static final String CALLER = "caller";
/**
* Tag used in LOOKUP_DISCOVERER inside CALLER tags for the component id
* Also used in LOOKUP_DISCOVERER_REPLY by discoverers
*/
public static final String CALLER_ID = "callerId";
/**
* Tag used in LOOKUP_DISCOVERER inside CALLER tags for the component type
* (baseobject(applications), widget, server, interpreter)
*/
public static final String TYPE = "type";
/**
* Tag used in LOOKUP_DISCOVERER inside CALLER tags for the component hostname
* Also used in LOOKUP_DISCOVERER_REPLY, DISCOVERER_REGISTRATION
*/
public static final String HOSTNAME = "hostname";
/**
* Tag used in LOOKUP_DISCOVERER inside CALLER tags for the component IP address
*/
public static final String HOSTADDRESS = "hostAddress";
/**
* Tag used in LOOKUP_DISCOVERER inside CALLER tags for the component port
* Also used in LOOKUP_DISCOVERER_REPLY, DISCOVERER_REGISTRATION
*/
public static final String PORT = "port";
/**
* Message tag used by discoverers to reply to a lookup message
*/
public static final String LOOKUP_DISCOVERER_REPLY = "lookupDiscovererReply";
/**
* Tag used in LOOKUP_DISCOVERER_REPLY to contain the discoverer description
*/
public static final String DISCOVERER = "discoverer";
/**
*
*/
public static final String DISCOVERER_TYPE = "discoverer";
/**
* Tag used in LOOKUP_DISCOVERER_REPLY for the discoverer id
* Also used in DISCOVERER_REGISTRATION
*/
public static final String DISCOVERER_ID = "discovererId";
// /**
// * Tag used in DISCOVERER_QUERY_REPLY to give the resulting component
// * id
// */
// public static final String COMPONENT_ID = "componentId";
// refactored to delete this tag, since it is essentially tags the same value (id) as #BaseObject.ID, and wasn't used much --Brian
/**
* Message tag used by discoverers to reply to a lookup message
*/
public static final String LOOKUP_DISCOVERER_OK = "lookupDiscovererOk";
/**
* Message tag used by components to send to the discoverer their description
*/
public static final String DISCOVERER_REGISTRATION = "discovererRegistration";
/**
* Message tag used by components to unregister from the discoverer
*/
public static final String DISCOVERER_UNREGISTRATION = "discovererUnregistration";
/**
* Message tag used by the discoverer to reply to a DISCOVERER_UNREGISTRATION message
*/
public static final String DISCOVERER_UNREGISTRATION_REPLY = "discovererUnregistrationReply";
/**
* Message tag used by components to send to the discoverer an update
*/
public static final String DISCOVERER_UPDATE = "discovererUpdate";
/**
* Message tag used by components to send to the discoverer a subscription
*/
public static final String DISCOVERER_SUBSCRIBE = "discovererSubscribe";
/**
* Message tag used by components to specify the update's type
*/
public static final String UPDATE_TYPE = "updateType";
/**
* Tag used in UPDATE_TYPE message to specify the method used to update information.
* Add type is used to add information.
*/
public static final String UPDATE_ADD_TYPE = "updateAddType";
/**
* Tag used in UPDATE_TYPE message to specify the method used to update information
* Replace type is used to remove old information before adding new one.
*/
public static final String UPDATE_REPLACE_TYPE = "updateReplaceType";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the description of the
* component registring
*/
public static final String REGISTERER = "registerer";
// /**
// * Tag used in DISCOVERER_REGISTRATION for the component id
// */
// public static final String REGISTERER_ID = "registererId";
// refactored to delete this tag, since it is essentially tags the same value (id) as #BaseObject.ID, and wasn't used much --Brian
/**
* Tag used in DISCOVERER_REGISTRATION for component classname
* Tag also used in DISCOVERER_QUERY
*/
public static final String COMPONENT_CLASSNAME = "componentClassname";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the constant attributes of
* the component
* Tag also used in DISCOVERER_QUERY
*
* This tag is also used in QueryElement to query the discoverer. Then it is used to match
* the name AND the value of a constant attribute. The match will be successful if
* the combination of the name&value exists for a component description
*
* @see context.arch.discoverer.query.QueryElement
* @see edu.cmu.intelligibility.query.query.Query
*/
public static final String CONSTANT_ATTRIBUTE_NAME_VALUES = "CANVS";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the non constant attributes of
* the component
* Tag also used in DISCOVERER_QUERY
*/
public static final String NON_CONSTANT_ATTRIBUTE_NAME_VALUES = "NCANVS";
/**
* Tag used in QueryElement to query the discoverer. It is used to match just the name of
* a constant attribute
*
* @see context.arch.discoverer.query.QueryElement
* @see edu.cmu.intelligibility.query.query.Query
*/
public static final String CONSTANT_ATTRIBUTE_NAME = "CAN";
/**
* Tag used in QueryElement to query the discoverer. It is used to match just the name of
* a non constant attribute
*
* @see context.arch.discoverer.query.QueryElement
* @see edu.cmu.intelligibility.query.query.Query
*/
public static final String NON_CONSTANT_ATTRIBUTE_NAME = "NCAN";
/**
* Tag used in QueryElement to query the discoverer. It is used to match just the value
* of a constant attribute
*
* @see context.arch.discoverer.query.QueryElement
* @see edu.cmu.intelligibility.query.query.Query
*/
public static final String CONSTANT_ATTRIBUTE_VALUE = "CAV";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the server constant attributes of
* the component
*/
public static final String SERVER_CONSTANT_ATTRIBUTES = "serverConstantAttributes";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the server non constant attributes of
* the component
*/
public static final String SERVER_NON_CONSTANT_ATTRIBUTES = "serverNonConstantAttributes";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the incoming attribute name
* values
* Tag also used in DISCOVERER_QUERY
*/
public static final String INCOMING_ATTRIBUTE_NAME_VALUES = "InANVS";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the outgoing attribute name
* values
* Tag also used in DISCOVERER_QUERY
*/
public static final String OUTGOING_ATTRIBUTE_NAME_VALUES = "OutANVS";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the widget callback names
* Tag also used in DISCOVERER_QUERY
*/
public static final String WIDGET_CALLBACKS = "widgetCallbacks";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the server callback names
*/
public static final String SERVER_CALLBACKS = "serverCallbacks";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the widget service names
* Tag also used in DISCOVERER_QUERY
*/
public static final String WIDGET_SERVICES = "widgetServices";
/**
* Tag used in DISCOVERER_REGISTRATION to contain the server service names
*/
public static final String SERVER_SERVICES = "serverServices";
/**
* Tag used by communicationsObject to retrieve the destination address
*/
public static final String TEMP_DEST = "tempDest";
/**
* Tag used for query messages to the discoverer
*/
public static final String DISCOVERER_QUERY = "discovererQuery";
/**
* Tag used in DISCOVERER_QUERY messages to the discoverer
*/
public static final String DISCOVERER_QUERY_CONTENT = "discovererQueryContent";
/**
* Tag used to determine level of detail of component description in
* response to subscribers
*/
public static final String DISCOVERER_DESCRIPTION_FULL_RESPONSE = "discovererFullResponse";
/**
* Tag used for query reply messages to the discoverer
*/
public static final String DISCOVERER_QUERY_REPLY = "discovererQueryReply";
/**
* Tag used in DISCOVERER_QUERY_REPLY messages to the discoverer
*/
public static final String DISCOVERER_QUERY_REPLY_CONTENTS = "discovererQueryReplyContents";
/**
* Tag used in DISCOVERER_QUERY_REPLY_CONTENTS messages
*/
public static final String DISCOVERER_QUERY_REPLY_CONTENT = "discovererQueryReplyContent";
/**
* Tag used in DISCOVERER_QUERY_CONTENT to indicate the type of query reply
*/
public static final String DISCOVERER_QUERY_TYPE = "type";
/**
* Tag used in the DISCOVERER_QUERY_REPLY_CONTENT to give
* the query id
*/
public static final String QUERY_ID = "queryId";
/**
* Tag used inside the DISCOVERER_QUERY_REPLY_CONTENT to
* give the order of the response
*/
public static final String QUERY_ORDER = "queryOrder";
/**
* Tag used inside the DISCOVERER_QUERY_REPLY_CONTENT to give
* the total number of answers to the query
*/
public static final String QUERY_NUM_ANSWERS = "numAnswers";
/**
* Type of query reply : returns the first element
*/
public static final String FIRST_RESULT = "first";
/**
* Type of query reply : returns no query id and all results (with a maximum
* set by
*/
public static final String ALL_RESULTS = "all";
/**
* Default number of responses sent in replyt to a DISOVERER_QUERY message
* with a ALL_RESULTS type
*/
public static final int DEFAULT_MAX_RESULTS = 10;
/**
* Type of query reply : returns the next result
*/
public static final String NEXT_RESULT = "next";
/**
* Tag used if DISCOVERER_QUERY to set the maximum number of responses wanted
*/
public static final String MAXIMUM_RESULTS = "maximumResults";
/**
* Type of matching for the query
*/
public static final String IGNORE_CASE = "ingnoreCase";
/**
* Type of matching for the query
*/
public static final String CASE_SENSITIVE = "caseSensitive";
/**
* Attribute tag used in the DISCOVERER_QUERY_CONTENT
*/
public static final String PRIORITY = "priority";
/**
* First or weakest priority for the query element
*/
public static final int PRIORITY_1 = 1;
/**
* Second or middle priority for the query element
*/
public static final int PRIORITY_2= 2;
/**
* Last or stronger priority for the query element
*/
public static final int PRIORITY_3 = 3;
/**
* Component type : for application
*/
public static final String APPLICATION = "application";
/**
* Component type : for widget
*/
public static final String WIDGET = "widget";
/**
* Component type : for servers
*/
public static final String SERVER = "server";
/**
* Component type : for interpreter
*/
public static final String INTERPRETER = "interpreter";
/**
* Separator used to separate fields (as for constant attribute name and value)
*/
public static final String FIELD_SEPARATOR = "+";
/**
* This tag is used in error object for DISCOVERER_UNREGISTRATION_REPLY messages
* when the context component that want to be unregistered is not found.
*/
public static final String ERROR_REGISTRATION_NOT_FOUND = "errorRegistrationNotFound";
/**
* This tag is used when a NEXT_RESULT query is received and the queryId is
* not reckognized
*/
public static final String ERROR_QUERY_NOT_FOUND = "errorQueryNotFound";
/**
* This tag is used when there is no response to the received query
*/
public static final String ERROR_EMPTY_QUERY = "errorEmptyQuery";
/**
* This tag is the callback name used to notify subscriber of a new component
*/
public static final String NEW_COMPONENT = "newComponent";
/**
*
*/
protected DiscovererMediator mediator;
/**
* Constructor that sets up internal variables for maintaining
* the list of discoverer attributes, callbacks, and services and setting up
* the BaseObject 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 discoverer id and persistent storage
* @see context.arch.storage.StorageObject
*/
public Discoverer(String clientClass, String serverClass, int serverPort,
String encoderClass, String decoderClass,
String storageClass) {
super(clientClass, serverClass, serverPort, encoderClass, decoderClass,
storageClass, Discoverer.createId(Discoverer.CLASSNAME,serverPort), CLASSNAME);
// initFull();
start(null);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of discoverer 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 discoverer id and persistent storage
* @see context.arch.storage.StorageObject
*/
public Discoverer(String clientClass, String serverClass, int serverPort, String encoderClass,
String decoderClass, boolean storageFlag) {
super(clientClass, serverClass, serverPort, encoderClass, decoderClass,
storageFlag, createId(CLASSNAME,serverPort), CLASSNAME);
start(null);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of discoverer 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 Discoverer id
* @param storageFlag Boolean flag to indicate whether storage should be turned on
*/
public Discoverer(int port, boolean storageFlag) {
this(null, null, port, null, null, storageFlag);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of discoverer 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 Discoverer id
*/
public Discoverer(int port) {
this(null, null, port, null, null, null);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of discoverer 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 Discoverer id
* @param storageFlag Boolean flag to indicate whether storage should be turned on
*/
public Discoverer(String id, boolean storageFlag) {
this(null, null, -1, null, null, storageFlag);
}
/**
* Constructor that sets up internal variables for maintaining
* the list of widget attributes, callbacks and services. It takes the
* widget id as a parameter. Port set to an available one.
*
* @param id ID of the discoverer
*/
public Discoverer() {
this(null, null, BaseObject.findFreePort(), null, null, null);
}
/**
* Convenience method to start a Discoverer at an assigned port.
* May fail if port is not free.
* @param port at which to start the Discoverer
* @return the Discoverer started, but it is not necessary to receive this instance
*/
public static Discoverer start(int port) {
return new Discoverer(port);
}
/**
* Convenience method to start a Discoverer using a found free port.
* @return the Discoverer started, but it is not necessary to receive this instance
*/
public static Discoverer start() {
return new Discoverer(BaseObject.findFreePort());
}
@Override
protected void initFull() {
super.initFull();
// Set the SearchEngine
mediator = new DiscovererMediator(this, true);
this.setLease (new Lease(10000));
this.discoverer = new DiscovererDescription(this.getId (), this.getHostAddress (), this.getPort());
discovererRegistration(new Lease(10000));
}
@Override
protected void init() {
/*
* Non-constant attributes
* Sets the attributes for the discoverer: they specify the information the
* discoverer is storing about the registered context components.
*/
addAttribute(Attribute.instance(ComponentDescription.ID_ELEMENT, String.class));
addAttribute(Attribute.instance(ComponentDescription.CLASSNAME_ELEMENT, String.class));
addAttribute(Attribute.instance(ComponentDescription.HOSTNAME_ELEMENT, String.class));
// addAttribute(Attribute.instance(ComponentDescription.HOSTADDRESS_ELEMENT, String.class));
addAttribute(Attribute.instance(ComponentDescription.PORT_ELEMENT, String.class));
addAttribute(Attribute.instance(ComponentDescription.CONST_ATT_ELEMENT, String.class));
addAttribute(Attribute.instance(ComponentDescription.NON_CONST_ATT_ELEMENT, String.class));
addAttribute(Attribute.instance(ComponentDescription.NON_CONST_ATT_NAME_ELEMENT, String.class));
addAttribute(Attribute.instance(ComponentDescription.CALLBACK_ELEMENT, String.class));
addAttribute(Attribute.instance(ComponentDescription.SUBSCRIBER_ELEMENT, String.class));
// constant attributes
}
/**
* Sets the callbacks for the discoverer
*
* @return Callbacks The callbacks of the discoverer
*/
protected Callbacks initCallbacks() {
Callbacks calls = new Callbacks();
Attributes atts = new Attributes();
atts.putAll(nonConstantAttributes);
atts.putAll(constantAttributes);
calls.addCallback(NEW_COMPONENT, atts);
return calls;
}
/**
* Sets the services for the discoverer
*
* @return Services The services provides by the discoverer
*/
protected Services initServices(){
return new Services();
}
/**
* This abstract method is called when the discoverer wants to get the latest generator
* info.
*
* @return AttributeNameValues containing the latest generator information
*/
protected Attributes queryGenerator() {
return new Attributes();
}
/**
* 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 dataObject DataObject containing the method to run and parameters
* @param error Error object
* @return DataObject containing the results of the executed method
* @see #LOOKUP_DISCOVERER
* @see #DISCOVERER_REGISTRATION
* @see #DISCOVERER_UNREGISTRATION
* @see #DISCOVERER_UPDATE
* @see #DISCOVERER_QUERY
* @see #DISCOVERER_SUBSCRIBE
*/
@Override
protected DataObject runWidgetMethod(DataObject dataObject, String error){
DataObject data = dataObject;
String methodType = data.getName();
// System.out.println("BaseObject.runMethod methodType = " + methodType);
// System.out.println("BaseObject.runMethod data = " + data);
if (methodType.equals(LOOKUP_DISCOVERER)) {
LOGGER.info("Discoverer: in lookup_discoverer");
return getDiscovererDescription(data);
}
else if (methodType.equals(DISCOVERER_REGISTRATION)) {
return componentRegistration(data);
}
else if (methodType.equals(DISCOVERER_UNREGISTRATION)) {
return componentUnregistration(data);
}
else if (methodType.equals(DISCOVERER_UPDATE)) {
return componentUpdate(data);
}
else if (methodType.equals(DISCOVERER_QUERY)) {
return handleQuery(data);
}
else if (methodType.equals (DISCOVERER_SUBSCRIBE)){
return componentSubscription(data);
}
else {
return runDiscovererMethod(data, error);
}
}
/**
* This method should be overriden by inheriting classes to handle other
* messages.
*
* @param dataObject The data object containing the message
* @param error The Error object
* @return DataObject The result
*/
protected DataObject runDiscovererMethod(DataObject dataObject, String error){
return new DataObject();
}
/**
* ?? TO DO : add errors control
*
* This method returns the DataObject replying to the LOOKUP_DISCOVERER
* message from a component.
* The reply contains the description of the discoverer and add the id, address
* and port of the component to reply to.
*
* @param data The DataObject containing the identification of the caller component
* @return DataObject It contains the LOOKUP_DISCOVERER_REPLY message
* @see context.arch.discoverer.DiscovererDescription
*/
public DataObject getDiscovererDescription(DataObject data){
LOGGER.info("Discoverer getDiscovererDescription");
DataObject result = null;
// Get component id
DataObject caller = data.getDataObject(CALLER);
DataObject callerId = data.getDataObject(CALLER_ID);
// Vector vId = callerId.getValue();
String cId = callerId.getValue();
DataObject id = new DataObject(ID, cId);
// Set the LOOKUP_DISCOVERER_REPLY tag
DataObject discovId = new DataObject(DISCOVERER_ID, getId());
DataObject host = new DataObject(HOSTNAME, getHostAddress());
DataObject port = new DataObject(PORT, new Integer(communications.getServerPort()).toString());
DataObjects v1 = new DataObjects();
v1.addElement(discovId);
v1.addElement(host);
v1.addElement(port);
DataObject disco = new DataObject(DISCOVERER, v1);
DataObjects v2 = new DataObjects();
v2.addElement(id);
v2.addElement(disco);
DataObject part2 = new DataObject(LOOKUP_DISCOVERER_REPLY, v2);
//Add a temporary tag useful just for the communicationsObject that has no
// reply address to send the message to.
// Because the comm object has just received a datagram packet, and it must
// open a new tcp connexion, so it needs the hostname port.
DataObject dAdd = caller.getDataObject(HOSTNAME);
DataObject dPort = caller.getDataObject(PORT);
DataObject dId = new DataObject(ID, cId);
DataObjects tempDest = new DataObjects();
tempDest.addElement(dAdd);
tempDest.addElement(dPort);
tempDest.addElement(dId);
DataObject part1 = new DataObject(TEMP_DEST, tempDest);
DataObjects v3 = new DataObjects();
v3.addElement(part1);
v3.addElement(part2);
result = new DataObject (LOOKUP_DISCOVERER_REPLY, v3);
return result;
}
/**
* This method handles a DISCOVERER_REGISTRATION message.
* It registers a component by storing its description and replies
* with an error code.
*
* @param data The DataObject containing the description on the caller component
* @return DataObject An error code
* @see context.arch.discoverer.ComponentDescription
*/
protected DataObject componentRegistration(DataObject data) {
LOGGER.info("Discoverer - componentRegistration");
Error error = new Error(Error.NO_ERROR);
DataObject component = data.getDataObject(ID);
// DataObject result = null;
if (component == null) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
String resultId = component.getValue();
if (!resultId.equals(getId())) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
ComponentDescription comp =
ComponentDescription.fromDataObject(data.getDataObject (Discoverer.REGISTERER));
LOGGER.info("comp to data object " + comp.toDataObject ());
Lease lease = Lease.dataObjectToLease(data.getDataObject (Lease.LEASE));
error.setError (mediator.add (comp, lease).getError ()); // Add to the database
checkSubscribers(new Callback(Discoverer.NEW_COMPONENT, null), comp);
}
}
return error.toDataObject();
}
/**
* This method allows to send a message to all subscribers whose interests are
* for the new registered component.
*
* @param comp The component newly registered
* @return int The number of subscribers a message has been sent to
*/
public int checkSubscribers(Callback callback, ComponentDescription comp) {
LOGGER.info("Discoverer <checkSubscribers>");
LOGGER.info("The new comp is= " + comp + " \n for callback " + callback );
AbstractQueryItem<?,?> subQuery;
int nbCorresponding = 0;
for (AbstractSubscriber asub : subscribers.values()) {
DiscovererSubscriber sub = (DiscovererSubscriber) asub;
LOGGER.info("Sub id= " + sub.getSubscriptionId ()+" - query= " + sub.getQuery () + " - sub callback " + sub.getSubscriptionCallback ());
LOGGER.info(" Test callbacks");
if (callback.getName ().equals(sub.getSubscriptionCallback ())) {
LOGGER.info("\ncallback are equal ");
subQuery = sub.getQuery ();
// Check if the component description corresponds to the subscriber query
Boolean queryResult = subQuery.match (comp);
LOGGER.info("Disco result of checkSubs " + queryResult);
if (queryResult != null && queryResult){
nbCorresponding ++;
DataObject subid = new DataObject(AbstractSubscriber.SUBSCRIBER_ID, sub.getSubscriptionId ());
DataObjects v = new DataObjects();
v.addElement(subid);
// If the discoSub wants a full description we give it to them, otherwise just the basic summary.
if (sub.isFullDescriptionResponse()) {
/**
* this is an ugly, ugly hack due to the fact that toDataObject()
* calls essentially type their return values without knowing what
* they will be used for! we will clean this up at some point by
* cleaning up the ComponentDescription code, but for now
* side effects are feared, so we stick to the hack.
*/
DataObjects vComp = comp.toDataObject().getChildren();
v.addElement(new DataObject(Discoverer.DISCOVERER_QUERY_REPLY_CONTENT,vComp));
} else {
v.addElement(comp.getBasicDataObject());
}
DataObject send = new DataObject(DiscovererSubscriber.SUBSCRIPTION_CALLBACK, v);
String host = sub.getSubscriberHostName ();
int port = new Integer(sub.getSubscriberPort ()).intValue();
// use independentUserRequest
try {
LOGGER.info("Discoverer before independent");
IndependentCommunication ic = new IndependentCommunication (
new RequestObject(send, DiscovererSubscriber.SUBSCRIPTION_CALLBACK, host, port));
independentUserRequest (ic);
sub.resetErrors();
} catch (EncodeException ee) {
LOGGER.severe("Widget sendToSubscribers EncodeException: "+ee);
} catch (InvalidEncoderException iee) {
LOGGER.severe("Widget sendToSubscribers InvalidEncoderException: "+iee);
}
}
else {
LOGGER.info("\nthe query doesn't correspond to the comp");
}
}
}
LOGGER.info("# of sub that corresponds " + nbCorresponding);
return nbCorresponding;
}
/**
* This method is used to unregister a context component from the discoverer.
* After the unregistration, the context component description will not
* be referenced in the discoverer.
*
* @param dataObject The content of the DISCOVERER_UNREGISTRATION message
* @return DataObject The result of the unregistration
* @see #DISCOVERER_UNREGISTRATION
* @see #ERROR_COMPONENT_NOT_FOUND
*/
protected DataObject componentUnregistration(DataObject dataObject) {
DataObject data = dataObject;
// DataObject result;
Error error = new Error();
DataObject discoId = data.getDataObject(ID);
if (discoId == null) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
String id = discoId.getValue();
if (!id.equals(getId())) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
// Get the component name
DataObject registerer = (DataObject) data.getDataObject(Discoverer.REGISTERER);
String compId = registerer.getDataObject(Discoverer.ID).getValue();
LOGGER.info("Discoverer removed : " + compId);
error = mediator.remove(compId);
}
}
return error.toDataObject();
}
/**
* This method handles the UPDATE_DISCOVERER messages.
* It gets the modified fields and update the component description
* and returns an error code.
*
* @param data The DataObject containing the modified fields
* @return DataObject The data containing an error code
* @see context.arch.discoverer.ComponentDescription
*/
public DataObject componentUpdate(DataObject dataObject) {
LOGGER.info("Discoverer - componentUpdate " + dataObject);
DataObject data = dataObject.getDataObject(Discoverer.DISCOVERER_UPDATE);
DataObject component = data.getDataObject(ID);
Error error = new Error();
// DataObject result = null;
if (component == null) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
String resultId = component.getValue();
if (! resultId.equals(getId())) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
ComponentDescription comp = ComponentDescription.fromDataObject(data.getDataObject (Discoverer.REGISTERER));
Lease lease = null; // TODO do we remove the previous lease or not??
// TODO pretty useless to have set this to null --Brian
error = mediator.update(comp, lease);
LOGGER.info("Discoverer <componentUpdate> error from mediator " + error);
}
}
return error.toDataObject();
}
/**
* This method is handling the subscription of a context component
* that wants to subscribe to be notified of the registration of
* context components of its interest.
*
* The content of the message is like a query message. The subscriber sends
* a query containing the description of the components of its interest.
*
* The discoverer adds this subscriber, and replies with the corresponding
* components that have already registered.
*
* @param data The data object containing the subscription details
* @return DataObject contains the reply to the subscriber
*/
public DataObject componentSubscription(DataObject dataObject){
LOGGER.info("\nDiscoverer <componentSubscription>");
DataObject data = dataObject.getDataObject(Discoverer.DISCOVERER_SUBSCRIBE);
DataObject component = data.getDataObject(ID);
Error error = new Error();
// DataObject result = null;
if (component == null) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
String resultId = component.getValue();
if (! resultId.equals(getId())) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
// Retrieves the query
DataObject qc = data.getDataObject(Discoverer.DISCOVERER_QUERY_CONTENT);
if (qc != null){
DataObjects vv = qc.getChildren ();
qc = ( vv!=null? (DataObject)vv.firstElement () : null);
}
DiscovererSubscriber sub = new DiscovererSubscriber(data);
LOGGER.info("Disco add sub " + sub.toString ());
// Add the subscriber
subscribers.add(sub);
// Update the dataModel to say that the discoverer has a new subscriber
ComponentDescription discoItself = ComponentDescription.fromDataObject (this.toDataObject ());
mediator.update (discoItself, getLease ());
DataObjects v = new DataObjects();
v.addElement(new DataObject(AbstractSubscriber.SUBSCRIBER_ID, sub.getSubscriptionId ()));
error.setError (Error.NO_ERROR);
v.add (error.toDataObject ());
return new DataObject(DiscovererSubscriber.DISCOVERER_SUBSCRIPTION_REPLY, v);
}
}
return error.toDataObject();
}
/**
* Handles a DISCOVERER_QUERY message from components and
* returns a DataObject containing the identification of the response
* and the first response.
*
* TODO To complete ...
*
* @param data The DataObject containing the query
* @return DataObject The first result of the query
* @see context.arch.intelligibility.query.Query
* @see context.arch.discoverer.QueryElement
* @see context.arch.discoverer.Response
* @see context.arch.discoverer.ResponseElement
* @see context.arch.discoverer.SearchEngine
*/
public DataObject handleQuery(DataObject data) {
LOGGER.info("\nDiscoverer - handleQuery : " + data);
DataObject component = data.getDataObject(ID);
Error error = new Error();
String componentId = null;
if (component == null) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
String resultId = component.getValue();
if (! resultId.equals(getId())) {
error.setError(Error.INVALID_ID_ERROR);
}
else {
componentId = data.getDataObject (Discoverer.CALLER_ID).getValue();
// Retrieves the query
DataObject qc = data.getDataObject(Discoverer.DISCOVERER_QUERY_CONTENT);
DataObject q = null;
if (qc != null){
DataObjects vv = qc.getChildren ();
q = ( vv != null ? vv.firstElement () : null);
}
// System.out.println();
LOGGER.log(Level.FINE, "Discoverer.handleQuery q = " + q);
DataObject results = null;
if (q != null){
AbstractQueryItem<?,?> query = AbstractQueryItem.fromDataObject (q);
// System.out.println("Discoverer.handleQuery query: " + query);
// System.out.println();
// Searchs in the SearchEngine
results = mediator.search(query);
// System.out.println("Discoverer.handleQuery resp: " + resp);
}
if (results == null) {
error.setError (Discoverer.ERROR_QUERY_NOT_FOUND);
}
else{
//Information about the component to reply to
DataObjects v1 = new DataObjects();
v1.addElement(new DataObject(Discoverer.ID, componentId));
v1.addElement(results);
DataObject result = new DataObject(Discoverer.DISCOVERER_QUERY_REPLY, v1);
return result ;
}
}
}
return error.toDataObject();
}
/**
* Returns the content of the search engine
*
* @return String The content of the search engine
*/
public String getSearchEngineContent(){
return mediator.toString ();
}
/**
* This method allows to send a lease end notification to each
* component whose lease ends. The reply received by the component
* either renew the lease or confirms it.
*
* @param componentIndices The components index
*/
public void sendLeaseEndNotificationTo(ArrayList<String> componentIndices) {
if (componentIndices.isEmpty()) { return; }
// For each component index, sends a lease end notification and handles the
// answer
int sentNb = 0;
for (String index : componentIndices) {
// Get the component description object corresponding to the index from the search engine
ComponentDescription comp = mediator.getComponentDescription(index);
if (comp != null) {
DataObject toSend;
DataObjects v = new DataObjects();
v.addElement(new DataObject(Discoverer.ID, comp.id));
toSend = new DataObject (Lease.LEASE_END_NOTIFICATION, v);
RequestObject ro = new RequestObject(toSend,Lease.LEASE_END_NOTIFICATION, comp.hostname, comp.port, comp.id);
// Sends the message , the result will be stored in results
if (sendLeaseEndNotificationTo(new IndependentCommunication(ro, false)))
sentNb++;
}
}
}
/**
* This method allows to send the Lease.LEASE_END_NOTIFICATION to a context
* component.
*
* TO complete to handle the lease_renewal
*
* @param data The data to send
* @param compHostname The hostname of the component to send the notification to
* @param compPort The component port
* @return Lease The lease object if the reply from the component is to renew the lease,
* or null if it confirms the lease end.
*/
protected boolean sendLeaseEndNotificationTo(IndependentCommunication comm) {
// Error error = new Error();
try {
LOGGER.info("Discoverer <sendLeaseEndNotification> before independent comm :" + comm);
comm.setResponseRequired (true);
independentUserRequest (comm);
return true; // message sent
} catch (EncodeException ee) {
LOGGER.severe("Discoverer <sendLeaseEndNotification> EncodeException: "+ee);
} catch (InvalidEncoderException iee) {
LOGGER.severe("Discoverer <sendLeaseEndNotification> InvalidEncoderException: "+iee);
}
return false; // message not sent
}
/**
* This method is called after the independentUserRequest has been called.
* The thread in charge of the communication sends the results to this method.
* This method should be overridden by classes that need to handle the responses.
*
* @param originalRequest The request sent by the thread
* @param reply The reply of the message
* @param exception If an exception occured during the communication, a copy of it
* @see context.arch.comm.clients.ClientsPool
* @see context.arch.comm.clients.Client
* @see context.arch.util.RequestObject
* @see context.arch.comm.DataObject
*/
public void handleIndependentReply(IndependentCommunication independentCommunication) {
LOGGER.info("The discoverer gets the reply from the the element =" + independentCommunication.getRequest().getUrl());
if (independentCommunication != null) {
independentCommunication.decodeReply (this);
DataObject replyContent = independentCommunication.getDecodedReply();
// For LEASE_END_NOTIFICATION
if (independentCommunication.getRequest().getUrl().equals(Lease.LEASE_END_NOTIFICATION)) {
// There are exceptions => remove the component
if ( ! independentCommunication.getExceptions().isEmpty()
|| replyContent == null
|| (replyContent != null && replyContent.getName().equalsIgnoreCase(Lease.LEASE_END))){
String compId = independentCommunication.getRequest().getReceiverId();
mediator.remove(compId);
}
// Renew the lease
else {
String compId = independentCommunication.getRequest ().getReceiverId ();
Lease newLease = Lease.dataObjectToLease (replyContent.getDataObject (Lease.LEASE));
mediator.updateLease (compId, newLease);
}
}
else if (independentCommunication.getRequest ().getUrl ().equals (DiscovererSubscriber.SUBSCRIPTION_CALLBACK_REPLY)){
// does nothing
LOGGER.info("\nReply from a component/subscriber with" + new Error(independentCommunication.getDecodedReply ()).getError ());
}
else if (independentCommunication.getRequest ().getUrl ().equals (PING)
&& independentCommunication.getSenderClassId ().equals (Discoverer.DISCOVERER+Discoverer.REGISTERER+PING)){
LOGGER.info(" Discoverer for mediator");
mediator.handleIndependentReply (independentCommunication);
}
else {
super.handleIndependentReply (independentCommunication);
}
}
return;
}
/**
*
*/
public String getType() {
return Discoverer.DISCOVERER_TYPE;
}
/**
* Returns a printable version of the list of discoverer subscribers
*
* @return String
*/
public String subscribersToString() {
StringBuffer sb = new StringBuffer();
sb.append ("Number of subscribers = " + subscribers.size());
for (AbstractSubscriber asub : subscribers.values()) {
DiscovererSubscriber sub = (DiscovererSubscriber) asub;
sb.append("\n - id= " +sub.getSubscriptionId());
sb.append(" - callback= " + sub.getSubscriptionCallback());
sb.append(" - query= " + sub.getQuery ());
}
return sb.toString();
}
/**
* Main method to create a discoverer with location and port specified by
* command line arguments
*/
public static void main(String argv[]) {
if (argv.length == 0) {
if (DEBUG) {
System.out.println("Attempting to create a discoverer on 5555 at with storage disabled");
}
// Discoverer disco =
new Discoverer(Discoverer.DEFAULT_PORT, false);
}
else if (argv.length == 1) {
if ((argv[0].equals("false")) || (argv[0].equals("true"))) {
if (DEBUG) {
System.out.println("Attempting to create a Discoverer on "+DEFAULT_PORT+" with storage set to "+argv[0]);
}
// Discoverer disco =
new Discoverer(Discoverer.DEFAULT_PORT, Boolean.valueOf(argv[0]).booleanValue());
}
else {
if (DEBUG) {
System.out.println("Attempting to create a Discoverer on "+argv[0]+" with storage enabled");
}
// Discoverer disco =
new Discoverer(Integer.parseInt(argv[0]));
}
}
else if (argv.length == 2) {
if (DEBUG) {
System.out.println("Attempting to create a Discoverer on "+argv[0]+" with storage set to "+argv[1]);
}
// Discoverer disco =
new Discoverer(Integer.parseInt(argv[0]), Boolean.valueOf(argv[1]).booleanValue());
}
else {
System.out.println("USAGE: java context.arch.discoverer.Discoverer [port] [storageFlag]");
}
}
}