/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * * The JSON-RPC client code is based on Jan-Klaas' JavaScript * o lait library (jsolait). * * $Id: jsonrpc.js,v 1.36.2.3 2006/03/08 15:09:37 mclark Exp $ * * Copyright (c) 2003-2004 Jan-Klaas Kollhof * Copyright (c) 2005 Michael Clark, Metaparadigm Pte Ltd * * Licensed under the Apache License, Version 2.0 (the "License"). */ /** * Escape a character. */ var JSONClient = new Object(); JSONClient.escapeJSONChar = function(c) { if(c == "\"" || c == "\\") return "\\" + c; else if (c == "\b") return "\\b"; else if (c == "\f") return "\\f"; else if (c == "\n") return "\\n"; else if (c == "\r") return "\\r"; else if (c == "\t") return "\\t"; var hex = c.charCodeAt(0).toString(16); if(hex.length == 1) return "\\u000" + hex; else if(hex.length == 2) return "\\u00" + hex; else if(hex.length == 3) return "\\u0" + hex; else return "\\u" + hex; }; /** * Encode a string into JSON format. */ JSONClient.escapeJSONString = function(s) { /* The following should suffice but Safari's regex is broken (doesn't support callback substitutions) return "\"" + s.replace(/([^\u0020-\u007f]|[\\\"])/g, JSONClient.escapeJSONChar) + "\""; */ /* Rather inefficient way to do it */ var parts = s.split(""); for(var i = 0; i < parts.length; i++) { var c = parts[i]; if(c == '"' || c == '\\' || c.charCodeAt(0) < 32 || c.charCodeAt(0) >= 128) parts[i] = JSONClient.escapeJSONChar(parts[i]); } return "\"" + parts.join("") + "\""; }; /** * Marshall objects to JSON format. */ JSONClient.toJSON = function(o) { if(o == null) { return "null"; } else if(o.constructor == String) { return JSONClient.escapeJSONString(o); } else if(o.constructor == Number) { return o.toString(); } else if(o.constructor == Boolean) { return o.toString(); } else if(o.constructor == Date) { return '{javaClass: "java.util.Date", time: ' + o.valueOf() +'}'; } else if(o.constructor == Array) { var v = []; for(var i = 0; i < o.length; i++) v.push(JSONClient.toJSON(o[i])); return "[" + v.join(", ") + "]"; } else { var v = []; for(attr in o) { if(o[attr] == null) v.push("\"" + attr + "\": null"); else if(typeof o[attr] == "function"); /* skip */ else v.push(JSONClient.escapeJSONString(attr) + ": " + JSONClient.toJSON(o[attr])); } return "{" + v.join(", ") + "}"; } }; /** * HTTPBindingClient.Exception. */ HTTPBindingClient.Exception = function(code, message, javaStack) { this.code = code; var name; if(javaStack) { this.javaStack = javaStack; var m = javaStack.match(/^([^:]*)/); if(m) name = m[0]; } if(name) this.name = name; else this.name = "HTTPBindingClientException"; this.message = message; }; HTTPBindingClient.Exception.CODE_REMOTE_EXCEPTION = 490; HTTPBindingClient.Exception.CODE_ERR_CLIENT = 550; HTTPBindingClient.Exception.CODE_ERR_PARSE = 590; HTTPBindingClient.Exception.CODE_ERR_NOMETHOD = 591; HTTPBindingClient.Exception.CODE_ERR_UNMARSHALL = 592; HTTPBindingClient.Exception.CODE_ERR_MARSHALL = 593; HTTPBindingClient.Exception.prototype = new Error(); HTTPBindingClient.Exception.prototype.toString = function(code, msg) { return this.name + ": " + this.message; }; /** * Default top level exception handler. */ HTTPBindingClient.default_ex_handler = function(e) { alert(e); }; /** * Client settable variables */ HTTPBindingClient.toplevel_ex_handler = HTTPBindingClient.default_ex_handler; HTTPBindingClient.profile_async = false; HTTPBindingClient.max_req_active = 1; HTTPBindingClient.requestId = 1; /** * HTTPBindingClient implementation */ HTTPBindingClient.prototype._createApplyMethod = function() { var fn = function() { var args = []; var callback = null; var methodName = arguments[0]; for(var i = 1; i < arguments.length; i++) args.push(arguments[i]); if(typeof args[args.length - 1] == "function") callback = args.pop(); var req = fn.client._makeRequest.call(fn.client, methodName, args, callback); if(callback == null) { return fn.client._sendRequest.call(fn.client, req); } else { HTTPBindingClient.async_requests.push(req); HTTPBindingClient.kick_async(); return req.requestId; } }; fn.client = this; return fn; }; HTTPBindingClient._getCharsetFromHeaders = function(http) { try { var contentType = http.getResponseHeader("Content-type"); var parts = contentType.split(/\s*;\s*/); for(var i = 0; i < parts.length; i++) { if(parts[i].substring(0, 8) == "charset=") return parts[i].substring(8, parts[i].length); } } catch (e) {} return "UTF-8"; }; /** * Async queue globals */ HTTPBindingClient.async_requests = []; HTTPBindingClient.async_inflight = {}; HTTPBindingClient.async_responses = []; HTTPBindingClient.async_timeout = null; HTTPBindingClient.num_req_active = 0; HTTPBindingClient._async_handler = function() { HTTPBindingClient.async_timeout = null; while(HTTPBindingClient.async_responses.length > 0) { var res = HTTPBindingClient.async_responses.shift(); if(res.canceled) continue; if(res.profile) res.profile.dispatch = new Date(); try { res.cb(res.result, res.ex, res.profile); } catch(e) { HTTPBindingClient.toplevel_ex_handler(e); } } while(HTTPBindingClient.async_requests.length > 0 && HTTPBindingClient.num_req_active < HTTPBindingClient.max_req_active) { var req = HTTPBindingClient.async_requests.shift(); if(req.canceled) continue; req.client._sendRequest.call(req.client, req); } }; HTTPBindingClient.kick_async = function() { if(HTTPBindingClient.async_timeout == null) HTTPBindingClient.async_timeout = setTimeout(HTTPBindingClient._async_handler, 0); }; HTTPBindingClient.cancelRequest = function(requestId) { /* If it is in flight then mark it as canceled in the inflight map and the XMLHttpRequest callback will discard the reply. */ if(HTTPBindingClient.async_inflight[requestId]) { HTTPBindingClient.async_inflight[requestId].canceled = true; return true; } /* If its not in flight yet then we can just mark it as canceled in the the request queue and it will get discarded before being sent. */ for(var i in HTTPBindingClient.async_requests) { if(HTTPBindingClient.async_requests[i].requestId == requestId) { HTTPBindingClient.async_requests[i].canceled = true; return true; } } /* It may have returned from the network and be waiting for its callback to be dispatched, so mark it as canceled in the response queue and the response will get discarded before calling the callback. */ for(var i in HTTPBindingClient.async_responses) { if(HTTPBindingClient.async_responses[i].requestId == requestId) { HTTPBindingClient.async_responses[i].canceled = true; return true; } } return false; }; HTTPBindingClient.prototype._makeRequest = function(methodName, args, cb) { var req = {}; req.client = this; req.requestId = HTTPBindingClient.requestId++; var obj = {}; obj.id = req.requestId; if (this.objectID) obj.method = ".obj#" + this.objectID + "." + methodName; else obj.method = methodName; obj.params = args; if (cb) req.cb = cb; if (HTTPBindingClient.profile_async) req.profile = { "submit": new Date() }; req.data = JSONClient.toJSON(obj); return req; }; HTTPBindingClient.prototype._sendRequest = function(req) { if(req.profile) req.profile.start = new Date(); /* Get free http object from the pool */ var http = HTTPBindingClient.poolGetHTTPRequest(); HTTPBindingClient.num_req_active++; /* Send the request */ http.open("POST", this.uri, (req.cb != null)); /* setRequestHeader is missing in Opera 8 Beta */ try { http.setRequestHeader("Content-type", "text/plain"); } catch(e) {} /* Construct call back if we have one */ if(req.cb) { var self = this; http.onreadystatechange = function() { if(http.readyState == 4) { http.onreadystatechange = function () {}; var res = { "cb": req.cb, "result": null, "ex": null}; if (req.profile) { res.profile = req.profile; res.profile.end = new Date(); } try { res.result = self._handleResponse(http); } catch(e) { res.ex = e; } if(!HTTPBindingClient.async_inflight[req.requestId].canceled) HTTPBindingClient.async_responses.push(res); delete HTTPBindingClient.async_inflight[req.requestId]; HTTPBindingClient.kick_async(); } }; } else { http.onreadystatechange = function() {}; } HTTPBindingClient.async_inflight[req.requestId] = req; try { http.send(req.data); } catch(e) { HTTPBindingClient.poolReturnHTTPRequest(http); HTTPBindingClient.num_req_active--; throw new HTTPBindingClient.Exception(HTTPBindingClient.Exception.CODE_ERR_CLIENT, "Connection failed"); } if(!req.cb) return this._handleResponse(http); }; HTTPBindingClient.prototype._handleResponse = function(http) { /* Get the charset */ if(!this.charset) { this.charset = HTTPBindingClient._getCharsetFromHeaders(http); } /* Get request results */ var status, statusText, data; try { status = http.status; statusText = http.statusText; data = http.responseText; } catch(e) { HTTPBindingClient.poolReturnHTTPRequest(http); HTTPBindingClient.num_req_active--; HTTPBindingClient.kick_async(); throw new HTTPBindingClient.Exception(HTTPBindingClient.Exception.CODE_ERR_CLIENT, "Connection failed"); } /* Return http object to the pool; */ HTTPBindingClient.poolReturnHTTPRequest(http); HTTPBindingClient.num_req_active--; /* Unmarshall the response */ if(status != 200) { throw new HTTPBindingClient.Exception(status, statusText); } var obj; try { eval("obj = " + data); } catch(e) { throw new HTTPBindingClient.Exception(550, "error parsing result"); } if(obj.error) throw new HTTPBindingClient.Exception(obj.error.code, obj.error.msg, obj.error.trace); var res = obj.result; /* Handle CallableProxy */ if(res && res.objectID && res.JSONRPCType == "CallableReference") return new HTTPBindingClient(this.uri, res.objectID); return res; }; /** * XMLHttpRequest wrapper code */ HTTPBindingClient.http_spare = []; HTTPBindingClient.http_max_spare = 8; HTTPBindingClient.poolGetHTTPRequest = function() { if(HTTPBindingClient.http_spare.length > 0) { return HTTPBindingClient.http_spare.pop(); } return HTTPBindingClient.getHTTPRequest(); }; HTTPBindingClient.poolReturnHTTPRequest = function(http) { if(HTTPBindingClient.http_spare.length >= HTTPBindingClient.http_max_spare) delete http; else HTTPBindingClient.http_spare.push(http); }; HTTPBindingClient.msxmlNames = [ "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ]; HTTPBindingClient.getHTTPRequest = function() { /* Mozilla XMLHttpRequest */ try { HTTPBindingClient.httpObjectName = "XMLHttpRequest"; return new XMLHttpRequest(); } catch(e) {} /* Microsoft MSXML ActiveX */ for (var i=0; i < HTTPBindingClient.msxmlNames.length; i++) { try { HTTPBindingClient.httpObjectName = HTTPBindingClient.msxmlNames[i]; return new ActiveXObject(HTTPBindingClient.msxmlNames[i]); } catch (e) {} } /* None found */ HTTPBindingClient.httpObjectName = null; throw new HTTPBindingClient.Exception(0, "Can't create XMLHttpRequest object"); }; HTTPBindingClient.prototype.get = function(id, responseFunction) { var xhr = this.createXMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { var strDocument = xhr.responseText; var xmlDocument = xhr.responseXML; if(!xmlDocument || xmlDocument.childNodes.length==0){ xmlDocument = (new DOMParser()).parseFromString(strDocument, "text/xml"); } if (responseFunction != null) responseFunction(xmlDocument); } else { alert("get - Error getting data from the server"); } } } xhr.open("GET", this.uri + '/' + id, true); xhr.send(null); }; HTTPBindingClient.prototype.post = function (entry, responseFunction) { var xhr = this.createXMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 201) { var strDocument = xhr.responseText; var xmlDocument = xhr.responseXML; if(!xmlDocument || xmlDocument.childNodes.length==0){ xmlDocument = (new DOMParser()).parseFromString(strDocument, "text/xml"); } if (responseFunction != null) responseFunction(xmlDocument); } else { alert("post - Error getting data from the server"); } } } xhr.open("POST", this.uri, true); xhr.setRequestHeader("Content-Type", "application/atom+xml"); xhr.send(entry); }; HTTPBindingClient.prototype.put = function (id, entry, responseFunction) { var xhr = this.createXMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { var strDocument = xhr.responseText; var xmlDocument = xhr.responseXML; if(!xmlDocument || xmlDocument.childNodes.length==0){ xmlDocument = (new DOMParser()).parseFromString(strDocument, "text/xml"); } if (responseFunction != null) responseFunction(xmlDocument); } else { alert("put - Error getting data from the server"); } } } xhr.open("PUT", this.uri + '/' + id, true); xhr.setRequestHeader("Content-Type", "application/atom+xml"); xhr.send(entry); }; HTTPBindingClient.prototype.del = function (id, responseFunction) { var xhr = this.createXMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { if (responseFunction != null) responseFunction(); } else { alert("delete - Error getting data from the server"); } } } xhr.open("DELETE", this.uri + '/' + id, true); xhr.send(null); }; HTTPBindingClient.prototype.createXMLHttpRequest = function () { /* Mozilla XMLHttpRequest */ try { return new XMLHttpRequest(); } catch(e) {} /* Microsoft MSXML ActiveX */ for (var i = 0; i < HTTPBindingClient.msxmlNames.length; i++) { try { return new ActiveXObject(HTTPBindingClient.msxmlNames[i]); } catch (e) {} } alert("XML http request not supported"); return null; } /** * Construct an HTTPBindingClient. */ function HTTPBindingClient(cname, uri, objectID) { this.uri = "/references/" + cname + "/" + uri; this.objectID = objectID; this.apply = this._createApplyMethod(); if (typeof DOMParser == "undefined") { DOMParser = function () {} DOMParser.prototype.parseFromString = function (str, contentType) { if (typeof ActiveXObject != "undefined") { var d = new ActiveXObject("MSXML.DomDocument"); d.loadXML(str); return d; } else if (typeof XMLHttpRequest != "undefined") { var req = new XMLHttpRequest; req.open("GET", "data:" + (contentType || "application/xml") + ";charset=utf-8," + encodeURIComponent(str), false); if (req.overrideMimeType) { req.overrideMimeType(contentType); } req.send(null); return req.responseXML; } } } }; /** * Construct a component. */ function ClientComponent(name) { this.name = name; } /** * Public API. */ /** * Return a component. */ function component(name) { return new ClientComponent(name); } /** * Return a reference proxy. */ function reference(comp, name) { return new HTTPBindingClient(comp.name, name); }; /** * Add proxy functions to a reference proxy. */ function defun(ref) { function defapply(name) { return function() { var args = new Array(); args[0] = name; for (i = 0, n = arguments.length; i < n; i++) args[i + 1] = arguments[i]; this.apply.apply(this, args); }; } for (f = 1; f < arguments.length; f++) { var fn = arguments[f]; ref[fn]= defapply(fn); } return ref; };