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