PageRenderTime 56ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/src/record/record-handler.js

https://gitlab.com/jasonparser/deepstream.io
JavaScript | 369 lines | 174 code | 42 blank | 153 comment | 47 complexity | e5435b34709567f470e6aaa2f6a9b993 MD5 | raw file
  1. var C = require( '../constants/constants' ),
  2. SubscriptionRegistry = require( '../utils/subscription-registry' ),
  3. ListenerRegistry = require( '../utils/listener-registry' ),
  4. RecordRequest = require( './record-request' ),
  5. RecordTransition = require( './record-transition' ),
  6. RecordDeletion = require( './record-deletion' ),
  7. messageParser = require( '../message/message-parser' ),
  8. messageBuilder = require( '../message/message-builder' );
  9. /**
  10. * The entry point for record related operations
  11. *
  12. * @param {Object} options deepstream options
  13. */
  14. var RecordHandler = function( options ) {
  15. this._options = options;
  16. this._subscriptionRegistry = new SubscriptionRegistry( options, C.TOPIC.RECORD );
  17. this._listenerRegistry = new ListenerRegistry( C.TOPIC.RECORD, options, this._subscriptionRegistry );
  18. this._subscriptionRegistry.setSubscriptionListener( this._listenerRegistry );
  19. this._hasReadTransforms = this._options.dataTransforms && this._options.dataTransforms.has( C.TOPIC.RECORD, C.ACTIONS.READ );
  20. this._hasUpdateTransforms = this._options.dataTransforms && this._options.dataTransforms.has( C.TOPIC.RECORD, C.ACTIONS.UPDATE );
  21. this._hasPatchTransforms = this._options.dataTransforms && this._options.dataTransforms.has( C.TOPIC.RECORD, C.ACTIONS.PATCH );
  22. this._transitions = [];
  23. };
  24. /**
  25. * Handles incoming record requests.
  26. *
  27. * Please note that neither CREATE nor READ is supported as a
  28. * client send action. Instead the client sends CREATEORREAD
  29. * and deepstream works which one it will be
  30. *
  31. * @param {SocketWrapper} socketWrapper the sender
  32. * @param {Object} message parsed and validated deepstream message
  33. *
  34. * @public
  35. * @returns {void}
  36. */
  37. RecordHandler.prototype.handle = function( socketWrapper, message ) {
  38. /*
  39. * All messages have to provide at least the name of the record they relate to
  40. * or a pattern in case of listen
  41. */
  42. if( !message.data || message.data.length < 1 ) {
  43. socketWrapper.sendError( C.TOPIC.RECORD, C.EVENT.INVALID_MESSAGE_DATA, message.raw );
  44. return;
  45. }
  46. /*
  47. * Return the record's contents and subscribes for future updates.
  48. * Creates the record if it doesn't exist
  49. */
  50. if( message.action === C.ACTIONS.CREATEORREAD ) {
  51. this._createOrRead( socketWrapper, message );
  52. }
  53. /*
  54. * Handle complete (UPDATE) or partial (PATCH) updates
  55. */
  56. else if( message.action === C.ACTIONS.UPDATE || message.action === C.ACTIONS.PATCH ) {
  57. this._update( socketWrapper, message );
  58. }
  59. /*
  60. * Deletes the record
  61. */
  62. else if( message.action === C.ACTIONS.DELETE ) {
  63. this._delete( socketWrapper, message );
  64. }
  65. /*
  66. * Unsubscribes (discards) a record that was previously subscribed to
  67. * using read()
  68. */
  69. else if( message.action === C.ACTIONS.UNSUBSCRIBE ) {
  70. this._subscriptionRegistry.unsubscribe( message.data[ 0 ], socketWrapper );
  71. }
  72. /*
  73. * Listen to requests for a particular record or records
  74. * whose names match a pattern
  75. */
  76. else if( message.action === C.ACTIONS.LISTEN ) {
  77. this._listenerRegistry.addListener( socketWrapper, message );
  78. }
  79. /*
  80. * Remove the socketWrapper as a listener for
  81. * the specified pattern
  82. */
  83. else if( message.action === C.ACTIONS.UNLISTEN ) {
  84. this._listenerRegistry.removeListener( socketWrapper, message );
  85. }
  86. /*
  87. * Default for invalid messages
  88. */
  89. else {
  90. this._options.logger.log( C.LOG_LEVEL.WARN, C.EVENT.UNKNOWN_ACTION, message.action );
  91. if( socketWrapper !== C.SOURCE_MESSAGE_CONNECTOR ) {
  92. socketWrapper.sendError( C.TOPIC.RECORD, C.EVENT.UNKNOWN_ACTION, 'unknown action ' + message.action );
  93. }
  94. }
  95. };
  96. /**
  97. * Tries to retrieve the record and creates it if it doesn't exist. Please
  98. * note that create also triggers a read once done
  99. *
  100. * @param {SocketWrapper} socketWrapper the socket that send the request
  101. * @param {Object} message parsed and validated message
  102. *
  103. * @private
  104. * @returns {void}
  105. */
  106. RecordHandler.prototype._createOrRead = function( socketWrapper, message ) {
  107. var recordName = message.data[ 0 ],
  108. onComplete = function( record ) {
  109. if( record ) {
  110. this._read( recordName, record, socketWrapper );
  111. } else {
  112. this._create( recordName, socketWrapper );
  113. }
  114. };
  115. new RecordRequest( recordName, this._options, socketWrapper, onComplete.bind( this ) );
  116. };
  117. /**
  118. * Creates a new, empty record and triggers a read operation once done
  119. *
  120. * @param {SocketWrapper} socketWrapper the socket that send the request
  121. * @param {Object} message parsed and validated message
  122. *
  123. * @private
  124. * @returns {void}
  125. */
  126. RecordHandler.prototype._create = function( recordName, socketWrapper ) {
  127. var record = {
  128. _v: 0,
  129. _d: {}
  130. };
  131. // store the records data in the cache and wait for the result
  132. this._options.cache.set( recordName, record, function( error ){
  133. if( error ) {
  134. this._options.logger.log( C.LOG_LEVEL.ERROR, C.EVENT.RECORD_CREATE_ERROR, recordName );
  135. socketWrapper.sendError( C.TOPIC.RECORD, C.EVENT.RECORD_CREATE_ERROR, recordName );
  136. }
  137. else {
  138. this._read( recordName, record, socketWrapper );
  139. }
  140. }.bind( this ));
  141. if( !this._options.storageExclusion || !this._options.storageExclusion.test( recordName ) ) {
  142. // store the record data in the persistant storage independently and don't wait for the result
  143. this._options.storage.set( recordName, record, function( error ) {
  144. if( error ) {
  145. this._options.logger.log( C.TOPIC.RECORD, C.EVENT.RECORD_CREATE_ERROR, 'storage:' + error );
  146. }
  147. }.bind( this ) );
  148. }
  149. };
  150. /**
  151. * Subscribes to updates for a record and sends its current data once done
  152. *
  153. * @param {String} recordName
  154. * @param {Object} record
  155. * @param {SocketWrapper} socketWrapper the socket that send the request
  156. *
  157. * @private
  158. * @returns {void}
  159. */
  160. RecordHandler.prototype._read = function( recordName, record, socketWrapper ) {
  161. this._subscriptionRegistry.subscribe( recordName, socketWrapper );
  162. var data = record._d;
  163. if( this._hasReadTransforms ) {
  164. data = this._options.dataTransforms.apply(
  165. C.TOPIC.RECORD,
  166. C.ACTIONS.READ,
  167. /*
  168. * Clone the object to make sure that the transform method doesn't accidentally
  169. * modify the object reference for other subscribers.
  170. *
  171. * JSON stringify/parse still seems to be the fastest way to achieve a deep copy.
  172. * TODO Update once native Object.clone // Object.copy becomes a thing
  173. */
  174. JSON.parse( JSON.stringify( data ) ),
  175. { recordName: recordName, receiver: socketWrapper.user }
  176. );
  177. }
  178. socketWrapper.sendMessage( C.TOPIC.RECORD, C.ACTIONS.READ, [ recordName, record._v, data ] );
  179. };
  180. /**
  181. * Applies both full and partial updates. Creates a new record transition that will live as long as updates
  182. * are in flight and new updates come in
  183. *
  184. * @param {SocketWrapper} socketWrapper the socket that send the request
  185. * @param {Object} message parsed and validated message
  186. *
  187. * @private
  188. * @returns {void}
  189. */
  190. RecordHandler.prototype._update = function( socketWrapper, message ) {
  191. if( message.data.length < 3 ) {
  192. socketWrapper.sendError( C.TOPIC.RECORD, C.EVENT.INVALID_MESSAGE_DATA, message.data[ 0 ] );
  193. return;
  194. }
  195. var recordName = message.data[ 0 ],
  196. version = parseInt( message.data[ 1 ], 10 );
  197. /*
  198. * If the update message is received from the message bus, rather than from a client,
  199. * assume that the original deepstream node has already updated the record in cache and
  200. * storage and only broadcast the message to subscribers
  201. */
  202. if( socketWrapper === C.SOURCE_MESSAGE_CONNECTOR ) {
  203. this._$broadcastUpdate( recordName, message, socketWrapper );
  204. return;
  205. }
  206. if( isNaN( version ) ) {
  207. socketWrapper.sendError( C.TOPIC.RECORD, C.EVENT.INVALID_VERSION, [ recordName, version ] );
  208. return;
  209. }
  210. if( this._transitions[ recordName ] && this._transitions[ recordName ].hasVersion( version ) ) {
  211. socketWrapper.sendError( C.TOPIC.RECORD, C.EVENT.VERSION_EXISTS, [ recordName, version ] );
  212. return;
  213. }
  214. if( !this._transitions[ recordName ] ) {
  215. this._transitions[ recordName ] = new RecordTransition( recordName, this._options, this );
  216. }
  217. this._transitions[ recordName ].add( socketWrapper, version, message );
  218. };
  219. /**
  220. * Invoked by RecordTransition. Notifies local subscribers and other deepstream
  221. * instances of record updates
  222. *
  223. * @param {String} name record name
  224. * @param {Object} message parsed and validated deepstream message
  225. * @param {SocketWrapper} originalSender the socket the update message was received from
  226. *
  227. * @package private
  228. * @returns {void}
  229. */
  230. RecordHandler.prototype._$broadcastUpdate = function( name, message, originalSender ) {
  231. var transformUpdate = message.action === C.ACTIONS.UPDATE && this._hasUpdateTransforms,
  232. transformPatch = message.action === C.ACTIONS.PATCH && this._hasPatchTransforms;
  233. if( transformUpdate || transformPatch ) {
  234. this._broadcastTransformedUpdate( transformUpdate, transformPatch, name, message, originalSender );
  235. } else {
  236. this._subscriptionRegistry.sendToSubscribers( name, message.raw, originalSender );
  237. }
  238. if( originalSender !== C.SOURCE_MESSAGE_CONNECTOR ) {
  239. this._options.messageConnector.publish( C.TOPIC.RECORD, message );
  240. }
  241. };
  242. /**
  243. * Called by _$broadcastUpdate if registered transform functions are detected. Disassembles
  244. * the message and invokes the transform function prior to sending it to every individual receiver
  245. * so that receiver specific transforms can be applied.
  246. *
  247. * @param {Boolean} transformUpdate is a update transform function registered that applies to this update?
  248. * @param {Boolean} transformPatch is a patch transform function registered that applies to this update?
  249. * @param {String} name the record name
  250. * @param {Object} message a parsed deepstream message object
  251. * @param {SocketWrapper|String} originalSender the original sender of the update or a string pointing at the messageBus
  252. *
  253. * @private
  254. * @returns {void}
  255. */
  256. RecordHandler.prototype._broadcastTransformedUpdate = function( transformUpdate, transformPatch, name, message, originalSender ) {
  257. var receiver = this._subscriptionRegistry.getSubscribers( name ) || [],
  258. metaData = {
  259. recordName: name,
  260. version: parseInt( message.data[ 1 ], 10 )
  261. },
  262. data,
  263. i;
  264. if( transformPatch ) {
  265. metaData.path = message.data[ 2 ];
  266. }
  267. for( i = 0; i < receiver.length; i++ ) {
  268. if( receiver[ i ] === originalSender ) {
  269. continue;
  270. }
  271. metaData.receiver = receiver[ i ].user;
  272. if( transformUpdate ) {
  273. // UPDATE
  274. // parse data every time to create a fresh copy
  275. data = JSON.parse( message.data[ 2 ] );
  276. data = this._options.dataTransforms.apply( message.topic, message.action, data, metaData );
  277. message.data[ 2 ] = JSON.stringify( data );
  278. } else {
  279. // PATCH
  280. // convert data every time to create a fresh copy
  281. data = messageParser.convertTyped( message.data[ 3 ] );
  282. data = this._options.dataTransforms.apply( message.topic, message.action, data, metaData );
  283. message.data[ 3 ] = messageBuilder.typed( data );
  284. }
  285. receiver[ i ].sendMessage( message.topic, message.action, message.data );
  286. }
  287. };
  288. /**
  289. * Called by a RecordTransition, either if it is complete or if an error occured. Removes
  290. * the transition from the registry
  291. *
  292. * @todo refactor - this is a bit of a mess
  293. * @param {String} recordName record name
  294. *
  295. * @package private
  296. * @returns {void}
  297. */
  298. RecordHandler.prototype._$transitionComplete = function( recordName ) {
  299. delete this._transitions[ recordName ];
  300. };
  301. /**
  302. * Deletes a record. If a transition is in progress it will be stopped. Once the
  303. * deletion is complete, an Ack is returned.
  304. *
  305. * If the deletion message is received from the message bus, rather than from a client,
  306. * we assume that the original deepstream node has already deleted the record from cache and
  307. * storage and we only need to broadcast the message to subscribers
  308. *
  309. * @param {SocketWrapper} socketWrapper the socket that send the request
  310. * @param {Object} message parsed and validated message
  311. *
  312. * @private
  313. * @returns {void}
  314. */
  315. RecordHandler.prototype._delete = function( socketWrapper, message ) {
  316. var recordName = message.data[ 0 ];
  317. if( this._transitions[ recordName ] ) {
  318. this._transitions[ recordName ].destroy();
  319. delete this._transitions[ recordName ];
  320. }
  321. if( socketWrapper === C.SOURCE_MESSAGE_CONNECTOR ) {
  322. this._$broadcastUpdate( recordName, message, socketWrapper );
  323. return;
  324. }
  325. new RecordDeletion( this._options, socketWrapper, message, this._$broadcastUpdate.bind( this ) );
  326. };
  327. module.exports = RecordHandler;