csworker.js 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303
  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. it is assumed that csworker _mmmk cud operations NEVER FAIL...
  7. 1. IMPLICATIONS
  8. a) we don't check for failure
  9. b) if failure were to occur, we wouldn't rollback the associated
  10. asworker perations
  11. c) *Icons.metamodels should NOT support actions, constraints or
  12. multiplicities to ensure neither of these block cud operations...
  13. they do however support parsing functions (designer code that
  14. translates CS updates into AS operations) which are somewhat akin to
  15. pre-edit actions: if the parsing function or the AS operations fail,
  16. the CS update fails
  17. 2. REASONING
  18. a) if we allowed csworker to rollback asworker operations, it would be
  19. a natural extension for csworkers to be allowed to fail and "respond
  20. negatively" to pushed changelogs and request asworker rollbacks...
  21. this might cause severe user experience problems in collaboration
  22. scenarios (e.g., A's changes could be repeatedly undone by failures
  23. produced by B's csworker)
  24. b) it doesn't make sense for logic that could overturn AS cud
  25. operations to live anywhere else than in the AS spec
  26. csworker cud operations are CHANGELOG-DRIVEN... in other words, when a client
  27. makes PUT/POST/DELETE requests to a csworker, these are translated into
  28. appropriate requests for an asworker...
  29. 1. on success, the asworker
  30. a) returns a 20x status code to the csworker who made the request, in
  31. turn, it forwards it to the client who made the request
  32. b) returns a changelog that describes the AS impacts of the request...
  33. this changelog is pushed to all of its csworker subscribers
  34. (including the one who made the request) and which point their
  35. internal CS models are adjusted to reflect AS changes... finally,
  36. the result of these CS model modifications are themselves bundled
  37. into changelogs that are forwarded to all subscribed clients
  38. (subscription managment is performed in httpwsd.js)
  39. 2. on failure, the asworker
  40. a) returns a 40x|50x status code to the csworker...
  41. so basically, when a client asks a csworker to change something, all that
  42. csworker does is forward the request to its asworker... if and when the
  43. csworker does comply, it will be in response to an asworker changelog
  44. received sometime after it has responded a 20x status code to the client
  45. 'HITCHHIKERS' allow subscriber information exchange... for instance, if A
  46. loads SC.purpleIcons.metamodel, we'd like for all csworkers subscribed to A's
  47. associated asworker to be told (1) that their asworker has loaded
  48. SC.metamodel *and* (2) that they should load SC.purpleIcons.metamodel... with
  49. hitchhikers, 'subscriber-relevant' but 'worker-irrelevant' data is sent to
  50. workers so that they may push it back to all of their subscribers upon
  51. returning
  52. technically, asworkers and csworkers could be run on DISTINCT MACHINES...
  53. however, for the moment, this is neither supported nor recommended:
  54. 1. csworkers as they are now are not robust to asworker failures caused by
  55. timeouts and/or other network problems
  56. 2. requests to asworkers would need to be prepended with the actual url of
  57. wherever the asworker is being served from
  58. there are at least 3 alternatives for EVALUATING A MAPPING FUNCTION
  59. 1. retrieve the full AS model and run the mapping function within this
  60. csworker within some scope where the said model is accessible via the
  61. designer API (ala. _mmmk.__runDesignerCode())
  62. + RESTful requests to asworker
  63. - possibly very inefficient to transmit AS model
  64. 2. run the mapping function within this csworker within some scope where
  65. calls to getAttr() are translated to REST that retrieve desired
  66. information from the asworker
  67. + RESTful requests to asworker
  68. - possibly numerous queries
  69. 3. send mapping function to asworker and run it there
  70. - non-RESTful request
  71. + most efficient in terms of data traffic
  72. we chose the 3rd approach for its efficiency... furthermore, when
  73. regenerating icons (who may contain numerous VisualObjects with numerous
  74. coded attributes), to avoid having to send many queries to the asworker
  75. (i.e., one for each mappingf), we bundle together all of the icon's mappingfs
  76. and send them to the asworker for evaluation in a single query
  77. CREATING AN ICON AT A SPECIFIC POSITION brings up a few issues in the context
  78. where icon position plays a role in AS attribute values... consider this
  79. scenario:
  80. 1. user creates a BuildingIcon at (500,600)
  81. 2. request is received by csworker and forwarded to asworker
  82. 3. asworker creates a Building and sets abstract attribute 'address' to
  83. metamodel-specified default (0,0)
  84. 4. later, __applyASWChanges() receives MKNODE
  85. a) it creates a BuildingIcon
  86. b) sets its 'position' to (500,600)
  87. c) calls __regenIcon() who determines that 'position' should be (0,0)
  88. after mapping AS attribute 'address'
  89. d) emits changelog instructing client to create a BuildingIcon at (0,0)
  90. the core issue here is that step 4b) is a hack... instead of 'position' being
  91. set via 'PUT *.cs' which would have parsed 'position', appropriately updated
  92. 'address', and only later -- via AS changelog -- mapped 'address' and updated
  93. 'position', step 4b) bypasses this whole pipeline... this causes CS and AS to
  94. be out-of-sync, which in turn causes the behaviour described for step 4c)...
  95. to address this issue, during step 2 we perform a task similar to that of
  96. 'PUT *.cs'... step 2 from the above scenario thus becomes:
  97. 0. request is received by csworker
  98. A. retrieve the BuildingIcon's parser
  99. B. create a dummy context where a *Icon's parser can be run... within this
  100. context, 'orientation' and 'scale' are set to their defaults, but
  101. 'position' is set to (500,600)
  102. C. run the BuildingIcon's parser within this dummy context, this could
  103. yield {'address':(5,6)}
  104. D. forwarded creation request to asworker *and* bundle the result from
  105. step C.
  106. step 3 is also slightly changed: the asworker creates a new Building *and*
  107. updates any specified attributes, in our example, 'address' would be set to
  108. (5,6)... step 4 is left unchanged but step 4c) no longer causes any problems
  109. because __regenIcon's mapping of 'address' will return (500,600)
  110. supporting undo/redo requires REMEMBERING HITCHHIKERS... __applyASWChanges()
  111. sometimes expects asworker changelogs to be bundled with hitchhikers... this
  112. is the case when changelogs are the result of 'normal' requests getting
  113. forwarded to the asworker by the csworker... however, for undos/redos, no
  114. hitchhikers are bundled and as such, when __applyASWChanges() is called as a
  115. result of undos and redos on the asworker, required information that would
  116. normally be in hitchhikers is missing... to address this, we 'remember'
  117. hitchhikers as we encounter them like so:
  118. 1. MKNODE hitchhikers are remembered by asid
  119. 2. LOADMM hitchhikers are remembered by asmm
  120. 3. RESETM hitchhikers are remembered by name *
  121. * for this case, we also create and remember a hitchhiker to enable
  122. undoing loading a model over an unsaved non-empty model
  123. long story short, when handling asworker changelogs, missing hitchhikers, if
  124. any, are retrieved from the __hitchhikerJournal
  125. supporting FULL UNDO/REDO in our distributed environment presents a few
  126. challenges
  127. 1. operations with no AS implications should be undone/redone by csworkers
  128. 2. operations with AS implications should be undone/redone by asworkers
  129. *but* resulting changelogs should be handled specially to ensure that
  130. csworkers undo/redo in response to asworer undo/redos
  131. challenge 1 requires means to determine what kind of operation the client
  132. wishes to undo/redo... we addressed this by logging handled sequence#s (via
  133. __checkpointUserOperation()... when an undo/redo request is received, the
  134. current sequence# to undo/redo dictates whether we're in case 1 or 2.
  135. challenge 2 requires means to determine whether or not an asworker changelog
  136. pertains to an undo/redo operation *and*, which csworker operations to undo/
  137. redo in response to an asworker undo/redo... we addressed this in 3 parts
  138. 1. when DOing something in response to asworker changelogs, the csworker
  139. sets a user-checkpoint (named after the asworker sequence#) in its
  140. journal
  141. 2. when forwarding undo/redo requests to asworkers, the asworker sequence#
  142. to undo/redo is bundled in a hitchhiker
  143. 3. when an asworker changelog has a bundled undo/redo hitchhiker, the
  144. csworker handles it by undoing/redoing all of the changes the bundled
  145. asworker sequence# had originally induced
  146. step 3 is paramount... if the csworker responded to undo/redos like it would
  147. any other request (e.g., respond to RMNODE by RMNODE), it would become out of
  148. sync with the asworker... see example below:
  149. 1. client creates A/0, AIcon/0
  150. 2. client moves AIcon/0
  151. 3. client undoes move (csworker only, OK)
  152. 4. client undoes create (would trigger RMNODE A/0, AIcon/0)
  153. 5. client redoes create (would trigger MKNODE A/0, AIcon/1)
  154. 6. client redoes move (will fail because AIcon/0 doesn't exist)
  155. in short, proper undo/redo requires that operations resultings from undo/redo
  156. be distinguishable from normal ones
  157. TBI:: undoing/redoing SYSOUT-only changelogs has no perceptible effect from
  158. client... one inconvenient side-effect of this is that rules require 2
  159. undos/redos to undo/redo: 1 to undo/redo the rule, 1 to undo/redo the
  160. SYSOUT message announcing the launching of the rule... a sensible and
  161. nice solution would be not to remember such changelogs in
  162. __handledSeqNums */
  163. const {
  164. __batchCheckpoint,
  165. __errorContinuable,
  166. GET__current_state,
  167. get__ids2uris,
  168. set__ids2uris,
  169. get__nextSequenceNumber,
  170. set__nextSequenceNumber,
  171. get__wtype,
  172. __httpReq,
  173. __id_to_uri,
  174. __wHttpReq,
  175. __postInternalErrorMsg, __postMessage,
  176. __postBadReqErrorMsg, __postForbiddenErrorMsg,
  177. __sequenceNumber,
  178. __successContinuable,
  179. __uri_to_id
  180. } = require("./__worker");
  181. const _do = require("./___do");
  182. const _utils = require('./utils');
  183. const _mmmk = require("./mmmk");
  184. const _fs = _do.convert(require('fs'), ['readFile', 'writeFile', 'readdir']);
  185. const _path = require('path');
  186. const _fspp = _do.convert(require('./___fs++'), ['mkdirs']);
  187. const _svg = require('./libsvg').SVG;
  188. const _mt = require('./libmt');
  189. const _siocl = require('socket.io-client');
  190. module.exports = {
  191. '__REGEN_ICON_RETRY_DELAY_MS':200,
  192. '__asmm2csmm':{},
  193. '__asid2csid':{},
  194. '__aswid':undefined,
  195. '__handledSeqNums':{'i':undefined,'#s':[]},
  196. /*************************** ASWORKER INTERACTION **************************/
  197. /* apply asworker changes
  198. 0. check the changelog's sequence number to know if we should handle it
  199. now or later
  200. 1. iterate through the AS changelog setting up sync/async actions that
  201. appropriately modify the CS while accumulating CS changelogs (for
  202. pushing to subscribed clients)
  203. 2. launch sync/async action chain... on error, post error... on success,
  204. a) flatten the CS changelogs into a single changelog
  205. b) post message to server with flattened CS changelog, the server will
  206. then push it to subscribed clients
  207. c) apply next pending asworker changelog, if any and if applicable
  208. __nextASWSequenceNumber
  209. used to determine if a changelog is received out of order, and if a
  210. pending changelog is now ready to be handled
  211. __pendingChangelogs
  212. stores out or order changelogs until we're ready to handle them
  213. __hitchhikerJournal
  214. stores encountered hithchikers for future use (see NOTES above) */
  215. '__nextASWSequenceNumber':'/asworker#1',
  216. '__pendingChangelogs':[],
  217. '__hitchhikerJournal':{},
  218. '__applyASWChanges' :
  219. function(changelog,aswSequenceNumber,hitchhiker)
  220. {
  221. console.error('w#'+__wid+' ++ ('+aswSequenceNumber+') '+
  222. _utils.jsons(changelog));
  223. if( _utils.sn2int(aswSequenceNumber) >
  224. _utils.sn2int(this.__nextASWSequenceNumber) )
  225. {
  226. this.__pendingChangelogs.push(
  227. {'changelog':changelog,
  228. 'sequence#':aswSequenceNumber,
  229. 'hitchhiker':hitchhiker});
  230. var self = this;
  231. this.__pendingChangelogs.sort(
  232. function(a,b)
  233. {
  234. return self.__sn2int(a['sequence#']) -
  235. self.__sn2int(b['sequence#']);
  236. });
  237. return;
  238. }
  239. else if( _utils.sn2int(aswSequenceNumber) <
  240. _utils.sn2int(this.__nextASWSequenceNumber) )
  241. throw 'invalid changelog sequence#';
  242. var cschangelogs = [],
  243. cshitchhiker,
  244. actions = [__successContinuable()],
  245. self = this;
  246. /* special handling of undo/redo changelogs (see NOTES above) */
  247. if( hitchhiker && 'undo' in hitchhiker )
  248. cschangelogs.push(_mmmk.undo(hitchhiker['undo'])['changelog']);
  249. else if( hitchhiker && 'redo' in hitchhiker )
  250. cschangelogs.push(_mmmk.redo(hitchhiker['redo'])['changelog']);
  251. /* special handling of batchCheckpoint changelogs (see NOTES above) */
  252. else if( changelog.length == 1 && changelog[0]['op'] == 'MKBTCCHKPT' )
  253. this.__checkpointUserOperation(changelog[0]['name']);
  254. /* handle any other changelog */
  255. else
  256. {
  257. var manageHitchhiker =
  258. function(hhid,hh)
  259. {
  260. /* remember/restore a hitchhiker given specified id */
  261. if( hh )
  262. self.__hitchhikerJournal[hhid] = hh;
  263. else if( hitchhiker )
  264. self.__hitchhikerJournal[hhid] = hitchhiker;
  265. else
  266. hitchhiker = self.__hitchhikerJournal[hhid];
  267. };
  268. this.__checkpointUserOperation(aswSequenceNumber);
  269. changelog.forEach(
  270. function(step)
  271. {
  272. /* no legal connections exist between *Icon types, so we
  273. simply simulate the CS change such a [dis]connection would
  274. incur */
  275. if( step['op'] == 'MKEDGE' || step['op'] == 'RMEDGE' )
  276. actions.push(
  277. function()
  278. {
  279. var asid1 = step['id1'],
  280. asid2 = step['id2'],
  281. csid1 = self.__asid_to_csid(asid1),
  282. csid2 = self.__asid_to_csid(asid2);
  283. cschangelogs.push(
  284. [{'op':step['op'],'id1':csid1,'id2':csid2}]);
  285. return __successContinuable();
  286. });
  287. /* create appropriate CS instance and associate it with new AS
  288. instance (remember the association in __asid2csid to
  289. optimize future operations) */
  290. else if (step['op'] == 'MKNODE') {
  291. actions.push(
  292. function () {
  293. manageHitchhiker(step['id']);
  294. let asid = step['id'],
  295. node = _utils.jsonp(step['node']),
  296. isLink = ('segments' in hitchhiker),
  297. fullastype = node['$type'],
  298. fullcstype = self.__astype_to_cstype(
  299. fullastype,
  300. isLink),
  301. asuri = fullastype + '/' + asid + '.instance',
  302. attrs = {'$asuri': asuri};
  303. if ('pos' in hitchhiker)
  304. attrs['position'] = hitchhiker['pos'];
  305. else if ('neighborhood' in hitchhiker) {
  306. let nc = self.__nodesCenter(
  307. hitchhiker['neighborhood']);
  308. attrs['position'] =
  309. [(nc[0] || 200), (nc[1] || 200)];
  310. }
  311. else if ('clone' in hitchhiker)
  312. attrs = _utils.mergeDicts(
  313. [attrs, hitchhiker['clone']]);
  314. else
  315. attrs['position'] = [200, 200];
  316. let res = _mmmk.create(fullcstype, attrs),
  317. csid = res['id'];
  318. self.__asid2csid[asid] = csid;
  319. cschangelogs.push(res['changelog']);
  320. if (isLink) {
  321. let s = {},
  322. src =
  323. hitchhiker['src'] ||
  324. self.__asuri_to_csuri(hitchhiker['asSrc']),
  325. dest =
  326. hitchhiker['dest'] ||
  327. self.__asuri_to_csuri(hitchhiker['asDest']),
  328. segments =
  329. hitchhiker['segments'] ||
  330. self.__defaultSegments(src, dest);
  331. s[src + '--' + __id_to_uri(csid)] = segments[0];
  332. s[__id_to_uri(csid) + '--' + dest] = segments[1];
  333. cschangelogs.push(
  334. _mmmk.update(
  335. csid,
  336. {'$segments': s})['changelog'],
  337. self.__positionLinkDecorators(csid));
  338. }
  339. return self.__regenIcon(csid);
  340. },
  341. function (riChangelog) {
  342. cschangelogs.push(riChangelog);
  343. return __successContinuable();
  344. });
  345. }
  346. /* remove appropriate CS instance... update __asid2csid for it
  347. to remain consistent */
  348. else if( step['op'] == 'RMNODE' )
  349. actions.push(
  350. function()
  351. {
  352. var asid = step['id'],
  353. csid = self.__asid_to_csid(asid);
  354. cschangelogs.push(_mmmk['delete'](csid)['changelog']);
  355. delete self.__asid2csid[asid];
  356. return __successContinuable();
  357. });
  358. /* regenerate the icon to re-evaluate any coded attributes
  359. NOTE:: CS changes may be bundled (i.e., if an AS update was
  360. simulated by a CS update)... if so, perform them
  361. before regenerating the icon */
  362. else if( step['op'] == 'CHATTR' )
  363. actions.push(
  364. function()
  365. {
  366. var asid = step['id'],
  367. csid = self.__asid_to_csid(asid);
  368. if( hitchhiker && 'cschanges' in hitchhiker )
  369. {
  370. var cschanges = hitchhiker['cschanges'];
  371. cschangelogs.push(
  372. _mmmk.update(csid,cschanges)['changelog'],
  373. ('$segments' in cschanges ?
  374. self.__positionLinkDecorators(csid) :
  375. []));
  376. }
  377. return self.__regenIcon(csid);
  378. },
  379. function(riChangelog)
  380. {
  381. cschangelogs.push(riChangelog);
  382. return __successContinuable();
  383. });
  384. /* load appropriate CS metamodel (stored in hitchhiker)...
  385. remember AS-to-CS metamodel mapping in __asmm2csmm to
  386. optimize future operations */
  387. else if( step['op'] == 'LOADMM' )
  388. actions.push(
  389. function()
  390. {
  391. manageHitchhiker(step['name']);
  392. var asmm = step['name'],
  393. csmm = hitchhiker['name'],
  394. data = hitchhiker['csmm'];
  395. cschangelogs.push(
  396. _mmmk.loadMetamodel(csmm,data)['changelog'],
  397. {'op':'LOADASMM',
  398. 'name':asmm,
  399. 'mm':step['mm']});
  400. self.__asmm2csmm[asmm] = csmm;
  401. return __successContinuable();
  402. });
  403. /* unload appropriate CS metamodel... update __asmm2csmm for
  404. it to remain consistent */
  405. else if( step['op'] == 'DUMPMM' )
  406. actions.push(
  407. function()
  408. {
  409. var asmm = step['name'],
  410. csmm = self.__asmm2csmm[asmm];
  411. cschangelogs.push(_mmmk.unloadMetamodel(csmm)['changelog']);
  412. delete self.__asmm2csmm[asmm];
  413. return __successContinuable();
  414. });
  415. /* load appropriate CS model (stored in hitchhiker) and
  416. overwrite past hitchhiker associated to initial load of
  417. current model, if any... when step['insert'] is specified,
  418. adjust $asuris to compensate for offsetting of inserted asm
  419. and $segments to compensate for upcoming offsetting of to-
  420. be-inserted csm */
  421. else if (step['op'] == 'RESETM')
  422. actions.push(
  423. function () {
  424. manageHitchhiker(
  425. step['old_name'],
  426. {'csm': _mmmk.read()});
  427. manageHitchhiker(step['new_name']);
  428. var csm = hitchhiker['csm'];
  429. var _csm = eval('(' + csm + ')');
  430. if (step['insert']) {
  431. var asoffset = parseInt(step['insert']),
  432. csoffset = _mmmk.next_id,
  433. incUri =
  434. function (oldUri, offset) {
  435. var matches = oldUri.match(/(.+\/)(.+)(\.instance)/);
  436. return matches[1] +
  437. (parseInt(matches[2]) + offset) +
  438. matches[3];
  439. };
  440. for (var id in _csm.nodes) {
  441. _csm.nodes[id]['$asuri']['value'] =
  442. incUri(_csm.nodes[id]['$asuri']['value'],
  443. asoffset);
  444. if (!('$segments' in _csm.nodes[id]))
  445. continue;
  446. var segments =
  447. _csm.nodes[id]['$segments']['value'],
  448. _segments = {};
  449. for (var edgeId in segments) {
  450. var uris = edgeId.match(
  451. /^(.*\.instance)--(.*\.instance)$/);
  452. _segments[incUri(uris[1], csoffset) + '--' +
  453. incUri(uris[2], csoffset)] =
  454. segments[edgeId];
  455. }
  456. _csm.nodes[id]['$segments']['value'] = _segments;
  457. }
  458. csm = _utils.jsons(_csm, null, '\t');
  459. }
  460. //see if any cs metamodels are missing
  461. //this fixes issue #28
  462. //this loading should be done elsewhere in the model loading chain
  463. for (var i in _csm.metamodels) {
  464. var mm = _csm.metamodels[i];
  465. if (!(_mmmk.model.metamodels.includes(mm))) {
  466. console.error("Last-minute loading for CS metamodel: " + mm);
  467. var csmm = _fs.readFile('./users/' + mm, 'utf8');
  468. _mmmk.loadMetamodel(mm, csmm);
  469. }
  470. }
  471. var res = _mmmk.loadModel(
  472. step['new_name'],
  473. csm,
  474. step['insert']);
  475. if (res["$err"] == undefined) {
  476. cschangelogs.push(res['changelog']);
  477. return __successContinuable();
  478. } else {
  479. return __errorContinuable();
  480. }
  481. });
  482. /* forward this SYSOUT command */
  483. else if( step['op'] == 'SYSOUT' )
  484. actions.push(
  485. function()
  486. {
  487. cschangelogs.push([step]);
  488. return __successContinuable();
  489. });
  490. });
  491. actions.push(
  492. function()
  493. {
  494. cschangelogs.push( self.__solveLayoutContraints(changelog) );
  495. return __successContinuable();
  496. });
  497. }
  498. _do.chain(actions)(
  499. function()
  500. {
  501. var cschangelog = _utils.flatten(cschangelogs);
  502. console.error('w#'+__wid+' -- ('+aswSequenceNumber+') '+
  503. _utils.jsons(cschangelog));
  504. __postMessage(
  505. {'statusCode':200,
  506. 'changelog':cschangelog,
  507. 'sequence#':aswSequenceNumber,
  508. 'hitchhiker':cshitchhiker});
  509. self.__nextASWSequenceNumber =
  510. _utils.incrementSequenceNumber(self.__nextASWSequenceNumber);
  511. self.__applyPendingASWChanges();
  512. },
  513. function(err)
  514. {
  515. throw 'unexpected error while applying changelogs :: '+err;
  516. }
  517. );
  518. },
  519. /* apply pending asworker changelogs, if any and if applicable */
  520. '__applyPendingASWChanges' :
  521. function()
  522. {
  523. if( this.__pendingChangelogs.length > 0 &&
  524. this.__nextASWSequenceNumber ==
  525. this.__pendingChangelogs[0]['sequence#'] )
  526. {
  527. var pc = this.__pendingChangelogs.shift();
  528. this.__applyASWChanges(
  529. pc['changelog'],
  530. pc['sequence#'],
  531. pc['hitchhiker']);
  532. }
  533. },
  534. /* initialize a socket that will listen for and handle changelogs returned by
  535. this csworker's associated asworker
  536. 1. onconnect() is triggered when 2 way communication is established, at
  537. which point we attempt to subscribe for specified asworker
  538. 2. onmessage() is triggered once in response to our subscription attempt:
  539. a) we set this.__aswid to specified aswid
  540. b) if a cswid was provided (i.e., a shared model session is being
  541. set up)
  542. i. retrieve the specified csworker's internal state
  543. ii. setup this csworker's state and _mmmk based on the
  544. results from step ii.
  545. iii. return the new state to the client (via callback())
  546. iv. remove any obsolete changelogs received since step i.
  547. and set __nextASWSequenceNumber to the same value as
  548. that of the csworker whose state we're cloning
  549. v. apply pending changelogs, if any
  550. all future triggers of onmessage() are due to the asworker pushing
  551. changelogs, these are handled by __applyASWChanges
  552. NOTE : actually, there is a 3rd case where onmessage() is triggered...
  553. between the moment where the socket is created and the moment
  554. where we receive the response to our subscription attempt, we
  555. will receive *all* messages broadcasted by the websocket server
  556. in httpwsd.js... these are detected and discarded */
  557. '__aswSubscribe' :
  558. function(aswid,cswid)
  559. {
  560. var self = this;
  561. return function(callback,errback)
  562. {
  563. var socket = _siocl.connect('127.0.0.1',{port:8124});
  564. socket.on('connect',
  565. function()
  566. {
  567. socket.emit('message',
  568. {'method':'POST','url':'/changeListener?wid='+aswid});
  569. });
  570. socket.on('disconnect',
  571. function() {self.__aswid = undefined;});
  572. socket.on('message',
  573. function(msg)
  574. {
  575. /* on POST /changeListener response */
  576. if( msg.statusCode != undefined )
  577. {
  578. if( ! _utils.isHttpSuccessCode(msg.statusCode) )
  579. return errback(msg.statusCode+':'+msg.reason);
  580. self.__aswid = aswid;
  581. if( cswid != undefined )
  582. {
  583. var actions =
  584. [__wHttpReq('GET','/internal.state?wid='+cswid)];
  585. _do.chain(actions)(
  586. function(respData)
  587. {
  588. var state = respData['data'];
  589. _mmmk.clone(state['_mmmk']);
  590. self.__clone(state['_wlib']);
  591. var __ids2uris = state['__ids2uris'];
  592. set__ids2uris(__ids2uris);
  593. var __nextSequenceNumber =
  594. state['__nextSequenceNumber'];
  595. set__nextSequenceNumber(__nextSequenceNumber);
  596. self.__pendingChangelogs =
  597. self.__pendingChangelogs.filter(
  598. function(pc)
  599. {
  600. return self.__sn2int(pc['sequence#']) >
  601. self.__sn2int(
  602. self.__nextASWSequenceNumber)
  603. });
  604. callback();
  605. self.__applyPendingASWChanges();
  606. },
  607. function(err) {errback(err);}
  608. );
  609. }
  610. else
  611. callback();
  612. }
  613. /* on changelog reception (ignore changelogs while not
  614. subscribed to an asworker... see NOTE) */
  615. else if( self.__aswid != undefined )
  616. self.__applyASWChanges(
  617. msg.data.changelog,
  618. msg.data['sequence#'],
  619. msg.data.hitchhiker);
  620. });
  621. };
  622. },
  623. /***************************** ICON GENERATION *****************************/
  624. /* determine the correct positions and orientations of the given link's
  625. decorators, adjust them and return changelogs
  626. 0. concatenate segments into a single path
  627. 1. for each link decorator (i.e., Link $contents)
  628. *. do nothing if link decoration information is missing... this ensures
  629. backward compatibility with pre-link decorator models
  630. a. extract link decoration information, i.e., xratio and yoffset
  631. b. determine point on path at xratio and its orientation
  632. c. adjust yoffset given orientation (yoffset was specified for 0deg)
  633. d. adjust endAt given orientation (endAt is specified for 0deg)... note
  634. that endAt is only relevant for arrowtails
  635. e. relativize point from step b. w.r.t. to Link center and adjust
  636. position by adjusted yoffset
  637. f. set new position and orientation in mmmk and remember changelogs
  638. 2. return flattened changelogs
  639. NOTE:: the initial values of vobject geometric attribute values must
  640. always be remembered... they are needed on the client to properly
  641. support the drawing and transformation of vobjects... this is
  642. captured by buildVobjGeomAttrVal(), which we use in step 1f */
  643. '__positionLinkDecorators' :
  644. function(id)
  645. {
  646. var link = _utils.jsonp(_mmmk.read(id)),
  647. vobjs = link['$contents']['value'].nodes,
  648. segments = _utils.values(link['$segments']['value']),
  649. path = segments[0]+
  650. segments[1].substring(segments[1].indexOf('L')),
  651. changelogs = [];
  652. for( var vid in vobjs )
  653. {
  654. if( !('$linkDecoratorInfo' in vobjs[vid]) )
  655. continue;
  656. var ldi = vobjs[vid]['$linkDecoratorInfo']['value'],
  657. pp = _svg.fns.getPointOnPathAtRatio(path,ldi.xratio);
  658. if (pp == undefined)
  659. continue;
  660. var yoffset = new _svg.types.Point(0,ldi.yoffset).rotate(pp.O),
  661. endAt = (ldi.xratio >= 1 ?
  662. new _svg.types.Point(100,0).rotate(pp.O) :
  663. new _svg.types.Point(0,0)),
  664. changes = {};
  665. pp.x += yoffset.x - link['position']['value'][0];
  666. pp.y += yoffset.y - link['position']['value'][1];
  667. changes['$contents/value/nodes/'+vid+'/position'] =
  668. [_utils.buildVobjGeomAttrVal(
  669. vobjs[vid]['position']['value'][0], pp.x+','+endAt.x+'%'),
  670. _utils.buildVobjGeomAttrVal(
  671. vobjs[vid]['position']['value'][1], pp.y+','+endAt.y+'%')];
  672. changes['$contents/value/nodes/'+vid+'/orientation'] =
  673. _utils.buildVobjGeomAttrVal(
  674. vobjs[vid]['orientation']['value'], pp.O);
  675. changelogs.push( _mmmk.update(id,changes)['changelog'] );
  676. }
  677. return _utils.flatten(changelogs);
  678. },
  679. /* regenerate specified icon... if newCsmm is specified, the regeneration
  680. process essentially transforms the icon for it to conform to whatever is
  681. specified by newCsmm... otherwise, it merely involves re-evaluating
  682. VisualObject mappers
  683. 1. if newCSmm is defined
  684. a) create a new instance I of node 'id''s icontype given newCsmm
  685. b) copy node 'id''s '$asuri', 'position', etc. attributes to I
  686. c) delete node 'id'
  687. d) update__asid2csid and 'id' variable to be I's id
  688. e) save the changelogs of steps a-c)
  689. 2. in either case, [re-]eval VisualObject mappers
  690. a) retrieve Icon and VisualObject mappers...
  691. i. fetch specified node from mmmk
  692. ii. retrieve its '$contents' attribute
  693. iii. retrieve the 'mapper' attribute for the node itself
  694. iii. retrieve the 'mapper' attribute of VisualObjects within
  695. '$contents'
  696. b) return empty changelog if all mappers are empty
  697. c) setup sync/async action chaining
  698. i. ask asworker to evaluate a bunch of mappers
  699. ii. save results for later access
  700. d) launch chain...
  701. on success, populate attributes with results from step 2ci and
  702. 'return' changelog OR 'return' SYSOUT error if evaluating mappers
  703. raised exceptions
  704. on failure (this only occurs if we were unable to obtain an
  705. asworker read-lock), relaunch chain after short delay
  706. TBI: step 2d) could potentially lead to an infinite loop if failure is due
  707. to unforeseen error or to very long delays if lock holder takes a
  708. long time to finish... we could address this by *not* relaunching the
  709. chain if some number of tries have failed, and instead setting coded
  710. attribute values to '<out-of-date>' */
  711. '__regenIcon' :
  712. function(id,newCsmm)
  713. {
  714. var changelogs = [],
  715. self = this;
  716. return function(callback,errback)
  717. {
  718. if( newCsmm != undefined )
  719. {
  720. var node = _utils.jsonp(_mmmk.read(id)),
  721. asuri = _mmmk.read(id,'$asuri'),
  722. asid = __uri_to_id(asuri),
  723. attrs = _utils.mergeDicts([
  724. {'$asuri':asuri,
  725. 'position':node['position']['value'],
  726. 'orientation':node['orientation']['value'],
  727. 'scale':node['scale']['value']},
  728. ((s=_mmmk.read(id,'$segments'))['$err'] ?
  729. {} : {'$segments':s}) ]),
  730. cres = _mmmk.create(
  731. newCsmm+'/'+node['$type'].match(/.*\/(.*)/)[1],
  732. attrs),
  733. csid = cres['id'],
  734. dres = _mmmk['delete'](id);
  735. self.__asid2csid[asid] = id = csid;
  736. changelogs.push(
  737. cres['changelog'],
  738. dres['changelog']);
  739. }
  740. var csuri = __id_to_uri(id),
  741. asuri = self.__csuri_to_asuri(csuri),
  742. icon = _utils.jsonp(_mmmk.read(id)),
  743. vobjects = icon['$contents']['value'],
  744. mappers = {};
  745. if( icon['mapper']['value'] != '' )
  746. mappers[''] = icon['mapper']['value'];
  747. for( var vid in vobjects['nodes'] )
  748. if( 'mapper' in vobjects['nodes'][vid] &&
  749. vobjects['nodes'][vid]['mapper']['value'] != '' )
  750. mappers['$contents/value/nodes/'+vid+'/'] =
  751. vobjects['nodes'][vid]['mapper']['value'];
  752. if( _utils.keys(mappers).length > 0 )
  753. {
  754. var actions =
  755. [__wHttpReq(
  756. 'POST',
  757. '/GET/'+asuri+'.mappings?wid='+self.__aswid,
  758. mappers)],
  759. successf =
  760. function(attrVals)
  761. {
  762. if( '$err' in attrVals )
  763. callback(
  764. [{'op':'SYSOUT',
  765. 'text':'ERROR :: '+attrVals['$err']}]);
  766. else
  767. {
  768. var changes = {};
  769. for( var fullattr in attrVals )
  770. changes[fullattr] = attrVals[fullattr];
  771. var result = _mmmk.update(id,changes);
  772. if ( '$err' in result )
  773. callback(
  774. [{'op':'SYSOUT',
  775. 'text':'ERROR :: '+result['$err']}]);
  776. else {
  777. changelogs.push(
  778. result['changelog'] );
  779. callback( _utils.flatten(changelogs) );
  780. }
  781. }
  782. },
  783. failuref =
  784. function(err)
  785. {
  786. console.error('"POST *.mappings" failed on :: '+err+
  787. '\n(will try again soon)');
  788. setTimeout(
  789. _do.chain(actions),
  790. self.__REGEN_ICON_RETRY_DELAY_MS,
  791. successf,
  792. failuref);
  793. };
  794. _do.chain(actions)(successf,failuref);
  795. }
  796. else
  797. callback( _utils.flatten(changelogs) );
  798. };
  799. },
  800. '__solveLayoutContraints':
  801. function(changelog)
  802. {
  803. // TBC actually implement this function
  804. // use ids in changelog to determine what changed
  805. // add 2 lines below to mmmk.__create() if necessary
  806. // if( type in this.metamodels[metamodel]['connectorTypes'] )
  807. // new_node['$linktype'] = this.metamodels[metamodel]['connectorTypes'][type];
  808. return [];
  809. // return [{'op':'SYSOUT',
  810. // 'text':'WARNING :: '+
  811. // 'a proper layout constraint solver has yet to be '+
  812. // 'implemented... inter-VisualObject relationships are '+
  813. // 'ignored and containers do not resize to fit their '+
  814. // 'contents'}];
  815. },
  816. /* transform all icons from tgtCsmm into appropriate icons of newCsmm
  817. 1. read model and newCsmm from _mmmk
  818. 2. for each icon of tgtCsmm, if newCsmm defines a replacement icon, save
  819. the icon's id in 'tgtIds'... otherwise, return error
  820. 3. init sync/async action chaining...
  821. a) for each id in 'tgtIds', add 2 entries to chain
  822. i. call __regenIcon on specified icon
  823. ii. save resulting changelog and continue
  824. 4. launch chain...
  825. on success,
  826. a) adjust $segments attributes from all Links to account for edge
  827. end change of id (and uri)
  828. b) 'return' flattened changelogs
  829. on failure, 'return' error */
  830. '__transformIcons' :
  831. function(tgtCsmm,newCsmm)
  832. {
  833. var self = this;
  834. return function(callback,errback)
  835. {
  836. var m = _utils.jsonp(_mmmk.read()),
  837. newCsmmData = _utils.jsonp(_mmmk.readMetamodels(newCsmm)),
  838. tgtIds = [],
  839. newIds = {},
  840. changelogs = [],
  841. actions = [__successContinuable()];
  842. for( var id in m.nodes )
  843. if( (matches = m.nodes[id]['$type'].match('^'+tgtCsmm+'/(.*)')) )
  844. {
  845. var type = matches[1];
  846. if( newCsmmData.types[type] == undefined )
  847. {
  848. errback('Icons mm should define type :: '+type);
  849. return;
  850. }
  851. tgtIds.push(id);
  852. }
  853. tgtIds.forEach(
  854. function(id)
  855. {
  856. actions.push(
  857. function() {return self.__regenIcon(id,newCsmm);},
  858. function(changelog)
  859. {
  860. var newId = changelog[0]['id'];
  861. newIds[id] = newId;
  862. changelogs.push(
  863. changelog.concat(
  864. '$err' in _mmmk.read(newId,'$segments') ?
  865. [] : self.__positionLinkDecorators(newId)) );
  866. return __successContinuable();
  867. });
  868. });
  869. _do.chain(actions)(
  870. function()
  871. {
  872. var m = _utils.jsonp(_mmmk.read());
  873. for( var id in m.nodes )
  874. if( (matches = m.nodes[id]['$type'].match(/Link$/)) )
  875. {
  876. var s = _mmmk.read(id,'$segments'),
  877. changed = false;
  878. for( var edgeId in s )
  879. {
  880. var ends =
  881. edgeId.match(/(.*\.instance)--(.*\.instance)/),
  882. id1 = __uri_to_id(ends[1]),
  883. id2 = __uri_to_id(ends[2]);
  884. if( id1 in newIds )
  885. {
  886. ends[1] = __id_to_uri( newIds[id1] );
  887. changed = true;
  888. }
  889. if( id2 in newIds )
  890. {
  891. ends[2] = __id_to_uri( newIds[id2] );
  892. changed = true;
  893. }
  894. if( changed )
  895. {
  896. s[ends[1]+'--'+ends[2]] = s[edgeId];
  897. delete s[edgeId];
  898. }
  899. }
  900. if( changed )
  901. changelogs.push(
  902. _mmmk.update(id,{'$segments':s})['changelog'] );
  903. }
  904. callback(_utils.flatten(changelogs));
  905. },
  906. function(err)
  907. {
  908. errback('__transformIcons() should never fail... '+
  909. 'failed on :: '+err);
  910. }
  911. );
  912. };
  913. },
  914. /************************** REST REQUEST HANDLING **************************/
  915. /* INTENT :
  916. ask our asworker's mtworker to do something (e.g., change
  917. transformation execution mode)
  918. IN PRACTICE:
  919. adjust uri and forward to asworker
  920. 1. setup sync/async action chaining
  921. a) ask asworker to forward request to its mtworker
  922. 2. launch chain... return success code or error */
  923. 'mtwRequest' :
  924. function(resp,method,uri,reqData)
  925. {
  926. var actions = [__wHttpReq(
  927. method,
  928. uri+'?wid='+this.__aswid,
  929. reqData)];
  930. _do.chain(actions)(
  931. function()
  932. {
  933. __postMessage({'statusCode':200, 'respIndex':resp});
  934. },
  935. function(err) {__postInternalErrorMsg(resp,err);}
  936. );
  937. },
  938. /* return sufficient information to clone the current csworker to the tinyest
  939. detail... this is a pimped out version of GET current.state for internal
  940. use only
  941. 1. bundle info about _mmmk and _wlib
  942. 2. return to querier */
  943. 'GET /internal.state' :
  944. function(resp)
  945. {
  946. __postMessage(
  947. {'statusCode':200,
  948. 'data':{'_mmmk':_mmmk.clone(),
  949. '_wlib':this.__clone(),
  950. '__ids2uris':_utils.clone(get__ids2uris()),
  951. '__nextSequenceNumber':get__nextSequenceNumber()},
  952. 'sequence#':__sequenceNumber(0),
  953. 'respIndex':resp});
  954. },
  955. /* subscribe to an existing or to-be-created asworker
  956. 1. validate parameters
  957. 2. if 'aswid' and 'cswid' are given,
  958. a) setup sync/async action chaining
  959. i) attempt to subsribe to specified asworker using specified
  960. csworker's current model as this csworker's initial model
  961. b) launch chain... on success, 'return' current state (via
  962. GET__current_state())... on error, return error
  963. 2. otherwise,
  964. a) setup sync/async action chaining
  965. i) spawn new asworker
  966. ii) subscribe to it
  967. b) launch chain... return success code or error */
  968. 'PUT /aswSubscription' :
  969. function(resp,uri,reqData/*wid*/)
  970. {
  971. if( this.__aswid > -1 )
  972. return __postForbiddenErrorMsg(
  973. resp,
  974. 'already subscribed to an asworker');
  975. if( reqData != undefined )
  976. {
  977. if( reqData['aswid'] == undefined ||
  978. reqData['cswid'] == undefined )
  979. return __postInternalErrorMsg(resp, 'missing AS and/or CS wid');
  980. var self = this,
  981. actions =
  982. [this.__aswSubscribe(reqData['aswid'],reqData['cswid'])];
  983. _do.chain(actions)(
  984. function()
  985. {
  986. GET__current_state(resp);
  987. },
  988. function(err) {__postInternalErrorMsg(resp,err);}
  989. );
  990. }
  991. else
  992. {
  993. var self = this,
  994. actions =
  995. [__httpReq('POST','/asworker'),
  996. function(aswid) {return self.__aswSubscribe(aswid);}];
  997. _do.chain(actions)(
  998. function()
  999. {
  1000. __postMessage(
  1001. {'statusCode':200,
  1002. 'data':self.__aswid,
  1003. 'respIndex':resp});
  1004. },
  1005. function(err) {__postInternalErrorMsg(resp,err);}
  1006. );
  1007. }
  1008. },
  1009. /* INTENT :
  1010. load a CS and an AS metamodel from disk
  1011. *OR*
  1012. switch between different CS metamodels
  1013. IN PRACTICE:
  1014. adjust uri and reqData and forward to asworker
  1015. *OR*
  1016. fulfill intent
  1017. 1. parse + validate parameters
  1018. 2. if no asmm is specified (CS switch),
  1019. a) setup sync/async action chaining
  1020. i. read specified csmm from disk
  1021. ii. load read data into _mmmk
  1022. iii. try to regen all icons from overwritten csmm
  1023. b) launch chain...
  1024. i. on failure, undo step 2.a)ii. if it ran, and return error
  1025. ii. on success,
  1026. j. return success code
  1027. jj. unload previous csmm
  1028. jjj. post bundled and flattened changelogs
  1029. 2. if an asmm is specified (MM load)
  1030. a) setup sync/async action chaining
  1031. i. read specified csmm from disk
  1032. ii. ask asworker to load AS metamodel + pass CS metamodel name and
  1033. data as 'hitchhiker'
  1034. b) launch chain... return success code or error */
  1035. 'PUT /current.metamodels' :
  1036. function(resp,uri,reqData/*[asmm,]csmm*/)
  1037. {
  1038. if( reqData == undefined )
  1039. return __postBadReqErrorMsg(resp, 'missing request data');
  1040. else if( reqData['csmm'] == undefined )
  1041. return __postBadReqErrorMsg(resp, 'missing CS mm');
  1042. else if( ! (matches = reqData['csmm'].
  1043. match(/.+?((\/.*)\..*Icons(\.pattern){0,1})\.metamodel/)) )
  1044. return __postBadReqErrorMsg(
  1045. resp,
  1046. 'bad uri for Icons mm :: '+reqData['csmm']);
  1047. var asmm = matches[2]+(matches[3] || ''),
  1048. csmm = matches[1];
  1049. if( reqData['asmm'] == undefined )
  1050. {
  1051. if( this.__asmm2csmm[asmm] == undefined )
  1052. return __postBadReqErrorMsg(resp, 'missing AS mm');
  1053. var lres = undefined,
  1054. sn = undefined,
  1055. self = this,
  1056. actions =
  1057. [_fs.readFile('./users'+reqData['csmm'],'utf8'),
  1058. function(csmmData)
  1059. {
  1060. sn = __sequenceNumber();
  1061. self.__checkpointUserOperation(sn);
  1062. lres = _mmmk.loadMetamodel(csmm,csmmData);
  1063. return __successContinuable();
  1064. },
  1065. function()
  1066. {
  1067. return self.__transformIcons(
  1068. self.__asmm2csmm[asmm],
  1069. csmm);
  1070. }];
  1071. _do.chain(actions)(
  1072. function(changelog)
  1073. {
  1074. __postMessage({'statusCode':202, 'respIndex':resp});
  1075. var ures = _mmmk.unloadMetamodel(self.__asmm2csmm[asmm]),
  1076. changelogs =
  1077. [lres['changelog'],changelog,ures['changelog']];
  1078. self.__asmm2csmm[asmm] = csmm;
  1079. __postMessage(
  1080. {'statusCode':200,
  1081. 'changelog':_utils.flatten(changelogs),
  1082. 'sequence#':sn});
  1083. },
  1084. function(err)
  1085. {
  1086. if( sn != undefined )
  1087. __postInternalErrorMsg(resp,
  1088. 'CS switch should never fail for non-I/O reason'+
  1089. '... backend may now be in unstable state... '+
  1090. 'failed on :: '+err);
  1091. else
  1092. __postInternalErrorMsg(resp,err);
  1093. }
  1094. );
  1095. }
  1096. else
  1097. {
  1098. var self = this,
  1099. actions =
  1100. [_fs.readFile('./users'+reqData['csmm'],'utf8'),
  1101. function(csmmData)
  1102. {
  1103. return __wHttpReq(
  1104. 'PUT',
  1105. uri+'?wid='+self.__aswid,
  1106. {'mm':reqData['asmm'],
  1107. 'hitchhiker':{'csmm':csmmData,'name':csmm}});
  1108. }];
  1109. _do.chain(actions)(
  1110. function()
  1111. {
  1112. __postMessage({'statusCode':202, 'respIndex':resp});
  1113. },
  1114. function(err) {__postInternalErrorMsg(resp,err);}
  1115. );
  1116. }
  1117. },
  1118. /* INTENT :
  1119. unload a metamodel (deletes all entities from that metamodel)
  1120. IN PRACTICE:
  1121. adjust uri and forward to asworker
  1122. 1. parse + validate parameters
  1123. 2. setup sync/async action chaining
  1124. a) ask asworker to unload corresponding AS mm
  1125. 3. launch chain... on success, unload specified metamodel */
  1126. 'DELETE *.metamodel' :
  1127. function(resp,uri)
  1128. {
  1129. var matches = uri.match(/(.*)\..*Icons(\.pattern){0,1}\.metamodel/);
  1130. if( ! matches )
  1131. return __postBadReqErrorMsg(resp,'bad uri for Icons mm :: '+uri);
  1132. var asuri = matches[1]+(matches[2] || '')+'.metamodel',
  1133. actions =
  1134. [__wHttpReq('DELETE',asuri+'?wid='+this.__aswid)];
  1135. _do.chain(actions)(
  1136. function()
  1137. {
  1138. __postMessage({'statusCode':202, 'respIndex':resp});
  1139. },
  1140. function(err) {__postInternalErrorMsg(resp,err);}
  1141. );
  1142. },
  1143. /* INTENT :
  1144. load a model from disk
  1145. OR
  1146. clear current model (and metamodels)
  1147. IN PRACTICE:
  1148. adjust reqData and forward to asworker
  1149. 1. parse + validate parameters
  1150. 2. read specified model from disk
  1151. OR
  1152. fabricate empty model
  1153. 3. setup sync/async action chaining
  1154. a) ask asworker to load associated AS model + pass CS model as
  1155. 'hitchhiker'
  1156. 4. launch chain... return success code or error */
  1157. 'PUT /current.model' :
  1158. function(resp,uri,reqData/*m[,insert]*/)
  1159. {
  1160. if( reqData == undefined )
  1161. return __postBadReqErrorMsg(resp, 'missing model');
  1162. var self = this,
  1163. mData = undefined,
  1164. actions = [];
  1165. if( reqData['m'] == undefined )
  1166. actions =
  1167. [__successContinuable(),
  1168. function()
  1169. {
  1170. mData = {"csm": {"nodes": {},
  1171. "edges": [],
  1172. "metamodels": []},
  1173. "asm": {"nodes": {},
  1174. "edges": [],
  1175. "metamodels": []}};
  1176. reqData['m'] = 'new';
  1177. return __successContinuable(mData);
  1178. }];
  1179. else {
  1180. try {
  1181. _fs.accessSync('./users/'+reqData['m']);
  1182. } catch (e) {
  1183. return __postBadReqErrorMsg(resp, 'cannot read ' + reqData['m']);
  1184. }
  1185. actions = [ _fs.readFile('./users/'+reqData['m'],'utf8'),
  1186. function(_mData)
  1187. {
  1188. mData = eval('('+_mData+')');
  1189. return __successContinuable(mData);
  1190. }];
  1191. actions.push(
  1192. function(m)
  1193. {
  1194. var asmData = _utils.jsons(m['asm']),
  1195. csmData = _utils.jsons(m['csm']);
  1196. return __wHttpReq(
  1197. 'PUT',
  1198. uri+'?wid='+self.__aswid,
  1199. {'m':asmData,
  1200. 'name':reqData['m']+(new Date().getTime()),
  1201. 'insert':reqData['insert'],
  1202. 'hitchhiker':{'csm':csmData}});
  1203. });
  1204. _do.chain(actions)(
  1205. function()
  1206. {
  1207. __postMessage({'statusCode':202,'respIndex':resp});
  1208. },
  1209. function(err)
  1210. {
  1211. var MISSING_MM_ERROR = 'metamodel not loaded :: ';
  1212. if( (matches = err.match('^500:'+MISSING_MM_ERROR+
  1213. '(.*?)(\\.pattern){0,1}$')) )
  1214. {
  1215. var asmm = matches[1],
  1216. pmm = (matches[2] != undefined),
  1217. csmm;
  1218. mData['csm'].metamodels.some(
  1219. function(mm)
  1220. {
  1221. if( (pmm && mm.match(
  1222. '^'+asmm+'\\.[a-zA-Z0-9]*Icons\\.pattern$')) ||
  1223. (!pmm && mm.match(
  1224. '^'+asmm+'\\.[a-zA-Z0-9]*Icons$')) )
  1225. csmm = mm;
  1226. return csmm;
  1227. });
  1228. __postInternalErrorMsg(resp,MISSING_MM_ERROR+csmm);
  1229. }
  1230. else
  1231. __postInternalErrorMsg(resp,err);
  1232. }
  1233. );
  1234. }
  1235. },
  1236. /* INTENT :
  1237. create a new instance of specified type (if reqData has
  1238. 'src' and 'dest' fields, type is a connector)
  1239. IN PRACTICE:
  1240. adjust uri (and reqData) and forward to asworker
  1241. 1. parse + validate parameters
  1242. 2. setup sync/async action chaining
  1243. a) construct reqData for asworker.POST *.type
  1244. i. handle connector ends if applicable
  1245. ii. handle 'pos'... pass as hitchhiker and evaluate the to-be
  1246. *Icon's parser within a dummy context where 'position' is set
  1247. to 'pos'... see NOTES above for more details on this
  1248. b) ask asworker to create an instance of appropriate AS type
  1249. 3. launch chain... return success code or error */
  1250. 'POST *.type':
  1251. function (resp, uri, reqData/*pos|clone,[segments,src,dest]*/) {
  1252. let matches =
  1253. uri.match(/((.*)\..*Icons)(\.pattern){0,1}\/((.*)Icon)\.type/) ||
  1254. uri.match(/((.*)\..*Icons)(\.pattern){0,1}\/((.*)Link)\.type/);
  1255. if (!matches)
  1256. return __postBadReqErrorMsg(
  1257. resp,
  1258. 'bad uri for Icon/Link type :: ' + uri);
  1259. let asuri = matches[2] + (matches[3] || '') + '/' + matches[5] + '.type',
  1260. csmm = matches[1] + (matches[3] || ''),
  1261. cstype = matches[4],
  1262. types = _utils.jsonp(_mmmk.readMetamodels(csmm))['types'];
  1263. if (!(cstype in types)) {
  1264. return __postBadReqErrorMsg(
  1265. resp, 'no concrete syntax definition found for ' + cstype);
  1266. }
  1267. let parser =
  1268. types[cstype].filter(
  1269. function (attr) {
  1270. return attr['name'] == 'parser';
  1271. })[0]['default'],
  1272. self = this,
  1273. actions =
  1274. [__successContinuable(),
  1275. function () {
  1276. if (reqData == undefined)
  1277. return __errorContinuable('missing creation parameters');
  1278. let hitchhiker = {},
  1279. reqParams = {},
  1280. segments = reqData['segments'],
  1281. src = reqData['src'],
  1282. dest = reqData['dest'],
  1283. pos = reqData['pos'];
  1284. if (src != undefined &&
  1285. dest != undefined) {
  1286. let src_asuri = self.__csuri_to_asuri(src);
  1287. if (src_asuri['$err'])
  1288. return __errorContinuable(src_asuri['$err']);
  1289. let dest_asuri = self.__csuri_to_asuri(dest);
  1290. if (dest_asuri['$err'])
  1291. return __errorContinuable(dest_asuri['$err']);
  1292. if (segments == undefined) {
  1293. segments = self.__defaultSegments(src, dest);
  1294. }
  1295. if (pos == undefined) {
  1296. pos = self.__nodesCenter([src_asuri, dest_asuri]);
  1297. }
  1298. hitchhiker = {
  1299. 'segments': segments,
  1300. 'src': src,
  1301. 'dest': dest
  1302. };
  1303. reqParams = {
  1304. 'src': src_asuri,
  1305. 'dest': dest_asuri
  1306. };
  1307. }
  1308. if (pos == undefined)
  1309. return __errorContinuable('missing position');
  1310. hitchhiker['pos'] = pos;
  1311. reqParams['attrs'] =
  1312. self.__runParser(
  1313. parser,
  1314. {
  1315. 'position': pos,
  1316. 'orientation': 0,
  1317. 'scale': [1, 1]
  1318. },
  1319. {});
  1320. return __successContinuable(
  1321. _utils.mergeDicts(
  1322. [{'hitchhiker': hitchhiker}, reqParams]));
  1323. },
  1324. function (asreqData) {
  1325. return __wHttpReq(
  1326. 'POST',
  1327. asuri + '?wid=' + self.__aswid,
  1328. asreqData);
  1329. }];
  1330. _do.chain(actions)(
  1331. function (res) {
  1332. __postMessage({'statusCode': 202, 'respIndex': resp, 'reason': res});
  1333. },
  1334. function (err) {
  1335. __postInternalErrorMsg(resp, err);
  1336. }
  1337. );
  1338. },
  1339. /* return an AS instance, and optionally also the associated CS instance
  1340. 1. setup sync/async action chaining
  1341. a) get AS instance uri for specified CS instance
  1342. b) ask asworker for AS instance
  1343. 2. launch chain
  1344. ... on success, 'return' instance possibly bundling CS instance
  1345. ... on error, 'return' error */
  1346. 'GET *.instance' :
  1347. function(resp,uri,reqData/*[full]*/)
  1348. {
  1349. var self = this,
  1350. actions =
  1351. [__successContinuable(),
  1352. function()
  1353. {
  1354. if( (asuri = self.__csuri_to_asuri(uri))['$err'] )
  1355. return __errorContinuable(asuri['$err']);
  1356. return __successContinuable(asuri);
  1357. },
  1358. function(asuri)
  1359. {
  1360. return __wHttpReq('GET',asuri+'?wid='+self.__aswid);
  1361. }];
  1362. _do.chain(actions)(
  1363. function(respData)
  1364. {
  1365. if( reqData && 'full' in reqData )
  1366. var data = {'cs':_mmmk.read(__uri_to_id(uri)),
  1367. 'as':respData['data']};
  1368. else
  1369. var data = respData['data'];
  1370. __postMessage(
  1371. {'statusCode':200,
  1372. 'data':data,
  1373. 'sequence#':respData['sequence#'],
  1374. 'respIndex':resp});
  1375. },
  1376. function(err) {__postInternalErrorMsg(resp,err);}
  1377. );
  1378. },
  1379. /* INTENT :
  1380. update an AS instance's attributes
  1381. IN PRACTICE:
  1382. adjust uri and forward to asworker
  1383. 1. setup sync/async action chaining
  1384. a) determine associated AS instance uri
  1385. b) ask asworker to update it
  1386. 2. launch chain... return success code or error */
  1387. 'PUT *.instance' :
  1388. function(resp,uri,reqData)
  1389. {
  1390. var self = this,
  1391. actions =
  1392. [__successContinuable(),
  1393. function()
  1394. {
  1395. if( (asuri = self.__csuri_to_asuri(uri))['$err'] )
  1396. return __errorContinuable(asuri['$err']);
  1397. return __successContinuable(asuri);
  1398. },
  1399. function(asuri)
  1400. {
  1401. return __wHttpReq(
  1402. 'PUT',
  1403. asuri+'?wid='+self.__aswid,
  1404. reqData);
  1405. }];
  1406. _do.chain(actions)(
  1407. function(asnode)
  1408. {
  1409. __postMessage(
  1410. {'statusCode':202,
  1411. 'respIndex':resp});
  1412. },
  1413. function(err) {__postInternalErrorMsg(resp,err);}
  1414. );
  1415. },
  1416. 'POST *.instance.click' :
  1417. function(resp,uri,reqData)
  1418. {
  1419. /* TBA
  1420. REST requests vs. code run on ASw
  1421. + checkout bak/ for _mmmk.handleVisualObjectClick */
  1422. },
  1423. /* INTENT :
  1424. delete an instance
  1425. IN PRACTICE:
  1426. adjust uri and forward to asworker
  1427. 1. setup sync/async action chaining
  1428. a) determine associated AS instance uri
  1429. b) ask asworker to delete it
  1430. 2. launch chain... return success code
  1431. NOTE:: this function does not return csworker errors to the client (i.e.,
  1432. errors triggered by __csuri_to_asuri() failures)... the reason for
  1433. this is that mmmk sometimes cascades deletes (e.g., deleting a
  1434. node alsos deletes any connected links) which often causes errors
  1435. during 'mass' deletions, e.g.,
  1436. a) client requests
  1437. delete A
  1438. delete A->B
  1439. delete B
  1440. b) csworker handles delete A (deletes A and A->B)
  1441. c) csworker handles delete A->B (error, A->B already deleted)
  1442. ... */
  1443. 'DELETE *.instance' :
  1444. function(resp,uri)
  1445. {
  1446. var self = this,
  1447. asuri = this.__csuri_to_asuri(uri),
  1448. actions =
  1449. [__wHttpReq('DELETE',asuri+'?wid='+self.__aswid)];
  1450. if( asuri['$err'] )
  1451. __postMessage({'statusCode':200, 'respIndex':resp});
  1452. else
  1453. _do.chain(actions)(
  1454. function()
  1455. {
  1456. __postMessage({'statusCode':202, 'respIndex':resp});
  1457. },
  1458. function(err)
  1459. {
  1460. __postInternalErrorMsg(resp,err);
  1461. }
  1462. );
  1463. },
  1464. /* INTENT :
  1465. update a CS instance's attributes (position,...)
  1466. IN PRACTICE :
  1467. perform intent *or* possibly adjust uri and reqData and forward to
  1468. asworker
  1469. 1. retrieve the icon's parser
  1470. 2. execute parser to determine AS impacts
  1471. 3. if parsing produced an error, return it
  1472. 3. if there are AS impacts, simulate an edit-via-dialog update by
  1473. 'forwarding' the request (with modified reqData) to 'PUT *.instance'
  1474. *and* bundle requested CS update (for later handling)
  1475. 3. if there are no AS impacts, perform requested CS update and post
  1476. changelog */
  1477. 'PUT *.cs' :
  1478. function(resp,uri,reqData)
  1479. {
  1480. var id = __uri_to_id(uri),
  1481. icon = _utils.jsonp( _mmmk.read(id) ),
  1482. updd = this.__runParser(
  1483. icon['parser']['value'],
  1484. reqData['changes'],
  1485. icon );
  1486. if( updd && updd['$err'] )
  1487. return __postInternalErrorMsg(resp,updd['$err']);
  1488. if( updd )
  1489. this['PUT *.instance'](
  1490. resp,
  1491. uri,
  1492. {'changes':updd,
  1493. 'hitchhiker':{'cschanges':reqData['changes']}} );
  1494. else
  1495. {
  1496. var sn = __sequenceNumber();
  1497. this.__checkpointUserOperation(sn);
  1498. __postMessage(
  1499. {'statusCode':200,
  1500. 'changelog':
  1501. _mmmk.update(id,reqData['changes'])['changelog'].
  1502. concat('$segments' in reqData['changes'] ?
  1503. this.__positionLinkDecorators(id) :
  1504. []),
  1505. 'sequence#':sn,
  1506. 'respIndex':resp});
  1507. }
  1508. },
  1509. /* INTENT :
  1510. update a VisualObject's attributes
  1511. IN PRACTICE:
  1512. perform intent *or* possibly adjust uri and reqData and forward to
  1513. asworker
  1514. 1. parse + validate parameters
  1515. 2. retrieve VisualObject and its parsing function
  1516. 3. execute parsing function to determine AS impacts
  1517. 4. if parsing produced an error, return it
  1518. 4. if there are AS impacts, simulate an edit-via-dialog update by
  1519. 'forwarding' the request (with modified reqData) to 'PUT *.instance'
  1520. *and* bundle requested CS update (for later handling)
  1521. 4. if there are no AS impacts, perform the requested VisualObject update
  1522. and post changelog */
  1523. 'PUT *.vobject' :
  1524. function(resp,uri,reqData)
  1525. {
  1526. var matches = uri.match(/.*\/(.*)\.instance\/(.*)\.vobject/);
  1527. if( ! matches )
  1528. return __postBadReqErrorMsg(
  1529. resp,
  1530. 'bad uri for VisualObject :: '+uri);
  1531. var id = matches[1],
  1532. vid = matches[2],
  1533. vobjAttr = '$contents/value/nodes/'+vid;
  1534. if( (res = _mmmk.read(id,'$contents'))['$err'] )
  1535. return __postBadReqErrorMsg(resp,res['$err']);
  1536. var __vo__ = res['nodes'][vid],
  1537. parsingf = __vo__['parser']['value'],
  1538. updd = this.__runParser(
  1539. parsingf,
  1540. reqData['changes'],
  1541. __vo__ );
  1542. if( updd && updd['$err'] )
  1543. return __postInternalErrorMsg(resp,updd['$err']);
  1544. var _reqData = {};
  1545. for( var attr in reqData['changes'] )
  1546. _reqData[vobjAttr+'/'+attr] = reqData['changes'][attr];
  1547. if( updd )
  1548. this['PUT *.instance'](
  1549. resp,
  1550. uri,
  1551. {'changes':updd,
  1552. 'hitchhiker':{'cschanges':_reqData}} );
  1553. else
  1554. {
  1555. var sn = __sequenceNumber();
  1556. this.__checkpointUserOperation(sn);
  1557. __postMessage(
  1558. {'statusCode':200,
  1559. 'changelog':_mmmk.update(id,_reqData)['changelog'],
  1560. 'sequence#':sn,
  1561. 'respIndex':resp});
  1562. }
  1563. },
  1564. /* INTENT :
  1565. A) generate a metamodel from the current AS model and write it to disk
  1566. OR
  1567. B) generate a CS metamodel (i.e., an icon definition metamodel) from
  1568. the current AS and CS models and write it to disk
  1569. OR
  1570. C) generate pattern metamodels from AS and CS metamodels
  1571. IN PRACTICE:
  1572. A) adjust uri and forward to asworker
  1573. OR
  1574. B) adjust uri and forward to asworker + bundle CS model
  1575. OR
  1576. C) do the deed
  1577. C) if the uri is a pattern metamodel uri,
  1578. 1. setup sync/async action chaining
  1579. a) read AS metamodel + store contents
  1580. b) read contents of parent dir
  1581. 2. launch chain... on error, return error... on success,
  1582. a) setup another sync/async action chaining
  1583. i. read all CS metamodels + store contents
  1584. ii. call _mt.ramify() with AS and CS metamodel data
  1585. iii. write results to disk
  1586. b) launch chain... return success or error code
  1587. B) if the uri is an icon definition metamodel uri,
  1588. 1. setup sync/async action chaining
  1589. a) ask asworker to generate metamodel and write it to disk... asworker
  1590. request reqData includes the current CS model
  1591. 2. launch chain... return success code or error
  1592. A) otherwise,
  1593. 1. setup sync/async action chaining
  1594. a) ask asworker to generate metamodel and write it to disk
  1595. 2. launch chain... return success code or error */
  1596. 'PUT *.metamodel' :
  1597. function(resp,uri)
  1598. {
  1599. if( (matches = uri.match(/\.pattern\.metamodel/)) )
  1600. {
  1601. var matches = uri.match(/\/GET(((.*\/).*).pattern.metamodel)/),
  1602. RAMasmmPath = './users'+matches[1],
  1603. asmmPath = './users'+matches[2]+'.metamodel',
  1604. parentDir = './users'+matches[3],
  1605. asmm = undefined,
  1606. csmms = {},
  1607. actions =
  1608. [_fs.readFile(asmmPath,'utf8'),
  1609. function(data)
  1610. {
  1611. asmm = data;
  1612. return _fs.readdir(parentDir);
  1613. }];
  1614. _do.chain(actions)(
  1615. function(files)
  1616. {
  1617. files =
  1618. files.filter(
  1619. function(f)
  1620. {return f.match(/\..*Icons\.metamodel/);});
  1621. var ramActions = [__successContinuable()];
  1622. files.forEach(
  1623. function(f)
  1624. {
  1625. ramActions.push(
  1626. function()
  1627. {
  1628. return _fs.readFile(parentDir+f,'utf8');
  1629. },
  1630. function(data)
  1631. {
  1632. csmms[f] = data;
  1633. return __successContinuable();
  1634. });
  1635. });
  1636. ramActions.push(
  1637. function()
  1638. {
  1639. var res = _mt.ramify(_utils.jsonp(asmm),csmms);
  1640. asmm = res['asmm'];
  1641. csmms = res['csmms'];
  1642. return _fs.writeFile(
  1643. RAMasmmPath,
  1644. _utils.jsons(asmm,null,'\t'));
  1645. });
  1646. files.forEach(
  1647. function(f)
  1648. {
  1649. ramActions.push(
  1650. function()
  1651. {
  1652. var RAMf = parentDir +
  1653. f.match(/(.*)\.metamodel/)[1] +
  1654. '.pattern.metamodel';
  1655. return _fs.writeFile(
  1656. RAMf,
  1657. _utils.jsons(csmms[f],null,'\t'));
  1658. });
  1659. });
  1660. _do.chain(ramActions)(
  1661. function()
  1662. {
  1663. __postMessage({'statusCode':200,'respIndex':resp});
  1664. },
  1665. function(err) {__postInternalErrorMsg(resp,err);}
  1666. );
  1667. },
  1668. function(err) {__postInternalErrorMsg(resp,err);}
  1669. );
  1670. }
  1671. else
  1672. {
  1673. var matches = uri.match(/\/GET(((.*\/).*)\..*Icons.metamodel)/),
  1674. asmmPath = (matches ? ('./users'+matches[2]+'.metamodel') : undefined),
  1675. asmm = undefined;
  1676. aswid = this.__aswid;
  1677. actions = [];
  1678. if (asmmPath) {
  1679. actions = [
  1680. _fs.readFile(asmmPath,'utf8'),
  1681. function(data) {
  1682. asmm = _utils.jsonp(data);
  1683. return __successContinuable();
  1684. },
  1685. function(result) {
  1686. return __wHttpReq('PUT',
  1687. uri+'?wid='+aswid,
  1688. ({'csm':_mmmk.read(), 'asmm': asmm}))
  1689. }]
  1690. } else {
  1691. actions = [__wHttpReq('PUT',
  1692. uri+'?wid='+aswid,
  1693. undefined)];
  1694. }
  1695. _do.chain(actions)(
  1696. function() { __postMessage({'statusCode':200,'respIndex':resp}); },
  1697. function(err) {__postInternalErrorMsg(resp,err);}
  1698. );
  1699. }
  1700. },
  1701. /* write a bundle containing this CS model and its associated AS model to
  1702. disk
  1703. 1. setup sync/async action chaining
  1704. a) ask asworker for current model (AS)
  1705. 2. launch chain... on error, return error... on success, compare sequence#
  1706. of returned AS model with next expected sequence# from asworker
  1707. a) if AS model is too old, restart
  1708. b) if AS model is too recent, wait 200 ms and restart
  1709. c) otherwise,
  1710. i. extract path info
  1711. ii. return error on invalid path
  1712. iii. ask _mmmk for current model (CS)
  1713. iv. setup another sync/async action chaining
  1714. a) write bundled AS and CS models to disk
  1715. v. launch chain... return success or error code */
  1716. 'PUT *.model' :
  1717. function(resp,uri)
  1718. {
  1719. var self = this,
  1720. actions = [__wHttpReq('GET','/current.model?wid='+this.__aswid)];
  1721. _do.chain(actions)(
  1722. function(asdata)
  1723. {
  1724. var sn = asdata['sequence#'];
  1725. if( self.__nextASWSequenceNumber - 1 > sn )
  1726. self['PUT *.model'](resp,uri);
  1727. else if( self.__nextASWSequenceNumber - 1 < sn )
  1728. setTimeout(self['PUT *.model'], 200, resp, uri);
  1729. else
  1730. {
  1731. if( (res = _mmmk.read())['$err'] )
  1732. __postInternalErrorMsg(resp,res['$err']);
  1733. else
  1734. {
  1735. var path = './users'+uri.substring('/GET'.length),
  1736. dir = _path.dirname(path);
  1737. if( dir.match(/"/) )
  1738. throw 'illegal filename... these characters are not'+
  1739. ' allowed in filenames :: "';
  1740. var mData = {
  1741. 'csm':_utils.jsonp(res),
  1742. 'asm':_utils.jsonp(asdata['data'])},
  1743. writeActions =
  1744. [_fspp.mkdirs(dir),
  1745. function()
  1746. {
  1747. return _fs.writeFile(
  1748. path,
  1749. _utils.jsons(mData,null,'\t'));
  1750. }];
  1751. _do.chain(writeActions)(
  1752. function()
  1753. {
  1754. __postMessage(
  1755. {'statusCode':200,
  1756. 'respIndex':resp});
  1757. },
  1758. function(writeErr)
  1759. {
  1760. __postInternalErrorMsg(resp,writeErr);
  1761. }
  1762. );
  1763. }
  1764. }
  1765. },
  1766. function(err) {__postInternalErrorMsg(resp,err);}
  1767. );
  1768. },
  1769. /* INTENT :
  1770. validate the associated AS model
  1771. IN PRACTICE:
  1772. adjust uri and forward to asworker
  1773. 1. setup sync/async action chaining
  1774. a) ask asworker to validate its model
  1775. 2. launch chain... return success code or error */
  1776. 'GET /validatem' :
  1777. function(resp,uri)
  1778. {
  1779. var actions = [__wHttpReq('GET',uri+'?wid='+this.__aswid)];
  1780. _do.chain(actions)(
  1781. function()
  1782. {
  1783. __postMessage({'statusCode':200, 'respIndex':resp});
  1784. },
  1785. function(err) {__postInternalErrorMsg(resp,err);}
  1786. );
  1787. },
  1788. /* INTENT :
  1789. undo/redo the effects of a client's last not yet undone/redone action
  1790. IN PRACTICE:
  1791. adjust uri and forward asworker undo/redos to asworker, and handle
  1792. csworker undo/redos locally
  1793. 1. if next __handledSeqNums is a non-csworker sequence#,
  1794. a) if it's a batchEdit marker:
  1795. i. adjust __handledSeqNums['i'] to account for multiple undos/redos
  1796. ii. populate reqData['redo/undoUntil'] with batchEdit marker s.t.
  1797. asworker knows to undo/redo everything til specified marker
  1798. iii. populate hitchhiker['redo/undo'] with batchEdit marker s.t. when
  1799. asworker undo/redo changelog comes around, csworkers know until
  1800. where they should undo/redo
  1801. iv. forward request to asworker and return success code or error
  1802. a) if it's an asworker marker:
  1803. i. populate hitchhiker['redo/undo'] with asworker marker s.t. when
  1804. asworker undo/redo changelog comes around, csworkers know until
  1805. where they should undo/redo
  1806. ii. forward request to asworker and return success code or error */
  1807. 'POST /undo' :
  1808. function(resp,uri,reqData/*[undoUntil]*/)
  1809. {
  1810. if( this.__handledSeqNums['i'] == undefined )
  1811. this.__handledSeqNums['i'] = this.__handledSeqNums['#s'].length-1;
  1812. if( this.__handledSeqNums['#s'][this.__handledSeqNums['i']] )
  1813. this.__undoredo(
  1814. resp,
  1815. uri,
  1816. (reqData != undefined && 'undoUntil' in reqData ?
  1817. reqData['undoUntil'] :
  1818. this.__handledSeqNums['#s'][this.__handledSeqNums['i']--]),
  1819. 'undo');
  1820. else
  1821. __postMessage({'statusCode':200, 'respIndex':resp});
  1822. },
  1823. 'POST /redo' :
  1824. function(resp,uri)
  1825. {
  1826. if( this.__handledSeqNums['i'] == undefined )
  1827. this.__handledSeqNums['i'] = this.__handledSeqNums['#s'].length-1;
  1828. if( this.__handledSeqNums['#s'][this.__handledSeqNums['i']+1] )
  1829. this.__undoredo(
  1830. resp,
  1831. uri,
  1832. this.__handledSeqNums['#s'][++this.__handledSeqNums['i']],
  1833. 'redo');
  1834. else
  1835. __postMessage({'statusCode':200, 'respIndex':resp});
  1836. },
  1837. '__undoredo' :
  1838. function(resp,uri,sn,func)
  1839. {
  1840. if( ! sn.match(get__wtype()) )
  1841. {
  1842. var hitchhiker = {},
  1843. reqData = {'hitchhiker':hitchhiker},
  1844. actions = [__wHttpReq(
  1845. 'POST',
  1846. uri+'?wid='+this.__aswid,
  1847. reqData) ];
  1848. if( (matches = sn.match(/^bchkpt@([0-9]+)/)) )
  1849. {
  1850. if( func == 'undo' )
  1851. {
  1852. for( ;
  1853. ! this.__handledSeqNums['#s'][this.__handledSeqNums['i']].
  1854. match('^bchkpt@'+matches[1]);
  1855. this.__handledSeqNums['i']-- )
  1856. ;
  1857. this.__handledSeqNums['i']--;
  1858. reqData[func+'Until'] = hitchhiker[func] =
  1859. __batchCheckpoint(matches[1],true);
  1860. }
  1861. else
  1862. {
  1863. this.__handledSeqNums['i']++;
  1864. for( ;
  1865. ! this.__handledSeqNums['#s'][this.__handledSeqNums['i']].
  1866. match('^bchkpt@'+matches[1]);
  1867. this.__handledSeqNums['i']++ )
  1868. ;
  1869. reqData[func+'Until'] = hitchhiker[func] =
  1870. __batchCheckpoint(matches[1]);
  1871. }
  1872. }
  1873. else
  1874. hitchhiker[func] = sn;
  1875. _do.chain( actions )(
  1876. function()
  1877. {
  1878. __postMessage({'statusCode':202, 'respIndex':resp});
  1879. },
  1880. function(err) {__postInternalErrorMsg(resp,err);}
  1881. );
  1882. }
  1883. else
  1884. __postMessage(
  1885. {'statusCode':200,
  1886. 'changelog':_mmmk[func](sn)['changelog'],
  1887. 'sequence#':__sequenceNumber(),
  1888. 'respIndex':resp});
  1889. },
  1890. /* INTENT :
  1891. place an easily identifiable user-checkpoint in the journal
  1892. IN PRACTICE:
  1893. adjust uri and forward to asworker
  1894. 1. setup sync/async action chaining
  1895. a) forward request to asworker
  1896. 2. launch chain... return success code or error */
  1897. 'POST /batchCheckpoint' :
  1898. function(resp,uri,reqData)
  1899. {
  1900. var actions = [
  1901. __wHttpReq('POST',uri+'?wid='+this.__aswid,reqData)];
  1902. _do.chain(actions)(
  1903. function()
  1904. {
  1905. __postMessage({'statusCode':202, 'respIndex':resp});
  1906. },
  1907. function(err) {__postInternalErrorMsg(resp,err);}
  1908. );
  1909. },
  1910. /********************************** UTILS **********************************/
  1911. /* wrapper around reads to '__asid2csid'... needed because loading a model
  1912. (as opposed to creating it from scratch) doesn't populate this data
  1913. structure... this wrapper enables its lazy and transparent population as
  1914. read queries are made */
  1915. '__asid_to_csid' :
  1916. function(asid)
  1917. {
  1918. if( this.__asid2csid[asid] != undefined )
  1919. return this.__asid2csid[asid];
  1920. var csm = _utils.jsonp(_mmmk.read());
  1921. for( var csid in csm.nodes )
  1922. if( __uri_to_id(_mmmk.read(csid,'$asuri')) == asid )
  1923. return (this.__asid2csid[asid] = csid);
  1924. },
  1925. /* return a fullcstype from a fullastype */
  1926. '__astype_to_cstype' :
  1927. function(fullastype,isLink)
  1928. {
  1929. var matches = fullastype.match(/(.*)\/(.*)/),
  1930. asmm = matches[1],
  1931. astype = matches[2];
  1932. return this.__asmm2csmm[asmm]+'/'+astype+(isLink ? 'Link' : 'Icon');
  1933. },
  1934. /* return the CS instance uri associated to the AS instance described by the
  1935. given uri */
  1936. '__asuri_to_csuri' :
  1937. function(uri)
  1938. {
  1939. if( (asid = __uri_to_id(uri))['$err'] )
  1940. return asid;
  1941. return __id_to_uri(this.__asid_to_csid(asid));
  1942. },
  1943. /* add a checkpointing marker in mmmk and log the said marker as a
  1944. non-undo/redo operation (remove any undone operations from log first) */
  1945. '__checkpointUserOperation' :
  1946. function(sn)
  1947. {
  1948. _mmmk.setUserCheckpoint(sn);
  1949. if( this.__handledSeqNums['i'] != undefined )
  1950. {
  1951. this.__handledSeqNums['#s'].splice( this.__handledSeqNums['i']+1 );
  1952. this.__handledSeqNums['i'] = undefined;
  1953. }
  1954. this.__handledSeqNums['#s'].push(sn);
  1955. },
  1956. /* produce a bundle of internal state variables sufficient to fully clone
  1957. this instance
  1958. OR
  1959. use a provided bundle to overwrite this instance's internal state */
  1960. '__clone' :
  1961. function(clone)
  1962. {
  1963. if( clone )
  1964. {
  1965. this.__asmm2csmm = clone.__asmm2csmm;
  1966. this.__asid2csid = clone.__asid2csid;
  1967. this.__aswid = clone.__aswid;
  1968. this.__handledSeqNums = clone.__handledSeqNums;
  1969. this.__nextASWSequenceNumber = clone.__nextASWSequenceNumber;
  1970. this.__pendingChangelogs = clone.__pendingChangelogs;
  1971. this.__hitchhikerJournal = clone.__hitchhikerJournal;
  1972. }
  1973. else
  1974. return _utils.clone(
  1975. {'__asmm2csmm': this.__asmm2csmm,
  1976. '__asid2csid': this.__asid2csid,
  1977. '__aswid': this.__aswid,
  1978. '__handledSeqNums': this.__handledSeqNums,
  1979. '__nextASWSequenceNumber': this.__nextASWSequenceNumber,
  1980. '__pendingChangelogs': this.__pendingChangelogs,
  1981. '__hitchhikerJournal': this.__hitchhikerJournal});
  1982. },
  1983. /* return the AS instance uri associated to the CS instance described by the
  1984. given uri */
  1985. '__csuri_to_asuri' :
  1986. function(uri)
  1987. {
  1988. if( (csid = __uri_to_id(uri))['$err'] )
  1989. return csid;
  1990. else if( (asuri = _mmmk.read(csid,'$asuri'))['$err'] )
  1991. return asuri;
  1992. return asuri;
  1993. },
  1994. /* compute 'default' segments between the given icons (specified via uri) */
  1995. '__defaultSegments' :
  1996. function(src,dest)
  1997. {
  1998. var pos1 = _mmmk.read(__uri_to_id(src),'position'),
  1999. pos2 = _mmmk.read(__uri_to_id(dest),'position'),
  2000. middle = [pos2[0]-(pos2[0]-pos1[0])/2.0,
  2001. pos2[1]-(pos2[1]-pos1[1])/2.0];
  2002. return ['M'+pos1+'L'+middle, 'M'+middle+'L'+pos2];
  2003. },
  2004. /* compute the x,y center of the icons given by the specified AS uris */
  2005. '__nodesCenter' :
  2006. function(asuris)
  2007. {
  2008. var sumx = 0,
  2009. sumy = 0,
  2010. self = this;
  2011. asuris.forEach(
  2012. function(asuri)
  2013. {
  2014. var asid = __uri_to_id(asuri),
  2015. csid = self.__asid_to_csid(asid),
  2016. pos = _mmmk.read(csid,'position');
  2017. sumx += parseFloat(pos[0]);
  2018. sumy += parseFloat(pos[1]);
  2019. });
  2020. return [sumx/asuris.length, sumy/asuris.length];
  2021. },
  2022. /* run given code within given contexts
  2023. 1. setup a very stripped down version of _mmmk.__runDesignerCode() with
  2024. getAttr() and safe_eval() (see _mmmk.__runDesignerCode() for more
  2025. elaborate comments)
  2026. 2. safely evaluate code and return result
  2027. NOTE: getAttr() first checks the 'local' context and then the 'global'
  2028. context to find requested attributes... this enables such behaviours
  2029. as accessing 'new' attribute values as opposed to those stored in
  2030. _mmmk */
  2031. '__runParser' :
  2032. function(parser,local,global)
  2033. {
  2034. function getAttr(_attr)
  2035. {
  2036. if( _attr in local )
  2037. return local[_attr];
  2038. else if( !(_attr in global) || _attr.charAt(0) == '$')
  2039. throw 'invalid getAttr() attribute :: '+_attr;
  2040. return _utils.clone(global[_attr]['value']);
  2041. }
  2042. function safe_eval(code)
  2043. {
  2044. try {return eval(code);}
  2045. catch(err) {return {'$err':err};}
  2046. }
  2047. return safe_eval(parser);
  2048. },
  2049. /* returns the numeric part of sequence# of the form 'src#number' */
  2050. '__sn2int' :
  2051. function(sn)
  2052. {
  2053. return parseInt(sn.match(/.*#(\d*)/)[1]);
  2054. }
  2055. };