@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,122 lines (1,078 loc) • 83.4 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/**
* Model and related classes like bindings for OData V4.
*
* <b>Note:</b> Smart controls (<code>sap.ui.comp</code> library) do not support the SAPUI5 OData V4
* model. Also controls such as {@link sap.ui.table.TreeTable} and
* {@link sap.ui.table.AnalyticalTable} are not supported together with the SAPUI5 OData V4 model.
* The interface for applications has been changed for easier and more efficient use of the model.
* For a summary of these changes, see
* {@link topic:abd4d7c7548d4c29ab8364d3904a6d74 Changes Compared to OData V2 Model}.
*
* @name sap.ui.model.odata.v4
* @namespace
* @public
* @since 1.37.0
*/
//Provides class sap.ui.model.odata.v4.ODataModel
sap.ui.define([
"./ODataContextBinding",
"./ODataListBinding",
"./ODataMetaModel",
"./ODataPropertyBinding",
"./SubmitMode",
"./lib/_GroupLock",
"./lib/_Helper",
"./lib/_MetadataRequestor",
"./lib/_Parser",
"./lib/_Requestor",
"sap/base/assert",
"sap/base/Log",
"sap/ui/base/SyncPromise",
"sap/ui/core/library",
"sap/ui/core/message/Message",
"sap/ui/model/BindingMode",
"sap/ui/model/Context",
"sap/ui/model/Model",
"sap/ui/model/odata/OperationMode",
"sap/ui/thirdparty/URI"
], function (ODataContextBinding, ODataListBinding, ODataMetaModel, ODataPropertyBinding,
SubmitMode, _GroupLock, _Helper, _MetadataRequestor, _Parser, _Requestor, assert, Log,
SyncPromise, coreLibrary, Message, BindingMode, BaseContext, Model, OperationMode, URI) {
"use strict";
var rApplicationGroupID = /^\w+$/,
sClassName = "sap.ui.model.odata.v4.ODataModel",
// system query options allowed within a $expand query option
aExpandQueryOptions = ["$count", "$expand", "$filter", "$levels", "$orderby", "$search",
"$select"],
rGroupID = /^(\$auto(\.\w+)?|\$direct|\w+)$/,
MessageType = coreLibrary.MessageType,
aMessageTypes = [
undefined,
MessageType.Success,
MessageType.Information,
MessageType.Warning,
MessageType.Error
],
mSupportedEvents = {
messageChange : true,
sessionTimeout : true
},
mSupportedParameters = {
annotationURI : true,
autoExpandSelect : true,
earlyRequests : true,
groupId : true,
groupProperties : true,
httpHeaders : true,
metadataUrlParams : true,
odataVersion : true,
operationMode : true,
serviceUrl : true,
sharedRequests : true,
supportReferences : true,
synchronizationMode : true,
updateGroupId : true
},
// system query options allowed in mParameters
aSystemQueryOptions = ["$apply", "$count", "$expand", "$filter", "$orderby", "$search",
"$select"],
// valid header values: non-empty, only US-ASCII, no control chars
rValidHeader = /^[ -~]+$/;
/**
* Constructor for a new ODataModel.
*
* @param {object} mParameters
* The parameters
* @param {string|string[]} [mParameters.annotationURI]
* The URL (or an array of URLs) from which the annotation metadata are loaded.
* The annotation files are merged into the service metadata in the given order (last one
* wins). The same annotations are overwritten; if an annotation file contains other elements
* (like a type definition) that are already merged, an error is thrown.
* Supported since 1.41.0
* @param {boolean} [mParameters.autoExpandSelect=false]
* Whether the OData model's bindings automatically generate $select and $expand system query
* options from the binding hierarchy. Note: Dynamic changes to the binding hierarchy are not
* supported. This parameter is supported since 1.47.0, and since 1.75.0 it also enables
* property paths containing navigation properties in <code>$select</code>.
* @param {boolean} [mParameters.earlyRequests=false]
* Whether the following is requested at the earliest convenience:
* <ul>
* <li> root $metadata document and annotation files;
* <li> the security token.
* </ul>
* Note: The root $metadata document and annotation files are just requested but not yet
* converted from XML to JSON unless really needed.
* Supported since 1.53.0.
* <b>BEWARE:</b> The default value may change to <code>true</code> in later releases.
* You may also set {@link topic:26ba6a5c1e5c417f8b21cce1411dba2c Manifest Model Preload} in
* order to further speed up the start of a UI5 component.
* @param {string} [mParameters.groupId="$auto"]
* Controls the model's use of batch requests: '$auto' bundles requests from the model in a
* batch request which is sent automatically before rendering; '$direct' sends requests
* directly without batch; other values result in an error
* @param {object} [mParameters.groupProperties]
* Controls the use of batch requests for application groups. A map of application
* group IDs having an object with exactly one property <code>submit</code>. Valid values are
* 'API', 'Auto', 'Direct' see {@link sap.ui.model.odata.v4.SubmitMode}.
* Supported since 1.51.0
* @param {object} [mParameters.httpHeaders]
* Map of HTTP header names to their values, see {@link #changeHttpHeaders}
* @param {object} [mParameters.metadataUrlParams]
* Additional map of URL parameters used specifically for $metadata requests. Note that
* "sap-context-token" applies only to the service's root $metadata, but not to
* "cross-service references". Supported since 1.81.0
* @param {string} [mParameters.odataVersion="4.0"]
* The version of the OData service. Supported values are "2.0" and "4.0".
* @param {sap.ui.model.odata.OperationMode} [mParameters.operationMode]
* The operation mode for filtering and sorting. Since 1.39.0, the operation mode
* {@link sap.ui.model.odata.OperationMode.Server} is supported. All other operation modes
* including <code>undefined</code> lead to an error if 'vFilters' or 'vSorters' are given or
* if {@link sap.ui.model.odata.v4.ODataListBinding#filter} or
* {@link sap.ui.model.odata.v4.ODataListBinding#sort} is called.
* @param {string} mParameters.serviceUrl
* Root URL of the service to request data from. The path part of the URL must end with a
* forward slash according to OData V4 specification ABNF, rule "serviceRoot". You may append
* OData custom query options to the service root URL separated with a "?", for example
* "/MyService/?custom=foo".
* See specification "OData Version 4.0 Part 2: URL Conventions", "5.2 Custom Query Options".
* OData system query options and OData parameter aliases lead to an error.
* @param {boolean} [mParameters.sharedRequests]
* Whether all list bindings for the same resource path share their data, so that it is
* requested only once; only the value <code>true</code> is allowed; see parameter
* "$$sharedRequest" of {@link #bindList}. Additionally,
* {@link sap.ui.model.BindingMode.OneWay} becomes the default binding mode and
* {@link sap.ui.model.BindingMode.TwoWay} is forbidden. Note: This makes all bindings
* read-only, so it may be especially useful for value list models. Supported since 1.80.0
* @param {boolean} [mParameters.supportReferences=true]
* Whether <code><edmx:Reference></code> and <code><edmx:Include></code> directives are
* supported in order to load schemas on demand from other $metadata documents and include
* them into the current service ("cross-service references").
* @param {string} mParameters.synchronizationMode
* Controls synchronization between different bindings which refer to the same data for the
* case data changes in one binding. Must be set to 'None' which means bindings are not
* synchronized at all; all other values are not supported and lead to an error.
* @param {string} [mParameters.updateGroupId]
* The group ID that is used for update requests. If no update group ID is specified, <code>
* mParameters.groupId</code> is used. Valid update group IDs are <code>undefined</code>,
* '$auto', '$direct' or an application group ID.
* @throws {Error} If an unsupported synchronization mode is given, if the given service root
* URL does not end with a forward slash, if an unsupported parameter is given, if OData
* system query options or parameter aliases are specified as parameters, if an invalid group
* ID or update group ID is given, if the given operation mode is not supported, if an
* annotation file cannot be merged into the service metadata, if an unsupported value for
* <code>odataVersion</code> is given.
*
* @alias sap.ui.model.odata.v4.ODataModel
* @author SAP SE
* @class Model implementation for OData V4.
*
* Every resource path (relative to the service root URL, no query options) according to
* "4 Resource Path" in specification "OData Version 4.0 Part 2: URL Conventions" is
* a valid data binding path within this model if a leading slash is added; for example
* "/" + "SalesOrderList('A%2FB%26C')" to access an entity instance with key "A/B&C". Note
* that appropriate URI encoding is necessary, see the example of
* {@link sap.ui.model.odata.v4.ODataUtils.formatLiteral}. "4.5.1 Addressing Actions" needs an
* operation binding, see {@link sap.ui.model.odata.v4.ODataContextBinding}.
*
* Note that the OData V4 model has its own {@link sap.ui.model.odata.v4.Context} class.
* Bindings which are relative to such a V4 context depend on their corresponding parent
* binding and do not access data with their own service requests unless parameters are
* provided.
*
* <b>Group IDs</b> control the model's use of batch requests. Valid group IDs are:
* <ul>
* <li> <b>$auto</b> and <b>$auto.*</b>: Bundles requests from the model in a batch request
* which is sent automatically before rendering. You can use different '$auto.*' group IDs
* to use different batch requests. The suffix may be any non-empty string consisting of
* alphanumeric characters from the basic Latin alphabet, including the underscore. The
* submit mode for these group IDs is always
* {@link sap.ui.model.odata.v4.SubmitMode#Auto}.
* <li> <b>$direct</b>: Sends requests directly without batch. The submit mode for this
* group ID is always {@link sap.ui.model.odata.v4.SubmitMode#Direct}.
* <li> An application group ID, which is a non-empty string consisting of alphanumeric
* characters from the basic Latin alphabet, including the underscore. By default, an
* application group has the submit mode {@link sap.ui.model.odata.v4.SubmitMode#API}. It
* is possible to use a different submit mode; for details see
* <code>mParameters.groupProperties</code>.
* </ul>
*
* @extends sap.ui.model.Model
* @public
* @since 1.37.0
* @version 1.89.1
*/
var ODataModel = Model.extend("sap.ui.model.odata.v4.ODataModel",
/** @lends sap.ui.model.odata.v4.ODataModel.prototype */
{
constructor : function (mParameters) {
var sGroupId,
oGroupProperties,
sLanguageTag = sap.ui.getCore().getConfiguration().getLanguageTag(),
sODataVersion,
sParameter,
sServiceUrl,
oUri,
that = this;
// do not pass any parameters to Model
Model.call(this);
if (!mParameters || mParameters.synchronizationMode !== "None") {
throw new Error("Synchronization mode must be 'None'");
}
sODataVersion = mParameters.odataVersion || "4.0";
this.sODataVersion = sODataVersion;
if (sODataVersion !== "4.0" && sODataVersion !== "2.0") {
throw new Error("Unsupported value for parameter odataVersion: "
+ sODataVersion);
}
for (sParameter in mParameters) {
if (!(sParameter in mSupportedParameters)) {
throw new Error("Unsupported parameter: " + sParameter);
}
}
sServiceUrl = mParameters.serviceUrl;
if (!sServiceUrl) {
throw new Error("Missing service root URL");
}
oUri = new URI(sServiceUrl);
if (oUri.path()[oUri.path().length - 1] !== "/") {
throw new Error("Service root URL must end with '/'");
}
if (mParameters.operationMode
&& mParameters.operationMode !== OperationMode.Server) {
throw new Error("Unsupported operation mode: "
+ mParameters.operationMode);
}
this.sOperationMode = mParameters.operationMode;
// Note: strict checking for model's URI parameters, but "sap-*" is allowed
this.mUriParameters = this.buildQueryOptions(oUri.query(true), false, true);
this.sServiceUrl = oUri.query("").toString();
this.sGroupId = mParameters.groupId;
if (this.sGroupId === undefined) {
this.sGroupId = "$auto";
}
if (this.sGroupId !== "$auto" && this.sGroupId !== "$direct") {
throw new Error("Group ID must be '$auto' or '$direct'");
}
this.checkGroupId(mParameters.updateGroupId, false,
"Invalid update group ID: ");
this.sUpdateGroupId = mParameters.updateGroupId || this.getGroupId();
this.mGroupProperties = {};
for (sGroupId in mParameters.groupProperties) {
that.checkGroupId(sGroupId, true);
oGroupProperties = mParameters.groupProperties[sGroupId];
if (typeof oGroupProperties !== "object"
|| Object.keys(oGroupProperties).length !== 1
|| !(oGroupProperties.submit in SubmitMode)) {
throw new Error("Group '" + sGroupId + "' has invalid properties: '"
+ oGroupProperties + "'");
}
}
this.mGroupProperties = _Helper.clone(mParameters.groupProperties) || {};
this.mGroupProperties.$auto = {submit : SubmitMode.Auto};
this.mGroupProperties.$direct = {submit : SubmitMode.Direct};
if (mParameters.autoExpandSelect !== undefined
&& typeof mParameters.autoExpandSelect !== "boolean") {
throw new Error("Value for autoExpandSelect must be true or false");
}
this.bAutoExpandSelect = mParameters.autoExpandSelect === true;
if ("sharedRequests" in mParameters && mParameters.sharedRequests !== true) {
throw new Error("Value for sharedRequests must be true");
}
this.bSharedRequests = mParameters.sharedRequests === true;
this.mHeaders = {"Accept-Language" : sLanguageTag};
this.mMetadataHeaders = {"Accept-Language" : sLanguageTag};
// BEWARE: do not share mHeaders between _MetadataRequestor and _Requestor!
this.oMetaModel = new ODataMetaModel(
_MetadataRequestor.create(this.mMetadataHeaders, sODataVersion,
Object.assign({}, this.mUriParameters, mParameters.metadataUrlParams)),
this.sServiceUrl + "$metadata", mParameters.annotationURI, this,
mParameters.supportReferences);
this.oInterface = {
fetchEntityContainer :
this.oMetaModel.fetchEntityContainer.bind(this.oMetaModel),
fetchMetadata : this.oMetaModel.fetchObject.bind(this.oMetaModel),
fireSessionTimeout : function () {
that.fireEvent("sessionTimeout");
},
getGroupProperty : this.getGroupProperty.bind(this),
onCreateGroup : function (sGroupId) {
if (that.isAutoGroup(sGroupId)) {
that.addPrerenderingTask(
that._submitBatch.bind(that, sGroupId, true));
}
},
reportBoundMessages : this.reportBoundMessages.bind(this),
reportUnboundMessages : this.reportUnboundMessages.bind(this)
};
this.oRequestor = _Requestor.create(this.sServiceUrl, this.oInterface,
this.mHeaders, this.mUriParameters, sODataVersion);
this.changeHttpHeaders(mParameters.httpHeaders);
if (mParameters.earlyRequests) {
this.oMetaModel.fetchEntityContainer(true);
this.initializeSecurityToken();
}
this.aAllBindings = [];
this.mSupportedBindingModes = {
OneTime : true,
OneWay : true
};
if (mParameters.sharedRequests) {
this.sDefaultBindingMode = BindingMode.OneWay;
} else {
this.sDefaultBindingMode = BindingMode.TwoWay;
this.mSupportedBindingModes.TwoWay = true;
}
this.aPrerenderingTasks = null; // @see #addPrerenderingTask
}
});
/**
* Submits the requests associated with this group ID in one batch request.
*
* @param {string} sGroupId
* The group ID
* @param {boolean} [bCatch=false]
* Whether the returned promise always resolves and never rejects
* @returns {sap.ui.base.SyncPromise}
* A promise on the outcome of the HTTP request resolving with <code>undefined</code>; it is
* rejected with an error if the batch request itself fails. Use <code>bCatch</code> to catch
* that error and make the promise resolve with <code>undefined</code> instead.
*
* @private
*/
ODataModel.prototype._submitBatch = function (sGroupId, bCatch) {
var that = this;
return this.oRequestor.submitBatch(sGroupId).catch(function (oError) {
that.reportError("$batch failed", sClassName, oError);
if (!bCatch) {
throw oError;
}
});
};
/**
* The 'parseError' event is not supported by this model.
*
* @event sap.ui.model.odata.v4.ODataModel#parseError
* @public
* @since 1.37.0
*/
/**
* The 'propertyChange' event is not supported by this model.
*
* @event sap.ui.model.odata.v4.ODataModel#propertyChange
* @public
* @since 1.37.0
*/
/**
* The 'requestCompleted' event is not supported by this model.
*
* @event sap.ui.model.odata.v4.ODataModel#requestCompleted
* @public
* @since 1.37.0
*/
/**
* The 'requestFailed' event is not supported by this model.
*
* @event sap.ui.model.odata.v4.ODataModel#requestFailed
* @public
* @since 1.37.0
*/
/**
* The 'requestSent' event is not supported by this model.
*
* @event sap.ui.model.odata.v4.ODataModel#requestSent
* @public
* @since 1.37.0
*/
/**
* The 'sessionTimeout' event is fired when the server has created a session for the model and
* this session ran into a timeout due to inactivity.
*
* @event sap.ui.model.odata.v4.ODataModel#sessionTimeout
* @public
* @since 1.66.0
*/
/**
* Adds a task that is guaranteed to run once, just before the next rendering without triggering
* a rendering request. A watchdog ensures that the task is executed soon, even if no rendering
* occurs.
*
* @param {function} fnPrerenderingTask
* A function that is called before the rendering
* @param {boolean} [bFirst=false]
* Whether the task should become the first one, not the last one
* @private
*/
ODataModel.prototype.addPrerenderingTask = function (fnPrerenderingTask, bFirst) {
var fnRunTasks, iTimeoutId, that = this;
function runTasks(aTasks) {
clearTimeout(iTimeoutId);
while (aTasks.length) {
aTasks.shift()();
}
if (that.aPrerenderingTasks === aTasks) {
that.aPrerenderingTasks = null;
}
}
if (!this.aPrerenderingTasks) {
this.aPrerenderingTasks = [];
fnRunTasks = runTasks.bind(null, this.aPrerenderingTasks);
sap.ui.getCore().addPrerenderingTask(fnRunTasks);
// Add a watchdog to run the tasks in case there is no rendering. Ensure that the task
// runs after all setTimeout(0) tasks scheduled from within the current task, even those
// that were scheduled afterwards. A simple setTimeout(n) with n > 0 is not sufficient
// because this doesn't help if the current task runs very long.
iTimeoutId = setTimeout(function() {
iTimeoutId = setTimeout(fnRunTasks, 0);
}, 0);
}
if (bFirst) {
this.aPrerenderingTasks.unshift(fnPrerenderingTask);
} else {
this.aPrerenderingTasks.push(fnPrerenderingTask);
}
};
/**
* See {@link sap.ui.base.EventProvider#attachEvent}
*
* @param {string} sEventId The identifier of the event to listen for
* @param {object} [_oData]
* @param {function} [_fnFunction]
* @param {object} [_oListener]
* @returns {this} <code>this</code> to allow method chaining
*
* @public
* @see sap.ui.base.EventProvider#attachEvent
* @since 1.37.0
*/
// @override sap.ui.base.EventProvider#attachEvent
ODataModel.prototype.attachEvent = function (sEventId, _oData, _fnFunction, _oListener) {
if (!(sEventId in mSupportedEvents)) {
throw new Error("Unsupported event '" + sEventId
+ "': v4.ODataModel#attachEvent");
}
return Model.prototype.attachEvent.apply(this, arguments);
};
/**
* Attach event handler <code>fnFunction</code> to the 'sessionTimeout' event of this model.
*
* @param {function} fnFunction The function to call when the event occurs
* @param {object} [oListener] Object on which to call the given function
* @returns {this} <code>this</code> to allow method chaining
*
* @public
* @since 1.66.0
*/
ODataModel.prototype.attachSessionTimeout = function (fnFunction, oListener) {
return this.attachEvent("sessionTimeout", fnFunction, oListener);
};
/**
* Creates a new context binding for the given path, context and parameters.
*
* @param {string} sPath
* The binding path in the model; must not end with a slash
* @param {sap.ui.model.odata.v4.Context} [oContext]
* The context which is required as base for a relative path
* @param {object} [mParameters]
* Map of binding parameters which can be OData query options as specified in
* "OData Version 4.0 Part 2: URL Conventions" or the binding-specific parameters as specified
* below.
* Note: The binding creates its own data service request if it is absolute or if it has any
* parameters or if it is relative and has a context created via
* {@link #createBindingContext}.
* The following OData query options are allowed:
* <ul>
* <li> All "5.2 Custom Query Options" except for those with a name starting with "sap-"
* (unless starting with "sap-valid-")
* <li> The $count, $expand, $filter, $levels, $orderby, $search and $select
* "5.1 System Query Options"; OData V4 only allows $count, $filter, $levels, $orderby and
* $search inside resource paths that identify a collection. In our case here, this means
* you can only use them inside $expand.
* </ul>
* All other query options lead to an error.
* Query options specified for the binding overwrite model query options.
* @param {boolean} [mParameters.$$canonicalPath]
* Whether a binding relative to a {@link sap.ui.model.odata.v4.Context} uses the canonical
* path computed from its context's path for data service requests; only the value
* <code>true</code> is allowed.
* @param {string} [mParameters.$$groupId]
* The group ID to be used for <b>read</b> requests triggered by this binding; if not
* specified, either the parent binding's group ID (if the binding is relative) or the
* model's group ID is used, see {@link sap.ui.model.odata.v4.ODataModel#constructor}.
* Valid values are <code>undefined</code>, '$auto', '$auto.*', '$direct' or application group
* IDs as specified in {@link sap.ui.model.odata.v4.ODataModel}.
* @param {boolean} [mParameters.$$inheritExpandSelect]
* For operation bindings only: Whether $expand and $select from the parent binding are used
* in the request sent on {@link #execute}. If set to <code>true</code>, the binding must not
* set the $expand or $select parameter itself, the operation must be bound, and the return
* value and the binding parameter must belong to the same entity set.
* @param {boolean} [mParameters.$$ownRequest]
* Whether the binding always uses an own service request to read its data; only the value
* <code>true</code> is allowed.
* @param {boolean} [mParameters.$$patchWithoutSideEffects]
* Whether implicit loading of side effects via PATCH requests is switched off; only the value
* <code>true</code> is allowed. This sets the preference "return=minimal" and requires the
* service to return an ETag header for "204 No Content" responses. If not specified, the
* value of the parent binding is used.
* @param {string} [mParameters.$$updateGroupId]
* The group ID to be used for <b>update</b> requests triggered by this binding;
* if not specified, either the parent binding's update group ID (if the binding is relative)
* or the model's update group ID is used, see
* {@link sap.ui.model.odata.v4.ODataModel#constructor}.
* For valid values, see parameter "$$groupId".
* @returns {sap.ui.model.odata.v4.ODataContextBinding}
* The context binding
* @throws {Error}
* If disallowed binding parameters are provided, for example if the binding parameter
* $$inheritExpandSelect is set to <code>true</code> and the binding is no operation binding
* or the binding has one of the parameters $expand or $select.
*
* @public
* @see sap.ui.model.Model#bindContext
* @since 1.37.0
*/
// @override sap.ui.model.Model#bindContext
ODataModel.prototype.bindContext = function (sPath, oContext, mParameters) {
return new ODataContextBinding(this, sPath, oContext, mParameters);
};
/**
* Callback function for all V4 bindings to add themselves to their model.
*
* @param {sap.ui.model.odata.v4.ODataContextBinding|sap.ui.model.odata.v4.ODataListBinding|sap.ui.model.odata.v4.ODataPropertyBinding} oBinding
* A context, list, or property binding
*
* @private
*/
ODataModel.prototype.bindingCreated = function (oBinding) {
this.aAllBindings.push(oBinding);
};
/**
* Callback function for all V4 bindings to remove themselves from their model.
*
* @param {sap.ui.model.odata.v4.ODataContextBinding|sap.ui.model.odata.v4.ODataListBinding|sap.ui.model.odata.v4.ODataPropertyBinding} oBinding
* A context, list, or property binding
* @throws {Error}
* If a binding is removed twice or without adding.
*
* @private
*/
ODataModel.prototype.bindingDestroyed = function (oBinding) {
var iIndex = this.aAllBindings.indexOf(oBinding);
if (iIndex < 0) {
throw new Error("Unknown " + oBinding);
}
this.aAllBindings.splice(iIndex, 1);
};
/**
* Creates a new list binding for the given path and optional context which must
* resolve to an absolute OData path for an entity set.
*
* @param {string} sPath
* The binding path in the model; must not be empty or end with a slash
* @param {sap.ui.model.Context} [oContext]
* The context which is required as base for a relative path
* @param {sap.ui.model.Sorter|sap.ui.model.Sorter[]} [vSorters]
* The dynamic sorters to be used initially. Call
* {@link sap.ui.model.odata.v4.ODataListBinding#sort} to replace them. Static sorters, as
* defined in the '$orderby' binding parameter, are always executed after the dynamic sorters.
* Supported since 1.39.0.
* @param {sap.ui.model.Filter|sap.ui.model.Filter[]} [vFilters]
* The dynamic application filters to be used initially. Call
* {@link sap.ui.model.odata.v4.ODataListBinding#filter} to replace them. Static filters, as
* defined in the '$filter' binding parameter, are always combined with the dynamic filters
* using a logical <code>AND</code>.
* Supported since 1.39.0.
* @param {object} [mParameters]
* Map of binding parameters which can be OData query options as specified in
* "OData Version 4.0 Part 2: URL Conventions" or binding-specific parameters as specified
* below.
* Note: The binding creates its own data service request if it is absolute or if it has any
* parameters or if it is relative and has a context created via {@link #createBindingContext}
* or if it has sorters or filters.
* The following OData query options are allowed:
* <ul>
* <li> All "5.2 Custom Query Options" except for those with a name starting with "sap-"
* (unless starting with "sap-valid-")
* <li> The $apply, $count, $expand, $filter, $levels, $orderby, $search, and $select
* "5.1 System Query Options"
* </ul>
* All other query options lead to an error.
* Query options specified for the binding overwrite model query options.
* @param {object} [mParameters.$$aggregation]
* An object holding the information needed for data aggregation, see
* {@link sap.ui.model.odata.v4.ODataListBinding#setAggregation} for details.
* @param {boolean} [mParameters.$$canonicalPath]
* Whether a binding relative to a {@link sap.ui.model.odata.v4.Context} uses the canonical
* path computed from its context's path for data service requests; only the value
* <code>true</code> is allowed.
* @param {string} [mParameters.$$groupId]
* The group ID to be used for <b>read</b> requests triggered by this binding; if not
* specified, either the parent binding's group ID (if the binding is relative) or the
* model's group ID is used, see {@link sap.ui.model.odata.v4.ODataModel#constructor}.
* Valid values are <code>undefined</code>, '$auto', '$auto.*', '$direct' or application group
* IDs as specified in {@link sap.ui.model.odata.v4.ODataModel}.
* @param {sap.ui.model.odata.OperationMode} [mParameters.$$operationMode]
* The operation mode for filtering and sorting with the model's operation mode as default.
* Since 1.39.0, the operation mode {@link sap.ui.model.odata.OperationMode.Server} is
* supported. All other operation modes including <code>undefined</code> lead to an error if
* 'vFilters' or 'vSorters' are given or if
* {@link sap.ui.model.odata.v4.ODataListBinding#filter} or
* {@link sap.ui.model.odata.v4.ODataListBinding#sort} is called.
* @param {boolean} [mParameters.$$patchWithoutSideEffects]
* Whether implicit loading of side effects via PATCH requests is switched off; only the value
* <code>true</code> is allowed. This sets the preference "return=minimal" and requires the
* service to return an ETag header for "204 No Content" responses. If not specified, the
* value of the parent binding is used.
* @param {boolean} [mParameters.$$ownRequest]
* Whether the binding always uses an own service request to read its data; only the value
* <code>true</code> is allowed.
* @param {boolean} [mParameters.$$sharedRequest]
* Whether multiple bindings for the same resource path share the data, so that it is
* requested only once; only the value <code>true</code> is allowed. This parameter can be
* inherited from the model's parameter "sharedRequests", see
* {@link sap.ui.model.odata.v4.ODataModel#constructor}. Supported since 1.80.0
* <b>Note:</b> These bindings are read-only, so they may be especially useful for value
* lists; the following APIs are <b>not</b> allowed
* <ul>
* <li> for the list binding itself:
* <ul>
* <li> {@link sap.ui.model.odata.v4.ODataListBinding#create}
* </ul>
* <li> for the {@link sap.ui.model.odata.v4.ODataListBinding#getHeaderContext header
* context} of a list binding:
* <ul>
* <li> {@link sap.ui.model.odata.v4.Context#requestSideEffects}
* </ul>
* <li> for the context of a list binding representing a single entity:
* <ul>
* <li> {@link sap.ui.model.odata.v4.Context#delete}
* <li> {@link sap.ui.model.odata.v4.Context#refresh}
* <li> {@link sap.ui.model.odata.v4.Context#requestSideEffects}
* <li> {@link sap.ui.model.odata.v4.Context#setProperty}
* </ul>
* <li> for a dependent property binding of the list binding:
* <ul>
* <li> {@link sap.ui.model.odata.v4.ODataPropertyBinding#setValue}
* </ul>
* </ul>
* @param {string} [mParameters.$$updateGroupId]
* The group ID to be used for <b>update</b> requests triggered by this binding;
* if not specified, either the parent binding's update group ID (if the binding is relative)
* or the model's update group ID is used,
* see {@link sap.ui.model.odata.v4.ODataModel#constructor}.
* For valid values, see parameter "$$groupId".
* @returns {sap.ui.model.odata.v4.ODataListBinding}
* The list binding
* @throws {Error}
* If disallowed binding parameters are provided or an unsupported operation mode is used
*
* @public
* @see sap.ui.model.Model#bindList
* @since 1.37.0
*/
ODataModel.prototype.bindList = function (sPath, oContext, vSorters, vFilters, mParameters) {
return new ODataListBinding(this, sPath, oContext, vSorters, vFilters, mParameters);
};
/**
* Creates a new property binding for the given path. This binding is inactive and will not
* know the property value initially. You have to call {@link sap.ui.model.Binding#initialize}
* to get it updated asynchronously and register a change listener at the binding to be informed
* when the value is available.
*
* It is possible to create a property binding pointing to metadata. A '##' in the
* binding's path is recognized as a separator and splits it into two parts.
* The part before the separator is resolved with the binding's context and the result is
* transformed into a metadata context (see
* {@link sap.ui.model.odata.v4.ODataMetaModel#getMetaContext}). The part following the
* separator is then interpreted relative to this metadata context, even if it starts with
* a '/'; a trailing '/' is allowed here, see
* {@link sap.ui.model.odata.v4.ODataMetaModel#requestObject} for the effect it has.
*
* If the target type specified in the corresponding control property's binding info is "any"
* and the binding is relative or points to metadata, the binding may have an object value;
* in this case and unless the binding refers to an action advertisement the binding's mode must
* be {@link sap.ui.model.BindingMode.OneTime}.
*
* @param {string} sPath
* The binding path in the model; must not be empty. Must not end with a '/' unless the
* binding points to metadata.
* @param {sap.ui.model.Context} [oContext]
* The context which is required as base for a relative path
* @param {object} [mParameters]
* Map of binding parameters which can be OData query options as specified in
* "OData Version 4.0 Part 2: URL Conventions" or the binding-specific parameter "$$groupId".
* All "5.2 Custom Query Options" are allowed except for those with a name starting with
* "sap-" (unless starting with "sap-valid-"). All other query options lead to an error.
* Query options specified for the binding overwrite model query options.
* Note: The binding only creates its own data service request if it is absolute or if it is
* relative to a context created via {@link #createBindingContext}. The binding parameters are
* ignored in case the binding creates no own data service request or in case the binding
* points to metadata.
* @param {string} [mParameters.$$groupId]
* The group ID to be used for <b>read</b> requests triggered by this binding; if not
* specified, either the parent binding's group ID (if the binding is relative) or the
* model's group ID is used, see {@link sap.ui.model.odata.v4.ODataModel#constructor}.
* Valid values are <code>undefined</code>, '$auto', '$auto.*', '$direct' or application group
* IDs as specified in {@link sap.ui.model.odata.v4.ODataModel}.
* @param {boolean} [mParameters.$$ignoreMessages]
* Whether this binding does not propagate model messages to the control; supported since
* 1.82.0. Some composite types like {@link sap.ui.model.odata.type.Currency} or
* {@link sap.ui.model.odata.type.Unit} automatically ignore messages for some of their parts
* depending on their format options; setting this parameter to <code>true</code> or
* <code>false</code> overrules the automatism of the type.
*
* For example, a binding for a currency code is used in a composite binding for rendering the
* proper number of decimals, but the currency code is not displayed in the attached control.
* In that case, messages for the currency code shall not be displayed at that control, only
* messages for the amount.
* @param {boolean} [mParameters.$$noPatch]
* Whether changing the value of this property binding is not causing a PATCH request; only
* the value <code>true</code> is allowed.
* @returns {sap.ui.model.odata.v4.ODataPropertyBinding}
* The property binding
* @throws {Error}
* If disallowed binding parameters are provided or in case the binding's value is an object
* and the preconditions specified above are not fulfilled
*
* @public
* @see sap.ui.base.ManagedObject#bindProperty
* @see sap.ui.model.Model#bindProperty
* @see sap.ui.model.PropertyBinding#setType
* @since 1.37.0
*/
ODataModel.prototype.bindProperty = function (sPath, oContext, mParameters) {
return new ODataPropertyBinding(this, sPath, oContext, mParameters);
};
/**
* Method not supported
*
* @param {string} _sPath
* @param {sap.ui.model.Context} [_oContext]
* @param {sap.ui.model.Filter[]} [_aFilters]
* @param {object} [_mParameters]
* @param {sap.ui.model.Sorter[]} [_aSorters]
* @returns {sap.ui.model.TreeBinding}
* @throws {Error}
*
* @public
* @see sap.ui.model.Model#bindTree
* @since 1.37.0
*/
// @override sap.ui.model.Model#bindTree
ODataModel.prototype.bindTree = function (_sPath, _oContext, _aFilters, _mParameters,
_aSorters) {
throw new Error("Unsupported operation: v4.ODataModel#bindTree");
};
/**
* Constructs a map of query options from the given binding parameters.
* Parameters starting with '$$' indicate binding-specific parameters, which must not be part
* of a back-end query; they are ignored and not added to the map.
* The following query options are disallowed:
* <ul>
* <li> System query options (key starts with "$"), unless
* <code>bSystemQueryOptionsAllowed</code> is set
* <li> Parameter aliases (key starts with "@")
* <li> Custom query options starting with "sap-" (unless starting with "sap-valid-"), unless
* <code>bSapAllowed</code> is set
* </ul>
*
* @param {object} [mParameters={}]
* Map of binding parameters
* @param {boolean} [bSystemQueryOptionsAllowed=false]
* Whether system query options are allowed
* @param {boolean} [bSapAllowed=false]
* Whether custom query options starting with "sap-" are allowed (Note: "sap-valid-" is always
* allowed)
* @throws {Error}
* If disallowed OData query options are provided
* @returns {object}
* The map of query options
*
* @private
*/
ODataModel.prototype.buildQueryOptions = function (mParameters, bSystemQueryOptionsAllowed,
bSapAllowed) {
var sParameterName,
mTransformedOptions = _Helper.clone(mParameters) || {};
/*
* Parses the query options for the given option name "sOptionName" in the given map of
* query options "mOptions" to an object if necessary.
* Validates if the given query option name is allowed.
*
* @param {object} mOptions Map of query options by name
* @param {string} sOptionName Name of the query option
* @param {string[]} aAllowed The allowed system query options
* @throws {error} If the given query option name is not allowed
*/
function parseAndValidateSystemQueryOption (mOptions, sOptionName, aAllowed) {
var sExpandOptionName,
mExpandOptions,
sExpandPath,
vValue = mOptions[sOptionName];
if (!bSystemQueryOptionsAllowed || aAllowed.indexOf(sOptionName) < 0) {
throw new Error("System query option " + sOptionName + " is not supported");
}
if ((sOptionName === "$expand" || sOptionName === "$select")
&& typeof vValue === "string") {
vValue = _Parser.parseSystemQueryOption(sOptionName + "=" + vValue)[sOptionName];
mOptions[sOptionName] = vValue;
}
if (sOptionName === "$expand") {
for (sExpandPath in vValue) {
mExpandOptions = vValue[sExpandPath];
if (mExpandOptions === null || typeof mExpandOptions !== "object") {
// normalize empty expand options to {}
mExpandOptions = vValue[sExpandPath] = {};
}
for (sExpandOptionName in mExpandOptions) {
parseAndValidateSystemQueryOption(mExpandOptions, sExpandOptionName,
aExpandQueryOptions);
}
}
} else if (sOptionName === "$count") {
if (typeof vValue === "boolean") {
if (!vValue) {
delete mOptions.$count;
}
} else {
switch (typeof vValue === "string" && vValue.toLowerCase()) {
case "false":
delete mOptions.$count;
break;
case "true":
mOptions.$count = true;
break;
default:
throw new Error("Invalid value for $count: " + vValue);
}
}
}
}
if (mParameters) {
for (sParameterName in mParameters) {
if (sParameterName.startsWith("$$")) { // binding-specific parameter
delete mTransformedOptions[sParameterName];
} else if (sParameterName[0] === "@") { // OData parameter alias
throw new Error("Parameter " + sParameterName + " is not supported");
} else if (sParameterName[0] === "$") { // OData system query option
parseAndValidateSystemQueryOption(mTransformedOptions, sParameterName,
aSystemQueryOptions);
// else: OData custom query option
} else if (!bSapAllowed && sParameterName.startsWith("sap-")
&& !sParameterName.startsWith("sap-valid-")) {
throw new Error("Custom query option " + sParameterName + " is not supported");
}
}
}
return mTransformedOptions;
};
/**
* Changes the HTTP headers used for data and metadata requests sent by this model.
*
* If batch requests are used, the headers will be set for the batch itself, as well as for the
* individual requests within the batch. The headers are changed according to the given map of
* headers: Headers with an <code>undefined</code> value are removed, the other headers are set,
* and missing headers remain unchanged. The following headers must not be used:
* <ul>
* <li> OData V4 requests headers as specified in "8.1 Common Headers" and
* "8.2 Request Headers" of the specification "OData Version 4.0 Part 1: Protocol"
* <li> OData V2 request headers as specified in "2.2.5 HTTP Header Fields" of the
* specification "OData Version 2 v10.1"
* <li> The headers "Content-Id" and "Content-Transfer-Encoding"
* <li> The header "SAP-ContextId"
* </ul>
* Note: The "X-CSRF-Token" header will not be used for metadata requests.
*
* If not <code>undefined</code>, a header value must conform to the following rules:
* <ul>
* <li> It must be a non-empty string.
* <li> It must be completely in the US-ASCII character set.
* <li> It must not contain control characters.
* </ul>
*
* @param {object} [mHeaders]
* Map of HTTP header names to their values
* @throws {Error}
* If <code>mHeaders</code> contains unsupported headers, the same header occurs more than
* once, a header value is invalid, or there are open requests.
*
* @public
* @see #getHttpHeaders
* @since 1.71.0
*/
ODataModel.prototype.changeHttpHeaders = function (mHeaders) {
var oHeaderCopy,
sHeaderName,
mHeadersCopy = {},
sHeaderValue,
sKey;
this.oRequestor.checkHeaderNames(mHeaders);
for (sKey in mHeaders) {
sHeaderName = sKey.toLowerCase();
sHeaderValue = mHeaders[sKey];
if (mHeadersCopy[sHeaderName]) {
throw new Error("Duplicate header " + sKey);
} else if (!(typeof sHeaderValue === "string" && rValidHeader.test(sHeaderValue)
|| sHeaderValue === undefined)) {
throw new Error("Unsupported value for header '" + sKey + "': " + sHeaderValue);
} else {
if (sHeaderName === "x-csrf-token") {
sKey = "X-CSRF-Token";
}
mHeadersCopy[sHeaderName] = {key : sKey, value: sHeaderValue};
}
}
this.oRequestor.checkForOpenRequests();
for (sKey in this.mHeaders) {
sHeaderName = sKey.toLowerCase();
oHeaderCopy = mHeadersCopy[sHeaderName];
if (oHeaderCopy) {
delete this.mHeaders[sKey];
delete this.mMetadataHeaders[sKey];
if (oHeaderCopy.value !== undefined) {
this.mHeaders[oHeaderCopy.key] = oHeaderCopy.value;
this.mMetadataHeaders[oHeaderCopy.key] = oHeaderCopy.value;
}
delete mHeadersCopy[sHeaderName];
}
}
for (sKey in mHeadersCopy) {
oHeaderCopy = mHeadersCopy[sKey];
if (oHeaderCopy.value !== undefined) {
this.mHeaders[oHeaderCopy.key] = oHeaderCopy.value;
if (sKey !== "x-csrf-token") {
this.mMetadataHeaders[oHeaderCopy.key] = oHeaderCopy.value;
}
}
}
};
/**
* Checks whether the given group ID is valid (see {@link #checkGroupId}) and does not have
* {@link sap.ui.model.odata.v4.SubmitMode.Direct}.
*
* @param {string} sGroupId
* The group ID
* @throws {Error}
* For invalid group IDs, or group IDs with {@link sap.ui.model.odata.v4.SubmitMode.Direct}
*
* @private
*/
ODataModel.prototype.checkBatchGroupId = function (sGroupId) {
this.checkGroupId(sGroupId);
if (this.isDirectGroup(sGroupId)) {
throw new Error("Group ID does not use batch requests: " + sGroupId);
}
};
/**
* Checks whether the given group ID is valid, which means it is either undefined, '$auto',
* '$auto.*', '$direct' or an application group ID as specified in
* {@link sap.ui.model.odata.v4.ODataModel}.
*
* @param {string} sGroupId
* The group ID
* @param {boolean} [bApplicationGroup]
* Whether only an application group ID is considered valid
* @param {string} [sErrorMessage]
* The error message to be used if group ID is not valid; the group ID will be appended
* @throws {Error}
* For invalid group IDs
*
* @private
*/
ODataModel.prototype.checkGroupId = function (sGroupId, bApplicationGroup, sErrorMessage) {
if (!bApplicationGroup && sGroupId === undefined
|| typeof sGroupId === "string"
&& (bApplicationGroup ? rApplicationGroupID : rGroupID).test(sGroupId)) {
return;
}
throw new Error((sErrorMessage || "Invalid group ID: ") + sGroupId);
};
/**
* Creates a binding context for the given path. A relative path can only be resolved if a
* context is provided.
* Note: The parameters <code>mParameters</code>, <code>fnCallBack</code>, and
* <code>bReload</code> from {@link sap.ui.model.Model#createBindingContext} are not supported.
*
* It is possible to create binding contexts pointing to metadata. A '##' is recognized
* as separator in the resolved path and splits it into two parts; note that '#' may also be
* used as separator but is deprecated since 1.51.
* The part before the separator is transformed into a metadata context (see
* {@link sap.ui.model.odata.v4.ODataMetaModel#getMetaContext}). The part following the
* separator is then interpreted relative to this metadata context, even if it starts with
* a '/'; a trailing '/' is allowed here, see
* {@link sap.ui.model.odata.v4.ODataMetaModel#requestObject} for the effect it has.
*
* A binding path may also point to an operation advertisement which is addressed with
* '#<namespace>.<operation>' and is part of the data payload, not the metadata. The metadata
* of an operation can be addressed via '##' as described above.
*
* Examples:
* <ul>
* <li> <code>/Products('42')/Name##@com.sap.vocabularies.Common.v1.Label</code>
* points to the "Label" annotation of the "Name" property of the entity set "Products".
* <li> <code>/##Products/Name@com.sap.vocabularies.Common.v1.Label</code> has no data path
* part and thus starts at the metadata root. It also points to the "Label" annotation of
* the "Name" property of the entity set "Products".
* <li> <code>/Products##/</code>
* points to the entity type (note the trailing '/') of the entity set "Products".
* <li> <code>/EMPLOYEES('1')/##com.sap.Action</code>
* points to the metadata of an action bound to the entity set "EMPLOYEES".
* <li> <code>/EMPLOYEES('1')/#com.sap.Action</code>
* does not point to metadata, but to the action advertisement.
* </ul>
*
* @param {string} sPath
* The binding path, may be relative to the provided context
* @param {sap.ui.model.Context} [oContext]
* The context which is required as base for a relative path
* @returns {sap.ui.model.Context}
* The binding context with the resolved path and the model instance
* @throws {Error}
* If a relative path is provided without a context or in case of unsupported parameters or
* if the given context is a {@link sap.ui.model.odata.v4.Context}
*
* @public
* @see sap.ui.model.Model#createBindingContext
* @since 1.37.0
*/
ODataModel.prototype.createBindingContext = function (sPath, oContext) {
var sDataPath,
oMetaContext,
sMetaPath,
sResolvedPath,
iSeparator;
/*
* Checks if the given meta path contains a dot in its first segment.
*
* @param {string} sMetaPath The meta path
* @returns {boolean} Whether the given meta path contains a dot in its first segment
*/
function startsWithQualifiedName(sMetaPath) {
var iDotPos = sMetaPath.indexOf("."),
iSlashPos = sMetaPath.indexOf("/");
return iDotPos > 0 && (iSlashPos < 0 || iDotPos < iSlashPos);
}
if (arguments.length > 2) {
throw new Error("Only the parameters sPath and oContext are supported");
}
if (oContext && oContext.getBinding) {
throw new Error("Unsupported type: oContext must be of type sap.ui.model.Context, "
+ "but was sap.ui.model.odata.v4.Context");
}
sResolvedPath = this.resolve(sPath, oContext);
if (sResolvedPath === undefined) {
throw new Error("Cannot create binding context from relative path '" + sPath
+ "' without context");
}
iSeparator = sResolvedPath.indexOf('#');
if (iSeparator >= 0) {
sDataPath = sResolvedPath.slice(0, iSeparator);
sMetaPath = sResolvedPath.slice(iSeparator + 1);
if (sMetaPath[0] === "#") {
sMetaPath = sMetaPath.slice(1);
} else if (sDataPath.length > 1 && sMetaPath[0] !== "@"
&& startsWithQualifiedName(sMet