diffusion
Version:
Diffusion JavaScript client
209 lines (167 loc) • 5.54 kB
JavaScript
var ConversationId = require('conversation/conversation-id');
var logger = require('util/logger');
var Long = require('long');
var NEXT_CID = (function() {
var id = new Long(0);
return function() {
id = id.add(1);
return new ConversationId(id);
};
}());
var Result = {
ALREADY_FINISHED : 0,
HANDLED_AND_ACTIVE : 1,
HANDLED_AND_FINISHED : 2
};
var LOG = logger.create('Conversation Set');
/**
* Implementation of ConversationSet.
* <P>
* See the Java ConversationSet implementation for more in-depth documentation on the design motivations behind this
* class.
*
* @param {Function} idGenerator function to return new conversation ids
*/
function ConversationSet(idGenerator) {
var nextCID = idGenerator;
var conversations = {};
var discardReason;
var self = this;
function completedExceptionally(cid, conversation, e) {
LOG.debug("Application handler threw exception [cid=" + cid + "]", e.stack);
conversation.close();
delete conversations[cid];
}
this.newConversation = function(responseHandler) {
var conversation = new Conversation(responseHandler);
var cid = nextCID();
conversations[cid.toString()] = conversation;
try {
conversation.open(cid);
} catch (e) {
completedExceptionally(cid, conversation, e);
throw e;
}
if (discardReason) {
self.discard(cid, discardReason);
}
return cid;
};
this.respond = function(cid, response) {
var conversation = conversations[cid];
if (conversation === undefined) {
LOG.debug("No conversation for cid: " + cid + ", response: ", response);
throw new Error("No such conversation");
}
var result = conversation.respond(cid, response);
switch (result) {
case Result.ALREADY_FINISHED :
throw new Error("No such conversation");
case Result.HANDLED_AND_ACTIVE:
break;
default:
delete conversations[cid];
}
};
this.respondIfPresent = function(cid, response) {
try {
self.respond(cid, response);
} catch (e) {
// No-op
}
};
this.discard = function(cid, reason) {
var conversation = conversations[cid];
delete conversations[cid];
if (conversation) {
conversation.discard(cid, reason);
}
};
this.discardAll = function(reason) {
if (discardReason !== undefined) {
return;
}
discardReason = reason;
for (var cid in conversations) {
self.discard(ConversationId.fromString(cid), reason);
}
};
this.size = function() {
return Object.keys(conversations).length;
};
this.toString = function() {
return "ConversationSet cids=[" + Object.keys(conversations) + "]";
};
/**
* Internal Conversation implementation. Maintains state for a provided conversation handler to ensure recursive
* operations remain in-order.
*
* @param {Object} handler the conversation handler to dispatch events to.
* @constructor
*/
function Conversation(handler) {
var pendingDiscard;
var reserved = false;
var closed = false;
this.open = function(cid) {
handler.onOpen(cid);
};
this.respond = function(cid, response) {
if (closed) {
return Result.ALREADY_FINISHED;
}
// Set as reserved so as to prevent recursive discards
var previous = reserved;
reserved = true;
var close;
try {
close = handler.onResponse(cid, response);
} catch (e) {
completedExceptionally(cid, this, e);
throw e;
}
if (closed) {
LOG.debug("Conversation already closed", cid);
return Result.HANDLED_AND_FINISHED;
} else if (close) {
LOG.debug("Response handler closed conversation", cid);
closed = true;
return Result.HANDLED_AND_FINISHED;
} else if (pendingDiscard) {
LOG.debug("Conversation closed with pending reason", cid);
notifyDiscard(cid, pendingDiscard);
return Result.HANDLED_AND_FINISHED;
} else {
reserved = previous;
return Result.HANDLED_AND_ACTIVE;
}
};
this.discard = function(cid, reason) {
if (reserved) {
pendingDiscard = reason;
} else {
notifyDiscard(cid, reason);
}
};
this.close = function() {
closed = true;
};
function notifyDiscard(cid, reason) {
if (!closed) {
closed = true;
try {
handler.onDiscard(cid, reason);
} catch (e) {
LOG.error("Application handler threw exception [cid=" + cid + "]", e.stack);
throw e;
}
}
}
}
}
module.exports = ConversationSet;
module.exports.create = function() {
return new ConversationSet(NEXT_CID);
};
module.exports.Result = Result;
module.exports.ConversationIdGenerator = NEXT_CID;