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