3 Modification information for LGPL compliance
5 r56990 - 2010-06-16 13:05:36 -0700 (Wed, 16 Jun 2010) - kjing - snapshot "Mango" svn branch to a new one for GitHub sync
7 r56989 - 2010-06-16 13:01:33 -0700 (Wed, 16 Jun 2010) - kjing - defunt "Mango" svn dev branch before github cutover
9 r55980 - 2010-04-19 13:31:28 -0700 (Mon, 19 Apr 2010) - kjing - create Mango (6.1) based on windex
11 r51719 - 2009-10-22 10:18:00 -0700 (Thu, 22 Oct 2009) - mitani - Converted to Build 3 tags and updated the build system
13 r51634 - 2009-10-19 13:32:22 -0700 (Mon, 19 Oct 2009) - mitani - Windex is the branch for Sugar Sales 1.0 development
15 r50375 - 2009-08-24 18:07:43 -0700 (Mon, 24 Aug 2009) - dwong - branch kobe2 from tokyo r50372
17 r42807 - 2008-12-29 11:16:59 -0800 (Mon, 29 Dec 2008) - dwong - Branch from trunk/sugarcrm r42806 to branches/tokyo/sugarcrm
19 r4085 - 2005-04-13 17:30:42 -0700 (Wed, 13 Apr 2005) - robert - adding meeting scheduler and accept/decline
25 Copyright (c) 2003-2004 Jan-Klaas Kollhof
27 This file is part of the JavaScript o lait library(jsolait).
29 jsolait is free software; you can redistribute it and/or modify
30 it under the terms of the GNU Lesser General Public License as published by
31 the Free Software Foundation; either version 2.1 of the License, or
32 (at your option) any later version.
34 This software is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU Lesser General Public License for more details.
39 You should have received a copy of the GNU Lesser General Public License
40 along with this software; if not, write to the Free Software
41 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
46 Provides an XML-RPC imlementation.
47 It is similar to python's xmlrpclib module.
49 Module("xmlrpc","1.3.3", function(mod){
50 var xmlext = importModule("xml");
51 var urllib = importModule("urllib");
53 Thrown if a server did not respond with response status 200 (OK).
55 mod.InvalidServerResponse = Class("InvalidServerResponse", mod.Exception, function(publ, supr){
57 Initializes the Exception.
58 @param status The status returned by the server.
60 publ.init= function(status){
61 supr(this).init("The server did not respond with a status 200 (OK) but with: " + status);
64 ///The status returned by the server.
69 Thrown if an XML-RPC response is not well formed.
71 mod.MalformedXmlRpc = Class("MalformedXmlRpc", mod.Exception, function(publ, supr){
73 Initializes the Exception.
74 @param msg The error message of the user.
75 @param xml The xml document's source.
76 @param trace=null The error causing this Exception
78 publ.init= function(msg, xml, trace){
79 supr(this).init(msg,trace);
82 ///The xml source which was mal formed.
86 Thrown if the RPC response is a Fault.
88 mod.Fault = Class("Fault", mod.Exception, function(publ, supr){
90 Initializes the Exception.
91 @param faultCode The fault code returned by the rpc call.
92 @param faultString The fault string returned by the rpc call.
94 publ.init= function(faultCode, faultString){
95 supr(this).init("XML-RPC Fault: " + faultCode + "\n\n" + faultString);
96 this.faultCode = faultCode;
97 this.faultString = faultString;
99 ///The fault code returned from the rpc call.
101 ///The fault string returned from the rpc call.
106 Marshalls an object to XML-RPC.(Converts an object into XML-RPC conforming xml.)
107 It just calls the toXmlRpc function of the objcect.
108 So, to customize serialization of objects one just needs to specify/override the toXmlRpc method
109 which should return an xml string conforming with XML-RPC spec.
110 @param obj The object to marshall
111 @return An xml representation of the object.
113 mod.marshall = function(obj){
115 return obj.toXmlRpc();
118 for(var attr in obj){
119 if(typeof obj[attr] != "function"){
120 s += "<member><name>" + attr + "</name><value>" + mod.marshall(obj[attr]) + "</value></member>";
129 Unmarshalls an XML document to a JavaScript object. (Converts xml to JavaScript object.)
130 It parses the xml source and creates a JavaScript object.
131 @param xml The xml document source to unmarshall.
132 @return The JavaScript object created from the XML.
134 mod.unmarshall = function(xml){
135 try {//try to parse xml ... this will throw an Exception if failed
136 var doc = xmlext.parseXML(xml);
138 throw new mod.MalformedXmlRpc("The server's response could not be parsed.", xml, e);
140 var rslt = mod.unmarshallDoc(doc, xml);
146 Unmarshalls an XML document to a JavaScript object like unmarshall but expects a DOM document as parameter.
147 It parses the xml source and creates a JavaScript object.
148 @param doc The xml document(DOM compatible) to unmarshall.
149 @return The JavaScript object created from the XML.
151 mod.unmarshallDoc = function(doc, xml){
153 var node = doc.documentElement;
154 if(node==null){//just in case parse xml didn't throw an Exception but returned nothing usefull.
155 throw new mod.MalformedXmlRpc("No documentElement found.", xml);
157 switch(node.tagName){
158 case "methodResponse":
159 return parseMethodResponse(node);
161 return parseMethodCall(node);
162 default://nothing usefull returned by parseXML.
163 throw new mod.MalformedXmlRpc("'methodCall' or 'methodResponse' element expected.\nFound: '" + node.tagName + "'", xml);
166 if(e instanceof mod.Fault){//just rethrow the fault.
169 throw new mod.MalformedXmlRpc("Unmarshalling of XML failed.", xml, e);
175 Parses a methodeResponse element.
176 @param node The methodResponse element.
177 @return The return value of the XML-RPC.
179 var parseMethodResponse=function(node){
181 for(var i=0;i<node.childNodes.length;i++){
182 var child = node.childNodes.item(i);
183 if (child.nodeType == 1){
184 switch (child.tagName){
185 case "fault": //a fault is thrown as an Exception
186 throw parseFault(child);
188 var params = parseParams(child);
189 if(params.length == 1){//params should only have one param
192 throw new mod.MalformedXmlRpc("'params' element inside 'methodResponse' must have exactly ONE 'param' child element.\nFound: " + params.length);
195 throw new mod.MalformedXmlRpc("'fault' or 'params' element expected.\nFound: '" + child.tagName + "'");
199 //no child elements found
200 throw new mod.MalformedXmlRpc("No child elements found.");
202 if(e instanceof mod.Fault){
205 throw new mod.MalformedXmlRpc("'methodResponse' element could not be parsed.",null,e);
210 Parses a methodCall element.
211 @param node The methodCall element.
212 @return Array [methodName,params].
214 var parseMethodCall = function(node){
216 var methodName = null;
217 var params = new Array();//default is no parameters
218 for(var i=0;i<node.childNodes.length;i++){
219 var child = node.childNodes.item(i);
220 if (child.nodeType == 1){
221 switch (child.tagName){
223 methodName = new String(child.firstChild.nodeValue);
226 params = parseParams(child);
229 throw new mod.MalformedXmlRpc("'methodName' or 'params' element expected.\nFound: '" + child.tagName + "'");
233 if(methodName==null){
234 throw new mod.MalformedXmlRpc("'methodName' element expected.");
236 return new Array(methodName, params);
239 throw new mod.MalformedXmlRpc("'methodCall' element could not be parsed.",null,e);
243 Parses a params element.
244 @param node The params element.
245 @return Array of params values.
247 var parseParams = function(node){
249 var params=new Array();
250 for(var i=0;i<node.childNodes.length;i++){
251 var child = node.childNodes.item(i);
252 if (child.nodeType == 1){
253 switch (child.tagName){
255 params.push(parseParam(child));
258 throw new mod.MalformedXmlRpc("'param' element expected.\nFound: '" + child.tagName + "'");
262 //the specs say a 'params' element can contain any number of 'param' elements. That includes 0 ?!
265 throw new mod.MalformedXmlRpc("'params' element could not be parsed.",null,e);
269 Parses a param element.
270 @param node The param node.
271 @return The value of the param.
273 var parseParam = function(node){
275 for(var i=0;i<node.childNodes.length;i++){
276 var child = node.childNodes.item(i);
277 if (child.nodeType == 1){
278 switch (child.tagName){
280 return parseValue(child);
282 throw new mod.MalformedXmlRpc("'value' element expected.\nFound: '" + child.tagName + "'");
286 //no child elements found, that's an error
287 throw new mod.MalformedXmlRpc("'value' element expected.But none found.");
289 throw new mod.MalformedXmlRpc("'param' element could not be parsed.",null,e);
293 Parses a value element.
294 @param node The value element.
297 var parseValue = function(node){
299 for(var i=0;i<node.childNodes.length;i++){
300 var child = node.childNodes.item(i);
301 if (child.nodeType == 1){
302 switch (child.tagName){
305 //Mozilla has many textnodes with a size of 4096 chars each instead of one large one.
306 //They all need to be concatenated.
307 for(var j=0;j<child.childNodes.length;j++){
308 s+=new String(child.childNodes.item(j).nodeValue);
314 return (child.firstChild) ? new Number(child.firstChild.nodeValue) : 0;
316 return Boolean(isNaN(parseInt(child.firstChild.nodeValue)) ? (child.firstChild.nodeValue == "true") : parseInt(child.firstChild.nodeValue));
318 return parseBase64(child);
319 case "dateTime.iso8601":
320 return parseDateTime(child);
322 return parseArray(child);
324 return parseStruct(child);
325 case "nil": //for python None todo: ??? is this valid XML-RPC
328 throw new mod.MalformedXmlRpc("'string','int','i4','double','boolean','base64','dateTime.iso8601','array' or 'struct' element expected.\nFound: '" + child.tagName + "'");
334 //Mozilla has many textnodes with a size of 4096 chars each instead of one large one.
335 //They all need to be concatenated.
336 for(var j=0;j<node.childNodes.length;j++){
337 s+=new String(node.childNodes.item(j).nodeValue);
344 throw new mod.MalformedXmlRpc("'value' element could not be parsed.",null,e);
348 Parses a base64 element.
349 @param node The base64 element.
350 @return A string with the decoded base64.
352 var parseBase64=function(node){
354 var s = node.firstChild.nodeValue;
355 return s.decode("base64");
357 throw new mod.MalformedXmlRpc("'base64' element could not be parsed.",null,e);
361 Parses a dateTime.iso8601 element.
362 @param node The dateTime.iso8601 element.
363 @return A JavaScript date.
365 var parseDateTime=function(node){
367 if(/^(\d{4})-?(\d{2})-?(\d{2})T(\d{2}):?(\d{2}):?(\d{2})/.test(node.firstChild.nodeValue)){
368 return new Date(Date.UTC(RegExp.$1, RegExp.$2-1, RegExp.$3, RegExp.$4, RegExp.$5, RegExp.$6));
369 }else{ //todo error message
370 throw new mod.MalformedXmlRpc("Could not convert the given date.");
373 throw new mod.MalformedXmlRpc("'dateTime.iso8601' element could not be parsed.",null,e);
377 Parses an array element.
378 @param node The array element.
381 var parseArray=function(node){
383 for(var i=0;i<node.childNodes.length;i++){
384 var child = node.childNodes.item(i);
385 if (child.nodeType == 1){
386 switch (child.tagName){
388 return parseData(child);
390 throw new mod.MalformedXmlRpc("'data' element expected.\nFound: '" + child.tagName + "'");
394 throw new mod.MalformedXmlRpc("'data' element expected. But not found.");
396 throw new mod.MalformedXmlRpc("'array' element could not be parsed.",null,e);
400 Parses a data element.
401 @param node The data element.
402 @return The value of a data element.
404 var parseData=function(node){
406 var rslt = new Array();
407 for(var i=0;i<node.childNodes.length;i++){
408 var child = node.childNodes.item(i);
409 if (child.nodeType == 1){
410 switch (child.tagName){
412 rslt.push(parseValue(child));
415 throw new mod.MalformedXmlRpc("'value' element expected.\nFound: '" + child.tagName + "'");
421 throw new mod.MalformedXmlRpc("'data' element could not be parsed.",null,e);
425 Parses a struct element.
426 @param node The struct element.
427 @return A JavaScript object. Struct memembers are properties of the object.
429 var parseStruct=function(node){
431 var struct = new Object();
432 for(var i=0;i<node.childNodes.length;i++){
433 var child = node.childNodes.item(i);
434 if (child.nodeType == 1){
435 switch (child.tagName){
437 var member = parseMember(child); //returns [name, value]
439 struct[member[0]] = member[1];
443 throw new mod.MalformedXmlRpc("'data' element expected.\nFound: '" + child.tagName + "'");
449 throw new mod.MalformedXmlRpc("'struct' element could not be parsed.",null,e);
453 Parses a member element.
454 @param node The member element.
455 @return Array containing [memberName, value].
457 var parseMember=function(node){
461 for(var i=0;i<node.childNodes.length;i++){
462 var child = node.childNodes.item(i);
463 if (child.nodeType == 1){
464 switch (child.tagName){
466 value = parseValue(child);
469 if(child.hasChildNodes()){
470 name = new String(child.firstChild.nodeValue);
474 throw new mod.MalformedXmlRpc("'value' or 'name' element expected.\nFound: '" + child.tagName + "'");
479 throw new mod.MalformedXmlRpc("Name for member not found/convertable.");
481 return new Array(name, value);
483 return [name, value];
485 throw new mod.MalformedXmlRpc("'member' element could not be parsed.",null,e);
489 Parses a fault element.
490 @param node The fault element.
491 @return A Fault Exception object.
493 var parseFault = function(node){
495 for(var i=0;i<node.childNodes.length;i++){
496 var child = node.childNodes.item(i);
497 if (child.nodeType == 1){
498 switch (child.tagName){
500 var flt = parseValue(child);
501 return new mod.Fault(flt.faultCode, flt.faultString);
503 throw new mod.MalformedXmlRpc("'value' element expected.\nFound: '" + child.tagName + "'");
507 throw new mod.MalformedXmlRpc("'value' element expected. But not found.");
509 throw new mod.MalformedXmlRpc("'fault' element could not be parsed.",null,e);
514 Class for creating XML-RPC methods.
515 Calling the created method will result in an XML-RPC call to the service.
516 The return value of this call will be the return value of the RPC call.
517 RPC-Faults will be raised as Exceptions.
519 Asynchronous operation:
520 If the last parameter passed to the method is an XMLRPCAsyncCallback object,
521 then the remote method will be called asynchronously.
522 The results and errors are passed to the callback.
524 mod.XMLRPCMethod =Class("XMLRPCMethod", function(publ){
526 var postData = function(url, user, pass, data, callback){
527 if(callback == null){
528 var rslt = urllib.postURL(url, user, pass, data, [["Content-Type", "text/xml"]]);
531 urllib.postURL(url, user, pass, data, [["Content-Type", "text/xml"]], callback);
535 var handleResponse=function(resp){
537 try{//see if the server responded with a response code 200 OK.
538 status = resp.status;
544 respDoc = resp.responseXML;
549 respTxt=resp.responseText;
553 if(respTxt == null || respTxt == ""){
554 throw new mod.MalformedXmlRpc("The server responded with an empty document.", "");
556 return mod.unmarshall(respTxt);
558 }else{ //use the respDoc directly so the xml does not have to be parsed.
559 return mod.unmarshallDoc(respDoc, respTxt);
562 throw new mod.InvalidServerResponse(status);
566 var getXML = function(methodName, args){
567 var data='<?xml version="1.0"?><methodCall><methodName>' + methodName + '</methodName>';
570 for(var i=0;i<args.length;i++){
571 data += '<param><value>' + mod.marshall(args[i]) + '</value></param>';
575 data += '</methodCall>';
579 Initializes the XML-RPC method.
580 @param url The URL of the service providing the method.
581 @param methodName The name of the method to invoke.
582 @param user=null The user name to use for HTTP authentication.
583 @param pass=null The password to use for HTTP authentication.
585 publ.init = function(url, methodName, user, pass){
587 //this is pretty much a hack.
588 //we create a function which mimics this class and return it instead of really instanciating an object.
591 if(typeof arguments[arguments.length-1] != "function"){
592 var data=getXML(fn.methodName,arguments);
593 var resp = postData(fn.url, fn.user, fn.password, data);
595 return handleResponse(resp);
597 var args=new Array();
598 for(var i=0;i<arguments.length;i++){
599 args.push(arguments[i]);
602 var data=getXML(fn.methodName, args);
603 postData(fn.url, fn.user, fn.password, data, function(resp){
607 rslt = handleResponse(resp);
611 try{//call the callback for the async call.
620 //make sure the function has the same property as an object created from this class.
621 fn.methodName = methodName;
625 fn.toMulticall = this.toMulticall;
626 fn.toString = this.toString;
627 fn.setAuthentication=this.setAuthentication;
628 fn.constructor = this.constructor;
633 Returns the method representation for system.multicall.
634 @param All params will be passed to the remote method.
635 @return An object containing a member methodName and a member params(As required by system.multicall).
637 publ.toMulticall = function(){
638 var multiCallable = new Object();
639 multiCallable.methodName = this.methodName;
641 for(var i=0;i<arguments.length;i++){
642 params[i] = arguments[i];
644 multiCallable.params = params;
645 return multiCallable;
648 Sets username and password for HTTP Authentication.
649 @param user The user name.
650 @param pass The password.
652 publ.setAuthentication = function(user, pass){
654 this.password = pass;
656 ///The name of the remote method.
658 ///The url of the remote service containing the method.
660 ///The user name used for HTTP authorization.
662 ///The password used for HTTP authorization.
667 Creates proxy objects which resemble the remote service.
668 Method calls of this proxy will result in calls to the service.
670 mod.ServiceProxy=Class("ServiceProxy", function(publ){
672 Initializes a new ServerProxy.
673 The arguments are interpreted as shown in the examples:
675 ServerProxy("url", ["methodName1",...])
676 ServerProxy("url", ["methodName1",...], "user", "pass")
677 ServerProxy("url", "user", "pass")
679 @param url The url of the service.
680 @param methodNames=[] Array of names of methods that can be called on the server.
681 If no methods are given then introspection is used to get the methodnames from the server.
682 @param user=null The user name to use for HTTP authentication.
683 @param pass=null The password to use for HTTP authentication.
685 publ.init = function(url, methodNames, user, pass){
686 if(methodNames instanceof Array){
687 if(methodNames.length > 0){
688 var tryIntrospection=false;
690 var tryIntrospection=true;
696 var tryIntrospection=true;
700 this._password = pass;
701 this._addMethodNames(methodNames);
702 if(tryIntrospection){
703 try{//it's ok if it fails.
711 Adds new XMLRPCMethods to the proxy server which can then be invoked.
712 @param methodNames Array of names of methods that can be called on the server.
714 publ._addMethodNames = function(methodNames){
715 for(var i=0;i<methodNames.length;i++){
717 //setup obj.childobj...method
718 var names = methodNames[i].split(".");
719 for(var n=0;n<names.length-1;n++){
724 obj[name] = new Object();
728 var name = names[names.length-1];
731 var mth = new mod.XMLRPCMethod(this._url, methodNames[i], this._user, this._password);
733 this._methods.push(mth);
739 Sets username and password for HTTP Authentication for all methods of this service.
740 @param user The user name.
741 @param pass The password.
743 publ._setAuthentication = function(user, pass){
745 this._password = pass;
746 for(var i=0;i<this._methods.length;i++){
747 this._methods[i].setAuthentication(user, pass);
752 Initiate XML-RPC introspection to retrieve methodnames from the server
753 and add them to the server proxy.
755 publ._introspect = function(){
756 this._addMethodNames(["system.listMethods","system.methodHelp", "system.methodSignature"]);
757 var m = this.system.listMethods();
758 this._addMethodNames(m);
760 ///The url of the service to resemble.
762 ///The user used for HTTP authentication.
764 ///The password used for HTTP authentication.
767 publ._methods=new Array();
770 ///@deprecated Use ServiceProxy instead.
771 mod.ServerProxy= mod.ServiceProxy;
774 XML-RPC representation of a string.
775 All '&' and '<' are replaced with the '&' and '<'.
776 @return A string containing the String's representation in XML.
778 String.prototype.toXmlRpc = function(){
779 return "<string>" + this.replace(/&/g, "&").replace(/</g, "<") + "</string>";
782 XML-RPC representation of a number.
783 @return A string containing the Number's representation in XML.
785 Number.prototype.toXmlRpc = function(){
786 if(this == parseInt(this)){
787 return "<int>" + this + "</int>";
788 }else if(this == parseFloat(this)){
789 return "<double>" + this + "</double>";
791 return false.toXmlRpc();
795 XML-RPC representation of a boolean.
796 @return A string containing the Boolean's representation in XML.
798 Boolean.prototype.toXmlRpc = function(){
800 return "<boolean>1</boolean>";
802 return "<boolean>0</boolean>";
806 XML-RPC representation of a date(iso 8601).
807 @return A string containing the Date's representation in XML.
809 Date.prototype.toXmlRpc = function(){
810 var padd=function(s, p){
812 return s.substring(s.length - p.length)
814 var y = padd(this.getUTCFullYear(), "0000");
815 var m = padd(this.getUTCMonth() + 1, "00");
816 var d = padd(this.getUTCDate(), "00");
817 var h = padd(this.getUTCHours(), "00");
818 var min = padd(this.getUTCMinutes(), "00");
819 var s = padd(this.getUTCSeconds(), "00");
821 var isodate = y + m + d + "T" + h + ":" + min + ":" + s
823 return "<dateTime.iso8601>" + isodate + "</dateTime.iso8601>";
826 XML-RPC representation of an array.
827 Each entry in the array is a value in the XML-RPC.
828 @return A string containing the Array's representation in XML.
830 Array.prototype.toXmlRpc = function(){
831 var retstr = "<array><data>";
832 for(var i=0;i<this.length;i++){
833 retstr += "<value>" + mod.marshall(this[i]) + "</value>";
835 return retstr + "</data></array>";
839 mod.test = function(){
840 print("creating ServiceProxy object using introspection for method construction...\n");
841 var s = new mod.ServiceProxy("http://localhost/testx.py");
842 print("%s created\n".format(s));
843 print("creating and marshalling test data:\n");
844 var o = [1.234, 5, {a:"Hello & < ", b:new Date()}];
845 print(mod.marshall(o));
846 print("\ncalling echo() on remote service...\n");
848 print("service returned data(marshalled again):\n")
849 print(mod.marshall(r));