__worker.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. /* This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
  2. * Copyright 2011 by the AToMPM team and licensed under the LGPL
  3. * See COPYING.lesser and README.md in the root of this project for full details
  4. */
  5. /* NOTES:
  6. because of the *asynchronous* nature of numerous operations in our system,
  7. brought about by client requests coming to csworkers who asynchronously
  8. forward them to asworkers that asynchronously return changelogs to subscribed
  9. csworkers who later asynchronously return their own changelogs to subscribed
  10. clients, CONCURRENCY CONTROL is needed to ensure weird interleavings of
  11. operations and/or responses don't bring the system (either *worker or the
  12. client) into incoherent or undesirable states...
  13. our approach is inspired by TCP and Readers-Writer locks :
  14. 1. 'read' requests increment the number of readers (read requests are
  15. either GETs or have uris that start with "/GET/")
  16. 2. 'write' requests increment the number of writers
  17. 3. locking read requests also read-lock this *worker (the only such query
  18. is 'POST /GET/batchRead')
  19. 4. locking write requests also write-lock this *worker (there is currently
  20. no such query... see *)
  21. 5. before processing incoming requests, onmessage() calls __canProceed()..
  22. this method succeeds when the current configuration of readers, writers
  23. and locks allows the incoming query to run immediately... otherwise,
  24. (e.g., if there is more than one reader or writer and a write lock is
  25. needed, or if the request queue is not-empty), this method fails, which
  26. triggers the queueing of the incoming request for handling asap... this
  27. essentially buys us a big 'synchronize' block around operations that
  28. change the state of _mmmk (and its journal), operations which are
  29. meant to be atomic...
  30. 6. individual requests that make up batchEdits bypass __canProceed():
  31. these may need to write to a *worker that the initial 'POST /batchEdit'
  32. locked... specifying a valid 'backstagePass' in the uri allows them to
  33. skip over __canProceed()
  34. 7. other than possible delays in query handling, queriers (be they clients
  35. or csworkers) are oblivious to the whole locking scheme... they are not
  36. expected to ask for locks (these are granted automatically) or to
  37. explicitly release them (locks and increments to reader/writer counts
  38. produced by a request are appropriately 'adjusted' upon receiving a
  39. response to the said request)
  40. *. although one might think that 'POST /batchEdit' requests would write-
  41. lock this *worker, they don't... instead, they read-lock this *worker
  42. and increment the number of writers... this has 2 side-effects
  43. a) no one can write to this *worker during a batchEdit
  44. b) people can still read from this *worker... thus, the
  45. 'intermediate' state of the system is visible to all
  46. this is important since effects of batchEdits may include the re-
  47. evaluation of VisualObject mapping functions... if the asworker were to
  48. reject such reads, the user would need to 'refresh' after each
  49. batchEdit to sync up his icons... this all stems from the fact that we
  50. preferred not to 'catch/queue' changelogs emitted by the asworker
  51. during batchEdits
  52. TBI:
  53. perhaps the most important point to improve on here is that locking
  54. queries could be made to lock individual objects (e.g., one or more AS
  55. node) rather than the entire *worker... this is not necessarily difficult
  56. to implement: before accessing any object for reading or writing in
  57. _mmmk, check if its locked and/or lock it
  58. the use of our verbose URIs in HTTP requests is mostly useful for debugging
  59. and to enhance the RESTful feel of our HTTP exchanges: in most cases, IDs are
  60. sufficient to uniquely identify and refer to nodes, and their associated URIs
  61. can easily be computed... this fact, as well as performance concerns (e.g.,
  62. minimizing bandwith needs and string matching) is behind our decision not to
  63. 'URIZE' changelogs sent from asworker to csworker, i.e., changelogs refer to
  64. nodes by ID rather than by URI... although this is acceptable in the backend,
  65. referring to nodes by ID in client-bound changelogs is not... thus, before
  66. sending changelogs to the client, any IDs they may contain are replaced by
  67. URIs (via __urizeChangelog())
  68. TBI:
  69. eventually, it may be possible to entirely strip out the sequence
  70. numbering mechanism... this is contingent (at least) on the WebSocket
  71. protocol and its implementations guaranteeing that messages are always
  72. delivered in order
  73. supporting ATOMIC UNDO/REDO OF BATCHEDITS requires that we keep track of
  74. which operations happened as part of the same batchEdit... this can get hairy
  75. in several cases, e.g., when:
  76. 1. batched requests are individually piped through the csworker to the
  77. asworker who thus has no way of knowing it should remember they're
  78. batched
  79. 2. certain requests are handled by the csworker while others are forwarded
  80. to the asworker which prevents any of the workers of really knowing
  81. which are the first and last requests in the batchEdit
  82. to address this, we place easily identifiable user-checkpoints in both the
  83. asworker and csworker journals at the start and end of any batchEdit via POST
  84. /batchCheckpoint (in practice, these are handled by asworkers who report
  85. setting these checkpoints via changelogs such that all subscribed csworkers
  86. can set them as well)... given that possibly dispersed and/or delocalized
  87. operations now all reside between identically named user-checkpoints on both
  88. asworker and csworker, we can undo/redo them atomically on both workers by
  89. undoing/redoing everything between the start and end checkpoints... this
  90. special undo/redo behavior is implemented in csworker.__undoredo (see its
  91. comments for details)
  92. it is assumed that csworker, asworker, mmmk and libmt are only ever imported
  93. from this file and as such inherit all of its imported libraries */
  94. /**************************** LIBRARIES and GLOBALS ****************************/
  95. var _util = require('util'),
  96. _path = require('path'),
  97. _http = require('http'),
  98. _do = require('./___do'),
  99. _fs = _do.convert(require('fs'), ['readFile', 'writeFile', 'readdir']),
  100. _fspp = _do.convert(require('./___fs++'), ['mkdirs']),
  101. _siocl = require('socket.io-client'),
  102. _utils = require('./utils'),
  103. _styleinfo = require('./styleinfo'),
  104. _svg = require('./libsvg').SVG,
  105. _wlib,
  106. _mmmk,
  107. _mt,
  108. _plugins,
  109. __wtype;
  110. //have worker id global so that workers can detect it when loaded
  111. global.__wid = null;
  112. var keepaliveAgent = new _http.Agent({keepAlive: true, maxSockets: 10, maxFreeSockets: 5}); // proposed by yentl to improve performance
  113. /*********************************** UTILS ************************************/
  114. /***************************** BASIC CONTINUABLES *****************************/
  115. /* return a failure continuable */
  116. function __errorContinuable(err)
  117. {
  118. return function(callback,errback) {errback(err);};
  119. }
  120. /* return a success continuable */
  121. function __successContinuable(arg)
  122. {
  123. return function(callback,errback) {callback(arg);};
  124. }
  125. /******************************* HTTP REQUESTS ********************************/
  126. /* make an HTTP request to 127.0.0.1:port */
  127. function __httpReq(method,url,data,port)
  128. {
  129. if( port == undefined )
  130. port = 8124;
  131. return function(callback,errback)
  132. {
  133. var options = {'port': port, 'path': url, 'method': method, 'agent': keepaliveAgent}; // agent proposed by yentl to improve performance
  134. if( data != undefined )
  135. {
  136. data = _utils.jsons(data);
  137. options['headers'] = {'Content-Length':unescape(encodeURIComponent(data)).length,
  138. 'Access-Control-Allow-Origin': '*'};
  139. }
  140. var request =
  141. _http.request(options,
  142. function(resp)
  143. {
  144. var resp_data = '';
  145. resp.on('data', function(chunk) {resp_data += chunk;});
  146. resp.on('end',
  147. function()
  148. {
  149. if( _utils.isHttpSuccessCode(resp.statusCode) )
  150. callback(resp_data);
  151. else
  152. errback(resp.statusCode+':'+resp_data);
  153. });
  154. });
  155. request.on('error',
  156. function(err)
  157. {
  158. errback('HTTP request ('+method+' '+url+':'+port+') '+
  159. 'failed on ::\n'+err);
  160. });
  161. request.end(data);
  162. };
  163. }
  164. /* make an http request to a *worker... this is basically just a wrapper than
  165. takes into account the fact that *workers respond data in respData.data and
  166. sometimes include sequence numbers in respData.sequence# */
  167. function __wHttpReq(method,url,data,port)
  168. {
  169. return function(callback,errback)
  170. {
  171. __httpReq(method,url,data,port)(
  172. function(respData)
  173. {
  174. respData = eval('('+respData+')');
  175. respData =
  176. (respData == undefined ||
  177. respData['sequence#'] != undefined ?
  178. respData :
  179. respData.data);
  180. callback(respData);
  181. },
  182. function(respData) {errback(respData);}
  183. );
  184. };
  185. }
  186. /******************************* URI PROCESSING *******************************/
  187. /* optimize __id_to_uri() by remembering computed mappings */
  188. var __ids2uris = {};
  189. /* try to construct a uri from an instance id */
  190. function __id_to_uri(id)
  191. {
  192. if( id == undefined )
  193. return undefined;
  194. else if( id in __ids2uris )
  195. return __ids2uris[id];
  196. else if( (res =_mmmk.read(id))['$err'] )
  197. return res;
  198. var uri = _utils.jsonp(res)['$type']+'/'+id+'.instance';
  199. __ids2uris[id] = uri;
  200. return uri;
  201. }
  202. /* try to extract an instance id from a uri */
  203. function __uri_to_id(uri)
  204. {
  205. var matches = uri.match(/.*\/(.*).instance/);
  206. if( matches != null )
  207. return matches[1];
  208. return {'$err':'bad instance uri :: '+uri};
  209. }
  210. /* replace ids in the given changelog by corresponding uris... see above NOTES
  211. on IDs vs. URIs for more on this
  212. NOTE:: when RESETM steps are encountered, we additionaly flush all currently
  213. remembered id-to-uri mappings */
  214. function __urizeChangelog(chlog)
  215. {
  216. chlog.forEach(
  217. function(step)
  218. {
  219. if( step['op'] == 'RESETM' )
  220. {
  221. __ids2uris = {};
  222. var newModel = _utils.jsonp(step['new_model']);
  223. for( var id in newModel.nodes )
  224. {
  225. newModel.nodes[__id_to_uri(id)] = newModel.nodes[id];
  226. delete newModel.nodes[id];
  227. }
  228. step['new_model'] = _utils.jsons(newModel);
  229. }
  230. else
  231. ['id','id1','id2'].forEach(
  232. function(x)
  233. {
  234. if( x in step )
  235. step[x] = __id_to_uri(step[x]);
  236. });
  237. });
  238. }
  239. /****************************** POST MESSAGE... *******************************/
  240. /* wrapper for : 400 Bad Request Syntax */
  241. function __postBadReqErrorMsg(respIndex,reason)
  242. {
  243. __postErrorMessage(respIndex,400,reason);
  244. }
  245. /* wrapper for all error messages */
  246. function __postErrorMessage(respIndex,statusCode,reason)
  247. {
  248. __postMessage(
  249. {'statusCode':statusCode,
  250. 'reason':reason,
  251. 'respIndex':respIndex});
  252. }
  253. /* wrapper for : 403 Forbidden */
  254. function __postForbiddenErrorMsg(respIndex,reason)
  255. {
  256. __postErrorMessage(respIndex,403,reason);
  257. }
  258. /* wrapper for : 500 Internal Server Error */
  259. function __postInternalErrorMsg(respIndex,reason)
  260. {
  261. __postErrorMessage(respIndex,500,reason);
  262. }
  263. /* wrapper for all messages */
  264. function __postMessage(msg)
  265. {
  266. console.error("w#"+__wid+" << ("+msg.respIndex+") "+msg.statusCode+" "+
  267. (msg.reason ||
  268. (typeof msg.data == 'object' ?
  269. _utils.jsons(msg.data) :
  270. msg.data)));
  271. //make sure that reason is a string
  272. if (typeof msg.reason == 'object'){
  273. msg.reason = _utils.jsons(msg.reason);
  274. }
  275. if( 'respIndex' in msg )
  276. __onRequestResponse(msg.respIndex);
  277. if( __wtype == '/csworker' && 'changelog' in msg )
  278. __urizeChangelog(msg['changelog']);
  279. process.send(msg);
  280. }
  281. /* wrapper for : 501 Not Implemented */
  282. function __postUnsupportedErrorMsg(respIndex)
  283. {
  284. __postErrorMessage(respIndex,501);
  285. }
  286. /********************************** LOCKING ***********************************/
  287. var __wLocked = false,
  288. __rLocks = 0,
  289. __numWriters = 0,
  290. __numReaders = 0,
  291. __reqs2lockInfo = {},
  292. __reqQueue = [],
  293. __NO_LOCK = 0,
  294. __LOCK = 1,
  295. __WLOCK = __LOCK | 2,
  296. __RLOCK = __LOCK | 4;
  297. /* determine whether this worker can proceed with the specified request given
  298. its current readers/writers/locks/queue... returns false if the worker can't
  299. proceed... otherwise, grants needed locks, increments number of readers/
  300. writers, and returns true
  301. NOTE:: the 'ignoreQueue' parameter disables queue-emptyness as a condition
  302. for this function's success */
  303. function __canProceed(method,uri,respIndex,ignoreQueue)
  304. {
  305. function __isRead(method,uri)
  306. {
  307. /* returns true if request is a read */
  308. return (method == 'GET' || uri.match(/^\/GET\//));
  309. }
  310. function __needsLock(method,uri)
  311. {
  312. /* returns lock type needed by request */
  313. if( method == 'POST' && uri.match(/^batch/) )
  314. return __RLOCK;
  315. return __NO_LOCK;
  316. }
  317. var isReader = __isRead(method,uri),
  318. needsLock = __needsLock(method,uri);
  319. /* disallow concurrent writes and queue if queue (see NOTES above) */
  320. if( (!isReader && __numWriters > 0) ||
  321. (!ignoreQueue && __reqQueue.length > 0) )
  322. return false;
  323. /* check current locks */
  324. if( __wLocked ||
  325. (__rLocks > 0 && !isReader) ||
  326. (__numReaders > 0 && (needsLock & __WLOCK)) ||
  327. (__numWriters > 0 && (needsLock & __LOCK)) )
  328. return false;
  329. /* access granted... */
  330. if( needsLock & __RLOCK )
  331. __rLocks++;
  332. else if( needsLock & __WLOCK )
  333. __wLocked = true;
  334. if( isReader )
  335. __numReaders++;
  336. else
  337. __numWriters++;
  338. __reqs2lockInfo[respIndex] = {'isReader':isReader,'needsLock':needsLock};
  339. return true;
  340. }
  341. /* unlock this *worker (if request had locked it), decrement number of readers/
  342. writers, and launch queued requests, if any... ignore requests that have no
  343. entry in __reqs2lockInfo (i.e., requests with backstage passes) */
  344. function __onRequestResponse(respIndex)
  345. {
  346. if( (li = __reqs2lockInfo[respIndex]) == undefined )
  347. return;
  348. if( li['needsLock'] & __RLOCK )
  349. __rLocks = Math.max(--__rLocks,0);
  350. else if( li['needsLock'] & __WLOCK )
  351. __wLocked = false;
  352. if( li['isReader'])
  353. __numReaders = Math.max(--__numReaders,0);
  354. else
  355. __numWriters = Math.max(--__numWriters,0);
  356. __runQueuedRequests();
  357. }
  358. /* run proceedable queued requests in FIFO order until a non-proceedable request
  359. is encountered...
  360. NOTE:: this function doesn't wait for request responses, if all queued
  361. responses can run concurrently (e.g., all reads), it will launch them
  362. all s.t. they can all be handled in parallel */
  363. function __runQueuedRequests()
  364. {
  365. if( __reqQueue.length > 0 )
  366. {
  367. var head = __reqQueue[0],
  368. uri = head['uri'],
  369. method = head['method'],
  370. reqData = head['reqData'],
  371. respIndex = head['respIndex'];
  372. if( __canProceed(method,uri,respIndex,true) )
  373. {
  374. __reqQueue.shift();
  375. __handleClientRequest(uri,method,reqData,respIndex);
  376. __runQueuedRequests();
  377. }
  378. }
  379. }
  380. /* push given request onto request queue for future handling */
  381. function __queueRequest(uri,method,reqData,respIndex)
  382. {
  383. __reqQueue.push(
  384. {'uri':uri,
  385. 'method':method,
  386. 'reqData':reqData,
  387. 'respIndex':respIndex});
  388. }
  389. /****************************** SEQUENCE NUMBERS ******************************/
  390. var __nextSequenceNumber = 0;
  391. function __sequenceNumber(inc)
  392. {
  393. if( inc == undefined || inc == 1 )
  394. inc = 1;
  395. else if( inc != 0 )
  396. throw '__sequenceNumber increment must be 0, 1 or undefined';
  397. return __wtype+'#'+(__nextSequenceNumber+=inc);
  398. }
  399. function __batchCheckpoint(id,start)
  400. {
  401. return 'bchkpt@'+id+(start ? '>>' : '<<');
  402. }
  403. process.on('message',
  404. function(msg)
  405. {
  406. console.error(">> "+JSON.stringify(msg));
  407. /* parse msg */
  408. var uri = msg['uri'],
  409. method = msg['method'],
  410. uriData = msg['uriData'],
  411. reqData = msg['reqData'],
  412. respIndex = msg['respIndex'];
  413. /* initial setup */
  414. if( _wlib == undefined )
  415. {
  416. /** enable/disable debugging messages **/
  417. console.error = function() {};
  418. __wtype = msg['workerType'];
  419. __wid = msg['workerId'];
  420. if (__wtype == "/asworker") {
  421. _wlib = require("./asworker");
  422. }else if (__wtype == "/csworker") {
  423. _wlib = require("./csworker");
  424. }else {
  425. throw "Error! Unknown worker type: " + __wtype;
  426. }
  427. _mmmk = require('./mmmk');
  428. _mt = require('./libmt');
  429. _plugins = {};
  430. _fs.readdirSync('./plugins').forEach(
  431. function(p)
  432. {
  433. try
  434. {
  435. if( ! p.match(/.*\.js$/) )
  436. return;
  437. //throw 'invalid plugin filename, see user\'s manual';
  438. p = p.match(/(.*)\.js$/)[1];
  439. _plugins[p] = require('./plugins/' + p);
  440. if( ! ('interfaces' in _plugins[p]) ||
  441. ! ('csworker' in _plugins[p]) ||
  442. ! ('asworker' in _plugins[p]) )
  443. throw 'invalid plugin specification, see user\'s manual';
  444. }
  445. catch(err)
  446. {
  447. _util.log('failed to load plugin ('+p+') on :: '+err);
  448. }
  449. });
  450. return;
  451. }
  452. /* concurrent access control */
  453. if( uriData != undefined && uriData['backstagePass'] != undefined )
  454. {
  455. if( uriData['backstagePass'] != __backstagePass )
  456. return __postErrorMessage(respIndex,401,'invalid backstage pass');
  457. }
  458. else if( ! __canProceed(method,uri,respIndex) )
  459. return __queueRequest(
  460. uri,
  461. method,
  462. (method == 'GET' ? uriData : reqData),
  463. respIndex);
  464. /* handle client requests
  465. POST <> create
  466. GET <> retrieve
  467. PUT <> update
  468. DELETE <> delete */
  469. __handleClientRequest(
  470. uri,
  471. method,
  472. (method == 'GET' ? uriData : reqData),
  473. respIndex);
  474. });
  475. /* handle a request described by the given parameters */
  476. function __handleClientRequest(uri,method,reqData,respIndex)
  477. {
  478. /********************** SHARED AS-CS WORKER BEHAVIOR ***********************/
  479. if( method == 'GET' && uri.match(/^\/current.model$/) )
  480. GET__current_model(respIndex);
  481. else if( method == 'GET' && uri.match(/^\/current.state$/) )
  482. GET__current_state(respIndex);
  483. else if( method == 'POST' && uri.match(/^\/GET\/batchRead$/) )
  484. POST_GET_batchread(respIndex,reqData);
  485. else if( method == 'POST' && uri.match(/^\/batchEdit$/) )
  486. POST_batchedit(respIndex,reqData);
  487. /********************* DISTINCT AS-CS WORKER BEHAVIOR **********************/
  488. else if( (method == 'DELETE' && uri.match(/\.metamodel$/)) ||
  489. (method == 'POST' && uri.match(/\.type$/)) ||
  490. (method == 'GET' && uri.match(/\.instance$/)) ||
  491. (method == 'PUT' && uri.match(/\.instance$/)) ||
  492. (method == 'DELETE' && uri.match(/\.instance$/)) ||
  493. (method == 'PUT' && uri.match(/\.instance.cs$/)) ||
  494. (method == 'PUT' && uri.match(/\.vobject$/)) ||
  495. (method == 'POST' && uri.match(/^\/GET\/.*\.mappings$/)) ||
  496. (method == 'PUT' && uri.match(/^\/GET\/.*\.metamodel$/)) ||
  497. (method == 'PUT' && uri.match(/^\/GET\/.*\.model$/)) )
  498. {
  499. var func = method+' *'+uri.match(/.*(\..*)$/)[1];
  500. if( _wlib[func] == undefined )
  501. return __postUnsupportedErrorMsg(respIndex);
  502. _wlib[func](respIndex,uri,reqData);
  503. }
  504. else if( (method == 'GET' && uri.match(/^\/internal.state$/)) ||
  505. (method == 'PUT' && uri.match(/^\/aswSubscription$/)) ||
  506. (method == 'PUT' && uri.match(/^\/current.metamodels$/)) ||
  507. (method == 'PUT' && uri.match(/^\/current.model$/)) ||
  508. (method == 'GET' && uri.match(/^\/validatem$/)) ||
  509. (method == 'POST' && uri.match(/^\/undo$/)) ||
  510. (method == 'POST' && uri.match(/^\/redo$/)) ||
  511. (method == 'PUT' && uri.match(/^\/GET\/console$/)) ||
  512. (method == 'POST' && uri.match(/^\/batchCheckpoint$/)) )
  513. {
  514. var func = method+' '+uri;
  515. if( _wlib[func] == undefined )
  516. return __postUnsupportedErrorMsg(respIndex);
  517. _wlib[func](respIndex,uri,reqData);
  518. }
  519. else if( uri.match(/^\/__mt\/.*$/) )
  520. _wlib.mtwRequest(respIndex,method,uri,reqData);
  521. /* plugin request */
  522. else if( uri.match(/^\/plugins\/.*$/) )
  523. {
  524. var matches = uri.match(/^\/plugins\/(.*?)(\/.*)$/),
  525. plugin = matches[1],
  526. requrl = matches[2],
  527. self = this;
  528. if( ! (plugin in _plugins) ||
  529. ! _plugins[plugin].interfaces.some(
  530. function(ifc)
  531. {
  532. if( method == ifc.method &&
  533. ('url=' in ifc && ifc['url='] == requrl) ||
  534. ('urlm' in ifc && requrl.match(ifc['urlm'])) )
  535. {
  536. _plugins[plugin][__wtype.substring(1)](
  537. respIndex,
  538. method,
  539. uri,
  540. reqData,
  541. _wlib);
  542. return true;
  543. }
  544. }) )
  545. __postUnsupportedErrorMsg(respIndex);
  546. }
  547. /* unsupported request */
  548. else
  549. __postUnsupportedErrorMsg(respIndex);
  550. }
  551. /************************ SHARED AS-CS WORKER BEHAVIOR ************************/
  552. /* returns the current model to the querier
  553. 1. ask _mmmk for a copy of the current model
  554. 2. return said copy to the querier */
  555. function GET__current_model(resp)
  556. {
  557. if( (res = _mmmk.read())['$err'] )
  558. __postInternalErrorMsg(resp,res['$err']);
  559. else
  560. __postMessage(
  561. {'statusCode':200,
  562. 'data':res,
  563. 'sequence#':__sequenceNumber(0),
  564. 'respIndex':resp});
  565. }
  566. /* returns the current 'state' of this *worker's _mmmk (i.e., its model,
  567. loaded metamodels, current sequence#, and next expected sequence#, if any)
  568. to the querier
  569. 1. ask _mmmk for a copy of its model, loaded metamodels and name
  570. 2. return said copies to the querier */
  571. function GET__current_state(resp)
  572. {
  573. if( (mms = _mmmk.readMetamodels())['$err'] )
  574. __postInternalErrorMsg(resp,mms['$err']);
  575. else if( (m = _mmmk.read())['$err'] )
  576. __postInternalErrorMsg(resp,m['$err']);
  577. else
  578. __postMessage(
  579. {'statusCode':200,
  580. 'data':{'mms':mms,
  581. 'm':m,
  582. 'name':_mmmk.readName(),
  583. 'asn':_wlib['__nextASWSequenceNumber'],
  584. 'asw':_wlib['__aswid']},
  585. 'sequence#':__sequenceNumber(0),
  586. 'respIndex':resp});
  587. }
  588. /* returns an array containing the results of a number of bundled read
  589. requests */
  590. function POST_GET_batchread(resp,reqData)
  591. {
  592. var actions = [__successContinuable()],
  593. results = [];
  594. reqData.forEach(
  595. function(r)
  596. {
  597. actions.push(
  598. function()
  599. {
  600. return __wHttpReq(r['method'],r['uri']+'?wid='+__wid);
  601. },
  602. function(res)
  603. {
  604. results.push(res);
  605. return __successContinuable();
  606. });
  607. });
  608. _do.chain(actions)(
  609. function()
  610. {
  611. __postMessage(
  612. {'statusCode':200,
  613. 'data':{'results':results},
  614. 'sequence#':__sequenceNumber(0),
  615. 'respIndex':resp});
  616. },
  617. function(err) {__postInternalErrorMsg(resp,err);}
  618. );
  619. }
  620. /* returns an array containing the results of a number of bundled edit
  621. requests (these results are mostly just statusCodes)... if any of the
  622. requests fail, every preceding request is undone (this is facilitated by
  623. setting a user-checkpoint before beginning)
  624. NOTE: requests may refer to the results of previously completed requests
  625. in their uri and reqData : all occurrences of '$i$' are replaced by
  626. the result of request #i
  627. NOTE: to enable undoing/redoing batchEdits atomically, easily identifiable
  628. user-checkpoints are set before performing any of the batched requests
  629. and after they've all been completed... more on this in NOTES above
  630. NOTE: nested batchEdits are not supported */
  631. function POST_batchedit(resp,reqData)
  632. {
  633. for( var i in reqData )
  634. if( reqData[i]['method'] == 'POST' &&
  635. reqData[i]['uri'].match(/^\/batchEdit$/) )
  636. return __postBadReqErrorMsg(
  637. 'nested batchEdit requests are not supported');
  638. var results = [],
  639. currtime = Date.now(),
  640. startchkpt = __batchCheckpoint(currtime,true),
  641. endchkpt = __batchCheckpoint(currtime),
  642. setbchkpt =
  643. function(name)
  644. {
  645. return function()
  646. {
  647. __backstagePass = Math.random();
  648. return __wHttpReq(
  649. 'POST',
  650. '/batchCheckpoint?wid='+__wid+
  651. '&backstagePass='+__backstagePass,
  652. {'name':name});
  653. };
  654. },
  655. actions = [__successContinuable(), setbchkpt(startchkpt)];
  656. reqData.forEach(
  657. function(r)
  658. {
  659. actions.push(
  660. function()
  661. {
  662. __backstagePass = Math.random();
  663. var replace = function(s,p1) {return results[p1]['data'];},
  664. uri = r['uri'].replace(/\$(\d+)\$/g,replace);
  665. if( r['reqData'] != undefined )
  666. var reqData =
  667. _utils.jsonp(
  668. _utils.jsons(r['reqData']).
  669. replace(/\$(\d+)\$/g,replace) );
  670. return __wHttpReq(
  671. r['method'],
  672. uri+'?wid='+__wid+
  673. '&backstagePass='+__backstagePass,
  674. reqData);
  675. },
  676. function(res)
  677. {
  678. results.push(res);
  679. return __successContinuable();
  680. });
  681. });
  682. actions.push(setbchkpt(endchkpt));
  683. _do.chain(actions)(
  684. function()
  685. {
  686. __backstagePass = undefined;
  687. __postMessage(
  688. {'statusCode':200,
  689. 'data':{'results':results},
  690. 'sequence#':__sequenceNumber(0),
  691. 'respIndex':resp});
  692. },
  693. function(err)
  694. {
  695. var undoActions =
  696. [__successContinuable(),
  697. function()
  698. {
  699. if( results.length == 0 )
  700. return __successContinuable();
  701. return __wHttpReq(
  702. 'POST',
  703. '/undo?wid='+__wid+'&backstagePass='+__backstagePass,
  704. {'undoUntil':startchkpt,
  705. 'hitchhiker':{'undo':startchkpt}});
  706. }];
  707. _do.chain(undoActions)(
  708. function()
  709. {
  710. __backstagePass = undefined;
  711. __postInternalErrorMsg(resp,err);
  712. },
  713. function(undoErr)
  714. {
  715. __backstagePass = undefined;
  716. __postInternalErrorMsg(
  717. resp,
  718. 'unexpected error occured on rollback :: '+undoErr);
  719. }
  720. );
  721. }
  722. );
  723. }
  724. //required so that csworker has access to these variables
  725. function get__ids2uris(){
  726. return __ids2uris;
  727. }
  728. function set__ids2uris(new__ids2uris){
  729. __ids2uris = new__ids2uris;
  730. }
  731. function get__nextSequenceNumber(){
  732. return get__nextSequenceNumber;
  733. }
  734. function set__nextSequenceNumber(new__nextSequenceNumber){
  735. __nextSequenceNumber = new__nextSequenceNumber;
  736. }
  737. function get__wtype(){
  738. return __wtype;
  739. }
  740. module.exports = {
  741. __errorContinuable,
  742. __successContinuable,
  743. __httpReq,
  744. __wHttpReq,
  745. __postInternalErrorMsg,
  746. __postForbiddenErrorMsg,
  747. __postBadReqErrorMsg,
  748. __sequenceNumber,
  749. __postMessage,
  750. __uri_to_id,
  751. __id_to_uri,
  752. __batchCheckpoint,
  753. GET__current_state,
  754. //GLOBAL VARS
  755. get__ids2uris,
  756. set__ids2uris,
  757. get__nextSequenceNumber,
  758. set__nextSequenceNumber,
  759. get__wtype,
  760. };