92d31081364efbf75b5c3e0eeda651b1ddcbad79
[contrib/cloudeebus.git] / cloudeebus / cloudeebus.js
1 /******************************************************************************
2  * Copyright 2012 Intel Corporation.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  * http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *****************************************************************************/
16
17
18
19 /*****************************************************************************/
20
21 var dbus = { // hook object for dbus types not translated by python-json
22                 Double: function(value, level) {
23                         return value;
24                 }
25 };
26
27
28
29 /*****************************************************************************/
30
31 var cloudeebus = window.cloudeebus = {
32                 version: "0.5.99",
33                 minVersion: "0.3.2"
34 };
35
36 cloudeebus.reset = function() {
37         cloudeebus.sessionBus = null;
38         cloudeebus.systemBus = null;
39         cloudeebus.wampSession = null;
40         cloudeebus.uri = null;
41 };
42
43
44 cloudeebus.log = function(msg) { 
45 };
46
47 cloudeebus.getError = function(error) {
48         if (error.desc && error.uri)
49                 return error.desc + " : " + error.uri; // error cloudeebus (from python)
50         if (error.desc)
51                 return error.desc;
52         if (error.uri)
53                 return error.uri;
54         if (error.name && error.message)
55                 return error.name + " : " + error.message;
56         if (error.message)
57                 return error.message;
58         if (error.name)
59                 return error.name;
60         return error; // error from Autobahn
61 };
62
63 cloudeebus.versionCheck = function(version) {
64         var ver = version.split(".");
65         var min = cloudeebus.minVersion.split(".");
66         for (var i=0; i<ver.length; i++) {
67                 if (Number(ver[i]) > Number(min[i]))
68                         return true;
69                 if (Number(ver[i]) < Number(min[i]))
70                         return false;
71         }
72         return true;
73 };
74
75
76 cloudeebus.connect = function(uri, manifest, successCB, errorCB) {
77         cloudeebus.reset();
78         cloudeebus.uri = uri;
79         
80         function onCloudeebusVersionCheckCB(version) {
81                 if (cloudeebus.versionCheck(version)) {
82                         cloudeebus.log("Connected to " + cloudeebus.uri);
83                         if (successCB)
84                                 successCB();
85                 } else {
86                         var errorMsg = "Cloudeebus server version " + version + " unsupported, need version " + cloudeebus.minVersion + " or superior";
87                         cloudeebus.log(errorMsg);
88                         if (errorCB)
89                                 errorCB(errorMsg);
90                 }
91         }
92         
93         function onWAMPSessionAuthErrorCB(error) {
94                 var errorStr = cloudeebus.getError(error);
95                 cloudeebus.log("Authentication error: " + errorStr);
96                 if (errorCB)
97                         errorCB(errorStr);
98         }
99         
100         function onWAMPSessionAuthenticatedCB(permissions) {
101                 cloudeebus.sessionBus = new cloudeebus.BusConnection("session", cloudeebus.wampSession);
102                 cloudeebus.systemBus = new cloudeebus.BusConnection("system", cloudeebus.wampSession);
103                 cloudeebus.wampSession.call("getVersion").then(onCloudeebusVersionCheckCB, errorCB);
104         }
105         
106         function onWAMPSessionChallengedCB(challenge) {
107                 var signature = cloudeebus.wampSession.authsign(challenge, manifest.key);
108                 cloudeebus.wampSession.auth(signature).then(onWAMPSessionAuthenticatedCB, onWAMPSessionAuthErrorCB);
109         }
110         
111         function onWAMPSessionConnectedCB(session) {
112                 cloudeebus.wampSession = session;
113                 if (manifest) {
114                         cloudeebus.wampSession.authreq(
115                                         manifest.name, 
116                                         {permissions: manifest.permissions, 
117                                                  services: manifest.services}
118                                 ).then(onWAMPSessionChallengedCB, onWAMPSessionAuthErrorCB);
119                 }
120                 else
121                         cloudeebus.wampSession.authreq().then(function() {
122                                 cloudeebus.wampSession.auth().then(onWAMPSessionAuthenticatedCB, onWAMPSessionAuthErrorCB);
123                                 }, onWAMPSessionAuthErrorCB);
124         }
125
126         function onWAMPSessionErrorCB(code, reason) {
127                 if (code == ab.CONNECTION_UNSUPPORTED) {
128                         cloudeebus.log("Browser is not supported");
129                 }
130                 else {
131                         cloudeebus.log("Failed to open session, code = " + code + ", reason = " + reason);
132                 }
133                 if (errorCB)
134                         errorCB(reason);
135         }
136
137         return ab.connect(cloudeebus.uri, onWAMPSessionConnectedCB, onWAMPSessionErrorCB);
138 };
139
140
141 cloudeebus.SessionBus = function() {
142         return cloudeebus.sessionBus;
143 };
144
145
146 cloudeebus.SystemBus = function() {
147         return cloudeebus.systemBus;
148 };
149
150
151
152 /*****************************************************************************/
153
154 cloudeebus.BusConnection = function(name, session) {
155         this.name = name;
156         this.wampSession = session;
157         return this;
158 };
159
160
161 cloudeebus.BusConnection.prototype.getObject = function(busName, objectPath, introspectCB, errorCB) {
162         var proxy = new cloudeebus.ProxyObject(this.wampSession, this, busName, objectPath);
163         if (introspectCB)
164                 proxy._introspect(introspectCB, errorCB);
165         return proxy;
166 };
167
168
169 cloudeebus.BusConnection.prototype.addService = function(serviceName) {
170         var self = this;
171
172         if (!serviceName)
173                 serviceName = "";
174         
175         var promise = new cloudeebus.Promise(function (resolver) {
176                 var cloudeebusService = new cloudeebus.Service(self.wampSession, self, serviceName);
177         
178                 function ServiceAddedSuccessCB(serviceName) {
179                         try {
180                                 cloudeebusService.isCreated = true;
181                                 resolver.fulfill(cloudeebusService, true);
182                         }
183                         catch (e) {
184                                 var errorStr = cloudeebus.getError(e);
185                                 cloudeebus.log("Method callback exception: " + errorStr);
186                                 resolver.reject(errorStr, true);
187                         }               
188                 }
189                 
190                 function ServiceAddedErrorCB(error) {
191                         var errorStr = cloudeebus.getError(error);
192                         cloudeebus.log("Error adding service method: " + self.name + ", error: " + errorStr);
193                         resolver.reject(errorStr, true);
194                 }
195
196                 var arglist = [
197                     self.name,
198                     serviceName
199                     ];
200
201                 // call dbusSend with bus type, destination, object, message and arguments
202                 self.wampSession.call("serviceAdd", arglist).then(ServiceAddedSuccessCB, ServiceAddedErrorCB);
203         });
204         
205         return promise;
206 };
207
208
209
210 /*****************************************************************************/
211 //Generic definition for an agent. An agent needs :
212 //srvDbusName : the DBus parent service
213 //objPath : a DBus path to access it
214 //jsHdl : a Javascript handler to process methods, 
215 //xml : the xml which describe interface/methods/signals...
216 cloudeebus.Agent = function(srvDbusName, objPath, jsHdl, xml) {
217         this.srvName = srvDbusName;
218         this.registered = false;
219         this.xml = xml;
220         this.objectPath = objPath;
221         this.jsHdl = jsHdl;
222         return this;
223 };
224
225
226 cloudeebus.Service = function(session, busConnection, name) {
227         this.wampSession = session;
228         this.busConnection = busConnection; 
229         this.name = name;
230         this.agents = [];
231         this.isCreated = false;
232         return this;
233 };
234
235
236 cloudeebus.Service.prototype.remove = function() {
237         var self = this;
238         
239         var promise = new cloudeebus.Promise(function (resolver) {
240                 function ServiceRemovedSuccessCB(serviceName) {
241                         try {
242                                 resolver.fulfill(serviceName, true);
243                         }
244                         catch (e) {
245                                 var errorStr = cloudeebus.getError(e);
246                                 cloudeebus.log("Method callback exception: " + errorStr);
247                                 resolver.reject(errorStr, true);
248                         }               
249                 }
250                 
251                 function ServiceRemovedErrorCB(error) {
252                         var errorStr = cloudeebus.getError(error);
253                         cloudeebus.log("Error removing service : " + self.name + ", error: " + errorStr);
254                         self.promise.resolver.reject(errorStr, true);
255                 }
256                 
257                 for (var idx in self.agents) {
258                         if (self.agents[idx]) {
259                                 self.removeAgent(self.agents[idx]);
260                         }
261                 }
262                 
263                 var arglist = [
264                     self.name
265                     ];
266         
267                 // call dbusSend with bus type, destination, object, message and arguments
268                 self.wampSession.call("serviceRelease", arglist).then(ServiceRemovedSuccessCB, ServiceRemovedErrorCB);
269         });
270         
271         return promise;
272 };
273
274
275 cloudeebus.Service.prototype._searchMethod = function(ifName, method, objectJS) {
276
277         var funcToCall = null;
278         
279         // Check if 'objectJS' has a member 'interfaceProxies' with an interface named 'ifName' 
280         // and a method named 'method'
281         if (objectJS.interfaceProxies && objectJS.interfaceProxies[ifName] &&
282                 objectJS.interfaceProxies[ifName][method]) {
283                 funcToCall = objectJS.interfaceProxies[ifName][method];
284         } else {
285                 // retrieve the method directly from 'root' of objectJs
286                 funcToCall = objectJS[method];
287         }
288
289         return funcToCall;
290 };
291
292
293 cloudeebus.Service.prototype._addMethod = function(ifName, method, agent) {
294
295         var service = this;
296         var methodId = this.name + "#" + agent.objectPath + "#" + ifName + "#" + method;
297         var funcToCall = this._searchMethod(ifName, method, agent.jsHdl);
298
299         if (funcToCall == null)
300                 cloudeebus.log("Method " + method + " doesn't exist in Javascript object");
301         else {
302                 agent.jsHdl.wrapperFunc[method] = function() {
303                         var result;
304                         var methodId = arguments[0];
305                         var callDict = {};
306                         // affectation of callDict in eval, otherwise dictionary(='{}') interpreted as block of code by eval
307                         eval("callDict = " + arguments[1]);
308                         try {
309                                 result = funcToCall.apply(agent.jsHdl, callDict.args);
310                                 service._returnMethod(methodId, callDict.callIndex, true, result);
311                         }
312                         catch (e) {
313                                 var errorStr = cloudeebus.getError(e);
314                                 cloudeebus.log("Method " + ifName + "." + method + " call on " + agent.objectPath + " exception: " + errorStr);
315                                 service._returnMethod(methodId, callDict.callIndex, false, errorStr);
316                         }
317                 };
318                 agent.jsHdl.methodId[agent.objectPath].push(methodId);
319                 this.wampSession.subscribe(methodId, agent.jsHdl.wrapperFunc[method]);
320         }
321 };
322
323
324 cloudeebus.Service.prototype._addSignal = function(ifName, signal, agent) {
325         var service = this;
326         var methodExist = false;
327
328         if (agent.jsHdl.interfaceProxies && agent.jsHdl.interfaceProxies[ifName])
329                 if (agent.jsHdl.interfaceProxies[ifName][signal]) {
330                         methodExist = true;
331                 } else {
332                         agent.jsHdl.interfaceProxies[ifName][signal] = function() {
333                                 service._emitSignal(agent.objectPath, signal, arguments[0]);
334                         };
335                 return;
336         }
337                 
338         if ((agent.jsHdl[signal] == undefined || agent.jsHdl[signal] == null) && !methodExist) 
339                 agent.jsHdl[signal] = function() {
340                         service._emitSignal(agent.objectPath, signal, arguments[0]);
341                 };
342         else
343                 cloudeebus.log("Can not create new method to emit signal '" + signal + "' in object JS this method already exist!");
344 };
345
346
347 cloudeebus.Service.prototype._createWrapper = function(agent) {
348         var self = this;
349         var parser = new DOMParser();
350         var xmlDoc = parser.parseFromString(agent.xml, "text/xml");
351         var ifXml = xmlDoc.getElementsByTagName("interface");
352         agent.jsHdl.wrapperFunc = [];
353         agent.jsHdl.methodId = [];
354         agent.jsHdl.methodId[agent.objectPath] = [];
355         for (var i=0; i < ifXml.length; i++) {
356                 var ifName = ifXml[i].attributes.getNamedItem("name").value;
357                 var ifChild = ifXml[i].firstChild;
358                 while (ifChild) {
359                         if (ifChild.nodeName == "method") {
360                                 var metName = ifChild.attributes.getNamedItem("name").value;
361                                 self._addMethod(ifName, metName, agent);
362                         }
363                         if (ifChild.nodeName == "signal") {
364                                 var metName = ifChild.attributes.getNamedItem("name").value;
365                                 self._addSignal(ifName, metName, agent);
366                         }
367                         ifChild = ifChild.nextSibling;
368                 }
369         }
370 };
371
372
373 cloudeebus.Service.prototype.addAgent = function(agent) {
374         var self = this;
375         
376         var promise = new cloudeebus.Promise(function (resolver) {
377                 function ServiceAddAgentSuccessCB(objPath) {
378                         try { // calling dbus hook object function for un-translated types
379                                 self.agents.push(agent);
380                                 agent.registered = true;
381                                 resolver.fulfill(objPath, true);
382                         }
383                         catch (e) {
384                                 var errorStr = cloudeebus.getError(e);
385                                 cloudeebus.log("Method callback exception: " + errorStr);
386                                 resolver.reject(errorStr, true);
387                         }               
388                 }
389                 
390                 function ServiceAddAgenterrorCB(error) {
391                         var errorStr = cloudeebus.getError(error);
392                         cloudeebus.log("Error adding agent : " + agent.objectPath + ", error: " + errorStr);
393                         self.promise.resolver.reject(errorStr, true);
394                 }
395                 
396                 try {
397                         self._createWrapper(agent);
398                 }
399                 catch (e) {
400                         var errorStr = cloudeebus.getError(e);
401                         cloudeebus.log("Exception creating agent wrapper " + agent.objectPath + " : " + errorStr);
402                         resolver.reject(errorStr, true);
403                         return;
404                 }
405                 
406                 var arglist = [
407                     agent.objectPath,
408                     agent.xml
409                     ];
410         
411                 // call dbusSend with bus type, destination, object, message and arguments
412                 self.wampSession.call("serviceAddAgent", arglist).then(ServiceAddAgentSuccessCB, ServiceAddAgenterrorCB);
413         });
414         
415         return promise;
416 };
417
418
419 cloudeebus.Service.prototype._deleteWrapper = function(agent) {
420         var objJs = agent.jsHdl;
421         if (objJs.methodId[agent.objectPath]) {
422                 for (var idx in objJs.methodId[agent.objectPath]) {
423                         try {
424                                 cloudeebus.log("unsubscribe " + objJs.methodId[agent.objectPath][idx]);
425                                 this.wampSession.unsubscribe(objJs.methodId[agent.objectPath][idx]);
426                                 objJs.methodId[agent.objectPath][idx] = null;
427                         }
428                         catch (e) {
429                                 cloudeebus.log("Unsubscribe error: " + cloudeebus.getError(e));
430                         }
431                 }
432                 delete objJs.methodId[agent.objectPath];
433         }
434 };
435
436
437 cloudeebus.Service.prototype.removeAgent = function(rmAgent) {
438         var self = this;
439         
440         var promise = new cloudeebus.Promise(function (resolver) {
441                 function ServiceRemoveAgentSuccessCB(agent) {
442                         try { // calling dbus hook object function for un-translated types
443                                 self.agents.pop(agent);
444                                 agent.registered = false;
445                                 resolver.fulfill(agent, true);
446                         }
447                         catch (e) {
448                                 var errorStr = cloudeebus.getError(e);
449                                 cloudeebus.log("Method callback exception: " + errorStr);
450                                 resolver.reject(errorStr, true);
451                         }               
452                 }
453
454                 function ServiceRemoveAgentErrorCB(error) {
455                         var errorStr = cloudeebus.getError(error);
456                         cloudeebus.log("Error removing agent : " + rmAgent.objectPath + ", error: " + errorStr);
457                         self.promise.resolver.reject(errorStr, true);
458                 }
459
460                 try {
461                         self._deleteWrapper(rmAgent);
462                 }
463                 catch (e) {
464                         var errorStr = cloudeebus.getError(e);
465                         cloudeebus.log("Exception removing wrapper of agent " + rmAgent.objectPath + " : " + errorStr);
466                         errorCB(errorStr);
467                 }
468                 
469                 var arglist = [
470                     rmAgent.objectPath
471                     ];
472         
473                 // call dbusSend with bus type, destination, object, message and arguments
474                 self.wampSession.call("serviceDelAgent", arglist).then(ServiceRemoveAgentSuccessCB, ServiceRemoveAgentErrorCB);
475         });
476         
477         return promise;
478 };
479
480
481 cloudeebus.Service.prototype._returnMethod = function(methodId, callIndex, success, result, successCB, errorCB) {
482         var arglist = [
483             methodId,
484             callIndex,
485             success,
486             result
487             ];
488
489         this.wampSession.call("returnMethod", arglist).then(successCB, errorCB);
490 };
491
492
493 cloudeebus.Service.prototype._emitSignal = function(objectPath, signalName, result, successCB, errorCB) {
494         var arglist = [
495             objectPath,
496             signalName,
497             result
498             ];
499
500         this.wampSession.call("emitSignal", arglist).then(successCB, errorCB);
501 };
502
503
504
505 /*****************************************************************************/
506
507 function _processWrappers(wrappers, value) {
508         for (var i=0; i<wrappers.length; i++)
509                 wrappers[i](value);
510 }
511
512
513 function _processWrappersAsync(wrappers, value) {
514         var taskid = -1;
515         function processAsyncOnce() {
516                 _processWrappers(wrappers, value);
517                 clearInterval(taskid);
518         }
519         taskid = setInterval(processAsyncOnce, 200);
520 }
521
522
523
524 /*****************************************************************************/
525
526 cloudeebus.PromiseResolver = function(promise) {
527         this.promise = promise;
528         this.resolved = null;
529     return this;
530 };
531
532
533 cloudeebus.PromiseResolver.prototype.resolve = function(value, sync) {
534         if (this.resolved)
535                 return;
536         
537         var then = (value && value.then && value.then.apply) ? value.then : null;
538         if (then) {
539                 var self = this;                
540                 var fulfillCallback = function(arg) {
541                         self.resolve(arg, true);
542                 };      
543                 var rejectCallback = function(arg) {
544                         self.reject(arg, true);
545                 };
546                 try {
547                         then.apply(value, [fulfillCallback, rejectCallback]);
548                 }
549                 catch (e) {
550                         this.reject(cloudeebus.getError(e), true);
551                 }
552         }
553         
554         this.fulfill(value, sync);
555 };
556
557
558 cloudeebus.PromiseResolver.prototype.fulfill = function(value, sync) {
559         if (this.resolved)
560                 return;
561         
562         var promise = this.promise;
563         promise.state = "fulfilled";
564         promise.result = value;
565         
566         this.resolved = true;
567         if (sync)
568                 _processWrappers(promise._fulfillWrappers, value);
569         else
570                 _processWrappersAsync(promise._fulfillWrappers, value);
571 };
572
573
574 cloudeebus.PromiseResolver.prototype.reject = function(value, sync) {
575         if (this.resolved)
576                 return;
577         
578         var promise = this.promise;
579         promise.state = "rejected";
580         promise.result = value;
581         
582         this.resolved = true;
583         if (sync)
584                 _processWrappers(promise._rejectWrappers, value);
585         else
586                 _processWrappersAsync(promise._rejectWrappers, value);
587 };
588
589
590
591 /*****************************************************************************/
592
593 cloudeebus.Promise = function(init) {
594         this.state = "pending";
595         this.result = null;
596         this._fulfillWrappers = [];
597         this._rejectWrappers = [];
598         this.resolver = new cloudeebus.PromiseResolver(this);
599         if (init) {
600                 try {
601                         init.apply(this, [this.resolver]);
602                 }
603                 catch (e) {
604                         this.resolver.reject(cloudeebus.getError(e), true);
605                 }
606         }
607     return this;
608 };
609
610
611 cloudeebus.Promise.prototype.appendWrappers = function(fulfillWrapper, rejectWrapper) {
612         if (fulfillWrapper)
613                 this._fulfillWrappers.push(fulfillWrapper);
614         if (rejectWrapper)
615                 this._rejectWrappers.push(rejectWrapper);
616         if (this.state == "fulfilled")
617                 _processWrappersAsync(this._fulfillWrappers, this.result);
618         if (this.state == "rejected")
619                 _processWrappersAsync(this._rejectWrappers, this.result);
620 };
621
622
623 cloudeebus.Promise.prototype.then = function(fulfillCB, rejectCB) {
624         var promise = new cloudeebus.Promise();
625         var resolver = promise.resolver;
626         var fulfillWrapper, rejectWrapper;
627         
628         if (fulfillCB)
629                 fulfillWrapper = function(arg) {
630                         try {
631                                 var value = fulfillCB.apply(promise, [arg]);
632                                 resolver.resolve(value, true);
633                         }
634                         catch (e) {
635                                 resolver.reject(cloudeebus.getError(e), true);
636                         }
637                 };
638         else
639                 fulfillWrapper = function(arg) {
640                         resolver.fulfill(arg, true);
641                 };
642         
643         if (rejectCB)
644                 rejectWrapper = function(arg) {
645                         try {
646                                 var value = rejectCB.apply(promise, [arg]);
647                                 resolver.resolve(value, true);
648                         }
649                         catch (e) {
650                                 resolver.reject(cloudeebus.getError(e), true);
651                         }
652                 };
653         else
654                 rejectWrapper = function(arg) {
655                         resolver.reject(arg, true);
656                 };
657         
658         this.appendWrappers(fulfillWrapper,rejectWrapper);
659         return promise;
660 };
661
662
663 cloudeebus.Promise.prototype["catch"] = function(rejectCB) {
664         return this.then(undefined,rejectCB);
665 };
666
667
668 cloudeebus.Promise.prototype.done = function(fulfillCB, rejectCB) {
669         this.appendWrappers(fulfillCB,rejectCB);
670 };
671
672
673 cloudeebus.Promise.resolve = function(value) {
674         var promise = new cloudeebus.Promise();
675         promise.resolver.resolve(value);
676         return promise;
677 };
678
679
680 cloudeebus.Promise.fulfill = function(value) {
681         var promise = new cloudeebus.Promise();
682         promise.resolver.fulfill(value);
683         return promise;
684 };
685
686
687 cloudeebus.Promise.reject = function(value) {
688         var promise = new cloudeebus.Promise();
689         promise.resolver.reject(value);
690         return promise;
691 };
692
693
694 cloudeebus.Promise.any = function() {
695         var promise = new cloudeebus.Promise();
696         var resolver = promise.resolver;
697         var fulfillCallback = function(arg) {
698                 resolver.resolve(arg, true);
699         };
700         var rejectCallback = function(arg) {
701                 resolver.reject(arg, true);
702         };
703         if (arguments.length == 0)
704                 resolver.resolve(undefined, true);
705         else
706                 for (i in arguments) 
707                         Promise.resolve(arguments[i]).appendWrappers(fulfillCallback,rejectCallback);
708         return promise;
709 };
710
711
712 cloudeebus.Promise.every = function() {
713         var promise = new cloudeebus.Promise();
714         var resolver = promise.resolver;
715         var index = 0;
716         var countdown = arguments.length;
717         var args = new Array(countdown);
718         var rejectCallback = function(arg) {
719                 resolver.reject(arg, true);
720         };
721         if (arguments.length == 0)
722                 resolver.resolve(undefined, true);
723         else
724                 for (i in arguments) {
725                         var fulfillCallback = function(arg) {
726                                 args[index] = arg;
727                                 countdown--;
728                                 if (countdown == 0)
729                                         resolver.resolve(args, true);
730                         };
731                         index++;
732                         Promise.resolve(arguments[i]).appendWrappers(fulfillCallback,rejectCallback);
733                 }
734         
735         return promise;
736 };
737
738
739 cloudeebus.Promise.some = function() {
740         var promise = new cloudeebus.Promise();
741         var resolver = promise.resolver;
742         var index = 0;
743         var countdown = arguments.length;
744         var args = new Array(countdown);
745         var fulfillCallback = function(arg) {
746                 resolver.resolve(arg, true);
747         };
748         if (arguments.length == 0)
749                 resolver.resolve(undefined, true);
750         else
751                 for (i in arguments) {
752                         var rejectCallback = function(arg) {
753                                 args[index] = arg;
754                                 countdown--;
755                                 if (countdown == 0)
756                                         resolver.reject(args, true);
757                         };
758                         index++;
759                         Promise.resolve(arguments[i]).appendWrappers(fulfillCallback,rejectCallback);
760                 }
761         
762         return promise;
763 };
764
765
766
767 /*****************************************************************************/
768
769 cloudeebus.ProxyObject = function(session, busConnection, busName, objectPath) {
770         this.wampSession = session; 
771         this.busConnection = busConnection; 
772         this.busName = busName; 
773         this.objectPath = objectPath; 
774         this.interfaceProxies = {};
775         return this;
776 };
777
778
779 cloudeebus.ProxyObject.prototype.getInterface = function(ifName) {
780         return this.interfaceProxies[ifName];
781 };
782
783
784 cloudeebus.ProxyObject.prototype._introspect = function(successCB, errorCB) {
785         
786         var self = this; 
787
788         function getAllPropertiesSuccessCB(props) {
789                 var ifProxy = self.interfaceProxies[self.propInterfaces[self.propInterfaces.length-1]];
790                 for (var prop in props)
791                         ifProxy[prop] = self[prop] = props[prop];
792                 getAllPropertiesNextInterfaceCB();
793         }
794         
795         function getAllPropertiesNextInterfaceCB() {
796                 self.propInterfaces.pop();
797                 if (self.propInterfaces.length > 0) 
798                         self.callMethod("org.freedesktop.DBus.Properties", 
799                                 "GetAll", 
800                                 [self.propInterfaces[self.propInterfaces.length-1]]).then(getAllPropertiesSuccessCB, 
801                                 errorCB ? errorCB : getAllPropertiesNextInterfaceCB);
802                 else {
803                         self.propInterfaces = null;
804                         if (successCB)
805                                 successCB(self);
806                 }
807         }
808         
809         function introspectSuccessCB(str) {
810                 var parser = new DOMParser();
811                 var xmlDoc = parser.parseFromString(str, "text/xml");
812                 var interfaces = xmlDoc.getElementsByTagName("interface");
813                 self.propInterfaces = [];
814                 var supportDBusProperties = false;
815                 for (var i=0; i < interfaces.length; i++) {
816                         var ifName = interfaces[i].attributes.getNamedItem("name").value;
817                         self.interfaceProxies[ifName] = new cloudeebus.ProxyObject(self.wampSession, self.busConnection, self.busName, self.objectPath);
818                         if (ifName == "org.freedesktop.DBus.Properties")
819                                 supportDBusProperties = true;
820                         var hasProperties = false;
821                         var ifChild = interfaces[i].firstChild;
822                         while (ifChild) {
823                                 if (ifChild.nodeName == "method") {
824                                         var nArgs = 0;
825                                         var signature = "";
826                                         var metChild = ifChild.firstChild;
827                                         while (metChild) {
828                                                 if (metChild.nodeName == "arg" &&
829                                                         metChild.attributes.getNamedItem("direction").value == "in") {
830                                                                 signature += metChild.attributes.getNamedItem("type").value;
831                                                                 nArgs++;
832                                                 }
833                                                 metChild = metChild.nextSibling;
834                                         }
835                                         var metName = ifChild.attributes.getNamedItem("name").value;
836                                         if (!self[metName])
837                                                 self._addMethod(ifName, metName, nArgs, signature);
838                                         self.interfaceProxies[ifName]._addMethod(ifName, metName, nArgs, signature);
839                                 }
840                                 else if (ifChild.nodeName == "property") {
841                                         if (!hasProperties)
842                                                 self.propInterfaces.push(ifName);
843                                         hasProperties = true;
844                                 }
845                                 ifChild = ifChild.nextSibling;
846                         }
847                 }
848                 if (supportDBusProperties && self.propInterfaces.length > 0) {
849                         self.callMethod("org.freedesktop.DBus.Properties", 
850                                 "GetAll", 
851                                 [self.propInterfaces[self.propInterfaces.length-1]]).then(getAllPropertiesSuccessCB, 
852                                 errorCB ? errorCB : getAllPropertiesNextInterfaceCB);
853                 }
854                 else {
855                         self.propInterfaces = null;
856                         if (successCB)
857                                 successCB(self);
858                 }
859         }
860
861         // call Introspect on self
862         self.callMethod("org.freedesktop.DBus.Introspectable", "Introspect", []).then(introspectSuccessCB, errorCB);
863 };
864
865
866 cloudeebus.ProxyObject.prototype._addMethod = function(ifName, method, nArgs, signature) {
867
868         var self = this;
869         
870         self[method] = function() {
871                 var args = [];
872                 for (var i=0; i < nArgs; i++ )
873                         args.push(arguments[i]);
874                 return self.callMethod(ifName, method, args, signature);
875         };      
876 };
877
878
879 cloudeebus.ProxyObject.prototype.callMethod = function(ifName, method, args, signature) {
880         
881         var self = this;
882         
883         var promise = new cloudeebus.Promise(function (resolver) {
884                 function callMethodSuccessCB(str) {
885                         try { // calling dbus hook object function for un-translated types
886                                 var result = eval(str);
887                                 resolver.fulfill(result[0], true);
888                         }
889                         catch (e) {
890                                 var errorStr = cloudeebus.getError(e);
891                                 cloudeebus.log("Method callback exception: " + errorStr);
892                                 resolver.reject(errorStr, true);
893                         }
894                 }
895
896                 function callMethodErrorCB(error) {
897                         var errorStr = cloudeebus.getError(error);
898                         cloudeebus.log("Error calling method: " + method + " on object: " + self.objectPath + " : " + errorStr);
899                         resolver.reject(errorStr, true);
900                 }
901
902                 var arglist = [
903                         self.busConnection.name,
904                         self.busName,
905                         self.objectPath,
906                         ifName,
907                         method,
908                         JSON.stringify(args)
909                 ];
910
911                 // call dbusSend with bus type, destination, object, message and arguments
912                 self.wampSession.call("dbusSend", arglist).then(callMethodSuccessCB, callMethodErrorCB);
913         });
914         
915         return promise;
916 };
917
918
919 cloudeebus.ProxyObject.prototype.connectToSignal = function(ifName, signal, handlerCB, errorCB) {
920         
921         var self = this; 
922
923         function signalHandler(id, data) {
924                 if (handlerCB) {
925                         try { // calling dbus hook object function for un-translated types
926                                 handlerCB.apply(self, eval(data));
927                         }
928                         catch (e) {
929                                 var errorStr = cloudeebus.getError(e);
930                                 cloudeebus.log("Signal handler exception: " + errorStr);
931                                 if (errorCB)
932                                         errorCB(errorStr);
933                         }
934                 }
935         }
936         
937         function connectToSignalSuccessCB(str) {
938                 try {
939                         self.wampSession.subscribe(str, signalHandler);
940                 }
941                 catch (e) {
942                         cloudeebus.log("Subscribe error: " + cloudeebus.getError(e));
943                 }
944         }
945
946         function connectToSignalErrorCB(error) {
947                 var errorStr = cloudeebus.getError(error);
948                 cloudeebus.log("Error connecting to signal: " + signal + " on object: " + self.objectPath + " : " + errorStr);
949                 if (errorCB)
950                         errorCB(errorStr);
951         }
952
953         var arglist = [
954                 self.busConnection.name,
955                 self.busName,
956                 self.objectPath,
957                 ifName,
958                 signal
959         ];
960
961         // call dbusSend with bus type, destination, object, message and arguments
962         self.wampSession.call("dbusRegister", arglist).then(connectToSignalSuccessCB, connectToSignalErrorCB);
963 };
964
965
966 cloudeebus.ProxyObject.prototype.disconnectSignal = function(ifName, signal) {
967         try {
968                 this.wampSession.unsubscribe(this.busConnection.name + "#" + this.busName + "#" + this.objectPath + "#" + ifName + "#" + signal);
969         }
970         catch (e) {
971                 cloudeebus.log("Unsubscribe error: " + cloudeebus.getError(e));
972         }
973 };