package context.arch.subscriber;
import java.util.concurrent.ConcurrentHashMap;
import context.arch.comm.DataObject;
import context.arch.comm.DataObjects;
import context.arch.comm.language.MessageHandler;
import context.arch.widget.Widget;
/**
* This class maintains a list of subscribers, allows additions, removals and
* updates to individual subscribers.
*
* Agathe: I have changed Subscribers and Subscriber to allow the addition of
* the DiscovererSubscriber class. Subscriber and DiscovererSubscriber
* implement an interface handled by Subscribers.
*
* Agathe: modify restart subscription
*
* TODO: consider using MySQL for persistence of subscription instead of a local flat file --Brian
* TODO: anyway, the storage files are not strict XML because they are delimited with ENTRY_STRING
*
* @author Anind, Agathe, Brian Y. Lim
* @see context.arch.subscriber.Subscriber
*/
public class Subscribers extends ConcurrentHashMap<String, AbstractSubscriber> {
public static final String WIDGET_SUBSCRIPTIONS_DIR = "widget-subscriptions/";
private static final long serialVersionUID = 4321311291016308706L;
/** Debug flag */
public static boolean DEBUG = false;
/**
* Tags written in the log file
*/
@SuppressWarnings("unused")
private final static String ENTRY_STRING = "entry:";
/*
* To be combined like "addSubR:", "updateSubD", etc
* See #writeLog
*/
private final static String ADD = "add";
private final static String REMOVE = "remove";
private final static String UPDATE = "update";
@SuppressWarnings("unused")
private final static String SUB_REG = "SubR";
@SuppressWarnings("unused")
private final static String SUB_DISCO = "SubD";
/**
* Tag used in messages
*/
public static final String SUBSCRIBERS = "subscribers";
/** */
@SuppressWarnings("unused")
private MessageHandler msgHandler;
/** */
@SuppressWarnings("unused")
private String filename;
/** The id of the component */
private String baseObjectId;
/**
* Basic constructor that takes an object that implements the MessageHandler
* interface and an id to create a logfile name from.
*/
public Subscribers(MessageHandler msgHandler, String id) {
super();
this.msgHandler = msgHandler;
baseObjectId = id;
// The filename for the log file with directory
filename = WIDGET_SUBSCRIPTIONS_DIR + id + "-subscription.xml";
restartSubscriptions();
}
/**
* Adds a subscriber to the subscriber list
*
* @param sub Subscriber object to add
*/
public void add(Subscriber sub) {
addSubscriber(sub, true);
}
public void add(DiscovererSubscriber sub) {
addSubscriber(sub, true);
}
public void addSubscriber(AbstractSubscriber sub, boolean log) {
if (contains(sub)) { return; } // ignore if already in
// Updates the unique subscription id
sub.setSubscriptionId(
sub.getBaseObjectId() + Widget.SPACER +
baseObjectId + Widget.SPACER +
sub.getSubscriptionCallback() + Widget.SPACER +
this.getCounterForUniqueIds());
put(sub.getSubscriptionId(), sub);
writeLog(log, ADD, sub);
}
/**
* Removes a subscriber from the subscriber list
*
* @param sub Subscriber object to remove
* @return whether the removal was successful or not
*/
public synchronized boolean removeSubscriber(AbstractSubscriber sub) {
return removeSubscriber(sub, true);
}
/**
* Removes a subscriber from the subscriber list
*
* @param sub Subscriber object to remove
* @param log Whether to log the subscribe or not
* @return whether the removal was successful or not
*/
public boolean removeSubscriber(AbstractSubscriber sub, boolean log) {
writeLog(log, REMOVE, sub);
return super.remove(sub.getSubscriptionId()) != null;
}
/** Remove an AbstractSubscriber
*
* @param subToRemove
*/
public boolean remove(AbstractSubscriber subToRemove) {
String subToRemoveId = subToRemove.getSubscriptionId();
AbstractSubscriber sub = null;
for (AbstractSubscriber asub : this.values()) {
if (subToRemoveId.equalsIgnoreCase(asub.getSubscriptionId())) {
sub = asub;
break;
}
}
if (sub != null) {
this.remove(sub);
return true;
}
return false;
// is equalsIgnoreCase really that important? Otherwise, can use the standard remove method
// super.remove(subToRemove);
}
/**
* Removes a subscriber from the subscriber list
*
* @param sub Subscriber object to remove
* @return whether the removal was successful or not
*/
public synchronized boolean removeSubscriber(String subId) {
return removeSubscriber(subId, true);
}
/**
* Removes a subscriber from the subscriber list
*
* @param sub Subscriber object to remove
* @param log Whether to log the subscribe or not
* @return whether the removal was successful or not
*/
public synchronized boolean removeSubscriber(String subId, boolean log) {
AbstractSubscriber sub = super.remove(subId);
writeLog(log, REMOVE, sub);
return sub != null;
}
/**
* Updates a subscriber in the subscriber list. The subscriber name is
* retrieved from the subscriber object and the old subscriber entry with
* this name is replaced by the given one.
*
* @param sub Subscriber object to update
*/
public synchronized void updateSubscriber(AbstractSubscriber sub) {
updateSubscriber(sub,true);
}
/**
* Updates a subscriber in the subscriber list. The subscriber name is
* retrieved from the subscriber object and the old subscriber entry with
* this name is replaced by the given one.
*
* @param sub Subscriber object to update
* @param log Whether to log the subscribe or not
*/
public synchronized void updateSubscriber(AbstractSubscriber sub, boolean log) {
put(sub.getSubscriptionId(), sub);
writeLog(log, UPDATE, sub);
}
/**
* Converts to a DataObject.
*
* @return
*/
public DataObject toDataObject() {
DataObjects v = new DataObjects();
for (AbstractSubscriber s : this.values()) {
v.add(s.toDataObject());
}
return new DataObject(SUBSCRIBERS,v);
}
/**
* Return the number to use
* @return
*/
private long getCounterForUniqueIds() {
// return counterForUniqueIds++; // this would not have been unique if the Subscribers was reset
return System.currentTimeMillis();
}
/**
* This method reads in the subscription log, restarts all the subscriptions
* that were valid at the time of this object being shut down and writes
* out the valid subscriptions to the log. It deletes the old log and
* creates a new one, so that it can clear out entries in the log for
* corresponding unsubscribes and subscribes.
*
* TODO: prone to corruption during prototyping; currently disabled
*/
private void restartSubscriptions() {
// String log = FileUtil.read(filename);
//
// /*
// * Format of log:
// * Text delimited by ENTRY_STRING ,
// * followed by a command (e.g. ADD_SUB_REG, REMOVE_SUB_REG),
// * followed by XML containing info about a subscriber
// */
// String[] entries = log.split(ENTRY_STRING);
//
// for (String entry : entries) {
// try { // Test the message code : ADD, REMOVE, UPDATE and creates a Subscriber
//
// int index = entry.indexOf(">"); // to skip "<?xml version="1.0"?>"
// String data = entry.substring(index + 1).trim();
// if (data.length() == 0) { continue; }
//
// DataObject decodedData = msgHandler.decodeData(new StringReader(data));
//
// // object based on the log file
// if (entry.startsWith(ADD + SUB_REG)) {
// addSubscriber(new Subscriber(decodedData), false);
// }
// else if (entry.startsWith(REMOVE + SUB_REG)) {
// removeSubscriber(new Subscriber(decodedData), false);
// }
// else if (entry.startsWith(UPDATE + SUB_REG)) {
// updateSubscriber(new Subscriber(decodedData), false);
// }
//
// // Discoverer subscribers
// else if (entry.startsWith(ADD + SUB_DISCO)) {
// addSubscriber(new DiscovererSubscriber(decodedData), false);
// }
// else if (entry.startsWith(REMOVE + SUB_DISCO)) {
// removeSubscriber(new DiscovererSubscriber(decodedData), false);
// }
// else if (entry.startsWith(UPDATE + SUB_DISCO)) {
// updateSubscriber(new DiscovererSubscriber(decodedData), false);
// }
//
// } catch (DecodeException de) {
// de.printStackTrace();
// } catch (InvalidDecoderException ide) {
// ide.printStackTrace();
// }
// }
//
// // why are we writing to the log when we have just read from it w/o making changes? --Brian
// for (AbstractSubscriber sub : this.values()) {
// writeLog(true, ADD, sub);
// }
}
/**
* This private method writes an entry to the logfile.
* TODO: prone to corruption during prototyping; currently disabled
*
* @param header Header of the entry to append to the logfile
* @param sub Subscriber information to put in the entry
*/
private void writeLog(boolean log, String action, AbstractSubscriber sub) {
if (!log) { return; }
// String header = ENTRY_STRING + action +
// (sub instanceof DiscovererSubscriber ?
// SUB_DISCO : // discoverer subscriber
// SUB_REG) + // widget subscriber
// ":";
//
// PrintWriter out = null;
// try {
// out = new PrintWriter(FileUtil.getWriter(filename, true));
// out.println(header + msgHandler.encodeData(sub.toDataObject()));
// out.flush();
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// FileUtil.closeWriter(out);
// }
}
/** Set the BaseObject id used to attribute the unique id for subscribers
* @param id
*/
public void setBaseObjectId(String id){
this.baseObjectId = id;
}
}