/******************************************************************************* * Copyright (c) 2015, 2017 itemis AG (http://www.itemis.eu) and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ define(['jquery'], function(jQuery) { var globalState = {}; /** * Generic service implementation that can serve as superclass for specialized services. */ function XtextService() {}; /** * Initialize the request metadata for this service class. Two variants: * - initialize(serviceUrl, serviceType, resourceId, updateService) * - initialize(xtextServices, serviceType) */ XtextService.prototype.initialize = function() { this._serviceType = arguments[1]; if (typeof(arguments[0]) === 'string') { this._requestUrl = arguments[0] + '/' + this._serviceType; var resourceId = arguments[2]; if (resourceId) this._encodedResourceId = encodeURIComponent(resourceId); this._updateService = arguments[3]; } else { var xtextServices = arguments[0]; if (xtextServices.options) { this._requestUrl = xtextServices.options.serviceUrl + '/' + this._serviceType; var resourceId = xtextServices.options.resourceId; if (resourceId) this._encodedResourceId = encodeURIComponent(resourceId); } this._updateService = xtextServices.updateService; } } XtextService.prototype.setState = function(state) { this._state = state; } /** * Invoke the service with default service behavior. */ XtextService.prototype.invoke = function(editorContext, params, deferred, callbacks) { if (deferred === undefined) { deferred = jQuery.Deferred(); } if (jQuery.isFunction(this._checkPreconditions) && !this._checkPreconditions(editorContext, params)) { deferred.reject(); return deferred.promise(); } var serverData = { contentType: params.contentType }; var initResult; if (jQuery.isFunction(this._initServerData)) initResult = this._initServerData(serverData, editorContext, params); var httpMethod = 'GET'; if (initResult && initResult.httpMethod) httpMethod = initResult.httpMethod; var self = this; if (!(initResult && initResult.suppressContent)) { if (params.sendFullText) { serverData.fullText = editorContext.getText(); httpMethod = 'POST'; } else { var knownServerState = editorContext.getServerState(); if (knownServerState.updateInProgress) { if (self._updateService) { self._updateService.addCompletionCallback(function() { self.invoke(editorContext, params, deferred); }); } else { deferred.reject(); } return deferred.promise(); } if (knownServerState.stateId !== undefined) { serverData.requiredStateId = knownServerState.stateId; } } } var onSuccess; if (jQuery.isFunction(this._getSuccessCallback)) { onSuccess = this._getSuccessCallback(editorContext, params, deferred); } else { onSuccess = function(result) { if (result.conflict) { if (self._increaseRecursionCount(editorContext)) { var onConflictResult; if (jQuery.isFunction(self._onConflict)) { onConflictResult = self._onConflict(editorContext, result.conflict); } if (!(onConflictResult && onConflictResult.suppressForcedUpdate) && !params.sendFullText && result.conflict == 'invalidStateId' && self._updateService) { self._updateService.addCompletionCallback(function() { self.invoke(editorContext, params, deferred); }); var knownServerState = editorContext.getServerState(); delete knownServerState.stateId; delete knownServerState.text; self._updateService.invoke(editorContext, params); } else { self.invoke(editorContext, params, deferred); } } else { deferred.reject(); } return false; } if (jQuery.isFunction(self._processResult)) { var processedResult = self._processResult(result, editorContext); if (processedResult) { deferred.resolve(processedResult); return true; } } deferred.resolve(result); }; } var onError = function(xhr, textStatus, errorThrown) { if (xhr.status == 404 && !params.loadFromServer && self._increaseRecursionCount(editorContext)) { var onConflictResult; if (jQuery.isFunction(self._onConflict)) { onConflictResult = self._onConflict(editorContext, errorThrown); } var knownServerState = editorContext.getServerState(); if (!(onConflictResult && onConflictResult.suppressForcedUpdate) && knownServerState.text !== undefined && self._updateService) { self._updateService.addCompletionCallback(function() { self.invoke(editorContext, params, deferred); }); delete knownServerState.stateId; delete knownServerState.text; self._updateService.invoke(editorContext, params); return true; } } deferred.reject(errorThrown); } self.sendRequest(editorContext, { type: httpMethod, data: serverData, success: onSuccess, error: onError }, !params.sendFullText); return deferred.promise().always(function() { self._recursionCount = undefined; }); } /** * Send an HTTP request to invoke the service. */ XtextService.prototype.sendRequest = function(editorContext, settings, needsSession) { var self = this; self.setState('started'); var corsEnabled = editorContext.xtextServices.options['enableCors']; if(corsEnabled) { settings.crossDomain = true; settings.xhrFields = {withCredentials: true}; } var onSuccess = settings.success; settings.success = function(result) { var accepted = true; if (jQuery.isFunction(onSuccess)) { accepted = onSuccess(result); } if (accepted || accepted === undefined) { self.setState('finished'); if (editorContext.xtextServices) { var successListeners = editorContext.xtextServices.successListeners; if (successListeners) { for (var i = 0; i < successListeners.length; i++) { var listener = successListeners[i]; if (jQuery.isFunction(listener)) { listener(self._serviceType, result); } } } } } }; var onError = settings.error; settings.error = function(xhr, textStatus, errorThrown) { var resolved = false; if (jQuery.isFunction(onError)) { resolved = onError(xhr, textStatus, errorThrown); } if (!resolved) { self.setState(undefined); self._reportError(editorContext, textStatus, errorThrown, xhr); } }; settings.async = true; var requestUrl = self._requestUrl; if (!settings.data.resource && self._encodedResourceId) { if (requestUrl.indexOf('?') >= 0) requestUrl += '&resource=' + self._encodedResourceId; else requestUrl += '?resource=' + self._encodedResourceId; } if (needsSession && globalState._initPending) { // We have to wait until the initial request has finished to make sure the client has // received a valid session id if (!globalState._waitingRequests) globalState._waitingRequests = []; globalState._waitingRequests.push({requestUrl: requestUrl, settings: settings}); } else { if (needsSession && !globalState._initDone) { globalState._initPending = true; var onComplete = settings.complete; settings.complete = function(xhr, textStatus) { if (jQuery.isFunction(onComplete)) { onComplete(xhr, textStatus); } delete globalState._initPending; globalState._initDone = true; if (globalState._waitingRequests) { for (var i = 0; i < globalState._waitingRequests.length; i++) { var request = globalState._waitingRequests[i]; jQuery.ajax(request.requestUrl, request.settings); } delete globalState._waitingRequests; } } } jQuery.ajax(requestUrl, settings); } } /** * Use this in case of a conflict before retrying the service invocation. If the number * of retries exceeds the limit, an error is reported and the function returns false. */ XtextService.prototype._increaseRecursionCount = function(editorContext) { if (this._recursionCount === undefined) this._recursionCount = 1; else this._recursionCount++; if (this._recursionCount >= 10) { this._reportError(editorContext, 'warning', 'Xtext service request failed after 10 attempts.', {}); return false; } return true; }, /** * Report an error to the listeners. */ XtextService.prototype._reportError = function(editorContext, severity, message, requestData) { if (editorContext.xtextServices) { var errorListeners = editorContext.xtextServices.errorListeners; if (errorListeners) { for (var i = 0; i < errorListeners.length; i++) { var listener = errorListeners[i]; if (jQuery.isFunction(listener)) { listener(this._serviceType, severity, message, requestData); } } } } } return XtextService; });