UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,518 lines (1,367 loc) 62.1 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ /*eslint-disable max-len */ // Provides class sap.ui.model.odata.ODataMetadata sap.ui.define([ "./_ODataMetaModelUtils", "sap/base/assert", "sap/base/Log", "sap/base/util/each", "sap/base/util/extend", "sap/base/util/isEmptyObject", "sap/base/util/uid", "sap/ui/base/EventProvider", "sap/ui/core/Configuration", "sap/ui/core/cache/CacheManager", "sap/ui/thirdparty/datajs" ], function(Utils, assert, Log, each, extend, isEmptyObject, uid, EventProvider, Configuration, CacheManager, OData) { "use strict"; /*eslint max-nested-callbacks: 0*/ var sClassName = "sap.ui.model.odata.ODataMetadata", sSAPAnnotationNamespace = "http://www.sap.com/Protocols/SAPData"; /** * Constructor for a new ODataMetadata. * * @param {string} sMetadataURI needs the correct metadata uri including $metadata * @param {object} [mParams] optional map of parameters. * @param {boolean} [mParams.async=true] request is per default async * @param {string} [mParams.user] <b>Deprecated</b> for security reasons. Use strong server side * authentication instead. UserID for the service. * @param {string} [mParams.password] <b>Deprecated</b> for security reasons. Use strong server * side authentication instead. Password for the service. * @param {object} [mParams.headers] (optional) map of custom headers which should be set with the request. * @param {string} [mParams.cacheKey] (optional) A valid cache key * * @class * Implementation to access OData metadata * * @author SAP SE * @version 1.111.5 * * @public * @alias sap.ui.model.odata.ODataMetadata * @extends sap.ui.base.EventProvider */ var ODataMetadata = EventProvider.extend("sap.ui.model.odata.ODataMetadata", /** @lends sap.ui.model.odata.ODataMetadata.prototype */ { constructor : function(sMetadataURI, mParams) { EventProvider.apply(this, arguments); this.bLoaded = false; this.bFailed = false; this.mEntityTypes = {}; this.mRequestHandles = {}; this.sUrl = sMetadataURI; this.bAsync = mParams.async; this.sUser = mParams.user; this.bWithCredentials = mParams.withCredentials; this.sPassword = mParams.password; this.mHeaders = mParams.headers; this.sCacheKey = mParams.cacheKey; this.oLoadEvent = null; this.oFailedEvent = null; this.oMetadata = null; this.bMessageScopeSupported = false; this.mNamespaces = mParams.namespaces || { sap:"http://www.sap.com/Protocols/SAPData", m:"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata", "":"http://schemas.microsoft.com/ado/2007/06/edmx" }; var that = this; // global promises this.pLoadedWithReject = new Promise(function (resolve, reject) { that.fnResolve = resolve; that.fnReject = reject; }); this.pLoaded = this.pLoadedWithReject.catch(function (oError) { return new Promise(function (resolve, reject) { that.fnResolve = resolve; }); }); function writeCache(mParams) { CacheManager.set(that.sCacheKey, JSON.stringify({ metadata: that.oMetadata, params: mParams })); } function logError(oError) { Log.error("[ODataMetadata] initial loading of metadata failed"); if (oError && oError.message) { Log.error("Error: " + oError.message); } } //check cache if (this.sCacheKey) { CacheManager.get(this.sCacheKey) .then(function(sMetadata) { if (sMetadata) { var oCacheMetadata = JSON.parse(sMetadata); this.oMetadata = oCacheMetadata.metadata; this._handleLoaded(this.oMetadata, oCacheMetadata.params, false); } else { this._loadMetadata() .then(writeCache) .catch(logError); } }.bind(this)) .catch(logError); } else { this._loadMetadata() .catch(logError); } }, metadata : { publicMethods : ["getServiceMetadata", "attachFailed", "detachFailed", "attachLoaded", "detachLoaded", "refresh"] } }); /** * Returns whether the function returns a collection. * * @param {object} mFunctionInfo The function info map * @returns {boolean} Whether the function returns a collection * @private */ ODataMetadata._returnsCollection = function (mFunctionInfo) { if (mFunctionInfo && mFunctionInfo.returnType && mFunctionInfo.returnType.startsWith("Collection(")) { return true; } return false; }; ODataMetadata.prototype._setNamespaces = function(mNamespaces) { this.mNamespaces = mNamespaces; }; /* * Handle Promise resolving/eventing when metadata is loaded * */ ODataMetadata.prototype._handleLoaded = function(oMetadata, mParams, bSuppressEvents) { var aEntitySets = []; this.oMetadata = this.oMetadata ? this.merge(this.oMetadata, oMetadata, aEntitySets) : oMetadata; this.oRequestHandle = null; mParams.entitySets = aEntitySets; // resolve global promises this.fnResolve(mParams); if (this.bAsync && !bSuppressEvents) { this.fireLoaded(this); } else if (!this.bAsync && !bSuppressEvents){ //delay the event so anyone can attach to this _before_ it is fired, but make //sure that bLoaded is already set properly this.bLoaded = true; this.bFailed = false; this.oLoadEvent = setTimeout(this.fireLoaded.bind(this, mParams), 0); } }; /** * Loads the metadata for the service. * * @param {string} sUrl The metadata URL * @param {boolean} bSuppressEvents Suppress metadata events * @returns {Promise} Promise for metadata loading * @private */ ODataMetadata.prototype._loadMetadata = function(sUrl, bSuppressEvents) { // request the metadata of the service var that = this; sUrl = sUrl || this.sUrl; var oRequest = this._createRequest(sUrl); return new Promise(function(resolve, reject) { var oRequestHandle; function _handleSuccess(oMetadata, oResponse) { if (!oMetadata || !oMetadata.dataServices) { var mParameters = { message: "Invalid metadata document", request: oRequest, response: oResponse }; _handleError(mParameters); return; } that.sMetadataBody = oResponse.body; that.oRequestHandle = null; var mParams = { metadataString: that.sMetadataBody }; var sLastModified = oResponse.headers["Last-Modified"]; if (sLastModified) { mParams.lastModified = sLastModified; } var sETag = oResponse.headers["eTag"]; if (sETag) { mParams.eTag = sETag; } that._handleLoaded(oMetadata, mParams, bSuppressEvents); resolve(mParams); } function _handleError(oError) { var mParams = { message: oError.message, request: oError.request, response: oError.response }; if (oError.response) { mParams.statusCode = oError.response.statusCode; mParams.statusText = oError.response.statusText; mParams.responseText = oError.response.body; } if (oRequestHandle && oRequestHandle.bSuppressErrorHandlerCall) { return; } if (that.bAsync) { delete that.mRequestHandles[oRequestHandle.id]; } reject(mParams); that.fnReject(mParams); if (that.bAsync && !bSuppressEvents) { that.fireFailed(mParams); } else if (!that.bAsync && !bSuppressEvents){ that.bFailed = true; that.oFailedEvent = setTimeout(that.fireFailed.bind(that, mParams), 0); } } // execute the request oRequestHandle = OData.request(oRequest, _handleSuccess, _handleError, OData.metadataHandler); if (that.bAsync) { oRequestHandle.id = uid(); that.mRequestHandles[oRequestHandle.id] = oRequestHandle; } }); }; /** * Refreshes the metadata creating a new request to the server. * * Returns a new promise which can be resolved or rejected depending on the metadata loading state. * * @returns {Promise} A promise on metadata loaded state * * @public */ ODataMetadata.prototype.refresh = function(){ return this._loadMetadata(); }; /** * Return the metadata object. * * @return {Object} Metadata object * @public */ ODataMetadata.prototype.getServiceMetadata = function() { return this.oMetadata; }; /** * Checks whether metadata is available. * * @public * @returns {boolean} Whether metadata is already loaded */ ODataMetadata.prototype.isLoaded = function() { return this.bLoaded; }; /** * Returns a promise for the loaded state of the metadata. * * @param {boolean} [bRejectOnFailure=false] * With <code>bRejectOnFailure=false</code> the returned promise is not rejected. In case of * failure this promise stays pending. * Since 1.79 with <code>bRejectOnFailure=true</code> the returned promise is rejected when * the initial loading of the metadata fails. * * @returns {Promise} A promise on metadata loaded state * @public */ ODataMetadata.prototype.loaded = function (bRejectOnFailure) { return bRejectOnFailure ? this.pLoadedWithReject : this.pLoaded; }; /** * Checks whether metadata loading has already failed. * * @public * @returns {boolean} Whether metadata request has failed */ ODataMetadata.prototype.isFailed = function() { return this.bFailed; }; /** * The <code>loaded</code> event is fired after metadata has been loaded and parsed. * * @name sap.ui.model.odata.ODataMetadata#loaded * @event * @param {sap.ui.base.Event} oEvent * @public */ /** * Fires event {@link #event:loaded loaded} to attached listeners. * * @param {object} [oParameters] Parameters to pass along with the event * @returns {this} Reference to <code>this</code> in order to allow method chaining * @protected */ ODataMetadata.prototype.fireLoaded = function(oParameters) { this.bLoaded = true; this.bFailed = false; this.fireEvent("loaded", oParameters); Log.debug(this + " - loaded was fired"); return this; }; /** * Attaches event handler <code>fnFunction</code> to the {@link #event:loaded loaded} event of this * <code>sap.ui.model.odata.ODataMetadata</code>. * * When called, the context of the event handler (its <code>this</code>) will be bound to <code>oListener</code> * if specified, otherwise it will be bound to this <code>sap.ui.model.odata.ODataMetadata</code> itself. * * @param {object} * [oData] An application-specific payload object that will be passed to the event handler * along with the event object when firing the event * @param {function} * fnFunction The function to be called, when the event occurs * @param {object} * [oListener] Context object to call the event handler with. Defaults to this * <code>sap.ui.model.odata.ODataMetadata</code> itself * * @returns {this} Reference to <code>this</code> in order to allow method chaining * @public */ ODataMetadata.prototype.attachLoaded = function(oData, fnFunction, oListener) { this.attachEvent("loaded", oData, fnFunction, oListener); return this; }; /** * Detaches event handler <code>fnFunction</code> from the {@link #event:loaded loaded} event of this * <code>sap.ui.model.odata.ODataMetadata</code>. * * The passed function and listener object must match the ones used for event registration. * * @param {function} * fnFunction The function to be called, when the event occurs * @param {object} * [oListener] Context object on which the given function had to be called * @returns {this} Reference to <code>this</code> in order to allow method chaining * @public */ ODataMetadata.prototype.detachLoaded = function(fnFunction, oListener) { this.detachEvent("loaded", fnFunction, oListener); return this; }; /** * The <code>failed</code> event is fired when loading or parsing metadata failed. * * @name sap.ui.model.odata.ODataMetadata#failed * @event * @param {sap.ui.base.Event} oEvent * @public */ /** * Fires event {@link #event:failed failed} to attached listeners. * * @param {object} [oParameters] Parameters to pass along with the event * @param {string} [oParameters.message] A text that describes the failure. * @param {string} [oParameters.statusCode] HTTP status code returned by the request (if available) * @param {string} [oParameters.statusText] The status as a text, details not specified, intended only for diagnosis output * @param {string} [oParameters.responseText] Response that has been received for the request, as a text string * * @returns {this} Reference to <code>this</code> in order to allow method chaining * @protected */ ODataMetadata.prototype.fireFailed = function(oParameters) { this.bFailed = true; this.fireEvent("failed", oParameters); return this; }; /** * Attaches event handler <code>fnFunction</code> to the {@link #event:failed failed} event of this * <code>sap.ui.model.odata.ODataMetadata</code>. * * When called, the context of the event handler (its <code>this</code>) will be bound to <code>oListener</code> * if specified, otherwise it will be bound to this <code>sap.ui.model.odata.ODataMetadata</code> itself. * * @param {object} * [oData] An application-specific payload object that will be passed to the event handler * along with the event object when firing the event * @param {function} * fnFunction The function to be called, when the event occurs * @param {object} * [oListener] Context object to call the event handler with. Defaults to this * <code>sap.ui.model.odata.ODataMetadata</code> itself * * @returns {this} Reference to <code>this</code> in order to allow method chaining * @public */ ODataMetadata.prototype.attachFailed = function(oData, fnFunction, oListener) { this.attachEvent("failed", oData, fnFunction, oListener); return this; }; /** * Detaches event handler <code>fnFunction</code> from the {@link #event:failed failed} event of this * <code>sap.ui.model.odata.ODataMetadata</code>. * * The passed function and listener object must match the ones used for event registration. * * @param {function} * fnFunction The function to be called, when the event occurs * @param {object} * [oListener] Context object on which the given function had to be called * @returns {this} Reference to <code>this</code> in order to allow method chaining * @public */ ODataMetadata.prototype.detachFailed = function(fnFunction, oListener) { this.detachEvent("failed", fnFunction, oListener); return this; }; /** * Retrieves the association end which contains the multiplicity. * @param {object} oEntityType the entity type * @param {string} sName the name * @returns {*} entity association end * @private */ ODataMetadata.prototype._getEntityAssociationEnd = function(oEntityType, sName) { var sCacheKey; if (!this._checkMetadataLoaded()) { return null; } this._mGetEntityAssociationEndCache = this._mGetEntityAssociationEndCache || {}; sCacheKey = oEntityType.namespace + "." + oEntityType.name + "/" + sName; // fill the cache if (this._mGetEntityAssociationEndCache[sCacheKey] === undefined) { var oNavigationProperty = oEntityType ? Utils.findObject(oEntityType.navigationProperty, sName) : null, oAssociation = oNavigationProperty ? Utils.getObject(this.oMetadata.dataServices.schema, "association", oNavigationProperty.relationship) : null, oAssociationEnd = oAssociation ? Utils.findObject(oAssociation.end, oNavigationProperty.toRole, "role") : null; this._mGetEntityAssociationEndCache[sCacheKey] = oAssociationEnd; } // return the value from the cache return this._mGetEntityAssociationEndCache[sCacheKey]; }; function getEntitySetsMap(schema){ var mEntitySets = {}; for (var i = 0; i < schema.length; i++) { var oSchema = schema[i]; if (oSchema.entityContainer) { for (var j = 0; j < oSchema.entityContainer.length; j++) { var oEntityContainer = oSchema.entityContainer[j]; if (oEntityContainer.entitySet) { for (var k = 0; k < oEntityContainer.entitySet.length; k++) { if (oEntityContainer.entitySet[k].name != null) { mEntitySets[oEntityContainer.entitySet[k].name] = oEntityContainer.entitySet[k]; } } } } } } return mEntitySets; } /** * Finds the first matching entity set by name. * @param {string} sName Name of the entityset * @returns {*} The first matching entity set by name * @private */ ODataMetadata.prototype._findEntitySetByName = function(sName) { // fill the cache if (!this.mEntitySets) { this.mEntitySets = getEntitySetsMap(this.oMetadata.dataServices.schema); } return this.mEntitySets[sName]; }; /** * Extract the entity type name of a given path. Also navigation properties in the path will be * followed to get the right entity type for that property. * eg. * /Categories(1)/Products(1)/Category --> will get the Categories entity type * /Products --> will get the Products entity type * * @param {string} sPath The entity types path * @return {object} The entity type or null if not found */ ODataMetadata.prototype._getEntityTypeByPath = function(sPath) { if (!sPath) { assert(undefined, "sPath not defined!"); return null; } if (this.mEntityTypes[sPath]) { return this.mEntityTypes[sPath]; } if (!this._checkMetadataLoaded()) { return null; } // remove starting and trailing / var sCandidate = sPath.replace(/^\/|\/$/g, ""), aParts = sCandidate.split("/"), iLength = aParts.length, oParentEntityType, oEntityTypeInfo, oEntityType, oResultEntityType, that = this; // remove key from first path segment if any (e.g. Products(555) --> Products) if (aParts[0].indexOf("(") != -1) { aParts[0] = aParts[0].substring(0,aParts[0].indexOf("(")); } if (iLength > 1 ) { // check if navigation property is used // e.g. Categories(1)/Products(1)/Category --> Category is a navigation property so we need the collection Categories oParentEntityType = that._getEntityTypeByPath(aParts[0]); for (var i = 1; i < aParts.length; i++ ) { if (oParentEntityType) { // remove key from current part if any if (aParts[i].indexOf("(") != -1) { aParts[i] = aParts[i].substring(0,aParts[i].indexOf("(")); } // check for navigation properties // if no navigation property found we assume that the current part is a normal property so we return the current oParentEntityType // which is the parent entity type of that property oResultEntityType = that._getEntityTypeByNavProperty(oParentEntityType, aParts[i]); if (oResultEntityType) { oParentEntityType = oResultEntityType; } oEntityType = oParentEntityType; } } } else { // if only one part exists it should be the name of the collection and we can get the entity type for it oEntityTypeInfo = this._splitName(this._getEntityTypeName(aParts[0])); oEntityType = this._getObjectMetadata("entityType", oEntityTypeInfo.name, oEntityTypeInfo.namespace); if (oEntityType) { // store the type name also in the oEntityType oEntityType.entityType = this._getEntityTypeName(aParts[0]); } } // check for function imports if (!oEntityType) { var sFuncCandName = aParts[aParts.length - 1]; // last segment is always a function import var oFuncType = this._getFunctionImportMetadata(sFuncCandName, "GET"); if (!oFuncType) { oFuncType = this._getFunctionImportMetadata(sFuncCandName, "POST"); } if (oFuncType && oFuncType.entitySet) { // only collections supported which have an entitySet oEntityType = Object.assign({}, this._getEntityTypeByPath(oFuncType.entitySet)); if (oEntityType) { // store the type name also in the oEntityType oEntityType.entityType = this._getEntityTypeName(oFuncType.entitySet); oEntityType.isFunction = true; } } } if (oEntityType) { this.mEntityTypes[sPath] = oEntityType; } return oEntityType; }; /** * Extract the entity type from a given sName. Retrieved types will be cached * so further calls must not iterate the metadata structure again. * * #/Category/CategoryName --> will get the Category entity type * @param {string} sName the qualified or unqualified name of the entity * @return {object} the entity type or null if not found */ ODataMetadata.prototype._getEntityTypeByName = function(sName) { var oEntityType, that = this, sEntityName, sNamespace, oEntityTypeInfo; if (!sName) { assert(undefined, "sName not defined!"); return null; } oEntityTypeInfo = this._splitName(sName); sNamespace = oEntityTypeInfo.namespace; sEntityName = oEntityTypeInfo.name; if (!this._checkMetadataLoaded()) { return null; } if (this.mEntityTypes[sName]) { oEntityType = this.mEntityTypes[sName]; } else { each(this.oMetadata.dataServices.schema, function(i, oSchema) { if (oSchema.entityType && (!sNamespace || oSchema.namespace === sNamespace)) { each(oSchema.entityType, function(k, oEntity) { if (oEntity.name === sEntityName) { oEntityType = oEntity; that.mEntityTypes[sName] = oEntityType; oEntityType.namespace = oSchema.namespace; return false; } return true; }); } }); } return oEntityType; }; /** * Checks whether the metadata was loaded. * * @returns {boolean} Returns true, if the metadata was loaded. */ ODataMetadata.prototype._checkMetadataLoaded = function(){ if (!this.oMetadata || isEmptyObject(this.oMetadata)) { assert(undefined, "No metadata loaded!"); return false; } return true; }; /** * Extracts an Annotation from given path parts. * * @param {string} sPath * The metadata path to the annotation * @returns {object|undefined} * The annotation for the given metadata path; returns <code>undefined</code> if no annotation * can be found for that path * * @private */ ODataMetadata.prototype._getAnnotation = function(sPath) { var oNode, aParts, sMetaPath, aMetaParts, oEntityType, sPropertyPath, oProperty; aParts = sPath.split('/#'); aMetaParts = aParts[1].split('/'); //check if we have an absolute meta binding if (!aParts[0]) { // first part must be the entityType oEntityType = this._getEntityTypeByName(aMetaParts[0]); assert(oEntityType, aMetaParts[0] + " is not a valid EntityType"); if (!oEntityType) { return undefined; } //extract property sPropertyPath = aParts[1].substr(aParts[1].indexOf('/') + 1); oProperty = this._getPropertyMetadata(oEntityType,sPropertyPath); assert(oProperty, sPropertyPath + " is not a valid property path"); if (!oProperty) { return undefined; } sMetaPath = sPropertyPath.substr(sPropertyPath.indexOf(oProperty.name)); sMetaPath = sMetaPath.substr(sMetaPath.indexOf('/') + 1); } else { //getentityType from data Path oEntityType = this._getEntityTypeByPath(aParts[0]); assert(oEntityType, aParts[0] + " is not a valid path"); if (!oEntityType) { return undefined; } //extract property sPath = aParts[0].replace(/^\/|\/$/g, ""); sPropertyPath = sPath; while (!oProperty && sPropertyPath.indexOf("/") > 0) { sPropertyPath = sPropertyPath.substr(sPropertyPath.indexOf('/') + 1); oProperty = this._getPropertyMetadata(oEntityType, sPropertyPath); } assert(oProperty, sPropertyPath + " is not a valid property path"); if (!oProperty) { return undefined; } sMetaPath = aMetaParts.join('/'); } oNode = this._getAnnotationObject(oEntityType, oProperty, sMetaPath); return oNode; }; /** * Gets the annotation specified by the given metadata path in the given metadata object for the * given type. * * @param {object} oEntityType The entity type of the property * @param {object} oObject The metadata object * @param {string} sMetaDataPath The metadata path * @return {object|undefined} The annotation object/value */ ODataMetadata.prototype._getAnnotationObject = function(oEntityType, oObject, sMetaDataPath) { var oAnnotation, sAnnotation, aAnnotationParts, oExtension, i, oNode, aParts; if (!oObject) { return undefined; } oNode = oObject; aParts = sMetaDataPath.split('/'); //V4 annotation if (aParts[0].indexOf('.') > -1) { return this._getV4AnnotationObject(oEntityType, oObject, aParts); } else if (aParts.length > 1) { // Additional namespace handling cannot be done to keep compatibility oNode = oNode[aParts[0]]; if (!oNode && oObject.extensions) { for (i = 0; i < oObject.extensions.length; i++) { oExtension = oObject.extensions[i]; if (oExtension.name == aParts[0]) { oNode = oExtension; break; } } } sMetaDataPath = aParts.splice(0,1); oAnnotation = this._getAnnotationObject(oEntityType, oNode, aParts.join('/')); } else if (aParts[0].indexOf('@') > -1) { //handle attributes sAnnotation = aParts[0].substr(1); aAnnotationParts = sAnnotation.split(':'); oAnnotation = oNode[aAnnotationParts[0]]; if (!oAnnotation && oNode.extensions) { for (i = 0; i < oNode.extensions.length; i++) { oExtension = oNode.extensions[i]; if (oExtension.name === aAnnotationParts[1] && oExtension.namespace === this.mNamespaces[aAnnotationParts[0]]) { oAnnotation = oExtension.value; break; } } } } else { // handle nodes aAnnotationParts = aParts[0].split(':'); oAnnotation = oNode[aAnnotationParts[0]]; oAnnotation = oNode[aParts[0]]; if (!oAnnotation && oNode.extensions) { for (i = 0; i < oNode.extensions.length; i++) { oExtension = oNode.extensions[i]; if (oExtension.name === aAnnotationParts[1] && oExtension.namespace === this.mNamespaces[aAnnotationParts[0]]) { oAnnotation = oExtension; break; } } } } return oAnnotation; }; /** * Gets the annotation specified by the given metadata path in the given metadata object for the * given type. * * @param {object} oEntityType The entity type of the property * @param {object} oObject The metadata object * @param {string[]} aParts The metadata path; must contain exactly one element * @return {object|undefined} The annotation object/value * * @private */ ODataMetadata.prototype._getV4AnnotationObject = function(oEntityType, oObject, aParts) { var oAnnotationNode, aAnnotations = []; if (aParts.length > 1) { assert(aParts.length == 1, "'" + aParts.join('/') + "' is not a valid annotation path"); return undefined; } var sTargetName = oEntityType.namespace ? oEntityType.namespace + "." : ""; sTargetName += oEntityType.name + "/" + oObject.name; each(this.oMetadata.dataServices.schema, function(i, oSchema) { if (oSchema.annotations) { each(oSchema.annotations, function(k, oObject) { //we do not support qualifiers on target level if (oObject.target === sTargetName && !oObject.qualifier) { aAnnotations.push(oObject.annotation); return false; } return true; }); } }); if (aAnnotations) { each(aAnnotations, function(i, aAnnotation) { each(aAnnotation, function(j, oAnnotation) { if (oAnnotation.term === aParts[0]) { oAnnotationNode = oAnnotation; } }); }); } return oAnnotationNode; }; /** * Splits a full qualified name into its namespace and its name, for example splits * "my.namespace.Foo" into {name : "Foo", namespace : "my.namespace"}. * * @param {string} sFullName * The full name * @returns {object} * An object containing the properties <code>name</code> and <code>namespace</code> */ ODataMetadata.prototype._splitName = function(sFullName) { var oInfo = {}; if (sFullName) { var iSepIdx = sFullName.lastIndexOf("."); oInfo.name = sFullName.substr(iSepIdx + 1); oInfo.namespace = sFullName.substr(0, iSepIdx); } return oInfo; }; /** * Gets the entity type name for the given entity set name. * * @param {string} sEntitySetName The collection name * @returns {string} The name of the collection's entity type */ ODataMetadata.prototype._getEntityTypeName = function(sEntitySetName) { var sEntityTypeName, oEntitySet; if (sEntitySetName) { oEntitySet = this._findEntitySetByName(sEntitySetName); if (oEntitySet){ sEntityTypeName = oEntitySet.entityType; } } return sEntityTypeName; }; /** * Gets the object of a specified type name and namespace. * * @param {string} sObjectType The object's type * @param {string} sObjectName The object's name * @param {string} sNamespace The object's namespace * @returns {object} The found object */ ODataMetadata.prototype._getObjectMetadata = function(sObjectType, sObjectName, sNamespace) { var oObject; if (sObjectName && sNamespace) { // search in all schemas for the sObjectName each(this.oMetadata.dataServices.schema, function(i, oSchema) { // check if we found the right schema which will contain the sObjectName if (oSchema[sObjectType] && oSchema.namespace === sNamespace) { each(oSchema[sObjectType], function(j, oCurrentObject) { if (oCurrentObject.name === sObjectName) { oObject = oCurrentObject; oObject.namespace = oSchema.namespace; return false; } return true; }); return !oObject; } return true; }); } return oObject; }; /** * Get the use-batch extension value if any * @return {boolean} true/false * @public */ ODataMetadata.prototype.getUseBatch = function() { var bUseBatch = false; // search in all schemas for the use batch extension each(this.oMetadata.dataServices.schema, function(i, oSchema) { if (oSchema.entityContainer) { each(oSchema.entityContainer, function(k, oEntityContainer) { if (oEntityContainer.extensions) { each(oEntityContainer.extensions, function(l, oExtension) { if (oExtension.name === "use-batch" && oExtension.namespace === "http://www.sap.com/Protocols/SAPData") { bUseBatch = (typeof oExtension.value === 'string') ? (oExtension.value.toLowerCase() === 'true') : !!oExtension.value; return false; } return true; }); } }); } }); return bUseBatch; }; ODataMetadata.prototype._getFunctionImportMetadataIterate = function(fnCheck, bSingleEntry) { var aObjects = []; // search in all schemas for the sObjectName each(this.oMetadata.dataServices.schema, function(iSchema, oSchema) { // check if we found the right schema which will contain the sObjectName if (oSchema["entityContainer"]) { each(oSchema["entityContainer"], function(iEntityContainer, oEntityContainer) { if (oEntityContainer["functionImport"]) { each(oEntityContainer["functionImport"], function(iFunctionImport, oFunctionImport) { if (fnCheck(oFunctionImport)) { aObjects.push(oFunctionImport); if (bSingleEntry) { return false; } } return true; }); } // break if single entry is wanted and there is exactly one return !(bSingleEntry && aObjects.length === 1); }); } // break if single entry is wanted and there is exactly one return !(bSingleEntry && aObjects.length === 1); }); return aObjects; }; ODataMetadata.prototype._getFirstMatchingFunctionImportMetadata = function(fnCheck){ var aObjects = this._getFunctionImportMetadataIterate(fnCheck, true); return aObjects.length === 1 ? aObjects[0] : null; }; /** * Retrieve the function import metadata for a name. * * @param {string} sFunctionName The name of the function import to look up * @return {object[]} array of matching function import metadata */ ODataMetadata.prototype._getFunctionImportMetadataByName = function(sFunctionName) { if (sFunctionName.indexOf("/") > -1) { sFunctionName = sFunctionName.substr(sFunctionName.indexOf("/") + 1); } return this._getFunctionImportMetadataIterate(function(oFunctionImport) { return oFunctionImport.name === sFunctionName; }); }; /** * Retrieve the function import metadata for a name and a method. * * @param {string} sFunctionName The name of the function import to look up * @param {string} sMethod The HTTP Method for which this function is requested * * @returns {object|null} The function import metadata */ ODataMetadata.prototype._getFunctionImportMetadata = function(sFunctionName, sMethod) { if (sFunctionName.indexOf("/") > -1) { sFunctionName = sFunctionName.substr(sFunctionName.indexOf("/") + 1); } return this._getFirstMatchingFunctionImportMetadata(function(oFunctionImport) { return oFunctionImport.name === sFunctionName && oFunctionImport.httpMethod === sMethod; }); }; /** * Returns the target EntityType for the NavigationProperty of a given EntityType object. The target is * defined as the toRole of the navigationproperty; this method looks up the corresponding matching End in the * corresponding Association and returns the matching entityType * @see sap.ui.model.odata.ODataMetadata#_getEntityTypeByNavPropertyObject * * @param {map} mEntityType - The EntityType that has the NavigationProperty * @param {string} sNavPropertyName - The name of the NavigationProperty in the EntityType * @returns {map|undefined} The EntityType that the NavigationProperty points to or undefined if not found * @private */ ODataMetadata.prototype._getEntityTypeByNavProperty = function(mEntityType, sNavPropertyName) { if (!mEntityType.navigationProperty) { return undefined; } for (var i = 0; i < mEntityType.navigationProperty.length; ++i) { var oNavigationProperty = mEntityType.navigationProperty[i]; if (oNavigationProperty.name === sNavPropertyName) { return this._getEntityTypeByNavPropertyObject(oNavigationProperty); } } return undefined; }; /** * Returns the target EntityType for a given NavigationProperty object. The target is defined as the toRole of * the navigationproperty; this method looks up the corresponding matching End in the corresponding Association * and returns the matching entityType * * @param {map} mNavProperty - The NavigationProperty (from the navigationProperty array of an EntityType) * @returns {map} The EntityType that the NavigationProperty points to * @private */ ODataMetadata.prototype._getEntityTypeByNavPropertyObject = function(mNavProperty) { var mToEntityType; var oAssociationInfo = this._splitName(mNavProperty.relationship); var mAssociation = this._getObjectMetadata("association", oAssociationInfo.name, oAssociationInfo.namespace); // get association for navigation property and then the collection name if (mAssociation) { var mEnd = mAssociation.end[0]; if (mEnd.role !== mNavProperty.toRole) { mEnd = mAssociation.end[1]; } var oEntityTypeInfo = this._splitName(mEnd.type); mToEntityType = this._getObjectMetadata("entityType", oEntityTypeInfo.name, oEntityTypeInfo.namespace); if (mToEntityType) { // store the type name also in the oEntityType mToEntityType.entityType = mEnd.type; } } return mToEntityType; }; /** * Get all navigation property names in an array by the specified entity type. * * @param {object} oEntityType The entity type * @returns {string[]} An array containing the navigation property names */ ODataMetadata.prototype._getNavigationPropertyNames = function(oEntityType) { var aNavProps = []; if (oEntityType.navigationProperty) { each(oEntityType.navigationProperty, function(k, oNavigationProperty) { aNavProps.push(oNavigationProperty.name); }); } return aNavProps; }; /** * Get dependent nav property name, entityset and key properties for given entity and property * name. If the property name is contained as key property in a referential constraint of one of * the navigation properties, return the name of the navigation property, as well as the * referenced entityset and the array of key properties. * * @param {object} oEntityType The entity type * @param {string} sPropertyName The property name * @returns {object} An object containing information about the navigation property */ ODataMetadata.prototype._getNavPropertyRefInfo = function(oEntityType, sPropertyName) { var oNavPropInfo, oAssociation, oAssociationInfo, oAssociationSet, oPrincipal, oDependent, bContainsProperty, sRole, oEnd, sEntitySet, aKeys, that = this; each(oEntityType.navigationProperty, function(i, oNavProperty) { oAssociationInfo = that._splitName(oNavProperty.relationship); oAssociation = that._getObjectMetadata("association", oAssociationInfo.name, oAssociationInfo.namespace); // Can't find referential info, if referentialConstraint isn't provided if (!oAssociation || !oAssociation.referentialConstraint) { return; } oDependent = oAssociation.referentialConstraint.dependent; oEnd = oAssociation.end.find(function(oEnd) { return oEnd.role === oDependent.role; }); // Only if dependent role type matches entity type, look for properties if (oEnd.type !== oEntityType.namespace + "." + oEntityType.name) { return; } bContainsProperty = oDependent.propertyRef.some(function(oPropertyRef) { return oPropertyRef.name === sPropertyName; }); // If dependent doesn't contain the property return if (!bContainsProperty) { return; } oPrincipal = oAssociation.referentialConstraint.principal; sRole = oPrincipal.role; oAssociationSet = that._getAssociationSetByAssociation(oNavProperty.relationship); oEnd = oAssociationSet.end.find(function(oEnd) { return oEnd.role === sRole; }); sEntitySet = oEnd.entitySet; aKeys = oPrincipal.propertyRef.map(function(oPropertyRef) { return oPropertyRef.name; }); oNavPropInfo = { name: oNavProperty.name, entitySet: sEntitySet, keys: aKeys }; }); return oNavPropInfo; }; /** * Extract the property metadata of a specified property of an entity type out of the metadata * document. * * @param {object} oEntityType The entity type * @param {string} sProperty The property in the entity type * @returns {object} The property's metadata */ ODataMetadata.prototype._getPropertyMetadata = function(oEntityType, sProperty) { var oPropertyMetadata, that = this; if (!oEntityType) { return undefined; } // remove starting/trailing / sProperty = sProperty.replace(/^\/|\/$/g, ""); var aParts = sProperty.split("/"); // path could point to a complex type or nav property each(oEntityType.property, function(k, oProperty) { if (oProperty.name === aParts[0]) { oPropertyMetadata = oProperty; return false; } return true; }); if (aParts.length > 1) { // check for navigation property and complex type if (!oPropertyMetadata) { while (oEntityType && aParts.length > 1) { oEntityType = this._getEntityTypeByNavProperty(oEntityType, aParts[0]); aParts.shift(); } if (oEntityType) { oPropertyMetadata = that._getPropertyMetadata(oEntityType, aParts[0]); } } else if (!oPropertyMetadata.type.toLowerCase().startsWith("edm.")) { var oNameInfo = this._splitName(oPropertyMetadata.type); oPropertyMetadata = this._getPropertyMetadata(this._getObjectMetadata("complexType", oNameInfo.name, oNameInfo.namespace), aParts[1]); } } return oPropertyMetadata; }; ODataMetadata.prototype.destroy = function() { delete this.oMetadata; var that = this; // Abort pending xml request each(this.mRequestHandles, function(sKey, oRequestHandle) { oRequestHandle.bSuppressErrorHandlerCall = true; oRequestHandle.abort(); delete that.mRequestHandles[sKey]; }); if (this.oLoadEvent) { clearTimeout(this.oLoadEvent); } if (this.oFailedEvent) { clearTimeout(this.oFailedEvent); } EventProvider.prototype.destroy.apply(this, arguments); }; /** * Creates caches for entity sets, entity types and navigation properties if not yet done and if * the metadata are loaded, otherwise nothing is done. * * <code>this._entitySetMap</code> maps the name of an entity type (including the namespace, * for example "GWSAMPLE_BASIC.SalesOrder") to its corresponding entity set object as returned * by datajs. * At each entity set object contained in <code>this.oMetadata</code> the reference * <code>__entityType</code> to the corresponding entity type object is added. * At each entity type object contained in <code>this.oMetadata</code> the map * <code>__navigationPropertiesMap</code> is added which maps the navigation property name to * the corresponding navigation property object as returned by datajs. */ ODataMetadata.prototype._fillElementCaches = function () { var that = this; if (this._entitySetMap || !this._checkMetadataLoaded()) { return; } this._entitySetMap = {}; this.oMetadata.dataServices.schema.forEach(function (mSchema) { (mSchema.entityContainer || []).forEach(function (mContainer) { (mContainer.entitySet || []).forEach(function (mEntitySet) { var oEntityType = that._getEntityTypeByName(mEntitySet.entityType); oEntityType.__navigationPropertiesMap = {}; (oEntityType.navigationProperty || []).forEach(function (oProp) { oEntityType.__navigationPropertiesMap[oProp.name] = oProp; }); mEntitySet.__entityType = oEntityType; that._entitySetMap[mEntitySet.entityType] = mEntitySet; }); }); }); }; /** * Creates a request object for changes. * * @param {string} sUrl The request URL * @return {object} The request object * * @private */ ODataMetadata.prototype._createRequest = function(sUrl) { // The 'sap-cancel-on-close' header marks the OData metadata request as cancelable. This helps to save resources at the back-end. var oDefaultHeaders = { "sap-cancel-on-close": true }, oLangHeader = { "Accept-Language": Configuration.getLanguageTag() }; extend(oDefaultHeaders, this.mHeaders, oLangHeader); var oRequest = { headers: oDefaultHeaders, requestUri: sUrl, method: 'GET', user: this.sUser, password: this.sPassword, async: this.bAsync }; if (this.bAsync) { oRequest.withCredentials = this.bWithCredentials; } return oRequest; }; /** * Returns the entity set to which the given entity path belongs * * @param {string} sEntityPath The path to the entity * @return {map|undefined} The EntitySet to which the path belongs or undefined if none */ ODataMetadata.prototype._getEntitySetByPath = function(sEntityPath) { var oEntityType; this._fillElementCaches(); oEntityType = this._getEntityTypeByPath(sEntityPath); return oEntityType && this._entitySetMap[oEntityType.entityType]; }; /** * Add metadata url: The response will be merged with the existing metadata object. * * @param {string|string[]} vUrl Either one URL as string or an array of URI strings * @returns {Promise} The Promise for metadata loading * @private */ ODataMetadata.prototype._addUrl = function(vUrl) { var aUrls = [].concat(vUrl); return Promise.all(aUrls.map(function(sUrl) { return this._loadMetadata(sUrl, true); }, this)); }; /** * merges two metadata objects * @param {object} oTarget Target metadata object * @param {object} oSource Source metadata object * @param {array} aEntitySets An array where the entitySets (metadata objects) from the source objects will * be collected and returned. * @return {object} oTarget The merged metadata object * @private */ ODataMetadata.prototype.merge = function(oTarget, oSource, aEntitySets) { var that = this; // invalidate cache for entity set map if (this.mEntitySets) { delete this.mEntitySets; } each(oTarget.dataServices.schema, function(i, oTargetSchema) { // find schema each(oSource.dataServices.schema, function(j, oSourceSchema) { if (oSourceSchema.namespace === oTargetSchema.namespace) { //merge entityTypes if (oSourceSchema.entityType) { //cache entityType names if (!that.mEntityTypeNames) { that.mEntityTypeNames = {}; oTargetSchema.entityType.map(function(o) { that.mEntityTypeNames[o.name] = true; }); } oTargetSchema.entityType = !oTargetSchema.entityType ? [] : oTargetSchema.entityType; for (var i = 0; i < oSourceSchema.entityType.length; i++) { if (!(oSourceSchema.entityType[i].name in that.mEntityTypeNames)) { oTargetSchema.entityType.push(oSourceSchema.entityType[i]); that.mEntityTypeNames[oSourceSchema.entityType[i].name] = true; } } } //find EntityContainer if any if (oTargetSchema.entityContainer && oSourceSchema.entityContainer) { each(oTargetSchema.entityContainer, function(k, oTargetContainer) { //merge entitySets each(oSourceSchema.entityContainer, function(l, oSourceContainer) { if (oSourceContainer.entitySet) { if (oSourceContainer.name === oTargetContainer.name) { //cache entitySet names if (!that.mEntitySetNames) { that.mEntitySetNames = {}; oTargetContainer.entitySet.map(function(o) { that.mEntitySetNames[o.name] = true; }); } oTargetContainer.entitySet = !oTargetContainer.entitySet ? [] : oTargetContainer.entitySet; for (var i = 0; i < oSourceContainer.entitySet.length; i++) { if (!(oSourceContainer.entitySet[i].name in that.mEntitySetNames)) { oTargetContainer.entitySet.push(oSourceContainer.entitySet[i]); that.mEntitySetNames[oSourceContainer.entitySet[i].name] = true; } } oSourceContainer.entitySet.forEach(function(oElement) { aEntitySets.push(oElement); }); } } }); }); } //merge Annotations if (oSourceSchema.annotations) { oTargetSchema.annotations = !oTargetSchema.annotations ? [] : oTargetSchema.annotations; oTargetSchema.annotations = oTargetSchema.annotations.concat(oSourceSchema.annotations); } } }); }); return oTarget; }; /** * Returns the first EntitySet from all EntityContainers that matches the namespace and name of the given EntityType * * @param {map} mEntityType - The EntityType object * @return {map|null} Returns the EntitySet object or null if not found */ ODataMetadata.prototype._getEntitySetByType = function(mEntityType) { var sEntityType = mEntityType.namespace + "." + mEntityType.name; var aSchema = this.oMetadata.dataServices.schema; for (var i = 0; i < aSchema.length; ++i) { var aContainers = aSchema[i].entityContainer; if (aContainers) { for (var n = 0; n < aContainers.length; ++n) { var aSets = aContainers[n].entitySet; if (aSets) { for (var m = 0; m < aSets.length; ++m) { if (aSets[m].entityType === sEntityType) { return aSets[m]; } } } } } } return null; }; /** * Calculates the canonical path of the given deep path. * * @param {string} sPath The deep path * @return {string|undefined} The canonical path or undefined * @private */ ODataMetadata.prototype._calculateCanonicalPath = function(sPath) { var sCanonicalPath, iIndex, aParts, sTempPath; if (sPath) { iIndex = sPath.lastIndexOf(")"); if (iIndex !== -1) { sTempPath = sPath.substr(0, iIndex + 1); var oEntitySet = this._getEntitySetByPath(sTempPath); if (oEntitySet) { if (oEntitySet.__entityType.isFunction) { sCanonicalPath = sPath; } else { aParts = sPath.split("/"); if (sTempPath === "/" + aParts[1]) { //check for nav prop if (!(aParts[2] in oEntitySet.__entityType.__navigationPropertiesMap)) { sCanonicalPath = sPath; } } else { aParts = sTempPath.split("/"); sTempPath = '/' + oEntitySet.name + aParts[aParts.length - 1].substr(aParts[aParts.length - 1].indexOf("(")) + sPath.substr(iIndex + 1); if (sTempPath !== sPath) { sCanonicalPath = sTempPath; } } } } } } return sCanonicalPath; }; /** * Returns the first AssociationSet from all EntityContainers that matches the association name * * @param {string} sAssociation The full qualified association name * @return {map|null} Returns the AssociationSet object or null if not found */ ODataMetadata.prototype._getAssociationSetByAssociation = function(sAssociation) { var aSchema = this.oMetadata.dataServices.schema; for (var i = 0; i < aSchema.length; ++i) { var aContainers = aSchema[i].entityContainer; if (aContainers) { for (var n = 0; n < aContainers.length; ++n) { var aSets = aContainers[n].associationSet; if (aSets) { for (var m = 0; m < aSets.length; ++m) { if (aSets[m].association === sAssociation) { return aSets[m]; } } } } } } return null; }; /** * Whether MessageScope is supported by service or not. * * @return {boolean} Whether MessageScope is supported * @private */ ODataMetadata.prototype._isMessageScopeSupported = function() { var aSchema = this.oMetadata.dataServices.schema, oContainer, aContainers; // Note: "The edmx:DataServices element contains zero or more edm:Schema elements..." if (!this.bMessageScopeSupported && aSchema) { for (var i = 0; i < aSchema.length; ++i) { aContainers = aSchema[i].entityContainer; if (aContainers) { for (var n = 0; n < aContainers.length; ++n) { oContainer = aContainers[n]; if (oContainer.extensions && Array.isArray(oContainer.extensions)) { for (var m = 0; m < oContainer.extension