UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,122 lines (1,078 loc) 83.4 kB
/*! * 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>&lt;edmx:Reference></code> and <code>&lt;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