package context.arch.enactor;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import context.arch.BaseObject;
import context.arch.InvalidMethodException;
import context.arch.MethodException;
import context.arch.comm.DataObject;
import context.arch.comm.clients.IndependentCommunication;
import context.arch.discoverer.ComponentDescription;
import context.arch.discoverer.Discoverer;
import context.arch.discoverer.component.AbstractElement;
import context.arch.discoverer.component.TypeElement;
import context.arch.discoverer.query.ANDQueryItem;
import context.arch.discoverer.query.AbstractQueryItem;
import context.arch.discoverer.query.BooleanQueryItem;
import context.arch.discoverer.query.ORQueryItem;
import context.arch.discoverer.query.RuleQueryItem;
import context.arch.enactor.WidgetReferenceRegistry.WidgetReferenceRegEntry;
import context.arch.handler.Handler;
import context.arch.server.Server;
import context.arch.service.helper.ServiceInput;
import context.arch.storage.Attributes;
import context.arch.subscriber.ClientSideSubscriber;
import context.arch.subscriber.DiscovererSubscriber;
import context.arch.widget.Widget;
/**
* This class manages CTK subscriptions on behalf of an enactor. It generates
* discovery queries out of enactor references, and notifies enactors of any
* new widgets that match those references.
*
* @author alann
* @author Brian Y. Lim
* @author Kanupriya Tavri
*/
public class EnactorSubscriptionManager implements Handler {
private static final Logger LOGGER = Logger.getLogger(EnactorSubscriptionManager.class.getName());
static { LOGGER.setLevel(Level.WARNING); }
/**
* The enactor that depends on this manager.
*/
protected Enactor enactor;
/**
*
* @param enactor needs to have been fully constructed, so do not instantiate EnactorSubscriptionManager in the constructor of Enactor
*/
EnactorSubscriptionManager(Enactor enactor) {
init(enactor.getId(), enactor.getPort());
setEnactor(enactor);
}
/**
* Initializes the subscription manager with with and id and a port. This
* method must be called prior to use.
*
*/
protected void init(String id, int port) {
LOGGER.info("starting SituationManager on port " + port);
baseObjDelegate = new BaseObject(port);
//initialize ctk object to set ID and find discoverer
baseObjDelegate.setId(id);
//TODO: actually register with Discover (passing in "true"), with all enactor info, to allow runtime or designtime inspection of enactor
// ctkObject.findDiscoverer(false);
baseObjDelegate.start(true);
final AbstractQueryItem<?,?> q = new ORQueryItem(
RuleQueryItem.instance(new TypeElement(Widget.WIDGET_TYPE)),
RuleQueryItem.instance(new TypeElement(Server.SERVER_TYPE)) // added compatibility for Server aggregator type, Jan 2010 --Brian
);
// make a new discoverersubscriber, send subscription.
discoSub = new DiscovererSubscriber(baseObjDelegate.getId(), BaseObject.getHostName(), baseObjDelegate.getPort(), Discoverer.NEW_COMPONENT, q);
// we want to receive full ComponentDescriptions
discoSub.setFullDescriptionResponse(true);
// System.out.println("ctkObject.discovererSubscribe(this, discoSub); - this: " + this);
baseObjDelegate.discovererSubscribe(this, discoSub);
// initialized = true;
}
/**
* Sets enactor to the ESM. If a new enactor, its references will be processed
* for subscriptions.
*
* @param enactor enactor to be added.
* @return <tt>true</tt> if rule was not already contained in the SM.
*/
public void setEnactor(Enactor enactor) {
this.enactor = enactor;
// add the references of the enactor
for (EnactorReference enactorRef : enactor.getReferences()) {
addEnactorReference(enactorRef);
}
// TODO: why not adding EnactorParameters?
// need to put this after adding enactorRef due to some dependencies
handleNew();
}
/**
* TODO still not sure what to properly name this method with.
* Note similarity to {@link #handleSubscriptionCallback()}
*/
protected void handleNew() {
// for(AbstractQueryItem<?,?>[] subscriptionQueries : enactor.getSubscriptionQueries()) {
for (AbstractQueryItem<?,?> subscriptionQuery : enactor.getInWidgetSubscriptionQuery()) {
// System.out.println("enactor = " + enactor);
// System.out.println("subscriptionQuery = " + subscriptionQuery + '\n' +
// "sendDiscovererAttributeQuery = " + sendDiscovererAttributeQuery(subscriptionQuery));
for (ComponentDescription cd : sendDiscovererAttributeQuery(subscriptionQuery)) {
// save pointers to widgets that were successfully subscribed to; do this before subscribing enactorRefs
saveWidgetComponentDescriptions(cd, subscriptionQuery);
// subscribe for each reference
for (EnactorReference er : enactor.getReferences()) {
subscribe(cd, er);
}
}
}
for (AbstractQueryItem<?,?> subscriptionQuery : enactor.getOutWidgetSubscriptionQuery()) {
for (ComponentDescription cd : sendDiscovererAttributeQuery(subscriptionQuery)) {
saveWidgetComponentDescriptions(cd, subscriptionQuery);
}
}
// }
}
/**
* This method registers an EnactorReference with the ESM. The reference must
* be part of a registered Enactor. Enactors can call this method if they add
* new references to themselves at runtime.
*
* @param enactorRef the EnactorReference to add.
*/
protected void addEnactorReference(EnactorReference enactorRef) {
widgetReferences.put(enactorRef, new WidgetReferenceRegEntry());
// replaced with #handleNew()
// for (AbstractQueryItem<?,?> subscriptionQuery : enactorRef.getEnactor().getSubscriptionQueries()) { // TODO why do this at enactorRef?
// //subscribe to each component matching our reference
// for (ComponentDescription cd : sendDiscovererAttributeQuery(subscriptionQuery)) { // TODO: check for obsolescence
// subscribe(cd, enactorRef);
// }
// }
}
/**
* removed EnactorReference from manager. This can be called at runtime by Enactors when they
* change their internal structure to notify the manager, but the Enactor must be part
* of the manager first by calling addRule.
*
* @param enactorRef the EnactorReference to be removed.
*/
protected void removeEnactorReference(EnactorReference enactorRef) throws EnactorException {
WidgetReferenceRegEntry wrre = widgetReferences.remove(enactorRef);
if (wrre != null) {
for (String subId : wrre.getWidgetSubscriptions()) {
EnactorComponentInfo wsre = widgetSubscriptions.get(subId);
if (wsre != null) {
wsre.removeReference(enactorRef);
if (wsre.getReferences().isEmpty()) {
unsubscribe(subId);
}
}
}
}
}
/**
* @param sml
*/
protected void fireAddEventsForAll(Enactor enactor, EnactorListener sml) {
// for (String subId : widgetSubscriptions.keySet()) {
// EnactorComponentInfo eci = widgetSubscriptions.get(subId);
for (EnactorComponentInfo eci : widgetSubscriptions.values()) {
// ComponentDescription cd = eci.getComponentDescription();
// TODO: what new info is gained from calling this for each reference???
// for (EnactorReference wr : eci.getReferences()) { // fire event for each reference
// enactor.fireComponentAdded(sml, eci, null);
// }
enactor.fireComponentAdded(sml, eci, null);
}
}
//////////////////////////////////////
// Begin CTK External Interaction Code
//////////////////////////////////////
protected BaseObject getBaseObject() {
return baseObjDelegate;
}
public DataObject executeWidgetService(ComponentDescription cd, ServiceInput serviceInput) {
return baseObjDelegate.executeSynchronousWidgetService(
cd.hostname, cd.port, cd.id,
serviceInput);
}
public DataObject updateOutWidget(ComponentDescription cd, Attributes input) {
// this would happen probably when the Enactor is not yet fully initialized,
// but something else is asking it to update.
if (cd == null) { return null; } // TODO: should notify with an error to say it is not yet ready
return baseObjDelegate.updateAndPollWidget(
cd.hostname, cd.port, cd.id,
input);
}
public void handleIndependentReply(IndependentCommunication independentCommunication) {
LOGGER.info("independent reply");
}
public DataObject handleCallback(String subscriptionId, DataObject data) throws InvalidMethodException, MethodException {
ComponentDescription widgetState = ComponentDescription.fromDataObject(data);
// check if there's actually any non-constants to compare against
// if (cd.getNonConstantAttributes().isEmpty()) {
// // TODO not really sure why this would happen... but sometimes want to query about constants!
//// new RuntimeException("cd.getNonConstantAttributes().isEmpty()").printStackTrace();
// return null;
// }
EnactorComponentInfo eci = widgetSubscriptions.get(subscriptionId);
if (eci == null) { return null; }
// put state into WSRegistry
eci.setCurrentState(widgetState);
// lookup listeners by widget's description
for (EnactorReference ref : eci.getReferences()) {
AbstractQueryItem<?,?> query = ref.getConditionQuery();
// System.out.println("EnactorSubscriptionManager.handleCallback ------: cd = " + cd);
// System.out.println("EnactorSubscriptionManager.handleCallback ------: query = " + query);
// new RuntimeException("EnactorSubscriptionManager.handleCallback ------: query.match(cd) = " + query.match(cd)).printStackTrace();
// Carrega o estado atual dos atributos dos widgets relacionados.
ComponentDescription allWidgetState = ref.getEnactor().getInWidgetState();
if (allWidgetState.getNonConstantAttributeNames().isEmpty()) {
allWidgetState = widgetState;
} else {
allWidgetState.updateAttributes(widgetState); // atualiza com o estado atual do widget em execu����o
}
Boolean queryResult;
if ( query != null &&
(queryResult = query.match(allWidgetState)) != null && queryResult) {
// execute references if they match
ref.evaluateComponent(eci);
if(ref._break) {
break;
}
}
}
return null; // then what is the point of returning? --Brian
}
/**
*
* @param subscriptionId refers to the enactor (?) subscribing to the widget (?)
* @param data referring to the ComponentDescription to be tested
*/
@Override
public DataObject handleSubscriptionCallback(String subscriptionId, DataObject data) throws InvalidMethodException, MethodException {
// make new ClientSubscriber object, subscribe to widget.
//TODO: we must need to do extra checking on messages from the discoverer (e.g. deletions), figure it out
handleNewComponent(ComponentDescription.fromDataObject(data));
return null; // then what is the point of returning? --Brian
}
/**
* We see if any registered enactors (through references) are interested in this
* component. If not we do nothing. If some enactor at a later date wants it,
* we'll get it again as a result of a query.
*
* @param cd description of component to be registered
*/
protected synchronized void handleNewComponent(ComponentDescription cd) {
LOGGER.info("handling new component " + cd.id);
// for (EnactorReference er : widgetReferences.getWidgetReferences()) {
// Enactor enactor = er.getEnactor();
//
// //TODO gather all descriptionQueries in the widget before subscribing
// for (AbstractQueryItem<?,?> subscriptionQuery : enactor.getSubscriptionQueries()) {
// Boolean queryResult = subscriptionQuery.match(cd);
// if (queryResult != null && queryResult) { // matches subscription
// subscribe(cd, er);
//
// }
// }
// }
for (AbstractQueryItem<?,?>[] subscriptionQueries : enactor.getSubscriptionQueries()) {
for (AbstractQueryItem<?,?> subscriptionQuery : subscriptionQueries) {
Boolean queryResult = subscriptionQuery.match(cd);
if (queryResult != null && queryResult) { // matches subscription
// save pointers to widgets that were successfully subscribed to; do this before subscribing enactorRefs
saveWidgetComponentDescriptions(cd, subscriptionQuery);
// subscribe for each reference
for (EnactorReference er : enactor.getReferences()) {
subscribe(cd, er);
}
}
}
}
}
/**
* This method is called after successfully subscribing to the widgets, so that we can have soft pointers back to them.
* @param cd
* @param enactor
*/
protected void saveWidgetComponentDescriptions(ComponentDescription cd, AbstractQueryItem<?,?> subscriptionQuery) {
// store component descriptions of widgets
// useful to update either of them later through the subscription mechanism (w/o direct memory reference)
// if (enactor.widgetSubscriptionQueries[Enactor.IN_WIDGET_INDEX] == subscriptionQuery) {
// enactor.widgetComponentDescriptions[Enactor.IN_WIDGET_INDEX] = cd;
// //new RuntimeException("EnactorSubscriptionManager.saveWidgetComponentDescriptions IN_WIDGET cd = " + cd).printStackTrace();
// }
//
// // note this is not "else if", since both IN and OUT may be the same
// // e.g. for querying part of a widget, and manipulating another part
// else if (enactor.widgetSubscriptionQueries[Enactor.OUT_WIDGET_INDEX] == subscriptionQuery) {
// enactor.widgetComponentDescriptions[Enactor.OUT_WIDGET_INDEX] = cd;
// //new RuntimeException("EnactorSubscriptionManager.saveWidgetComponentDescriptions OUT_WIDGET cd = " + cd).printStackTrace();
// }
for (int i = 0; i < enactor.widgetSubscriptionQueries.length; i++) {
for (int j = 0; j < enactor.widgetSubscriptionQueries[i].length; j++) {
if(enactor.widgetSubscriptionQueries[i][j] == subscriptionQuery) {
enactor.widgetComponentDescriptions[i][j] = cd;
}
}
}
}
/**
* Subscribes to the widget described by the component description, and binds
* it to the enactor reference. We use the registry to bind the reference to an
* existing subscription if possible, before making a new CTK subscription.
*
*/
protected void subscribe(ComponentDescription cd, EnactorReference er) {
// LOGGER.info("subscribing to widget " + cd.id +
// " \n\tclassname = " + cd.classname +
// ", hostaddress = " + cd.hostaddress +
// ", hostname = " + cd.hostname +
// ", port = " + cd.port);
// System.out.println("subscribing to widget " + cd.id +
// " \n\tclassname = " + cd.classname +
// ", hostaddress = " + cd.hostaddress +
// ", hostname = " + cd.hostname +
// ", port = " + cd.port);
EnactorComponentInfo eci = null;
//Enactor enactor = er.getEnactor();
//saveWidgetComponentDescriptions(cd, enactor);
if (!widgetIds.containsKey(cd.id)) {
for (AbstractQueryItem<?,?> subscriptionQuery : er.getEnactor().getInWidgetSubscriptionQuery()) {
if (subscriptionQuery == null) { return; } // may be null if Enactor is actually a Generator with no In, but only and Out
if(matchSubscribe(subscriptionQuery, cd)) {
eci = new EnactorComponentInfo();
// create subscription; do we set the subscriberID here???
ClientSideSubscriber subscriber = new ClientSideSubscriber(
baseObjDelegate.getId(), BaseObject.getHostName(), baseObjDelegate.getPort(),
Widget.CALLBACK_UPDATE, subscriptionQuery, null);
baseObjDelegate.subscribeTo(this, cd.id, cd.hostname, cd.port, subscriber);
// put an entry in the widgetSubscriptions that will allow us to lookup the description & css later
eci.addReference(er);
eci.setClientSideSubscriber(subscriber);
eci.setComponentDescription(cd);
// initialize current state to be initial component description
eci.setCurrentState(cd);
String subscriptionId = subscriber.getSubscriptionId();
widgetSubscriptions.put(subscriptionId, eci);
discoSub.setSubscriptionId(subscriptionId); // TODO: not sure if this is correct, seems like it would be changing for each enactorRef --Brian
widgetReferences.get(er).addWidgetSubscription(subscriptionId);
widgetIds.put(cd.id, subscriptionId);
//notify EnactorReference
er.componentAdded(eci);
}
}
} else {
// for now we assume only one subscription per widget. This could change in the future.
String subscriptionId = widgetIds.get(cd.id);
eci = widgetSubscriptions.get(subscriptionId);
eci.addReference(er);
updateWidgetSubscriptionCondition(eci);
//notify EnactorReference
er.componentAdded(eci);
}
//notify EnactorReference
// er.componentAdded(eci);
}
private boolean matchSubscribe(AbstractQueryItem<?,?> sq, ComponentDescription cd) {
Boolean result = false;
for(AbstractQueryItem<?,?> child : ((ANDQueryItem)sq).getChildren()) {
AbstractElement<?, ?, ?> ae = ((RuleQueryItem<?,?>) child).getElementToMatch();
if(ae.getValue().equals(cd.classname)) {
result = true;
}
}
return result;
}
/**
* Unsubscribes to the widget with the given subscription id, and notifies
* all bound enactor references.
*/
protected void unsubscribe(String subscriptionId) {
LOGGER.info("unsubscribing to widget");
EnactorComponentInfo eci = widgetSubscriptions.remove(subscriptionId);
if (eci != null) {
//potentially tell all widgetreferences about unsubscription
//remove entry from widgetIds
ComponentDescription cd = eci.getComponentDescription();
String widgetId = cd.id;
//WidgetIdRegEntry wire = widgetIds.get(widgetId);
if (widgetIds.containsKey(widgetId)) {
widgetIds.remove(widgetId);
}
baseObjDelegate.unsubscribeFrom(subscriptionId);
//notify widgets of removal
for (EnactorReference er : eci.getReferences()) {
er.componentRemoved(eci);
}
}
}
/**
* Resubscribe to a currently subscribed widget with new conditions.
*/
protected void updateWidgetSubscriptionCondition(EnactorComponentInfo eci) {
if (eci != null) {
ClientSideSubscriber css = eci.getClientSideSubscriber();
ComponentDescription cd = eci.getComponentDescription();
BooleanQueryItem query = new ORQueryItem();
for (EnactorReference er : eci.getReferences()) {
for (AbstractQueryItem<?,?>[] subscriptionQueries : er.getEnactor().getSubscriptionQueries()) {
for (AbstractQueryItem<?,?> subscriptionQuery : subscriptionQueries) {
query.add(subscriptionQuery);
}
}
}
css.setCondition(query);
// resubscribe using the same Subscriber: widget should take care of removing the old one
baseObjDelegate.subscribeTo(this, cd.id, cd.hostname, cd.port, css);
}
}
/**
*
* @param q
* @return
*/
protected Collection<ComponentDescription> sendDiscovererAttributeQuery(AbstractQueryItem<?,?> q) {
if (q != null) {
Collection<ComponentDescription> comps = baseObjDelegate.discovererQuery(q);
return comps;
}
else {
return Collections.emptySet();
}
}
////////////////////////////////////
// End CTK External Interaction Code
////////////////////////////////////
/**
* Delegate base object to handle communication with the discoverer
*/
private BaseObject baseObjDelegate;
//to be replace by an embedded database...
private WidgetReferenceRegistry widgetReferences = new WidgetReferenceRegistry();
private Map<String, EnactorComponentInfo> widgetSubscriptions = new HashMap<String, EnactorComponentInfo>();
private Map<String, String> widgetIds= new HashMap<String, String>(); // <ComponentDescription.id, subscriptionId>
private DiscovererSubscriber discoSub;
}