error.uri is autobahn-related, not needed here
[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, successCB, errorCB) {
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.push(agent);
441                                 agent.registered = true;
442                                 var result = [ agent ];
443                                 resolver.fulfill(result[0], true);
444                         }
445                         catch (e) {
446                                 var errorStr = cloudeebus.getError(e);
447                                 cloudeebus.log("Method callback exception: " + errorStr);
448                                 resolver.reject(errorStr, true);
449                         }               
450                 }
451
452                 function ServiceRemoveAgentErrorCB(error) {
453                         var errorStr = cloudeebus.getError(error);
454                         cloudeebus.log("Error removing agent : " + rmAgent.objectPath + ", error: " + errorStr);
455                         self.promise.resolver.reject(errorStr, true);
456                 }
457
458                 try {
459                         self._deleteWrapper(rmAgent);
460                 }
461                 catch (e) {
462                         var errorStr = cloudeebus.getError(e);
463                         cloudeebus.log("Exception removing wrapper of agent " + rmAgent.objectPath + " : " + errorStr);
464                         errorCB(errorStr);
465                 }
466                 
467                 var arglist = [
468                     rmAgent.objectPath
469                     ];
470         
471                 // call dbusSend with bus type, destination, object, message and arguments
472                 self.wampSession.call("serviceDelAgent", arglist).then(ServiceRemoveAgentSuccessCB, ServiceRemoveAgentErrorCB);
473         });
474         
475         return promise;
476 };
477
478
479 cloudeebus.Service.prototype._returnMethod = function(methodId, callIndex, success, result, successCB, errorCB) {
480         var arglist = [
481             methodId,
482             callIndex,
483             success,
484             result
485             ];
486
487         this.wampSession.call("returnMethod", arglist).then(successCB, errorCB);
488 };
489
490
491 cloudeebus.Service.prototype._emitSignal = function(objectPath, signalName, result, successCB, errorCB) {
492         var arglist = [
493             objectPath,
494             signalName,
495             result
496             ];
497
498         this.wampSession.call("emitSignal", arglist).then(successCB, errorCB);
499 };
500
501
502
503 /*****************************************************************************/
504
505 function _processWrappers(wrappers, value) {
506         for (var i=0; i<wrappers.length; i++)
507                 wrappers[i](value);
508 }
509
510
511 function _processWrappersAsync(wrappers, value) {
512         var taskid = -1;
513         function processAsyncOnce() {
514                 _processWrappers(wrappers, value);
515                 clearInterval(taskid);
516         }
517         taskid = setInterval(processAsyncOnce, 200);
518 }
519
520
521
522 /*****************************************************************************/
523
524 cloudeebus.PromiseResolver = function(promise) {
525         this.promise = promise;
526         this.resolved = null;
527     return this;
528 };
529
530
531 cloudeebus.PromiseResolver.prototype.resolve = function(value, sync) {
532         if (this.resolved)
533                 return;
534         
535         var then = (value && value.then && value.then.apply) ? value.then : null;
536         if (then) {
537                 var self = this;                
538                 var fulfillCallback = function(arg) {
539                         self.resolve(arg, true);
540                 };      
541                 var rejectCallback = function(arg) {
542                         self.reject(arg, true);
543                 };
544                 try {
545                         then.apply(value, [fulfillCallback, rejectCallback]);
546                 }
547                 catch (e) {
548                         this.reject(cloudeebus.getError(e), true);
549                 }
550         }
551         
552         this.fulfill(value, sync);
553 };
554
555
556 cloudeebus.PromiseResolver.prototype.fulfill = function(value, sync) {
557         if (this.resolved)
558                 return;
559         
560         var promise = this.promise;
561         promise.state = "fulfilled";
562         promise.result = value;
563         
564         this.resolved = true;
565         if (sync)
566                 _processWrappers(promise._fulfillWrappers, value);
567         else
568                 _processWrappersAsync(promise._fulfillWrappers, value);
569 };
570
571
572 cloudeebus.PromiseResolver.prototype.reject = function(value, sync) {
573         if (this.resolved)
574                 return;
575         
576         var promise = this.promise;
577         promise.state = "rejected";
578         promise.result = value;
579         
580         this.resolved = true;
581         if (sync)
582                 _processWrappers(promise._rejectWrappers, value);
583         else
584                 _processWrappersAsync(promise._rejectWrappers, value);
585 };
586
587
588
589 /*****************************************************************************/
590
591 cloudeebus.Promise = function(init) {
592         this.state = "pending";
593         this.result = null;
594         this._fulfillWrappers = [];
595         this._rejectWrappers = [];
596         this.resolver = new cloudeebus.PromiseResolver(this);
597         if (init) {
598                 try {
599                         init.apply(this, [this.resolver]);
600                 }
601                 catch (e) {
602                         this.resolver.reject(cloudeebus.getError(e), true);
603                 }
604         }
605     return this;
606 };
607
608
609 cloudeebus.Promise.prototype.appendWrappers = function(fulfillWrapper, rejectWrapper) {
610         if (fulfillWrapper)
611                 this._fulfillWrappers.push(fulfillWrapper);
612         if (rejectWrapper)
613                 this._rejectWrappers.push(rejectWrapper);
614         if (this.state == "fulfilled")
615                 _processWrappersAsync(this._fulfillWrappers, this.result);
616         if (this.state == "rejected")
617                 _processWrappersAsync(this._rejectWrappers, this.result);
618 };
619
620
621 cloudeebus.Promise.prototype.then = function(fulfillCB, rejectCB) {
622         var promise = new cloudeebus.Promise();
623         var resolver = promise.resolver;
624         var fulfillWrapper, rejectWrapper;
625         
626         if (fulfillCB)
627                 fulfillWrapper = function(arg) {
628                         try {
629                                 var value = fulfillCB.apply(promise, [arg]);
630                                 resolver.resolve(value, true);
631                         }
632                         catch (e) {
633                                 resolver.reject(cloudeebus.getError(e), true);
634                         }
635                 };
636         else
637                 fulfillWrapper = function(arg) {
638                         resolver.fulfill(arg, true);
639                 };
640         
641         if (rejectCB)
642                 rejectWrapper = function(arg) {
643                         try {
644                                 var value = rejectCB.apply(promise, [arg]);
645                                 resolver.resolve(value, true);
646                         }
647                         catch (e) {
648                                 resolver.reject(cloudeebus.getError(e), true);
649                         }
650                 };
651         else
652                 rejectWrapper = function(arg) {
653                         resolver.reject(arg, true);
654                 };
655         
656         this.appendWrappers(fulfillWrapper,rejectWrapper);
657         return promise;
658 };
659
660
661 cloudeebus.Promise.prototype["catch"] = function(rejectCB) {
662         return this.then(undefined,rejectCB);
663 };
664
665
666 cloudeebus.Promise.prototype.done = function(fulfillCB, rejectCB) {
667         this.appendWrappers(fulfillCB,rejectCB);
668 };
669
670
671 cloudeebus.Promise.resolve = function(value) {
672         var promise = new cloudeebus.Promise();
673         promise.resolver.resolve(value);
674         return promise;
675 };
676
677
678 cloudeebus.Promise.fulfill = function(value) {
679         var promise = new cloudeebus.Promise();
680         promise.resolver.fulfill(value);
681         return promise;
682 };
683
684
685 cloudeebus.Promise.reject = function(value) {
686         var promise = new cloudeebus.Promise();
687         promise.resolver.reject(value);
688         return promise;
689 };
690
691
692 cloudeebus.Promise.any = function() {
693         var promise = new cloudeebus.Promise();
694         var resolver = promise.resolver;
695         var fulfillCallback = function(arg) {
696                 resolver.resolve(arg, true);
697         };
698         var rejectCallback = function(arg) {
699                 resolver.reject(arg, true);
700         };
701         if (arguments.length == 0)
702                 resolver.resolve(undefined, true);
703         else
704                 for (i in arguments) 
705                         Promise.resolve(arguments[i]).appendWrappers(fulfillCallback,rejectCallback);
706         return promise;
707 };
708
709
710 cloudeebus.Promise.every = function() {
711         var promise = new cloudeebus.Promise();
712         var resolver = promise.resolver;
713         var index = 0;
714         var countdown = arguments.length;
715         var args = new Array(countdown);
716         var rejectCallback = function(arg) {
717                 resolver.reject(arg, true);
718         };
719         if (arguments.length == 0)
720                 resolver.resolve(undefined, true);
721         else
722                 for (i in arguments) {
723                         var fulfillCallback = function(arg) {
724                                 args[index] = arg;
725                                 countdown--;
726                                 if (countdown == 0)
727                                         resolver.resolve(args, true);
728                         };
729                         index++;
730                         Promise.resolve(arguments[i]).appendWrappers(fulfillCallback,rejectCallback);
731                 }
732         
733         return promise;
734 };
735
736
737 cloudeebus.Promise.some = function() {
738         var promise = new cloudeebus.Promise();
739         var resolver = promise.resolver;
740         var index = 0;
741         var countdown = arguments.length;
742         var args = new Array(countdown);
743         var fulfillCallback = function(arg) {
744                 resolver.resolve(arg, true);
745         };
746         if (arguments.length == 0)
747                 resolver.resolve(undefined, true);
748         else
749                 for (i in arguments) {
750                         var rejectCallback = function(arg) {
751                                 args[index] = arg;
752                                 countdown--;
753                                 if (countdown == 0)
754                                         resolver.reject(args, true);
755                         };
756                         index++;
757                         Promise.resolve(arguments[i]).appendWrappers(fulfillCallback,rejectCallback);
758                 }
759         
760         return promise;
761 };
762
763
764
765 /*****************************************************************************/
766
767 cloudeebus.ProxyObject = function(session, busConnection, busName, objectPath) {
768         this.wampSession = session; 
769         this.busConnection = busConnection; 
770         this.busName = busName; 
771         this.objectPath = objectPath; 
772         this.interfaceProxies = {};
773         return this;
774 };
775
776
777 cloudeebus.ProxyObject.prototype.getInterface = function(ifName) {
778         return this.interfaceProxies[ifName];
779 };
780
781
782 cloudeebus.ProxyObject.prototype._introspect = function(successCB, errorCB) {
783         
784         var self = this; 
785
786         function getAllPropertiesSuccessCB(props) {
787                 var ifProxy = self.interfaceProxies[self.propInterfaces[self.propInterfaces.length-1]];
788                 for (var prop in props)
789                         ifProxy[prop] = self[prop] = props[prop];
790                 getAllPropertiesNextInterfaceCB();
791         }
792         
793         function getAllPropertiesNextInterfaceCB() {
794                 self.propInterfaces.pop();
795                 if (self.propInterfaces.length > 0) 
796                         self.callMethod("org.freedesktop.DBus.Properties", 
797                                 "GetAll", 
798                                 [self.propInterfaces[self.propInterfaces.length-1]]).then(getAllPropertiesSuccessCB, 
799                                 errorCB ? errorCB : getAllPropertiesNextInterfaceCB);
800                 else {
801                         self.propInterfaces = null;
802                         if (successCB)
803                                 successCB(self);
804                 }
805         }
806         
807         function introspectSuccessCB(str) {
808                 var parser = new DOMParser();
809                 var xmlDoc = parser.parseFromString(str, "text/xml");
810                 var interfaces = xmlDoc.getElementsByTagName("interface");
811                 self.propInterfaces = [];
812                 var supportDBusProperties = false;
813                 for (var i=0; i < interfaces.length; i++) {
814                         var ifName = interfaces[i].attributes.getNamedItem("name").value;
815                         self.interfaceProxies[ifName] = new cloudeebus.ProxyObject(self.wampSession, self.busConnection, self.busName, self.objectPath);
816                         if (ifName == "org.freedesktop.DBus.Properties")
817                                 supportDBusProperties = true;
818                         var hasProperties = false;
819                         var ifChild = interfaces[i].firstChild;
820                         while (ifChild) {
821                                 if (ifChild.nodeName == "method") {
822                                         var nArgs = 0;
823                                         var signature = "";
824                                         var metChild = ifChild.firstChild;
825                                         while (metChild) {
826                                                 if (metChild.nodeName == "arg" &&
827                                                         metChild.attributes.getNamedItem("direction").value == "in") {
828                                                                 signature += metChild.attributes.getNamedItem("type").value;
829                                                                 nArgs++;
830                                                 }
831                                                 metChild = metChild.nextSibling;
832                                         }
833                                         var metName = ifChild.attributes.getNamedItem("name").value;
834                                         if (!self[metName])
835                                                 self._addMethod(ifName, metName, nArgs, signature);
836                                         self.interfaceProxies[ifName]._addMethod(ifName, metName, nArgs, signature);
837                                 }
838                                 else if (ifChild.nodeName == "property") {
839                                         if (!hasProperties)
840                                                 self.propInterfaces.push(ifName);
841                                         hasProperties = true;
842                                 }
843                                 ifChild = ifChild.nextSibling;
844                         }
845                 }
846                 if (supportDBusProperties && self.propInterfaces.length > 0) {
847                         self.callMethod("org.freedesktop.DBus.Properties", 
848                                 "GetAll", 
849                                 [self.propInterfaces[self.propInterfaces.length-1]]).then(getAllPropertiesSuccessCB, 
850                                 errorCB ? errorCB : getAllPropertiesNextInterfaceCB);
851                 }
852                 else {
853                         self.propInterfaces = null;
854                         if (successCB)
855                                 successCB(self);
856                 }
857         }
858
859         // call Introspect on self
860         self.callMethod("org.freedesktop.DBus.Introspectable", "Introspect", []).then(introspectSuccessCB, errorCB);
861 };
862
863
864 cloudeebus.ProxyObject.prototype._addMethod = function(ifName, method, nArgs, signature) {
865
866         var self = this;
867         
868         self[method] = function() {
869                 var args = [];
870                 for (var i=0; i < nArgs; i++ )
871                         args.push(arguments[i]);
872                 return self.callMethod(ifName, method, args, signature);
873         };      
874 };
875
876
877 cloudeebus.ProxyObject.prototype.callMethod = function(ifName, method, args, signature) {
878         
879         var self = this;
880         
881         var promise = new cloudeebus.Promise(function (resolver) {
882                 function callMethodSuccessCB(str) {
883                         try { // calling dbus hook object function for un-translated types
884                                 var result = eval(str);
885                                 resolver.fulfill(result[0], true);
886                         }
887                         catch (e) {
888                                 var errorStr = cloudeebus.getError(e);
889                                 cloudeebus.log("Method callback exception: " + errorStr);
890                                 resolver.reject(errorStr, true);
891                         }
892                 }
893
894                 function callMethodErrorCB(error) {
895                         var errorStr = cloudeebus.getError(error);
896                         cloudeebus.log("Error calling method: " + method + " on object: " + self.objectPath + " : " + errorStr);
897                         resolver.reject(errorStr, true);
898                 }
899
900                 var arglist = [
901                         self.busConnection.name,
902                         self.busName,
903                         self.objectPath,
904                         ifName,
905                         method,
906                         JSON.stringify(args)
907                 ];
908
909                 // call dbusSend with bus type, destination, object, message and arguments
910                 self.wampSession.call("dbusSend", arglist).then(callMethodSuccessCB, callMethodErrorCB);
911         });
912         
913         return promise;
914 };
915
916
917 cloudeebus.ProxyObject.prototype.connectToSignal = function(ifName, signal, handlerCB, errorCB) {
918         
919         var self = this; 
920
921         function signalHandler(id, data) {
922                 if (handlerCB) {
923                         try { // calling dbus hook object function for un-translated types
924                                 handlerCB.apply(self, eval(data));
925                         }
926                         catch (e) {
927                                 var errorStr = cloudeebus.getError(e);
928                                 cloudeebus.log("Signal handler exception: " + errorStr);
929                                 if (errorCB)
930                                         errorCB(errorStr);
931                         }
932                 }
933         }
934         
935         function connectToSignalSuccessCB(str) {
936                 try {
937                         self.wampSession.subscribe(str, signalHandler);
938                 }
939                 catch (e) {
940                         cloudeebus.log("Subscribe error: " + cloudeebus.getError(e));
941                 }
942         }
943
944         function connectToSignalErrorCB(error) {
945                 var errorStr = cloudeebus.getError(error);
946                 cloudeebus.log("Error connecting to signal: " + signal + " on object: " + self.objectPath + " : " + errorStr);
947                 if (errorCB)
948                         errorCB(errorStr);
949         }
950
951         var arglist = [
952                 self.busConnection.name,
953                 self.busName,
954                 self.objectPath,
955                 ifName,
956                 signal
957         ];
958
959         // call dbusSend with bus type, destination, object, message and arguments
960         self.wampSession.call("dbusRegister", arglist).then(connectToSignalSuccessCB, connectToSignalErrorCB);
961 };
962
963
964 cloudeebus.ProxyObject.prototype.disconnectSignal = function(ifName, signal) {
965         try {
966                 this.wampSession.unsubscribe(this.busConnection.name + "#" + this.busName + "#" + this.objectPath + "#" + ifName + "#" + signal);
967         }
968         catch (e) {
969                 cloudeebus.log("Unsubscribe error: " + cloudeebus.getError(e));
970         }
971 };