UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,149 lines (1,076 loc) 39 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ "./_AnnotationHelperBasics", "sap/base/Log", "sap/base/util/deepExtend", "sap/base/util/extend" ], function (_AnnotationHelperBasics, Log, deepExtend, extend) { "use strict"; var oBoolFalse = { "Bool" : "false" }, oBoolTrue = { "Bool" : "true" }, // maps V2 sap:semantics value for a date part to corresponding V4 term relative to // com.sap.vocabularies.Common.v1. mDatePartSemantics2CommonTerm = { "fiscalyear" : "IsFiscalYear", "fiscalyearperiod" : "IsFiscalYearPeriod", "year" : "IsCalendarYear", "yearmonth" : "IsCalendarYearMonth", "yearmonthday" : "IsCalendarDate", "yearquarter" : "IsCalendarYearQuarter", "yearweek" : "IsCalendarYearWeek" }, // maps V2 filter-restriction value to corresponding V4 FilterExpressionType enum value mFilterRestrictions = { "interval" : "SingleInterval", "multi-value" : "MultiValue", "single-value" : "SingleValue" }, sLoggingModule = "sap.ui.model.odata.ODataMetaModel", // maps V2 sap semantics annotations to a V4 annotations relative to // com.sap.vocabularies.Communication.v1. mSemanticsToV4AnnotationPath = { // contact annotations "bday" : "Contact", "city" : "Contact/adr", "country" : "Contact/adr", "email" : "Contact/email", "familyname" : "Contact/n", "givenname" : "Contact/n", "honorific" : "Contact/n", "middlename" : "Contact/n", "name" : "Contact", "nickname" : "Contact", "note" : "Contact", "org" : "Contact", "org-role" : "Contact", "org-unit" : "Contact", "photo" : "Contact", "pobox" : "Contact/adr", "region" : "Contact/adr", "street" : "Contact/adr", "suffix" : "Contact/n", "tel" : "Contact/tel", "title" : "Contact", "zip" : "Contact/adr", // event annotations "class" : "Event", "dtend" : "Event", "dtstart" : "Event", "duration" : "Event", "fbtype" : "Event", "location" : "Event", "status" : "Event", "transp" : "Event", "wholeday" : "Event", // message annotations "body" : "Message", "from" : "Message", "received" : "Message", "sender" : "Message", "subject" : "Message", // task annotations "completed" : "Task", "due" : "Task", "percent-complete" : "Task", "priority" : "Task" }, rSemanticsWithTypes = /(\w+)(?:;type=([\w,]+))?/, mV2SemanticsToV4TypeInfo = { "email" : { typeMapping : { "home" : "home", "pref" : "preferred", "work" : "work" }, v4EnumType : "com.sap.vocabularies.Communication.v1.ContactInformationType", v4PropertyAnnotation : "com.sap.vocabularies.Communication.v1.IsEmailAddress" }, "tel" : { typeMapping : { "cell" : "cell", "fax" : "fax", "home" : "home", "pref" : "preferred", "video" : "video", "voice" : "voice", "work" : "work" }, v4EnumType : "com.sap.vocabularies.Communication.v1.PhoneType", v4PropertyAnnotation : "com.sap.vocabularies.Communication.v1.IsPhoneNumber" } }, // map from V2 to V4 for NON-DEFAULT cases only mV2ToV4 = { creatable : { "Org.OData.Capabilities.V1.InsertRestrictions" : { "Insertable" : oBoolFalse } }, // deletable : { // "Org.OData.Capabilities.V1.DeleteRestrictions" : { "Deletable" : oBoolFalse } // }, // see handleXableAndXablePath() pageable : { "Org.OData.Capabilities.V1.SkipSupported" : oBoolFalse, "Org.OData.Capabilities.V1.TopSupported" : oBoolFalse }, "requires-filter" : { "Org.OData.Capabilities.V1.FilterRestrictions" : { "RequiresFilter" : oBoolTrue } }, topable : { "Org.OData.Capabilities.V1.TopSupported" : oBoolFalse } // updatable : { // "Org.OData.Capabilities.V1.UpdateRestrictions" : { "Updatable" : oBoolFalse } // } // see handleXableAndXablePath() }, // only if V4 name is different from V2 name mV2ToV4Attribute = { "city" : "locality", "email" : "address", "familyname" : "surname", "givenname" : "given", "honorific" : "prefix", "middlename" : "additional", "name" : "fn", "org-role" : "role", "org-unit" : "orgunit", "percent-complete" : "percentcomplete", "tel" : "uri", "zip" : "code" }, // map from V2 annotation to an array of an annotation term and a name in that annotation // that holds a collection of property references mV2ToV4PropertyCollection = { "sap:filterable" : [ "Org.OData.Capabilities.V1.FilterRestrictions", "NonFilterableProperties" ], "sap:required-in-filter" : [ "Org.OData.Capabilities.V1.FilterRestrictions", "RequiredProperties" ], "sap:sortable" : [ "Org.OData.Capabilities.V1.SortRestrictions", "NonSortableProperties" ] }, rValueList = /^com\.sap\.vocabularies\.Common\.v1\.ValueList(#.*)?$/, iWARNING = Log.Level.WARNING, Utils; /** * This object contains helper functions for ODataMetaModel. * * @since 1.29.0 */ Utils = { /** * Adds EntitySet V4 annotation for current extension if extension value is equal to * the given non-default value. Depending on bDeepCopy the annotation will be merged * with deep copy. * @param {object} o * any object * @param {object} oExtension * the SAP Annotation (OData Version 2.0) for which a V4 annotation needs to be added * @param {string} sTypeClass * the type class of the given object; supported type classes are "Property" and * "EntitySet" * @param {string} sNonDefaultValue * if current extension value is equal to this sNonDefaultValue the annotation is * added * @param {boolean} bDeepCopy * if true the annotation is mixed in as deep copy of the entry in mV2ToV4 map */ addEntitySetAnnotation : function (o, oExtension, sTypeClass, sNonDefaultValue, bDeepCopy) { if (sTypeClass === "EntitySet" && oExtension.value === sNonDefaultValue) { // potentially nested structure so do deep copy if (bDeepCopy) { deepExtend(o, mV2ToV4[oExtension.name]); } else { // Warning: Passing false for the first argument is not supported! extend(o, mV2ToV4[oExtension.name]); } } }, /** * Adds corresponding V4 annotation for V2 <code>sap:filter-restriction</code> to the given * entity set. * * @param {object} oProperty * the property of the entity * @param {object} oEntitySet * the entity set to which the corresponding V4 annotations need to be added */ addFilterRestriction : function (oProperty, oEntitySet) { var aFilterRestrictions, sFilterRestrictionValue = mFilterRestrictions[oProperty["sap:filter-restriction"]]; if (!sFilterRestrictionValue) { if (Log.isLoggable(iWARNING, sLoggingModule)) { Log.warning("Unsupported sap:filter-restriction: " + oProperty["sap:filter-restriction"], oEntitySet.entityType + "." + oProperty.name, sLoggingModule); } return; } aFilterRestrictions = oEntitySet["com.sap.vocabularies.Common.v1.FilterExpressionRestrictions"] || []; aFilterRestrictions.push({ "Property" : { "PropertyPath" : oProperty.name}, "AllowedExpressions" : { "EnumMember" : "com.sap.vocabularies.Common.v1.FilterExpressionType/" + sFilterRestrictionValue } }); oEntitySet["com.sap.vocabularies.Common.v1.FilterExpressionRestrictions"] = aFilterRestrictions; }, /** * Adds a V4 navigation restriction annotation with a filter restriction to the given entity * set for the given navigation property with the V2 annotation * <code>sap:filterable="false"</code>. * * @param {object} oNavigationProperty * the navigation property of the entity with the V2 annotation * <code>sap:filterable="false"</code> * @param {object} oEntitySet * the entity set to which the corresponding V4 annotation needs to be added */ addNavigationFilterRestriction : function (oNavigationProperty, oEntitySet) { var oNavigationRestrictions = oEntitySet["Org.OData.Capabilities.V1.NavigationRestrictions"] || {}; oNavigationRestrictions.RestrictedProperties = oNavigationRestrictions.RestrictedProperties || []; oNavigationRestrictions.RestrictedProperties.push({ "FilterRestrictions" : { "Filterable": oBoolFalse }, "NavigationProperty" : { "NavigationPropertyPath" : oNavigationProperty.name } }); oEntitySet["Org.OData.Capabilities.V1.NavigationRestrictions"] = oNavigationRestrictions; }, /** * Adds current property to the property collection for given V2 annotation. * * @param {string} sV2AnnotationName * V2 annotation name (key in map mV2ToV4PropertyCollection) * @param {object} oEntitySet * the entity set * @param {object} oProperty * the property of the entity */ addPropertyToAnnotation : function (sV2AnnotationName, oEntitySet, oProperty) { var aNames = mV2ToV4PropertyCollection[sV2AnnotationName], sTerm = aNames[0], sCollection = aNames[1], oAnnotation = oEntitySet[sTerm] || {}, aCollection = oAnnotation[sCollection] || []; aCollection.push({ "PropertyPath" : oProperty.name }); oAnnotation[sCollection] = aCollection; oEntitySet[sTerm] = oAnnotation; }, /** * Collects sap:semantics annotations of the given type's properties at the type. * * @param {object} oType * the entity type or the complex type for which sap:semantics needs to be added */ addSapSemantics : function (oType) { if (oType.property) { oType.property.forEach(function (oProperty) { var aAnnotationParts, bIsCollection, aMatches, sSubStructure, vTmp, sV2Semantics = oProperty["sap:semantics"], sV4Annotation, sV4AnnotationPath, oV4Annotation, oV4TypeInfo, sV4TypeList; if (!sV2Semantics) { return; } if (sV2Semantics === "url") { oProperty["Org.OData.Core.V1.IsURL"] = oBoolTrue; return; } if (sV2Semantics in mDatePartSemantics2CommonTerm) { sV4Annotation = "com.sap.vocabularies.Common.v1." + mDatePartSemantics2CommonTerm[sV2Semantics]; oProperty[sV4Annotation] = oBoolTrue; return; } aMatches = rSemanticsWithTypes.exec(sV2Semantics); if (!aMatches) { if (Log.isLoggable(iWARNING, sLoggingModule)) { Log.warning("Unsupported sap:semantics: " + sV2Semantics, oType.name + "." + oProperty.name, sLoggingModule); } return; } if (aMatches[2]) { sV2Semantics = aMatches[1]; sV4TypeList = Utils.getV4TypesForV2Semantics(sV2Semantics, aMatches[2], oProperty, oType); } oV4TypeInfo = mV2SemanticsToV4TypeInfo[sV2Semantics]; bIsCollection = sV2Semantics === "tel" || sV2Semantics === "email"; sV4AnnotationPath = mSemanticsToV4AnnotationPath[sV2Semantics]; if (sV4AnnotationPath) { aAnnotationParts = sV4AnnotationPath.split("/"); sV4Annotation = "com.sap.vocabularies.Communication.v1." + aAnnotationParts[0]; oType[sV4Annotation] = oType[sV4Annotation] || {}; oV4Annotation = oType[sV4Annotation]; sSubStructure = aAnnotationParts[1]; if (sSubStructure) { oV4Annotation[sSubStructure] = oV4Annotation[sSubStructure] || (bIsCollection ? [] : {}); if (bIsCollection) { vTmp = {}; oV4Annotation[sSubStructure].push(vTmp); oV4Annotation = vTmp; } else { oV4Annotation = oV4Annotation[sSubStructure]; } } oV4Annotation[mV2ToV4Attribute[sV2Semantics] || sV2Semantics] = { "Path" : oProperty.name }; if (sV4TypeList) { // set also type attribute oV4Annotation.type = { "EnumMember" : sV4TypeList }; } } // Additional annotation at the property with sap:semantics "tel" or "email"; // ensure not to overwrite existing V4 annotations if (oV4TypeInfo) { oProperty[oV4TypeInfo.v4PropertyAnnotation] = oProperty[oV4TypeInfo.v4PropertyAnnotation] || oBoolTrue; } }); } }, /** * Adds unit annotations to properties that have a <code>sap:unit</code> OData V2 * annotation. * * Iterates over the given schemas and searches in their complex and entity types for * properties with a <code>sap:unit</code> OData V2 annotation. Creates a corresponding * OData V4 annotation <code>Org.OData.Measures.V1.Unit</code> or * <code>Org.OData.Measures.V1.ISOCurrency</code> based on the * <code>sap:semantics</code> V2 annotation of the referenced unit property, unless such an * annotation already exists. * * @param {object[]} aSchemas * the array of schemas * @param {sap.ui.model.odata.ODataMetaModel} oMetaModel * the OData meta model */ addUnitAnnotations : function (aSchemas, oMetaModel) { /** * Process all types in the given array. * @param {object[]} [aTypes] A list of complex types or entity types. */ function processTypes(aTypes) { (aTypes || []).forEach(function (oType) { (oType.property || []).forEach(function (oProperty) { var sAnnotationName, oInterface, sSemantics, oTarget, oUnitPath, sUnitPath = oProperty["sap:unit"], oUnitProperty; if (sUnitPath) { oInterface = { getModel : function () { return oMetaModel; }, getPath : function () { return oType.$path; } }; oUnitPath = {"Path" : sUnitPath}; oTarget = _AnnotationHelperBasics.followPath(oInterface, oUnitPath); if (oTarget && oTarget.resolvedPath) { oUnitProperty = oMetaModel.getProperty(oTarget.resolvedPath); sSemantics = oUnitProperty["sap:semantics"]; if (sSemantics === "unit-of-measure") { sAnnotationName = "Org.OData.Measures.V1.Unit"; } else if (sSemantics === "currency-code") { sAnnotationName = "Org.OData.Measures.V1.ISOCurrency"; } else if (Log.isLoggable(iWARNING, sLoggingModule)) { Log.warning("Unsupported sap:semantics='" + sSemantics + "' at sap:unit='" + sUnitPath + "'; " + "expected 'currency-code' or 'unit-of-measure'", oType.namespace + "." + oType.name + "/" + oProperty.name, sLoggingModule); } // Do not overwrite an existing annotation if (sAnnotationName && !(sAnnotationName in oProperty)) { oProperty[sAnnotationName] = oUnitPath; } } else if (Log.isLoggable(iWARNING, sLoggingModule)) { Log.warning("Path '" + sUnitPath + "' for sap:unit cannot be resolved", oType.namespace + "." + oType.name + "/" + oProperty.name, sLoggingModule); } } }); }); } aSchemas.forEach(function (oSchema) { processTypes(oSchema.complexType); processTypes(oSchema.entityType); }); }, /** * Adds the corresponding V4 annotation to the given object based on the given SAP * extension. * * @param {object} o * any object * @param {object} oExtension * the SAP Annotation (OData Version 2.0) for which a V4 annotation needs to be added * @param {string} sTypeClass * the type class of the given object; supported type classes are "Property" and * "EntitySet" */ addV4Annotation : function (o, oExtension, sTypeClass) { switch (oExtension.name) { case "aggregation-role": if (oExtension.value === "dimension") { o["com.sap.vocabularies.Analytics.v1.Dimension"] = oBoolTrue; } else if (oExtension.value === "measure") { o["com.sap.vocabularies.Analytics.v1.Measure"] = oBoolTrue; } break; case "display-format": if (oExtension.value === "NonNegative") { o["com.sap.vocabularies.Common.v1.IsDigitSequence"] = oBoolTrue; } else if (oExtension.value === "UpperCase") { o["com.sap.vocabularies.Common.v1.IsUpperCase"] = oBoolTrue; } break; case "pageable": case "topable": Utils.addEntitySetAnnotation(o, oExtension, sTypeClass, "false", false); break; case "creatable": Utils.addEntitySetAnnotation(o, oExtension, sTypeClass, "false", true); break; case "deletable": case "deletable-path": Utils.handleXableAndXablePath(o, oExtension, sTypeClass, "Org.OData.Capabilities.V1.DeleteRestrictions", "Deletable"); break; case "updatable": case "updatable-path": Utils.handleXableAndXablePath(o, oExtension, sTypeClass, "Org.OData.Capabilities.V1.UpdateRestrictions", "Updatable"); break; case "requires-filter": Utils.addEntitySetAnnotation(o, oExtension, sTypeClass, "true", true); break; case "field-control": o["com.sap.vocabularies.Common.v1.FieldControl"] = { "Path" : oExtension.value }; break; case "heading": o["com.sap.vocabularies.Common.v1.Heading"] = { "String" : oExtension.value }; break; case "label": o["com.sap.vocabularies.Common.v1.Label"] = { "String" : oExtension.value }; break; case "precision": o["Org.OData.Measures.V1.Scale"] = { "Path" : oExtension.value }; break; case "quickinfo": o["com.sap.vocabularies.Common.v1.QuickInfo"] = { "String" : oExtension.value }; break; case "text": o["com.sap.vocabularies.Common.v1.Text"] = { "Path" : oExtension.value }; break; case "visible": if (oExtension.value === "false") { o["com.sap.vocabularies.Common.v1.FieldControl"] = { "EnumMember" : "com.sap.vocabularies.Common.v1.FieldControlType/Hidden" }; o["com.sap.vocabularies.UI.v1.Hidden"] = oBoolTrue; } break; default: // no transformation for V2 annotation supported or necessary } }, /** * Iterate over all properties of the associated entity type for given entity * set and check whether the property needs to be added to an annotation at the * entity set. * For example all properties with "sap:sortable=false" are collected in * annotation Org.OData.Capabilities.V1.SortRestrictions/NonSortableProperties. * * @param {object} oEntitySet * the entity set * @param {object} oEntityType * the corresponding entity type */ calculateEntitySetAnnotations : function (oEntitySet, oEntityType) { if (oEntityType.property) { oEntityType.property.forEach(function (oProperty) { if (oProperty["sap:filterable"] === "false") { Utils.addPropertyToAnnotation("sap:filterable", oEntitySet, oProperty); } if (oProperty["sap:required-in-filter"] === "true") { Utils.addPropertyToAnnotation("sap:required-in-filter", oEntitySet, oProperty); } if (oProperty["sap:sortable"] === "false") { Utils.addPropertyToAnnotation("sap:sortable", oEntitySet, oProperty); } if (oProperty["sap:filter-restriction"]) { Utils.addFilterRestriction(oProperty, oEntitySet); } }); } if (oEntityType.navigationProperty) { oEntityType.navigationProperty.forEach(function (oNavigationProperty) { if (oNavigationProperty["sap:filterable"] === "false") { Utils.addNavigationFilterRestriction(oNavigationProperty, oEntitySet); // keep deprecated conversion for compatibility reasons Utils.addPropertyToAnnotation("sap:filterable", oEntitySet, oNavigationProperty); } Utils.handleCreatableNavigationProperty(oEntitySet, oNavigationProperty); }); } }, /** * Returns the index of the first object inside the given array, where the property with the * given name has the given expected value. * * @param {object[]} [aArray] * some array * @param {any} vExpectedPropertyValue * expected value of the property with given name * @param {string} [sPropertyName="name"] * some property name * @returns {number} * the index of the first object found or <code>-1</code> if no such object is found */ findIndex : function (aArray, vExpectedPropertyValue, sPropertyName) { var i, n; sPropertyName = sPropertyName || "name"; if (aArray) { for (i = 0, n = aArray.length; i < n; i += 1) { if (aArray[i][sPropertyName] === vExpectedPropertyValue) { return i; } } } return -1; }, /** * Returns the object inside the given array, where the property with the given name has * the given expected value. * * @param {object[]} aArray * some array * @param {any} vExpectedPropertyValue * expected value of the property with given name * @param {string} [sPropertyName="name"] * some property name * @returns {object} * the object found or <code>null</code> if no such object is found */ findObject : function (aArray, vExpectedPropertyValue, sPropertyName) { var iIndex = Utils.findIndex(aArray, vExpectedPropertyValue, sPropertyName); return iIndex < 0 ? null : aArray[iIndex]; }, /** * Gets the map from child name to annotations for a parent with the given qualified * name which lives inside the entity container as indicated. * * @param {sap.ui.model.odata.ODataAnnotations} oAnnotations * the OData annotations * @param {string} sQualifiedName * the parent's qualified name * @param {boolean} bInContainer * whether the parent lives inside the entity container (or beside it) * @returns {object} * the map from child name to annotations */ getChildAnnotations : function (oAnnotations, sQualifiedName, bInContainer) { var o = bInContainer ? oAnnotations.EntityContainer : oAnnotations.propertyAnnotations; return o && o[sQualifiedName] || {}; }, /** * Returns the thing with the given simple name from the given entity container. * * @param {object} oEntityContainer * the entity container * @param {string} sArrayName * name of array within entity container which will be searched * @param {string} sName * a simple name, e.g. "Foo" * @param {boolean} [bAsPath=false] * determines whether the thing itself is returned or just its path * @returns {object|string} * (the path to) the thing with the given qualified name; <code>undefined</code> (for a * path) or <code>null</code> (for an object) if no such thing is found */ getFromContainer : function (oEntityContainer, sArrayName, sName, bAsPath) { var k, vResult = bAsPath ? undefined : null; if (oEntityContainer) { k = Utils.findIndex(oEntityContainer[sArrayName], sName); if (k >= 0) { vResult = bAsPath ? oEntityContainer.$path + "/" + sArrayName + "/" + k : oEntityContainer[sArrayName][k]; } } return vResult; }, /** * Returns the thing with the given qualified name from the given model's array (within a * schema) of given name. * * @param {sap.ui.model.Model|object[]} vModel * either a model or an array of schemas * @param {string} sArrayName * name of array within schema which will be searched * @param {string} sQualifiedName * a qualified name, e.g. "ACME.Foo" * @param {boolean} [bAsPath=false] * determines whether the thing itself is returned or just its path * @returns {object|string} * (the path to) the thing with the given qualified name; <code>undefined</code> (for a * path) or <code>null</code> (for an object) if no such thing is found */ getObject : function (vModel, sArrayName, sQualifiedName, bAsPath) { var aArray, vResult = bAsPath ? undefined : null, oSchema, iSeparatorPos, sNamespace, sName; sQualifiedName = sQualifiedName || ""; iSeparatorPos = sQualifiedName.lastIndexOf("."); sNamespace = sQualifiedName.slice(0, iSeparatorPos); sName = sQualifiedName.slice(iSeparatorPos + 1); oSchema = Utils.getSchema(vModel, sNamespace); if (oSchema) { aArray = oSchema[sArrayName]; if (aArray) { aArray.some(function (oThing) { if (oThing.name === sName) { vResult = bAsPath ? oThing.$path : oThing; return true; } return false; }); } } return vResult; }, /** * Returns the schema with the given namespace. * * @param {sap.ui.model.Model|object[]} vModel * either a model or an array of schemas * @param {string} sNamespace * a namespace, e.g. "ACME" * @returns {object} * the schema with the given namespace; <code>null</code> if no such schema is found */ getSchema : function (vModel, sNamespace) { var oSchema = null, aSchemas = Array.isArray(vModel) ? vModel : vModel.getObject("/dataServices/schema"); if (aSchemas) { aSchemas.some(function (o) { if (o.namespace === sNamespace) { oSchema = o; return true; } return false; }); } return oSchema; }, /** * Compute a space-separated list of V4 annotation enumeration values for the given * sap:semantics "tel" and "email". * E.g. for <code>sap:semantics="tel;type=fax"</code> this function returns * "com.sap.vocabularies.Communication.v1.PhoneType/fax". * * @param {string} sSemantics * the sap:semantics value ("tel" or "email") * @param {string} sTypesList * the comma-separated list of types for sap:semantics * @param {object} oProperty * the property * @param {object} oType * the type * @returns {string} * the corresponding space-separated list of V4 annotation enumeration values; * returns an empty string if the sap:semantics value is not supported; unsupported types * are logged and skipped; */ getV4TypesForV2Semantics : function (sSemantics, sTypesList, oProperty, oType) { var aResult = [], oV4TypeInfo = mV2SemanticsToV4TypeInfo[sSemantics]; if (oV4TypeInfo) { sTypesList.split(",").forEach(function (sType) { var sTargetType = oV4TypeInfo.typeMapping[sType]; if (sTargetType) { aResult.push(oV4TypeInfo.v4EnumType + "/" + sTargetType); } else if (Log.isLoggable(iWARNING, sLoggingModule)) { Log.warning("Unsupported type for sap:semantics: " + sType, oType.name + "." + oProperty.name, sLoggingModule); } }); } return aResult.join(" "); }, /** * Returns the map representing the <code>com.sap.vocabularies.Common.v1.ValueList</code> * annotations of the given property. * * @param {object} oProperty the property * @returns {object} map of ValueList annotations contained in oProperty */ getValueLists : function (oProperty) { var aMatches, sName, sQualifier, mValueLists = {}; for (sName in oProperty) { aMatches = rValueList.exec(sName); if (aMatches){ sQualifier = (aMatches[1] || "").slice(1); // remove leading # mValueLists[sQualifier] = oProperty[sName]; } } return mValueLists; }, /** * Convert sap:creatable and sap:creatable-path at navigation property to V4 annotation * 'Org.OData.Capabilities.V1.InsertRestrictions/NonInsertableNavigationProperties' at * the given entity set. * If both V2 annotations 'sap:creatable' and 'sap:creatable-path' are given the service is * broken and the navigation property is added as non-insertable navigation property. * If neither 'sap:creatable' nor 'sap:creatable-path' are given this function does * nothing. * * @param {object} oEntitySet * The entity set * @param {object} oNavigationProperty * The navigation property */ handleCreatableNavigationProperty : function (oEntitySet, oNavigationProperty) { var sCreatable = oNavigationProperty["sap:creatable"], sCreatablePath = oNavigationProperty["sap:creatable-path"], oInsertRestrictions, oNonInsertable = {"NavigationPropertyPath" : oNavigationProperty.name}, aNonInsertableNavigationProperties; if (sCreatable && sCreatablePath) { // inconsistent service if both v2 annotations are set Log.warning("Inconsistent service", "Use either 'sap:creatable' or 'sap:creatable-path' at navigation property " + "'" + oEntitySet.entityType + "/" + oNavigationProperty.name + "'", sLoggingModule); sCreatable = "false"; sCreatablePath = undefined; } if (sCreatable === "false" || sCreatablePath) { oInsertRestrictions = oEntitySet["Org.OData.Capabilities.V1.InsertRestrictions"] = oEntitySet["Org.OData.Capabilities.V1.InsertRestrictions"] || {}; aNonInsertableNavigationProperties = oInsertRestrictions["NonInsertableNavigationProperties"] = oInsertRestrictions["NonInsertableNavigationProperties"] || []; if (sCreatablePath) { oNonInsertable = { "If" : [{ "Not" : { "Path" : sCreatablePath } }, oNonInsertable] }; } aNonInsertableNavigationProperties.push(oNonInsertable); } }, /** * Converts deletable/updatable and delatable-path/updatable-path into corresponding V4 * annotation. * If both deletable/updatable and delatable-path/updatable-path are defined the service is * broken and the object is marked as non-deletable/non-updatable. * * @param {object} o * any object * @param {object} oExtension * the SAP Annotation (OData Version 2.0) for which a V4 annotation needs to be added * @param {string} sTypeClass * the type class of the given object; supported type is "EntitySet" * @param {string} sTerm * the V4 annotation term to use * @param {string} sProperty * the V4 annotation property to use */ handleXableAndXablePath : function (o, oExtension, sTypeClass, sTerm, sProperty) { var sV2Annotation = sProperty.toLowerCase(), oValue; if (sTypeClass !== "EntitySet") { return; // "Property" not supported here, see liftSAPData() } if (o["sap:" + sV2Annotation] && o["sap:" + sV2Annotation + "-path"]) { // the first extension (sap:xable or sap:xable-path) is processed as usual; // only if a second extension (sap:xable-path or sap:xable) is processed, // the warning is logged and the entity set is marked as non-deletable or // non-updatable Log.warning("Inconsistent service", "Use either 'sap:" + sV2Annotation + "' or 'sap:" + sV2Annotation + "-path'" + " at entity set '" + o.name + "'", sLoggingModule); oValue = oBoolFalse; } else if (sV2Annotation !== oExtension.name) { // delatable-path/updatable-path oValue = { "Path" : oExtension.value }; } else if (oExtension.value === "false") { oValue = oBoolFalse; } if (oValue) { o[sTerm] = o[sTerm] || {}; o[sTerm][sProperty] = oValue; } }, /** * Lift all extensions from the <a href="http://www.sap.com/Protocols/SAPData"> SAP * Annotations for OData Version 2.0</a> namespace up as attributes with "sap:" prefix. * * @param {object} o * any object * @param {string} sTypeClass * the type class of the given object; supported type classes are "Property" and * "EntitySet" */ liftSAPData : function (o, sTypeClass) { if (!o.extensions) { return; } o.extensions.forEach(function (oExtension) { if (oExtension.namespace === "http://www.sap.com/Protocols/SAPData") { o["sap:" + oExtension.name] = oExtension.value; Utils.addV4Annotation(o, oExtension, sTypeClass); } }); // after all SAP V2 annotations are lifted up add V4 annotations that are calculated // by multiple V2 annotations or that have a different default value switch (sTypeClass) { case "Property": if (o["sap:updatable"] === "false") { if (o["sap:creatable"] === "false") { o["Org.OData.Core.V1.Computed"] = oBoolTrue; } else { o["Org.OData.Core.V1.Immutable"] = oBoolTrue; } } break; case "EntitySet": if (o["sap:searchable"] !== "true") { o["Org.OData.Capabilities.V1.SearchRestrictions"] = { "Searchable" : oBoolFalse }; } break; default: // nothing to do } }, /** * Merges the given annotation data into the given metadata and lifts SAPData extensions. * * @param {object} oAnnotations * annotations "JSON" * @param {object} oData * metadata "JSON" * @param {sap.ui.model.odata.ODataMetaModel} oMetaModel * the metamodel */ merge : function (oAnnotations, oData, oMetaModel) { var aSchemas = oData.dataServices.schema; if (!aSchemas) { return; } aSchemas.forEach(function (oSchema, i) { var sSchemaVersion; // remove datajs artefact for inline annotations in $metadata delete oSchema.annotations; Utils.liftSAPData(oSchema); oSchema.$path = "/dataServices/schema/" + i; sSchemaVersion = oSchema["sap:schema-version"]; if (sSchemaVersion) { oSchema["Org.Odata.Core.V1.SchemaVersion"] = { String : sSchemaVersion }; } extend(oSchema, oAnnotations[oSchema.namespace]); Utils.visitParents(oSchema, oAnnotations, "association", function (oAssociation, mChildAnnotations) { Utils.visitChildren(oAssociation.end, mChildAnnotations); }); Utils.visitParents(oSchema, oAnnotations, "complexType", function (oComplexType, mChildAnnotations) { Utils.visitChildren(oComplexType.property, mChildAnnotations, "Property"); Utils.addSapSemantics(oComplexType); }); // visit all entity types before visiting the entity sets to ensure that V2 // annotations are already lifted up and can be used for calculating entity // set annotations which are based on V2 annotations on entity properties Utils.visitParents(oSchema, oAnnotations, "entityType", Utils.visitEntityType); }); aSchemas.forEach(function (oSchema) { // visit entity container after all entity types of all schemas are visited Utils.visitParents(oSchema, oAnnotations, "entityContainer", function (oEntityContainer, mChildAnnotations) { Utils.visitChildren(oEntityContainer.associationSet, mChildAnnotations); Utils.visitChildren(oEntityContainer.entitySet, mChildAnnotations, "EntitySet", aSchemas); Utils.visitChildren(oEntityContainer.functionImport, mChildAnnotations, "", null, Utils.visitParameters.bind(this, oAnnotations, oSchema, oEntityContainer)); }); }); Utils.addUnitAnnotations(aSchemas, oMetaModel); }, /** * Visits all children inside the given array, lifts "SAPData" extensions and * inlines OData V4 annotations for each child. * * @param {object[]} aChildren * any array of children * @param {object} mChildAnnotations * map from child name (or role) to annotations * @param {string} [sTypeClass] * the type class of the given children; supported type classes are "Property" * and "EntitySet" * @param {object[]} [aSchemas] * Array of OData data service schemas (needed only for type class "EntitySet") * @param {function} [fnCallback] * optional callback for each child * @param {number} [iStartIndex=0] * optional start index in the given array */ visitChildren : function (aChildren, mChildAnnotations, sTypeClass, aSchemas, fnCallback, iStartIndex) { if (!aChildren) { return; } if (iStartIndex) { aChildren = aChildren.slice(iStartIndex); } aChildren.forEach(function (oChild) { // lift SAP data for easy access to SAP Annotations for OData V 2.0 Utils.liftSAPData(oChild, sTypeClass); }); aChildren.forEach(function (oChild) { var oEntityType; if (sTypeClass === "EntitySet") { // calculated entity set annotations need to be added before V4 // annotations are merged oEntityType = Utils.getObject(aSchemas, "entityType", oChild.entityType); Utils.calculateEntitySetAnnotations(oChild, oEntityType); } if (fnCallback) { fnCallback(oChild); } // merge V4 annotations after child annotations are processed extend(oChild, mChildAnnotations[oChild.name || oChild.role]); }); }, /** * Visits the given entity type and its (structural or navigation) properties. * * @param {object} oEntityType * the entity type * @param {object} mChildAnnotations * map from child name (or role) to annotations */ visitEntityType : function (oEntityType, mChildAnnotations) { Utils.visitChildren(oEntityType.property, mChildAnnotations, "Property"); Utils.visitChildren(oEntityType.navigationProperty, mChildAnnotations); Utils.addSapSemantics(oEntityType); }, /** * Visits all parameters of the given function import. * * @param {object} oAnnotations * annotations "JSON" * @param {object} oSchema * OData data service schema * @param {object} oEntityContainer * the entity container * @param {object} oFunctionImport * a function import's V2 metadata object */ visitParameters : function (oAnnotations, oSchema, oEntityContainer, oFunctionImport) { var mAnnotations; if (!oFunctionImport.parameter) { return; } mAnnotations = Utils.getChildAnnotations(oAnnotations, oSchema.namespace + "." + oEntityContainer.name, true); oFunctionImport.parameter.forEach( function (oParam) { Utils.liftSAPData(oParam); extend(oParam, mAnnotations[oFunctionImport.name + "/" + oParam.name]); } ); }, /** * Visits all parents (or a single parent) inside the current schema's array of given name, * lifts "SAPData" extensions, inlines OData V4 annotations, and adds <code>$path</code> * for each parent. * * @param {object} oSchema * OData data service schema * @param {object} oAnnotations * annotations "JSON" * @param {string} sArrayName * name of array of parents * @param {function} fnCallback * mandatory callback for each parent, child annotations are passed in * @param {number} [iIndex] * optional index of a single parent to visit; default is to visit all */ visitParents : function (oSchema, oAnnotations, sArrayName, fnCallback, iIndex) { var aParents = oSchema[sArrayName]; function visitParent(oParent, j) { var sQualifiedName = oSchema.namespace + "." + oParent.name, mChildAnnotations = Utils.getChildAnnotations(oAnnotations, sQualifiedName, sArrayName === "entityContainer"); Utils.liftSAPData(oParent); // @see sap.ui.model.odata.ODataMetadata#_getEntityTypeByName oParent.namespace = oSchema.namespace; oParent.$path = oSchema.$path + "/" + sArrayName + "/" + j; fnCallback(oParent, mChildAnnotations); // merge V4 annotations after child annotations are processed extend(oParent, oAnnotations[sQualifiedName]); } if (!aParents) { return; } if (iIndex !== undefined) { visitParent(aParents[iIndex], iIndex); } else { aParents.forEach(visitParent); } } }; return Utils; });