blob: 339619f0105acb68c9fa2ed371d02ddb4f8dea09 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2013 Innoopract Informationssysteme GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
* EclipseSource - ongoing development
******************************************************************************/
/*global confirm: false*/
qx.Class.define( "org.eclipse.swt.Request", {
type : "singleton",
extend : qx.core.Target,
construct : function() {
this.base( arguments );
// the URL to which the requests are sent
this._url = "";
// the map of parameters that will be posted with the next call to 'send()'
this._parameters = {};
// instance variables that hold the essential request parameters
this._uiRootId = "";
this._requestCounter = null;
// Number of currently running or scheduled requests, used to determine when
// to show the wait hint (e.g. hour-glass cursor)
this._runningRequestCount = 0;
// Flag that is set to true if send() was called but the delay timeout
// has not yet timed out
this._inDelayedSend = false;
// As the CallBackRequests get blocked at the server to wait for
// background activity I choose a large timeout...
var requestQueue = qx.io.remote.RequestQueue.getInstance();
requestQueue.setDefaultTimeout( 60000 * 60 * 24 ); // 24h
// Initialize the request queue to allow only one request at a time
requestQueue.setMaxConcurrentRequests( 1 );
// References the currently running request or null if no request is active
this._currentRequest = null;
this._waitHintTime = 700;
this._waitHintTimer = new qx.client.Timer( this._waitHintTime );
this._waitHintTimer.addEventListener( "interval", this._showWaitHint, this );
},
destruct : function() {
this._currentRequest = null;
this._waitHintTimer.dispose();
this._waitHintTimer = null;
},
events : {
"send" : "qx.event.type.DataEvent",
"received" : "qx.event.type.DataEvent"
},
members : {
setUrl : function( url ) {
this._url = url;
},
getUrl : function() {
return this._url;
},
setUIRootId : function( uiRootId ) {
this._uiRootId = uiRootId;
},
getUIRootId : function() {
return this._uiRootId;
},
setRequestCounter : function( requestCounter ) {
this._requestCounter = requestCounter;
},
getRequestCounter : function() {
return this._requestCounter;
},
/**
* Adds a request parameter to this request with the given name and value
*/
addParameter : function( name, value ) {
this._parameters[ name ] = value;
},
/**
* Removes the parameter denoted by name from this request.
*/
removeParameter : function( name ) {
delete this._parameters[ name ];
},
/**
* Returns the parameter value for the given name or null if no parameter
* with such a name exists.
*/
getParameter : function( name ) {
var result = this._parameters[ name ];
if( result === undefined ) {
result = null;
}
return result;
},
/**
* Adds the given eventType to this request. The sourceId denotes the id of
* the widget that caused the event.
*/
addEvent : function( eventType, sourceId ) {
this._parameters[ eventType ] = sourceId;
},
/**
* Sends this request asynchronously. All parameters that were added since
* the last 'send()' will now be sent.
*/
send : function() {
if( !this._inDelayedSend ) {
this._inDelayedSend = true;
var func = function() {
this._sendImmediate( true );
};
qx.client.Timer.once( func, this, 60 );
}
},
sendSyncronous : function() {
this._sendImmediate( false );
},
_sendImmediate : function( async ) {
this._dispatchSendEvent();
// set mandatory parameters; do this after regular params to override them
// in case of conflict
this._parameters[ "uiRoot" ] = this._uiRootId;
if( this._requestCounter == -1 ) {
// TODO [fappel]: This is a workaround that prevents sending a request
// without a valid request id. Needed for background proccessing.
this._inDelayedSend = false;
this.send();
} else {
if( this._requestCounter != null ) {
this._parameters[ "requestCounter" ] = this._requestCounter;
this._requestCounter = -1;
}
// create and configure request object
var request = this._createRequest();
request.setAsynchronous( async );
// copy the _parameters map which was filled during client interaction
// to the request
this._inDelayedSend = false;
this._copyParameters( request );
this._runningRequestCount++;
// notify user when request takes longer than 500 ms
if( this._runningRequestCount === 1 ) {
this._waitHintTimer.startWith( this._waitHintTime );
}
// clear the parameter list
this._parameters = {};
// queue request to be sent (async) or send and block (sync)
if( async ) {
request.send();
} else {
this._sendStandalone( request );
}
}
},
_copyParameters : function( request ) {
var data = [];
for( var parameterName in this._parameters ) {
data.push( encodeURIComponent( parameterName )
+ "="
+ encodeURIComponent( this._parameters[ parameterName ] ) );
}
request.setData( data.join( "&" ) );
},
_createRequest : function() {
var result = new qx.io.remote.Request( this._url, "POST", "application/javascript" );
result.addEventListener( "sending", this._handleSending, this );
result.addEventListener( "completed", this._handleCompleted, this );
result.addEventListener( "failed", this._handleFailed, this );
return result;
},
_sendStandalone : function( request ) {
// TODO [rh] WORKAROUND
// we would need two requestQueues (one for 'normal' requests that
// is limited to 1 concurrent request and one for the 'independant'
// requests created here
// Until qooxdoo supports multiple requestQueues we create and
// send this kind of request without knownledge of the request queue
var vRequest = request;
var vTransport = new qx.io.remote.Exchange(vRequest);
// Establish event connection between qx.io.remote.Exchange instance and
// qx.io.remote.Request
vTransport.addEventListener( "sending", vRequest._onsending, vRequest );
vTransport.addEventListener( "receiving", vRequest._onreceiving, vRequest );
vTransport.addEventListener( "completed", vRequest._oncompleted, vRequest );
vTransport.addEventListener( "aborted", vRequest._onaborted, vRequest );
vTransport.addEventListener( "timeout", vRequest._ontimeout, vRequest );
vTransport.addEventListener( "failed", vRequest._onfailed, vRequest );
vTransport._start = ( new Date() ).valueOf();
vTransport.send();
// END WORKAROUND
},
////////////////////////
// Handle request events
_handleSending : function( evt ) {
var exchange = evt.getTarget();
this._currentRequest = exchange.getRequest();
},
_handleFailed : function( evt ) {
// [vt] workaround for bug #253756: Copied code from _handleSending since
// the sending phase is skipped on failure in IE
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=253756
var exchange = evt.getTarget();
this._currentRequest = exchange.getRequest();
var giveUp = true;
if( this._isConnectionError( evt.getStatusCode() ) ) {
giveUp = !this._handleConnectionError( evt );
}
if( giveUp ) {
var request = exchange.getImplementation().getRequest();
var text = request.responseText;
// [if] typeof(..) == "unknown" is IE specific. Used to prevent error:
// "The data necessary to complete this operation is not yet available"
if( typeof( text ) == "unknown" ) {
text = undefined;
}
if( text && text.length > 0 ) {
if( this._isJsonResponse( request ) ) {
var messageObject = JSON.parse( text );
org.eclipse.rwt.ErrorHandler.showErrorBox( messageObject.meta.message, true );
} else {
org.eclipse.rwt.ErrorHandler.showErrorPage( text );
}
} else {
var statusCode = String( evt.getStatusCode() );
text = "<p>Request failed.</p><pre>HTTP Status Code: " + statusCode + "</pre>";
org.eclipse.rwt.ErrorHandler.showErrorPage( text );
}
this._hideWaitHint();
}
// [if] Dispose only finished transport - see bug 301261, 317616
exchange.dispose();
},
_handleCompleted : function( evt ) {
var exchange = evt.getTarget();
var text = exchange.getImplementation().getRequest().responseText;
var errorOccured = false;
try {
var messageObject = JSON.parse( text );
org.eclipse.swt.EventUtil.setSuspended( true );
org.eclipse.rwt.protocol.Processor.processMessage( messageObject );
qx.ui.core.Widget.flushGlobalQueues();
org.eclipse.swt.EventUtil.setSuspended( false );
org.eclipse.rwt.UICallBack.getInstance().sendUICallBackRequest();
} catch( ex ) {
org.eclipse.rwt.ErrorHandler.processJavaScriptErrorInResponse( text,
ex,
this._currentRequest );
errorOccured = true;
}
if( !errorOccured ) {
this._dispatchReceivedEvent();
}
this._runningRequestCount--;
this._hideWaitHint();
// [if] Dispose only finished transport - see bug 301261, 317616
exchange.dispose();
},
///////////////////////////////
// Handling connection problems
_handleConnectionError : function( evt ) {
var msg
= "The server seems to be temporarily unavailable.\n"
+ "Would you like to retry?";
var result = confirm( msg );
if( result ) {
org.eclipse.rwt.ErrorHandler.hideErrorBox();
var request = this._createRequest();
var failedRequest = this._currentRequest;
request.setAsynchronous( failedRequest.getAsynchronous() );
// Reusing the same request object causes strange behaviour, therefore
// create a new request and copy the relevant parts from the failed one
var failedHeaders = failedRequest.getRequestHeaders();
for( var headerName in failedHeaders ) {
request.setRequestHeader( headerName, failedHeaders[ headerName ] );
}
var failedParameters = failedRequest.getParameters();
for( var parameterName in failedParameters ) {
request.setParameter( parameterName,
failedParameters[ parameterName ] );
}
request.setData( failedRequest.getData() );
this._restartRequest( request );
}
return result;
},
_restartRequest : function( request ) {
// TODO [rh] this is adapted from qx.io.remote.RequestQueue#add as there
// is no official way to insert a new request as the first one in
// RequestQueue
request.setState( "queued" );
var requestQueue = qx.io.remote.RequestQueue.getInstance();
qx.lang.Array.insertAt( requestQueue._queue, request, 0 );
requestQueue._check();
if( requestQueue.getEnabled() ) {
requestQueue._timer.start();
}
},
_isConnectionError : qx.core.Variant.select( "qx.client", {
"mshtml|newmshtml" : function( statusCode ) {
// for a description of the IE status codes, see
// http://support.microsoft.com/kb/193625
var result = ( statusCode === 12007 // ERROR_INTERNET_NAME_NOT_RESOLVED
|| statusCode === 12029 // ERROR_INTERNET_CANNOT_CONNECT
|| statusCode === 12030 // ERROR_INTERNET_CONNECTION_ABORTED
|| statusCode === 12031 // ERROR_INTERNET_CONNECTION_RESET
|| statusCode === 12152 ); // ERROR_HTTP_INVALID_SERVER_RESPONSE
return result;
},
"gecko" : function( statusCode ) {
// Firefox 3 reports other statusCode than oder versions (bug #249814)
var result;
// Check if Gecko > 1.9 is running (used in FF 3)
// Gecko/app integration overview: http://developer.mozilla.org/en/Gecko
if( org.eclipse.rwt.Client.getMajor() * 10 + org.eclipse.rwt.Client.getMinor() >= 19 ) {
result = ( statusCode === 0 );
} else {
result = ( statusCode === -1 );
}
return result;
},
"default" : function( statusCode ) {
return statusCode === 0;
}
} ),
_isJsonResponse : function( request ) {
var contentType = request.getResponseHeader( "Content-Type" );
return contentType.indexOf( qx.util.Mime.JSON ) !== -1;
},
///////////////////////////////////////////////////
// Wait hint - UI feedback while request is running
_showWaitHint : function() {
this._waitHintTimer.stop();
if( this._runningRequestCount > 0 ) {
var doc = qx.ui.core.ClientDocument.getInstance();
doc.setGlobalCursor( qx.constant.Style.CURSOR_PROGRESS );
org.eclipse.rwt.ErrorHandler.showWaitHint();
}
},
_hideWaitHint : function() {
this._waitHintTimer.stop();
if( this._runningRequestCount === 0 ) {
var doc = qx.ui.core.ClientDocument.getInstance();
doc.setGlobalCursor( null );
org.eclipse.rwt.ErrorHandler.hideErrorBox();
}
},
_dispatchSendEvent : function() {
if( this.hasEventListeners( "send" ) ) {
var event = new qx.event.type.DataEvent( "send", this );
this.dispatchEvent( event, true );
}
},
_dispatchReceivedEvent : function() {
if( this.hasEventListeners( "received" ) ) {
var event = new qx.event.type.DataEvent( "received", this );
this.dispatchEvent( event, true );
}
}
}
});