@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,518 lines (1,367 loc) • 62.1 kB
JavaScript
/*!
* 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