UNPKG

diffusion

Version:

Diffusion JavaScript client

399 lines (322 loc) 13.9 kB
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; }; });