// ------------------------------------ // PendingCall - this is the actual SOAP encoding/decoding code. // // Each PendingCall is an instance of an active SOAP interaction, containing // a request and a response envelope and a pointer back to the associated // SOAPCall (see above). // ------------------------------------ import mx.services.SOAPConstants; import mx.services.Log; import mx.services.QName; import mx.services.SOAPCall; import mx.services.SOAPFault; import mx.services.SOAPHeader; import mx.services.DataType; import mx.services.SchemaVersion; import mx.services.ArrayProxy; import mx.services.RowSetProxy; import mx.services.PrefixedQName; /** @helpid 1746 @tiptext information regarding an outstanding call to a service */ class mx.services.PendingCall { /** @helpid 1727 @tiptext current soap call */ var myCall:SOAPCall; var soapConstants:SOAPConstants; var log:Log; var doLazyDecoding:Boolean = true; var doDecoding:Boolean = true; var timeoutFunc:Function; var timerID:Number; /** @helpid 1733 @tiptext current SOAP request document */ var request:Object; /** @helpid 1734 @tiptext current SOAP result document */ var response:Object; var actionURI:String; var requestHeaders; var fault:SOAPFault; var responseHeaders; var bodyNode; var cancelled:Boolean = false; // Have we been cancelled? /** @helpid 1725 @tiptext called when a fault occurs */ var onFault:Function; /** @helpid 1726 @tiptext called when results are ready */ var onResult:Function; var onHeaders:Function; var __handleResult:Function; var __handleFault:Function; var handleDelayedFault : Function; public function PendingCall(myCall) { this.myCall = myCall; this.soapConstants = myCall.soapConstants; this.log = myCall.log; // This switch controls whether we hand back fully decoded Arrays and // RowSets, or whether we "lazily" decode them as needed via a proxy object. this.doLazyDecoding = myCall.doLazyDecoding; // Should we decode the body at all, or just hand back an array of its child nodes? // (note that we'll still process headers regardless) this.doDecoding = myCall.doDecoding; }; function setTimeout(timeoutMS) { this.timeoutFunc = function() { clearInterval(this.timerID); this.timerID = undefined; var fault = new SOAPFault("Timeout", "Timeout while calling method " + this.myCall.operationName + "!"); this.onFault(fault); } this.timerID = setInterval(this, "timeoutFunc", timeoutMS); } function cancel() { // Clear any timeout if (this.timerID != undefined) { clearInterval(this.timerID); } // And mark us as cancelled so we don't react to events this.cancelled = true; } function encode() { this.log.logInfo("Encoding SOAPCall request", Log.VERBOSE); this.encodeHTTPRequest(); this.encodeSOAPEnvelope(); } function encodeHTTPRequest() { this.log.logDebug("Creating HTTP request object"); // create an XML object to represent the request var request = new XML(); request.ignoreWhite = true; request.contentType = this.soapConstants.contentType; request.xmlDecl = SOAPConstants.XML_DECLARATION; // add SOAPAction HTTP Header, composing one if it has not been set // NOTE: this method not available prior to Flash version 6,0,65: request.addRequestHeader("SOAPAction", "\"" + this.myCall.actionURI + "\""); // associate the XML request with this PendingCall this.request = request; } function encodeSOAPEnvelope() { this.log.logDebug("Encoding SOAP request envelope"); var request = this.request; var schemaVersion = this.myCall.schemaVersion; // create the SOAPEnvelope in the Document var envQName = soapConstants.envelopeQName; var envelopeElement = request.createElement(SOAPConstants.SOAP_ENV_PREFIX + ":" + envQName.localPart); envelopeElement.attributes["xmlns:" + SOAPConstants.SOAP_ENV_PREFIX] = soapConstants.ENVELOPE_URI; envelopeElement.attributes["xmlns:" + SOAPConstants.XML_SCHEMA_PREFIX] = schemaVersion.xsdURI; envelopeElement.attributes["xmlns:" + SOAPConstants.XML_SCHEMA_INSTANCE_PREFIX] = schemaVersion.xsiURI; request.appendChild(envelopeElement); request["soapEnvelope"] = envelopeElement; // create a header element for each header node passed in as a parameter this.encodeSOAPHeader(); // create a single body element for the method invocation this.encodeSOAPBody(); } function encodeSOAPHeader() { var request = this.request; var envelope = this.request["soapEnvelope"]; // each Header Entry in the array should contain a fully-qualified // XML Node in the element property var headerArray = this.requestHeaders; if (headerArray.length > 0) { var headerElement = request.createElement(SOAPConstants.SOAP_ENV_PREFIX + ":" + soapConstants.headerQName.localPart); envelope.appendChild(headerElement); var numHeaders = headerArray.length; for (var i=0; i 1) { for (var i = 0; i < dims.length; i++) { if (i > 0) attr += ","; attr += dims[i]; } } else { if (first) { attr += "[" + numMembers + "]"; first = false; } else { attr += "[]"; } } thisType = thisType.arrayType; } elementNode.attributes[SOAPConstants.ARRAY_TYPE_PQNAME] = getStringFromQName(elementNode, valueType.qname) + attr; elementNode.attributes[typeAttr] = SOAPConstants.ARRAY_PQNAME; } for (var i=0; i Log.BRIEF) { this.response._decodeTimeMark = new Date(); this.response._decodeTime = Math.round(this.response._decodeTimeMark - this.response._parseTimeMark); this.log.logInfo("Decoded SOAP response into result [" + this.response._decodeTime + " millis]"); } } /* Callbacks: * (1) onFault if fault exists OR * (2) onHeader if header exists (NOTE: if callback does not exist and the header * must be understood, a fault will occur and onHeader will not be called) AND * (3) onResult if RPC method succeeded even if it returned void AND * (4) Any user-defined callback regardless of whether the other callbacks were made */ if (this.fault != undefined) { this.__handleFault(this.fault); this.onFault(this.fault, response); this.myCall.onFault(this.fault, response); } else { if (this.responseHeaders != undefined) { this.onHeaders(this.responseHeaders, response); } this.onResult(result, response); // First our own result handler for this particular invocation this.myCall.onResult(result, response); // Then call onResult on the SOAPCall too this.__handleResult(result); // Do seekrit internal stuff (callbacks for Royale frameworks) } // and always call a user-defined callback regardless of other callbacks: this[callbackMethod](result, response); } function decodeSOAPEnvelope(response) { this.log.logDebug("Decoding SOAP response envelope"); var result; var constants = this.soapConstants; var envelopeNode = response.firstChild; // check for Version mismatch if (envelopeNode.getNamespaceURI() != this.soapConstants.ENVELOPE_URI) { var versionFault = new SOAPFault(); versionFault.faultcode = "VersionMismatch"; versionFault.faultstring = "Request implements version: " + this.soapConstants.ENVELOPE_URI + " Response implements version " + envelopeNode.getNamespaceURI(); this.fault = versionFault; } else { var soapenvPrefix = envelopeNode.getPrefixForNamespace(constants.ENVELOPE_URI); var xsiPrefix = envelopeNode.getPrefixForNamespace(this.myCall.schemaVersion.xsiURI); var xsdPrefix = envelopeNode.getPrefixForNamespace(this.myCall.schemaVersion.xsdURI); var headerPrefixedQName = new PrefixedQName(soapenvPrefix, constants.headerQName); var bodyPrefixedQName = new PrefixedQName(soapenvPrefix, constants.bodyQName); var faultPrefixedQName = new PrefixedQName(soapenvPrefix, constants.faultQName); var envelopeChildren = envelopeNode.childNodes; var numEnvChildren = envelopeChildren.length; for (var c=0; c // // Field1 // Field2 // // // // a string // 52 // // // // // (That would decode into an Array with a single object {Field1 : "a string", Field2 : 52}) // function decodeRowSet(rowSetNode) { var returnVal = new Array(); var types = new Array(); var fields = new Array(); var descriptorNodes = rowSetNode.childNodes[0].childNodes; var numCols = descriptorNodes.length; // First child is array of names and types for (var i = 0; i < numCols; i++) { var node = descriptorNodes[i]; var typeAttr = node.attributes["type"]; if (typeAttr == undefined) return; var typeQName = node.getQNameFromString(typeAttr); if (typeQName == undefined) return; types[i] = this.myCall.schemaContext.getTypeByQName(typeQName); fields[i] = node.childNodes[0].nodeValue; } var itemNodes = rowSetNode.childNodes[1].childNodes; var numItems = itemNodes.length; if (this.doLazyDecoding) { return new RowSetProxy(this, itemNodes, types, fields); } // Next child is array of items, each of which is an array of fields for (var i = 0; i < numItems; i++) { returnVal[i] = this.decodeRowSetItem(itemNodes[i], types, fields); } return returnVal; } function decodeQueryBean(beanNode) { var returnVal = new Array(); var fields = new Array(); var colNode = beanNode.getElementsByLocalName("columnList")[0]; if (colNode == undefined) return; // error var numCols = colNode.childNodes.length; for (var j = 0; j < numCols; j++) { var thisNode = colNode.childNodes[j]; fields[j] = thisNode.childNodes[0].nodeValue; } var dataNode = beanNode.getElementsByLocalName("data")[0]; if (dataNode == undefined) return; // error // This is an array of arrays - each array will contain // fields.length elements, which should be associated with // the fields of our result var data = this.decodeSOAPArrayNode(dataNode); for (var j = 0; j < data.length; j++) { var item = data[j]; var resultItem = new Object(); for (var f = 0; f < fields.length; f++) { resultItem[fields[f]] = item[f]; } returnVal[j] = resultItem; } return returnVal; } function decodeRowSetItem(itemNode, types, fields) { var colNodes = itemNode.childNodes; var value = new Object(); var numCols = types.length; for (var j = 0; j < numCols; j++) { var colNode = colNodes[j]; value[fields[j]] = this.decodeResultNode(colNode, types[j]); } return value; } function decodeMap(node) { var returnVal = new Object(); var itemNodes = node.childNodes; // Each node has a key and a value subnode for (var i = 0; i < itemNodes.length; i++) { var childNodes = itemNodes[i].childNodes; var _key; var _value; for (var j = 0; j < childNodes.length; j++) { var child = childNodes[j]; if (child.nodeName == "key") { _key = this.decodeResultNode(child); } else if (child.nodeName == "value") { _value = this.decodeResultNode(child); } else { // TODO : Fault here! } } returnVal[_key] = _value; } return returnVal; } function getTypeFromNode(node) { // TODO : Fix this not to care about the xsi: prefix (will be slower, // but correct)! var typeAttr = node.attributes["xsi:type"]; if (typeAttr == undefined) return; var typeQName = node.getQNameFromString(typeAttr); if (typeQName == undefined) return; return this.myCall.schemaContext.getTypeByQName(typeQName); } function decodeDate(rawValue) { var d; var datePart; var timePart; var sep = rawValue.indexOf("T"); var tsep = rawValue.indexOf(":"); var dsep = rawValue.indexOf("-"); if (sep != -1) { datePart = rawValue.substring(0, sep); timePart = rawValue.substring(sep+1); } else if (tsep != -1) { timePart = rawValue; } else if (dsep != -1) { datePart = rawValue; } if (datePart != undefined) { var year = datePart.substring(0, datePart.indexOf("-")); var month = datePart.substring(5, datePart.indexOf("-", 5)); var day = datePart.substring(8, 10); d = new Date(Date.UTC(year, month - 1, day)); } else { d = new Date(); } if (timePart != undefined) { // check for timezone offset var tz = "Z"; var hourOffset = 0; if (datePart.length > 10) { tz = datePart.substring(10); } else if (timePart.length > 12) { tz = timePart.substring(12); } if (tz != "Z") { var len = tz.length; hourOffset = tz.substring((len - 5), (len - 3)); if (tz.charAt(len - 6) == '+') hourOffset = 0 - hourOffset; } var hours = Number(timePart.substring(0, timePart.indexOf(":"))); var minutes = timePart.substring(3, timePart.indexOf(":", 3)); var seconds = timePart.substring(6, timePart.indexOf(".", 6)); var millis = timePart.substr(9, 3); d.setUTCHours(hours, minutes, seconds, millis); d = new Date(d.getTime() + (hourOffset * 3600000)); } return d; } function decodeSOAPFault(faultNode) { this.log.logDebug("SOAP: Decoding SOAP response fault"); var fault = new SOAPFault(); fault.element = faultNode; var faultChildren = faultNode.childNodes; var numFaultChildren = faultChildren.length; for (var i=0; i