Index: bridge/lib/ajax.js =================================================================== --- bridge/lib/ajax.js (revision 80) +++ bridge/lib/ajax.js (working copy) @@ -195,6 +195,10 @@ } }, + getAllResponseHeaders: function() { + return this.request.getAllResponseHeaders(); + }, + containsResponseHeader: function(name) { try { var header = this.request.getResponseHeader(name); Index: bridge/src/connection.async.js =================================================================== --- bridge/src/connection.async.js (revision 80) +++ bridge/src/connection.async.js (working copy) @@ -44,10 +44,10 @@ this.connectionDownListeners = []; this.connectionTroubleListeners = []; - this.listener = { close: Function.NOOP, abort: Function.NOOP }; - this.listening = { remove: Function.NOOP }; - this.timeoutBomb = { cancel: Function.NOOP }; - this.heartbeat = { stop: Function.NOOP }; + this.listener = {close: Function.NOOP, abort: Function.NOOP}; + this.listening = {remove: Function.NOOP}; + this.timeoutBomb = {cancel: Function.NOOP}; + this.heartbeat = {stop: Function.NOOP}; this.pingURI = configuration.pingURI; this.getURI = configuration.receiveUpdatesURI; @@ -59,6 +59,11 @@ this.connectionDownListeners.clear(); }.bind(this)); + var orderedResponseProcessing = configuration.orderedResponseProcessing; + this.responseHandler = orderedResponseProcessing ? + new This.OrderedResponseHandler(this) : + new This.StandardResponseHandler(this); + var timeout = configuration.timeout ? configuration.timeout : 60000; this.onSend(function() { this.timeoutBomb.cancel(); @@ -69,14 +74,16 @@ this.timeoutBomb.cancel(); }.bind(this)); + this.beforeSend = function(request) { + this.responseHandler.add(request.identifier); + } + this.serverErrorCallback = this.onServerErrorListeners.broadcaster(); + this.receiveCallback = function(response) { - try { - this.onReceiveListeners.broadcast(response); - } catch (e) { - this.logger.error('receive broadcast failed', e); - } + this.responseHandler.processResponse(response); }.bind(this); + this.sendXWindowCookie = Function.NOOP; this.receiveXWindowCookie = function (response) { var xWindowCookie = response.getResponseHeader("X-Set-Window-Cookie"); @@ -188,6 +195,7 @@ Connection.FormPost(request); request.on(Connection.OK, this.receiveCallback); request.on(Connection.OK, Connection.Close); + request.on(Connection.OK, this.beforeSend(request)); }.bind(this)); }.bind(this)); @@ -265,6 +273,7 @@ Connection.FormPost(request); request.on(Connection.OK, this.receiveCallback); request.on(Connection.OK, Connection.Close); + request.on(Connection.OK, this.beforeSend(request)); }.bind(this)); }.bind(this); @@ -287,7 +296,7 @@ this.lock = configuration.blockUI ? new Connection.Lock() : new Connection.NOOPLock(); - this.logger.info('asynchronous mode'); + this.logger.info(orderedResponseProcessing ? 'asynchronous mode with ordered processing' : 'asynchronous mode'); }, send: function(query) { @@ -307,6 +316,7 @@ request.on(Connection.OK, this.receiveCallback); request.on(Connection.ServerError, this.serverErrorCallback); request.on(Connection.OK, Connection.Close); + request.on(Connection.OK, this.beforeSend(request)); this.onSendListeners.broadcast(); }.bind(this)); } catch (e) { @@ -359,5 +369,128 @@ } } }); + + This.StandardResponseHandler = Object.subclass({ + initialize: function(connection) { + this.connection = connection; + }, + + add: function(requestId) { + //nothing + }, + + processResponse: function(response) { + try { + this.connection.onReceiveListeners.broadcast(response); + } catch (e) { + this.connection.logger.error('receive broadcast failed', e); + } + } + + }); + + This.OrderedResponseHandler = Object.subclass({ + initialize: function(connection) { + // An array of Request Ids. These should be added in order + this.ids = []; + // An array of responses. The order should match the appropriate request id + this.responses = []; + + this.connection = connection; + this.logger = this.connection.logger.child("request-queue"); + }, + + add: function(requestId) { + this.ids.push(requestId); + this.responses.push('none'); + }, + + processResponse: function(response) { + //Find the position in the array that has been reserver for the request's response + var foundIdx = -1; + for (var idx = 0; idx < this.ids.length; idx=idx+1) { + if (this.ids[idx] == response.identifier) { + this.responses[idx] = new This.PersistentResponse(response); + foundIdx = idx; + } + } + + if (foundIdx == 0) { + // We've found a reserved spot, and it's the first one we're waiting for. + //Process it and any subsequent responses we have received so far + while ((this.responses.size() > 0) && (this.responses[0] != 'none')) { + this.ids.shift(); + var localResponse = this.responses.shift(); + this.sendResponse(localResponse); + } + } else if (foundIdx == -1) { + // We have a response for a request we didn't know about. Just process it + this.logger.debug('unknown request => ' + response.identifier); + this.sendResponse(response); + } else { + this.logger.debug('queuing response for ' + response.identifier); + } + }, + + sendResponse: function(response) { + this.logger.debug('processing response: ' + response.identifier); + try { + this.connection.onReceiveListeners.broadcast(response); + } catch (e) { + this.logger.error('receive broadcast failed', e); + } + } + }); + + This.PersistentResponse = Object.subclass({ + initialize: function(response) { + // Store the header values, as we can't rely on the request to + // hold onto the values + this.responseHeaders = $H(); + + var allHeaders = response.getAllResponseHeaders(); + var headerArray = allHeaders.split('\n'); + for (var pos = 0; pos < headerArray.length; pos = pos + 1) { + var keyValueArray = headerArray[pos].split(':'); + var s = keyValueArray[0]; + var key = s.trim(); + s = keyValueArray[1]; + if (s) { + var value = s.trim(); + if (keyValueArray[1]) { + this.responseHeaders.set(key, value); + } + } + } + + this.identifier = response.identifier; + this.contentValue = response.content(); + + this.content = function() { + return this.contentValue; + }.bind(this); + this.contentAsDOM = function() { + var returnValue; + if (window.DOMParser) { + var parser = new DOMParser(); + returnValue = parser.parseFromString(this.contentValue, "text/xml"); + } else { + returnValue = new ActiveXObject("Microsoft.XMLDOM"); + returnValue.async = "false"; + returnValue.loadXML(this.contentValue); + } + return returnValue; + }.bind(this); + }, + + identifier: function() { + return this.identifier; + }, + + getResponseHeader: function(key) { + return this.responseHeaders.get(key); + } + }); + }); Index: core/src/com/icesoft/faces/context/DOMResponseWriter.java =================================================================== --- core/src/com/icesoft/faces/context/DOMResponseWriter.java (revision 80) +++ core/src/com/icesoft/faces/context/DOMResponseWriter.java (working copy) @@ -390,6 +390,11 @@ ResourceBundle localizedBundle = bridgeMessageResolver.bundleFor(context.getViewRoot().getLocale()); //todo: build startup script only once on aplication startup boolean synchronousMode = configuration.getAttributeAsBoolean("synchronousUpdate", false); + boolean orderedResponseProcessing = false; + if (!(synchronousMode)) { + orderedResponseProcessing = configuration.getAttributeAsBoolean("orderedResponseProcessing", false); + } + String startupScript = "window.disposeViewsURI = '" + blockingRequestHandlerContext + "block/dispose-views';\n" + "var container = '" + configurationID + "'.asElement().parentNode;\n" + @@ -416,6 +421,7 @@ "timeout: " + configuration.getAttributeAsLong("heartbeatTimeout", 30000) + "," + "retries: " + configuration.getAttributeAsLong("heartbeatRetries", 3) + "}," ) + + "orderedResponseProcessing: " + orderedResponseProcessing + "," + "timeout: " + configuration.getAttributeAsLong("connectionTimeout", 60000) + "}," + "messages: {" +