diffusion
Version:
Diffusion JavaScript client
399 lines (322 loc) • 13.9 kB
JavaScript
var _implements = require('util/interface')._implements;
var Services = require('services/services');
var Emitter = require('events/emitter');
var Result = require('events/result');
var CommandService = require('client/command-service');
var ControlGroup = require('control/control-group');
var registerTopicHandler = require('control/registration').registerTopicHandler;
var updateResponseHandler = require('features/update-response-handler');
var TopicAddFailReason = require('../../topics/topics').TopicAddFailReason;
var TopicType = require('../../topics/topics').TopicType;
var WillResult = require('services/wills/will-registration-result');
var WillParams = require('services/wills/topic-will-parameters');
var TopicSpecification = require('../../topics/topic-specification');
var UniversalUpdater = require('features/topic-control/universal-updater');
var ValueCache = require('features/topic-control/value-cache');
var parseSelector = require('topics/topic-selector-parser');
var api = require('../../features/topic-control');
var logger = require('util/logger').create('Session.Topics');
// See ErrorReasonException.java
var errorReasonIdToAddFailReason = {
103: TopicAddFailReason.PERMISSIONS_FAILURE,
110: TopicAddFailReason.CLUSTER_REPARTITION,
9004: TopicAddFailReason.EXISTS_MISMATCH,
9005: TopicAddFailReason.INVALID_NAME,
9006: TopicAddFailReason.INVALID_DETAILS,
9007: TopicAddFailReason.EXCEEDED_LICENSE_LIMIT,
9008: TopicAddFailReason.INCOMPATIBLE_PARENT,
9009: TopicAddFailReason.INCOMPATIBLE_MASTER,
9010: TopicAddFailReason.EXISTS_INCOMPATIBLE,
9011: TopicAddFailReason.UNEXPECTED_ERROR
};
module.exports = _implements(api.TopicControl, function TopicControlImpl(internal) {
var serviceLocator = internal.getServiceLocator();
var TOPIC_ADD_SERVICE = serviceLocator.obtain(Services.TOPIC_ADD);
var TOPIC_REMOVAL = serviceLocator.obtain(Services.TOPIC_REMOVAL);
var TOPIC_WILL_REGISTRATION = serviceLocator.obtain(Services.TOPIC_SCOPED_WILL_REGISTRATION);
var TOPIC_WILL_DEREGISTRATION = serviceLocator.obtain(Services.TOPIC_SCOPED_WILL_DEREGISTRATION);
var UPDATE_SOURCE_REGISTRATION = serviceLocator.obtain(Services.UPDATE_SOURCE_REGISTRATION);
var valueCache = new ValueCache();
var universalUpdater = new UniversalUpdater(internal);
// Updater state service
var serviceRegistry = internal.getServiceRegistry();
serviceRegistry.add(Services.UPDATE_SOURCE_STATE, CommandService.create(function(internal, request, callback) {
callback.respond();
internal.getConversationSet().respondIfPresent(request.cid, {
old : request.old,
current : request.current
});
}));
serviceRegistry.add(Services.MISSING_TOPIC, CommandService.create(function(internal, request, callback) {
internal.getConversationSet().respond(request.cid, {
request: request,
callback: function (val) {
callback.respond(val);
}
});
}));
function isSupportedTopicType(type) {
for (var t in TopicType) {
if (TopicType[t] === type) {
return true;
}
}
return false;
}
this.add = function (path, specification) {
var emitter = new Emitter();
var result = new Result(emitter);
if (!path) {
emitter.error({
id: 0,
reason: 'Path cannot be empty or null'
});
return result;
}
if (internal.checkConnected(emitter)) {
logger.debug('Adding topic', path);
// Convenient case to convert a supported topic type to topic specification
// and use the Protocol 13 - TopicAdd service to add the topic.
if (isSupportedTopicType(specification)) {
TOPIC_ADD_SERVICE.send({
path: path, specification: new TopicSpecification(specification)
}, function(err, result) {
if (err) {
emitter.error(errorReasonIdToAddFailReason[err.id]);
}
else {
handleAddResult(err, result, path, undefined, emitter);
}
});
}
// Use Protocol 13 - TopicAdd service
else if (specification instanceof TopicSpecification) {
TOPIC_ADD_SERVICE.send({
path: path, specification: specification
}, function(err, result) {
if (err) {
emitter.error(errorReasonIdToAddFailReason[err.id]);
}
else {
handleAddResult(err, result, path, undefined, emitter);
}
});
}
}
return result;
};
function handleAddResult(err, result, path, content, emitter) {
switch (result.status) {
case 0:
case 1:
logger.debug("Topic add complete", path);
valueCache.put(path, content);
emitter.emit('complete', {
topic: path,
added: true
});
break;
case 2:
// Special case - the topic already exists with same details
// This emits 'complete' because add topic is idempotent
if (result.reason === TopicAddFailReason.EXISTS) {
logger.debug("Topic add complete (already exists)", path);
valueCache.put(path, content);
emitter.emit('complete', {
topic: path,
added: false
});
} else {
logger.debug("Topic add failed", path);
emitter.error(result.reason);
}
break;
case 3:
emitter.error({id: 0, reason: 'Cache failure'});
break;
}
}
this.remove = function (expression) {
var emitter = new Emitter();
var result = new Result(emitter);
if (internal.checkConnected(emitter)) {
logger.debug('Removing topics', expression);
try {
TOPIC_REMOVAL.send({
selector: (arguments.length>1)
? parseSelector(Array.prototype.slice.call(arguments))
: parseSelector(expression)
}, function (err) {
if (err) {
logger.debug('Topic removal failed', expression);
emitter.error(err);
} else {
logger.debug('Topic removal complete', expression);
emitter.emit('complete');
}
});
} catch (err) {
logger.debug('Error parsing selector', expression);
emitter.error(err);
}
}
return result;
};
this.removeWithSession = function (path) {
var emitter = new Emitter();
var result = new Result(emitter);
if (internal.checkConnected(emitter)) {
logger.debug('Registering removeWithSession', path);
var params = new WillParams(path, WillParams.Will.REMOVE_TOPICS);
TOPIC_WILL_REGISTRATION.send(params, function (err, result) {
if (err) {
logger.debug('removeWithSession registration failed', path);
emitter.error(err);
} else if (result === WillResult.SUCCESS) {
logger.debug('removeWithSession registration complete', path);
var registered = true;
var dEmitter = new Emitter();
var dResult = new Result(dEmitter);
var deregister = function () {
if (registered && internal.checkConnected(dEmitter)) {
logger.debug('Deregistering removeWithSession', path);
TOPIC_WILL_DEREGISTRATION.send(params, function (err) {
if (err) {
dEmitter.error(err);
} else {
registered = false;
dEmitter.emit('complete', {
topic: path
});
}
});
}
return dResult;
};
emitter.emit('complete', {
deregister: deregister
});
} else {
logger.debug('removeWithSession registration failed', path);
emitter.error(new Error('REGISTRATION_CONFLICT'));
}
});
}
return result;
};
this.update = function (path, content) {
var emitter = new Emitter();
var result = new Result(emitter);
if (internal.checkConnected(emitter)) {
logger.debug('Updating topic', path);
var callback = function (error, result) {
if (error) {
logger.debug('Update failed', path);
emitter.error(error);
} else if (result.isError) {
logger.debug('Update failed', path);
emitter.error(result.reason);
} else {
logger.debug('Update complete', path);
emitter.emit('complete', path);
}
};
universalUpdater.update(path, content, callback);
}
return result;
};
this.updateValue = function (path, content, datatype) {
var emitter = new Emitter();
var result = new Result(emitter);
if (!datatype) {
emitter.error(new Error("Data type is required"));
return result;
}
if (internal.checkConnected(emitter)) {
logger.debug('Updating topic', path);
var callback = function (error, result) {
if (error) {
logger.debug('Update failed', path);
emitter.error(error);
} else if (result.isError) {
logger.debug('Update failed', path);
emitter.error(result.reason);
} else {
logger.debug('Update complete', path);
emitter.emit('complete', path);
}
};
universalUpdater.updateValue(path, content, datatype, callback);
}
return result;
};
this.registerUpdateSource = function (path, handler) {
var emitter = new Emitter();
var result = new Result(emitter);
var conversations = internal.getConversationSet();
var cid = conversations.newConversation(updateResponseHandler(internal, valueCache, path, handler));
if (internal.checkConnected(emitter)) {
UPDATE_SOURCE_REGISTRATION.send({
cid: cid,
path: path
}, function (err, state) {
if (err || state === 'closed') {
conversations.discard(cid, err);
emitter.error(err);
} else {
conversations.respond(cid, {old: 'init', current: state});
emitter.emit('complete');
}
});
}
return result;
};
this.addMissingTopicHandler = function (path, handler) {
var emitter = new Emitter();
var result = new Result(emitter);
if (!handler) {
emitter.error(new Error('Missing Topic handler is null or undefined'));
return result;
}
if (internal.checkConnected(emitter)) {
var adapter = {
active: function (close) {
logger.debug('Missing Topic Handler registered for ' + path);
handler.onRegister(path, close);
},
respond: function (response) {
logger.debug('Missing Topic Handler notification for ' +
response.request.sessionID +
' using ' +
response.request.selector);
handler.onMissingTopic({
path: response.request.selector.prefix,
selector: response.request.selector,
sessionID: response.request.sessionID,
proceed: function () {
response.callback(true);
},
cancel: function () {
response.callback(false);
}
});
},
close: function (err) {
logger.debug('Missing Topic Handler closed for ' + path);
if (err) {
handler.onError(path, err);
} else {
handler.onClose(path);
}
}
};
var params = {
definition: Services.MISSING_TOPIC,
group: ControlGroup.DEFAULT,
path: path
};
return registerTopicHandler(internal, params, adapter);
}
return result;
};
});