package context.arch.enactor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import context.arch.BaseObject;
import context.arch.comm.DataObject;
import context.arch.discoverer.ComponentDescription;
import context.arch.discoverer.ComponentDescriptions;
import context.arch.discoverer.query.AbstractQueryItem;
import context.arch.enactor.server.EnactorXMLServer;
import context.arch.intelligibility.Explainer;
import context.arch.intelligibility.rules.RulesExplainer;
import context.arch.logging.EnactorRegistrationLogger;
import context.arch.logging.EnactorRuntimeLogger;
import context.arch.logging.LoggingException;
import context.arch.service.helper.ServiceInput;
import context.arch.storage.Attribute;
import context.arch.storage.AttributeNameValue;
import context.arch.storage.Attributes;
import context.arch.widget.Widget;
import context.arch.widget.Widget.WidgetData;
/**
* The Enactor component encapsulates application logic and simplifies
* the acquisition of context data. Use it by making subclasses that
* define their own EnactorReferences and EnactorParameters.
*
* An enactor subclass will typically define all necessary metadata upon
* construction. Users of the enactor can construct it, and then call
* {@link #start()}rt() to begin operation and initialization into the CTK network. Calling
* startXMLServer() will allow the enactor to send and receive information in XML
* to the specified port, useful for Flash or .NET clients.
*
* @see Generator
*
* @author alann
* @author Brian Y. Lim
* @author Kanupriya Tavri
*/
@SuppressWarnings("unchecked")
public abstract class Enactor {
private static final Logger LOGGER = Logger.getLogger(Enactor.class.getName());
static {LOGGER.setLevel(Level.INFO);} // this should be set in a configuration file
//we keep one listener and use a thread-safe multicaster
protected EnactorListener enactorListener;
protected List<EnactorParameter> enactorParameters = new ArrayList<EnactorParameter>();
protected Map<String, List<EnactorReference>> enactorReferences = new LinkedHashMap<String, List<EnactorReference>>(); // <outcomeValue, List<enactorReference>> multi-map; linked hashmap to maintain insertion order
protected EnactorXMLServer xmlServer = null;
protected EnactorSubscriptionManager subscriptionManager;
protected String id;
protected String hostname;
protected int port;
protected String outcomeName;
protected String outcomeValue;
protected ComponentDescriptions inWidgetState;
public static final int IN_WIDGET_INDEX = 0;
public static final int OUT_WIDGET_INDEX = 1;
protected AbstractQueryItem<?,?>[][] widgetSubscriptionQueries = (AbstractQueryItem<String,String>[][]) new AbstractQueryItem[2][]; // {inWidgetSubscriptionQuery, outWidgetSubscriptionQuery}
protected ComponentDescription[][] widgetComponentDescriptions = new ComponentDescription[2][]; // {inWidgetComponentDescription, outWidgetComponentDescription}
// protected Class<? extends Widget> inWidgetClass;
// protected Class<? extends Widget> outWidgetClass;
protected AbstractQueryItem<?, ?> lastSatisfiedQuery;
protected ComponentDescription lastSatisfiedInWidgetState;
protected Explainer explainer;
public Enactor(AbstractQueryItem<?,?>[] inWidgetSubscriptionQuery, AbstractQueryItem<?,?>[] outWidgetSubscriptionQuery,
String outcomeName, String shortId) {
// this.inWidgetClass = inWidgetClass; // may be null for Generator (subclass)
// this.outWidgetClass = outWidgetClass;
// null for Generator, non-null for Enactor
if (inWidgetSubscriptionQuery != null) {
this.widgetSubscriptionQueries[0] = inWidgetSubscriptionQuery;
}
// this.widgetSubscriptionQueries[1] = new RuleQueryItem<String,String>(new ClassnameElement(outWidgetClass.getName()));
this.widgetSubscriptionQueries[1] = outWidgetSubscriptionQuery;
setOutcomeName(outcomeName);
setId(BaseObject.createId(getClassname(), shortId));
this.hostname = BaseObject.getHostName();
this.port = BaseObject.findFreePort();
// subscriptionManager = new EnactorSubscriptionManager(this);
// Explainer is lazy loaded (see getExplainer())
widgetComponentDescriptions[0] = new ComponentDescription[widgetSubscriptionQueries[0].length];
widgetComponentDescriptions[1] = new ComponentDescription[widgetSubscriptionQueries[1].length];
inWidgetState = new ComponentDescriptions();
}
public Enactor(AbstractQueryItem<?,?> inWidgetSubscriptionQuery, AbstractQueryItem<?,?> outWidgetSubscriptionQuery,
String outcomeName, String shortId) {
this(new AbstractQueryItem<?,?>[] {inWidgetSubscriptionQuery}, new AbstractQueryItem<?,?>[] {outWidgetSubscriptionQuery}, outcomeName, shortId);
}
/**
* Can override this to replace class name; particularly for enactors created by XML declaration
* @return
*/
public String getClassname() {
return this.getClass().getName();
}
public AbstractQueryItem<?,?>[] getInWidgetSubscriptionQuery() {
return widgetSubscriptionQueries[0];
}
public void setInWidgetSubscriptionQueryQuery(AbstractQueryItem<String,String>[] query) {
this.widgetSubscriptionQueries[0] = query;
}
public AbstractQueryItem<?,?>[] getOutWidgetSubscriptionQuery() {
return widgetSubscriptionQueries[1];
}
public void setOutWidgetSubscriptionQueryQuery(AbstractQueryItem<String,String>[] query) {
this.widgetSubscriptionQueries[1] = query;
}
/**
*
* @return {inWidgetSubscriptionQuery, outWidgetSubscriptionQuery}
*/
public AbstractQueryItem<?,?>[][] getSubscriptionQueries() {
return widgetSubscriptionQueries;
}
public String getOutcomeName() {
return outcomeName;
}
protected void setOutcomeName(String outcomeName) {
this.outcomeName = outcomeName;
}
/**
* Checks if the out-widget contains a non-constant attribute with the name.
* @return
*/
public boolean containsOutAttribute(String attName) {
//System.out.println("widgetComponentDescriptions[OUT_WIDGET_INDEX].getNonConstantAttributes() = " + widgetComponentDescriptions[OUT_WIDGET_INDEX].getNonConstantAttributes());
for(ComponentDescription cp :widgetComponentDescriptions[OUT_WIDGET_INDEX])
if(cp.getNonConstantAttributes().contains(attName))
return true;
return false;
// TODO: sometimes attributes are empty, i.e. never set when subscribing
}
public String getOutcomeValue() {
return outcomeValue;
}
public void setOutcomeValue(String outcomeValue) {
this.outcomeValue = outcomeValue;
}
/**
* Can retrieve output explanation from this.
* @return
*/
public Collection<String> getOutcomeValues() {
return enactorReferences.keySet();
}
// alterei
public ComponentDescription getInWidgetState() {
return inWidgetState.mergeComponentDescriptions();
}
// alterei
public void setInWidgetState(ComponentDescription widgetState) {
int index = this.inWidgetState.indexOf(widgetState);
if(index >= 0) {
this.inWidgetState.get(index).updateAttributes(widgetState);
} else {
this.inWidgetState.add(widgetState);
}
}
public AbstractQueryItem<?,?> getLastSatisfiedQuery() {
return lastSatisfiedQuery;
}
/**
* Saving the last satisfied query and state in the Enactor helps track the state of Enactor
* due to an EnactorReference being activate.
* Ultimately, this can help explainers when users want an explanation during a non-event.
* Then they can just return the last state.
*
* @param query
* @param inWidgetState may be used by subclasses to do conditional setting
*/
public void setLastSatisfied(AbstractQueryItem<?,?> query, ComponentDescription inWidgetState) {
this.lastSatisfiedQuery = query;
this.lastSatisfiedInWidgetState = inWidgetState;
}
/**
* Convenience method to start Enactor and the XML server its clients listen to.
* Both started with found free ports.
*/
public void start() {
try {
startSubscriptionManager();
startXMLServer();
} catch (EnactorException e) {
e.printStackTrace();
}
// final String threadName = getId();
// new Thread(threadName) {
// @Override
// public void run() {
// try {
// Enactor.this.start();
// Enactor.this.startXMLServer();
// System.out.println("started enactor: " + getId());
// } catch (EnactorException e) {
// e.printStackTrace();
// }
// }
// }.start();
LOGGER.info(getId() + " started (port = " + this.getPort() + ")");
}
/**
* Starts operation of this enactor. Before a call to
* this method, the enactor is "dormant". This call registers
* the enactor with the CTK and processes any necessary subscriptions.
*/
public void startSubscriptionManager() throws EnactorException {
subscriptionManager = new EnactorSubscriptionManager(this);
// register this first before adding to subscription manager that also registers references, parameters, etc,
// where that would expect this enactor to already have been registered
try{
EnactorRegistrationLogger.getEnactorRegistrationLogger()
.insertEnactorRegistrationEntry(id,
enactorParameters, getReferences());
} catch (LoggingException e) {
// e.printStackTrace();
/*
* thrown if HibernateException is thrown
* e.g. if Hibernate is not properly set up or the application doesn't want to suppor it
*/
}
}
/**
* Starts an EnactorXMLServer running at the specified port. This
* server notifies clients of Enactor events, and can accept
* parameter changes.
*
* @param port
*/
public void startXMLServer(int port) {
//stop any existing execution -- only one XMLServer at a time
//(if you need more you can construct your own)
stopXMLServer();
xmlServer = new EnactorXMLServer(this, port);
}
/**
* Starts the XML server using a found free port.
* @throws EnactorException
*/
public void startXMLServer() {
this.startXMLServer(BaseObject.findFreePort());
// System.out.println(this.getClass().getSimpleName() + ".startXMLServer() port = " + port);
}
/**
* Stops execution of a running EnactorXMLServer, if it exists.
*/
public void stopXMLServer() {
if (xmlServer != null) xmlServer.stop();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getPort() {
return port;
}
public EnactorParameter getParameter(String name) {
if (name != null) {
Iterator<EnactorParameter> i = enactorParameters.iterator();
while (i.hasNext()) {
EnactorParameter rp = (EnactorParameter) i.next();
if (name.equals(rp.getName()))
return rp;
}
}
return null;
}
public List<EnactorParameter> getParameters() {
return enactorParameters;
}
/**
* The result can be added to or removed from; i.e. not unmodifiable
* @return
*/
public Collection<EnactorReference> getReferences() {
Collection<EnactorReference> references = new ArrayList<EnactorReference>();
for (List<EnactorReference> refs : enactorReferences.values()) {
references.addAll(refs);
}
return references;
}
/**
* Retrieve the EnactorReference associated with the outcome value.
* @param outcomeValue
* @return
*/
public List<EnactorReference> getReferences(String outcomeValue) {
return enactorReferences.get(outcomeValue);
}
/**
* Convenience method to retrieve one reference, especially if there is only one per outcome value.
* @param outcomeValue
* @return
*/
public EnactorReference getReference(String outcomeValue) {
return enactorReferences.get(outcomeValue).get(0);
}
/**
* Internal method for executing a service. Whenever an enactor wants to make something
* happen through the CTK, it will execute some service on a component. Components are
* specified via a subscriptionID which is exposed to enactors through add and removal events.
*
* @param subscriptionId The identifier for the CTK component hosting the service.
* @param serviceName The service name
* @param functionName The function name (services can have multiple functions)
* @param input Input attributes to the service call
* @return a DataObject representing any data returned by the Service
*/
protected DataObject executeWidgetService(
// EnactorComponentInfo eci,
ComponentDescription cd,
ServiceInput serviceInput) {
DataObject returnDataObject = subscriptionManager.executeWidgetService(
// eci.getComponentDescription(),
cd,
serviceInput);
// TODO: need to redo to use eci of out widget, not in widget
//fireServiceExecuted(eci, serviceName, functionName, input, returnDataObject);
return returnDataObject;
}
/**
* Call this to update the attribute(s) of the out Widget.
* @param data
* @return a DataObject representing any data returned by the Service
*/
public void updateOutWidget(WidgetData data) {
// public DataObject updateOutWidget(WidgetData data) {
// return updateOutWidget(data.toAttributes());
updateOutWidget(data.toAttributes());
}
/**
* Call this to update the attribute(s) of the out Widget.
* @param atts Attributes of AttributeNameValue to set attribute values to
* @return a DataObject representing any data returned by the Service
*/
public void updateOutWidget(Attributes atts) {
// public DataObject updateOutWidget(Attributes atts) {
// System.out.println("Enactor.updateOutWidget\t\t" + data);
if (widgetComponentDescriptions[Enactor.OUT_WIDGET_INDEX] == null) { // not yet started
// return null;
return ;
}
/*
* Set timestamp
*/
Long originalTimestamp = atts.getAttributeValue(Widget.TIMESTAMP);
if (originalTimestamp == null) { // null if atts forgets to set TIMESTAMP
originalTimestamp = System.currentTimeMillis();
atts.addAttribute(Widget.TIMESTAMP, originalTimestamp); // add current time as timestamp
}
addLastModifiedProperties(atts, originalTimestamp);
/*
* Set constant attribute values
*/
// TODO: may not need to set, since these would always be constant
/*
* Update via delegate
*/
for(ComponentDescription cd:widgetComponentDescriptions[Enactor.OUT_WIDGET_INDEX])
subscriptionManager.updateOutWidget(cd, atts);
// DataObject returnDataObject = subscriptionManager.updateOutWidget(
// widgetComponentDescriptions[Enactor.OUT_WIDGET_INDEX], atts);
//// System.out.println("Enactor.updateOutWidget returnDataObject = " + returnDataObject);
// return returnDataObject;
// TODO maybe should also fireUpdateOutWidget
}
/**
* Convenience method to request to set attribute value of the widget associated with this Generator.
* @param <T>
* @param attName
* @param value
*/
@SuppressWarnings("serial")
public <T extends Comparable<? super T>> void setAttributeValue(final String attName, final T value) {
Attributes data = new Attributes() {{
addAttribute(Widget.TIMESTAMP, System.currentTimeMillis()); // add timestamp
addAttribute(attName, value);
}};
this.updateOutWidget(data);
}
/**
* Convenience method to add modification details to attributes before sending them off.
* @param atts
* @param timestamp
*/
protected void addLastModifiedProperties(Attributes atts, long timestamp) {
for (Attribute<?> att : atts.values()) {
((AttributeNameValue<?>)att).setLastModified(
timestamp,
this.id, this.hostname, this.port);
}
}
protected boolean addParameter(EnactorParameter ep) {
ep.setEnactor(this);
return enactorParameters.add(ep);
}
protected boolean removeParameter(EnactorParameter ep) {
ep.setEnactor(null);
return enactorParameters.remove(ep);
}
/**
* Convenience method to add reference, where the outcome value is extracted from er.
* @param er
*/
protected void addReference(EnactorReference er) {
String outcomeValue = er.getOutcomeValue();
er.setEnactor(this);
List<EnactorReference> refs = enactorReferences.get(outcomeValue);
if (refs == null) {
refs = new ArrayList<EnactorReference>();
enactorReferences.put(outcomeValue, refs);
}
refs.add(er);
// also notify subscriptionManager
// subscriptionManager.addEnactorReference(er);
}
protected void removeAllReferences() {
for (List<EnactorReference> refs : enactorReferences.values()) {
for (EnactorReference er : refs) {
er.setEnactor(null);
}
refs.clear();
}
enactorReferences.clear();
}
protected void setExplainer(Explainer explainer) {
this.explainer = explainer;
}
public Explainer getExplainer() {
// lazily initialize explainer if not already set
if (explainer == null) {
explainer = new RulesExplainer(this);
}
return explainer;
}
//////////////////////
// Begin Listener Code
//////////////////////
public void addListener(EnactorListener sml) {
fireInitialAddEvents(sml);
enactorListener = EnactorListenerMulticaster.add(enactorListener, sml);
}
public void removeListener(EnactorListener sml) {
enactorListener = EnactorListenerMulticaster.remove(enactorListener, sml);
fireFinalRemoveEvents(sml);
}
/* ---------------------------------------------------------------------------------------------
* Fire methods that call all listeners. Refer to EnactorListener class for documentation.
* --------------------------------------------------------------------------------------------- */
protected final void fireComponentEvaluated(EnactorComponentInfo eci) {
EnactorRuntimeLogger erl = EnactorRuntimeLogger.getEnactorRuntimeLogger();
try{
erl.insertComponentEvaluatedEntry(getId(),eci.getReference(),eci.getCurrentState());
}catch(LoggingException e){
e.printStackTrace();
}
fireComponentEvaluated(enactorListener, eci);
}
protected final void fireComponentAdded(EnactorComponentInfo eci, Attributes paramAtts) {
EnactorRuntimeLogger erl = EnactorRuntimeLogger.getEnactorRuntimeLogger();
try{
erl.insertComponentAddedEntry(getId(),eci.getReference(),eci.getComponentDescription(),paramAtts);
}catch(LoggingException e){
e.printStackTrace();
}
fireComponentAdded(enactorListener, eci, paramAtts);
}
protected final void fireComponentRemoved(EnactorComponentInfo eci, Attributes paramAtts) {
//TODO: logging hooks here
fireComponentRemoved(enactorListener, eci, paramAtts);
}
protected final void fireParameterValueChanged(EnactorParameter parameter, Attributes paramAtts, Object value) {
fireParameterValueChanged(enactorListener, parameter, paramAtts, value);
}
protected final void fireServiceExecuted(EnactorComponentInfo eci, String serviceName, String functionName, Attributes input, DataObject returnDataObject) {
EnactorRuntimeLogger erl = EnactorRuntimeLogger.getEnactorRuntimeLogger();
try{
erl.insertServiceExecutionEntry(getId(), eci, serviceName, functionName, input);
}catch(LoggingException e){
e.printStackTrace();
}
fireServiceExecuted(enactorListener, eci, serviceName, functionName, input, returnDataObject);
}
//methods that inform particular listeners. override these in subclasses.
protected void fireComponentEvaluated(EnactorListener listener, EnactorComponentInfo eci) {
if (listener != null) listener.componentEvaluated(eci);
}
protected void fireComponentAdded(EnactorListener listener, EnactorComponentInfo eci, Attributes paramAtts) {
if (listener != null) listener.componentAdded(eci, paramAtts);
}
protected void fireComponentRemoved(EnactorListener listener, EnactorComponentInfo eci, Attributes paramAtts) {
if (paramAtts == null) {
paramAtts = new Attributes();
}
if (listener != null) listener.componentRemoved(eci, paramAtts);
}
protected void fireParameterValueChanged(EnactorListener listener, EnactorParameter parameter, Attributes paramAtts, Object value) {
if (paramAtts == null) {
paramAtts = new Attributes();
}
if (listener != null) listener.parameterValueChanged(parameter, paramAtts, value);
//ADDED for logging purposes
EnactorRuntimeLogger erl = EnactorRuntimeLogger.getEnactorRuntimeLogger();
try{
erl.insertParameterValueChangedEntry(getId(),parameter,paramAtts,value);
}catch(LoggingException e){
e.printStackTrace();
}
}
protected void fireServiceExecuted(EnactorListener listener, EnactorComponentInfo eci, String serviceName, String functionName, Attributes input, DataObject returnDataObject) {
if (listener != null) {
listener.serviceExecuted(eci, serviceName, functionName, input, returnDataObject);
}
}
//semantics-preserving methods
/**
* fires add events to "catch up" listeners to the state of the enactor.
*
* @param sml
*/
private void fireInitialAddEvents(EnactorListener sml) {
if (subscriptionManager != null) subscriptionManager.fireAddEventsForAll(this, sml);
}
/**
* fires remove events to "catch up" listeners to the state of the enactor.
*
* @param sml
*/
private void fireFinalRemoveEvents(EnactorListener sml) {
//TODO: implement removal
}
}