|
10 | 10 |
|
11 | 11 | var events = require('events'); |
12 | 12 |
|
| 13 | + var CQ_FACTORY = 'continuous-query-filter-converter-factory'; |
| 14 | + |
13 | 15 | module.exports = listeners; |
14 | 16 |
|
15 | 17 | /** |
|
132 | 134 | dispatchEvent: function(event, listenerId, bytebuf, emitFunc) { |
133 | 135 | return function() { |
134 | 136 | var l = listeners.get(listenerId); |
135 | | - if (f.existy(l)) |
136 | | - return emitFunc(event, l.emitter, bytebuf, listenerId); |
| 137 | + if (f.existy(l)) { |
| 138 | + var emit = l.cqEmit || emitFunc; |
| 139 | + return emit(event, l.emitter, bytebuf, listenerId); |
| 140 | + } |
137 | 141 |
|
138 | 142 | logger.error('No emitter exists for listener %s', listenerId); |
139 | 143 | return true; |
|
152 | 156 | setProtocol: function(newProtocol) { |
153 | 157 | protocol = newProtocol; |
154 | 158 | }, |
| 159 | + /** |
| 160 | + * Register a continuous query listener. |
| 161 | + * @param {Object} transport Transport instance. |
| 162 | + * @param {Object} ctx Request context. |
| 163 | + * @param {string} queryString Ickle query string. |
| 164 | + * @param {Object} [opts] Options with optional params map. |
| 165 | + * @returns {Promise<Object>} ContinuousQuery handle. |
| 166 | + */ |
| 167 | + addContinuousQueryListener: function(transport, ctx, queryString, opts) { |
| 168 | + opts = opts || {}; |
| 169 | + var listenerId = _.uniqueId('cq_'); |
| 170 | + logger.debugl(function() { |
| 171 | + return ['Invoke addContinuousQueryListener(msgId=%d,query=%s,listenerId=%s)', |
| 172 | + ctx.id, queryString, listenerId]; }); |
| 173 | + |
| 174 | + var cqParams = buildCQParams(queryString, opts.params); |
| 175 | + |
| 176 | + var listenerOpts = { |
| 177 | + includeState: true, |
| 178 | + useRawData: true, |
| 179 | + filterFactory: { name: CQ_FACTORY, params: cqParams }, |
| 180 | + converterFactory: { name: CQ_FACTORY, params: cqParams } |
| 181 | + }; |
| 182 | + |
| 183 | + var encodeListenerAddCommon = protocol.encodeListenerAdd(listenerId, listenerOpts)(); |
| 184 | + var encodeListenerAddInterests = protocol.encodeListenerInterests(listenerOpts); |
| 185 | + var encodeListenerAdd = function() { |
| 186 | + return f.cat(encodeListenerAddCommon, encodeListenerAddInterests); |
| 187 | + }; |
| 188 | + |
| 189 | + var bufferedEvents = []; |
| 190 | + var buffering = true; |
| 191 | + |
| 192 | + var cqEmitFn = function(event, emitter, bytebuf) { |
| 193 | + var payloadLength = codec.decodeVariableBytes()(bytebuf); |
| 194 | + if (!f.existy(payloadLength) || !f.existy(payloadLength.answer)) |
| 195 | + return false; |
| 196 | + var payload = payloadLength.answer; |
| 197 | + var wrapped = codec.decodeWrappedMessage(payload); |
| 198 | + if (!f.existy(wrapped) || !f.existy(wrapped.wrappedMessage)) |
| 199 | + return true; |
| 200 | + var cqResult = codec.decodeContinuousQueryResult(wrapped.wrappedMessage); |
| 201 | + if (buffering) { |
| 202 | + bufferedEvents.push(cqResult); |
| 203 | + } else { |
| 204 | + emitter.emit(cqResult.resultType, cqResult.key, cqResult.value, cqResult.projection); |
| 205 | + } |
| 206 | + return true; |
| 207 | + }; |
| 208 | + |
| 209 | + var preWrite = function(conn) { |
| 210 | + var emitter = new events.EventEmitter(); |
| 211 | + listeners.put(listenerId, { |
| 212 | + id: listenerId, |
| 213 | + emitter: emitter, |
| 214 | + conn: conn, |
| 215 | + cqEmit: cqEmitFn |
| 216 | + }); |
| 217 | + }; |
| 218 | + |
| 219 | + var remote = futurePreWrite(transport, ctx, 0x25 |
| 220 | + , encodeListenerAdd, protocol.complete(protocol.hasSuccess) |
| 221 | + , listenerOpts, preWrite); |
| 222 | + |
| 223 | + return remote |
| 224 | + .then(function(success) { |
| 225 | + if (success) { |
| 226 | + var l = listeners.get(listenerId); |
| 227 | + var emitter = l.emitter; |
| 228 | + return { |
| 229 | + on: function(event, callback) { |
| 230 | + emitter.on(event, callback); |
| 231 | + if (buffering) { |
| 232 | + buffering = false; |
| 233 | + bufferedEvents.forEach(function(ev) { |
| 234 | + emitter.emit(ev.resultType, ev.key, ev.value, ev.projection); |
| 235 | + }); |
| 236 | + bufferedEvents = []; |
| 237 | + } |
| 238 | + return this; |
| 239 | + }, |
| 240 | + getListenerId: function() { return listenerId; } |
| 241 | + }; |
| 242 | + } |
| 243 | + listen.removeListeners(listenerId); |
| 244 | + return undefined; |
| 245 | + }) |
| 246 | + .catch(function(err) { |
| 247 | + listen.removeListeners(listenerId); |
| 248 | + throw err; |
| 249 | + }); |
| 250 | + }, |
155 | 251 | }; |
| 252 | + |
| 253 | + /** |
| 254 | + * Build CQ factory params from query and named params. |
| 255 | + * @param {string} queryString Ickle query. |
| 256 | + * @param {Object} [params] Named parameter map. |
| 257 | + * @returns {Buffer[]} Array of wrapped param byte arrays. |
| 258 | + */ |
| 259 | + function buildCQParams(queryString, params) { |
| 260 | + var result = [codec.wrapScalar(queryString)]; |
| 261 | + if (f.existy(params)) { |
| 262 | + _.each(params, function(value, name) { |
| 263 | + result.push(codec.wrapScalar(name)); |
| 264 | + result.push(codec.wrapScalar(value)); |
| 265 | + }); |
| 266 | + } |
| 267 | + return result; |
| 268 | + } |
| 269 | + |
156 | 270 | return listen; |
157 | 271 | } |
158 | 272 |
|
|
0 commit comments