513 lines
22 KiB
C#
513 lines
22 KiB
C#
// ------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
|
|
// ------------------------------------------------------------
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Microsoft.OData.Edm;
|
|
using Microsoft.OData.Edm.Vocabularies;
|
|
using Microsoft.OpenApi.OData.Common;
|
|
using Microsoft.OpenApi.OData.Vocabulary;
|
|
using Microsoft.OpenApi.OData.Vocabulary.Authorization;
|
|
|
|
namespace Microsoft.OpenApi.OData.Edm
|
|
{
|
|
/// <summary>
|
|
/// Vocabulary Annotation Extension methods for <see cref="IEdmModel"/>
|
|
/// </summary>
|
|
internal static class EdmVocabularyAnnotationExtensions
|
|
{
|
|
private static IDictionary<IEdmVocabularyAnnotatable, IDictionary<string, object>> _cachedAnnotations;
|
|
private static IEdmModel _savedModel = null; // if diffenent model, the cache will be cleaned.
|
|
private static object _objectLock = new object();
|
|
|
|
/// <summary>
|
|
/// Gets the boolean term value for the given <see cref="IEdmVocabularyAnnotatable"/>.
|
|
/// </summary>
|
|
/// <param name="model">The Edm model.</param>
|
|
/// <param name="target">The Edm target.</param>
|
|
/// <param name="qualifiedName">The Term qualified name.</param>
|
|
/// <returns>Null or the boolean value for this annotation.</returns>
|
|
public static bool? GetBoolean(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName)
|
|
{
|
|
Utils.CheckArgumentNull(model, nameof(model));
|
|
Utils.CheckArgumentNull(target, nameof(target));
|
|
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
|
|
|
|
return GetOrAddCached(model, target, qualifiedName, () =>
|
|
{
|
|
bool? value = null;
|
|
IEdmTerm term = model.FindTerm(qualifiedName);
|
|
if (term != null)
|
|
{
|
|
value = model.GetBoolean(target, term);
|
|
if (value != null)
|
|
{
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
// Note: Graph has a lot of annotations applied to the type, not to the navigation source.
|
|
// Here's a work around to retrieve these annotations from type if we can't find it from navigation source.
|
|
// It's the same reason for belows
|
|
IEdmNavigationSource navigationSource = target as IEdmNavigationSource;
|
|
if (navigationSource != null)
|
|
{
|
|
IEdmEntityType entityType = navigationSource.EntityType();
|
|
value = model.GetBoolean(entityType, term);
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the string term value for the given <see cref="IEdmVocabularyAnnotatable"/>.
|
|
/// </summary>
|
|
/// <param name="model">The Edm model.</param>
|
|
/// <param name="target">The Edm target.</param>
|
|
/// <param name="qualifiedName">The Term qualified name.</param>
|
|
/// <returns>Null or the string value for this annotation.</returns>
|
|
public static string GetString(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName)
|
|
{
|
|
Utils.CheckArgumentNull(model, nameof(model));
|
|
Utils.CheckArgumentNull(target, nameof(target));
|
|
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
|
|
|
|
return GetOrAddCached(model, target, qualifiedName, () =>
|
|
{
|
|
string value = null;
|
|
IEdmTerm term = model.FindTerm(qualifiedName);
|
|
if (term != null)
|
|
{
|
|
value = model.GetString(target, term);
|
|
if (value != null)
|
|
{
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
IEdmNavigationSource navigationSource = target as IEdmNavigationSource;
|
|
if (navigationSource != null)
|
|
{
|
|
IEdmEntityType entityType = navigationSource.EntityType();
|
|
value = model.GetString(entityType, term);
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the record value (a complex type) for the given <see cref="IEdmVocabularyAnnotatable"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">The CLR mapping type.</typeparam>
|
|
/// <typeparam name="T">The CLR mapping type.</typeparam>
|
|
/// <param name="model">The Edm model.</param>
|
|
/// <returns>Null or the record value (a complex type) for this annotation.</returns>
|
|
public static T GetRecord<T>(this IEdmModel model, IEdmVocabularyAnnotatable target)
|
|
where T : IRecord, new()
|
|
{
|
|
string qualifiedName = Utils.GetTermQualifiedName<T>();
|
|
return model.GetRecord<T>(target, qualifiedName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the record value (a complex type) for the given <see cref="IEdmVocabularyAnnotatable"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">The CLR mapping type.</typeparam>
|
|
/// <param name="model">The Edm model.</param>
|
|
/// <param name="target">The Edm target.</param>
|
|
/// <param name="qualifiedName">The Term qualified name.</param>
|
|
/// <returns>Null or the record value (a complex type) for this annotation.</returns>
|
|
public static T GetRecord<T>(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName)
|
|
where T : IRecord, new()
|
|
{
|
|
Utils.CheckArgumentNull(model, nameof(model));
|
|
Utils.CheckArgumentNull(target, nameof(target));
|
|
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
|
|
|
|
return GetOrAddCached(model, target, qualifiedName, () =>
|
|
{
|
|
T value = default;
|
|
IEdmTerm term = model.FindTerm(qualifiedName);
|
|
if (term != null)
|
|
{
|
|
value = model.GetRecord<T>(target, term);
|
|
if (value != null)
|
|
{
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
IEdmNavigationSource navigationSource = target as IEdmNavigationSource;
|
|
if (navigationSource != null)
|
|
{
|
|
IEdmEntityType entityType = navigationSource.EntityType();
|
|
value = model.GetRecord<T>(entityType, term);
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the collection of string term value for the given <see cref="IEdmVocabularyAnnotatable"/>.
|
|
/// </summary>
|
|
/// <param name="model">The Edm model.</param>
|
|
/// <param name="target">The Edm target.</param>
|
|
/// <param name="qualifiedName">The Term qualified name.</param>
|
|
/// <returns>Null or the collection of string value for this annotation.</returns>
|
|
public static IEnumerable<string> GetCollection(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName)
|
|
{
|
|
Utils.CheckArgumentNull(model, nameof(model));
|
|
Utils.CheckArgumentNull(target, nameof(target));
|
|
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
|
|
|
|
return GetOrAddCached(model, target, qualifiedName, () =>
|
|
{
|
|
IEnumerable<string> value = null;
|
|
IEdmTerm term = model.FindTerm(qualifiedName);
|
|
if (term != null)
|
|
{
|
|
value = model.GetCollection(target, term);
|
|
if (value != null)
|
|
{
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
IEdmNavigationSource navigationSource = target as IEdmNavigationSource;
|
|
if (navigationSource != null)
|
|
{
|
|
IEdmEntityType entityType = navigationSource.EntityType();
|
|
value = model.GetCollection(entityType, term);
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the collection of record value (a complex type) for the given <see cref="IEdmVocabularyAnnotatable"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">The CLR mapping type.</typeparam>
|
|
/// <param name="model">The Edm model.</param>
|
|
/// <param name="target">The Edm target.</param>
|
|
/// <returns>Null or the colllection of record value (a complex type) for this annotation.</returns>
|
|
public static IEnumerable<T> GetCollection<T>(this IEdmModel model, IEdmVocabularyAnnotatable target)
|
|
where T : IRecord, new()
|
|
{
|
|
Utils.CheckArgumentNull(model, nameof(model));
|
|
Utils.CheckArgumentNull(target, nameof(target));
|
|
|
|
string qualifiedName = Utils.GetTermQualifiedName<T>();
|
|
return GetCollection<T>(model, target, qualifiedName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the collection of record value (a complex type) for the given <see cref="IEdmVocabularyAnnotatable"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">The CLR mapping type.</typeparam>
|
|
/// <param name="model">The Edm model.</param>
|
|
/// <param name="target">The Edm target.</param>
|
|
/// <param name="qualifiedName">The Term qualified name.</param>
|
|
/// <returns>Null or the colllection of record value (a complex type) for this annotation.</returns>
|
|
public static IEnumerable<T> GetCollection<T>(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName)
|
|
where T : IRecord, new()
|
|
{
|
|
Utils.CheckArgumentNull(model, nameof(model));
|
|
Utils.CheckArgumentNull(target, nameof(target));
|
|
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
|
|
|
|
return GetOrAddCached(model, target, qualifiedName, () =>
|
|
{
|
|
IEnumerable<T> value = null;
|
|
IEdmTerm term = model.FindTerm(qualifiedName);
|
|
if (term != null)
|
|
{
|
|
value = model.GetCollection<T>(target, term);
|
|
if (value != null)
|
|
{
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
IEdmNavigationSource navigationSource = target as IEdmNavigationSource;
|
|
if (navigationSource != null)
|
|
{
|
|
IEdmEntityType entityType = navigationSource.EntityType();
|
|
value = model.GetCollection<T>(entityType, term);
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the corresponding Authorization object.
|
|
/// </summary>
|
|
/// <param name="record">The input record.</param>
|
|
/// <returns>The created <see cref="Authorization"/> object.</returns>
|
|
public static IEnumerable<Authorization> GetAuthorizations(this IEdmModel model, IEdmVocabularyAnnotatable target)
|
|
{
|
|
Utils.CheckArgumentNull(model, nameof(model));
|
|
Utils.CheckArgumentNull(target, nameof(target));
|
|
|
|
return GetOrAddCached(model, target, AuthorizationConstants.Authorizations, () =>
|
|
{
|
|
IEdmTerm term = model.FindTerm(AuthorizationConstants.Authorizations);
|
|
if (term != null)
|
|
{
|
|
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
|
|
if (annotation != null && annotation.Value != null && annotation.Value.ExpressionKind == EdmExpressionKind.Collection)
|
|
{
|
|
IEdmCollectionExpression collection = (IEdmCollectionExpression)annotation.Value;
|
|
if (collection.Elements != null)
|
|
{
|
|
return collection.Elements.Select(e =>
|
|
{
|
|
Debug.Assert(e.ExpressionKind == EdmExpressionKind.Record);
|
|
|
|
IEdmRecordExpression recordExpression = (IEdmRecordExpression)e;
|
|
Authorization auth = Authorization.CreateAuthorization(recordExpression);
|
|
return auth;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the vocabulary annotation from a target annotatable.
|
|
/// </summary>
|
|
/// <param name="model">The model referenced to.</param>
|
|
/// <param name="target">The target Annotatable to find annotation</param>
|
|
/// <returns>The annotation or null.</returns>
|
|
public static IEdmVocabularyAnnotation GetVocabularyAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term)
|
|
{
|
|
Utils.CheckArgumentNull(model, nameof(model));
|
|
Utils.CheckArgumentNull(target, nameof(target));
|
|
Utils.CheckArgumentNull(term, nameof(term));
|
|
|
|
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
|
|
if (annotation != null)
|
|
{
|
|
return annotation;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static T GetOrAddCached<T>(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName, Func<T> createFunc)
|
|
{
|
|
if (model == null || target == null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
lock (_objectLock)
|
|
{
|
|
if (!ReferenceEquals(_savedModel, model))
|
|
{
|
|
if (_cachedAnnotations != null)
|
|
{
|
|
_cachedAnnotations.Clear();
|
|
}
|
|
|
|
_savedModel = model;
|
|
}
|
|
|
|
if (_cachedAnnotations == null)
|
|
{
|
|
_cachedAnnotations = new Dictionary<IEdmVocabularyAnnotatable, IDictionary<string, object>>();
|
|
}
|
|
|
|
object restriction;
|
|
if (_cachedAnnotations.TryGetValue(target, out IDictionary<string, object> value))
|
|
{
|
|
// Here means we visited target before and we are sure that the value is not null.
|
|
if (value.TryGetValue(qualifiedName, out restriction))
|
|
{
|
|
T ret = (T)restriction;
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
T ret = createFunc();
|
|
value[qualifiedName] = ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// It's first time to query this target, create new dictionary and restriction.
|
|
value = new Dictionary<string, object>();
|
|
_cachedAnnotations[target] = value;
|
|
T newAnnotation = createFunc();
|
|
value[qualifiedName] = newAnnotation;
|
|
return newAnnotation;
|
|
}
|
|
}
|
|
|
|
private static bool? GetBoolean(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term)
|
|
{
|
|
Debug.Assert(model != null);
|
|
Debug.Assert(target != null);
|
|
Debug.Assert(term != null);
|
|
|
|
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
|
|
if (annotation != null && annotation.Value != null && annotation.Value.ExpressionKind == EdmExpressionKind.BooleanConstant)
|
|
{
|
|
IEdmBooleanConstantExpression boolConstant = (IEdmBooleanConstantExpression)annotation.Value;
|
|
if (boolConstant != null)
|
|
{
|
|
return boolConstant.Value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string GetString(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term)
|
|
{
|
|
Debug.Assert(model != null);
|
|
Debug.Assert(target != null);
|
|
Debug.Assert(term != null);
|
|
|
|
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
|
|
if (annotation != null && annotation.Value != null && annotation.Value.ExpressionKind == EdmExpressionKind.StringConstant)
|
|
{
|
|
IEdmStringConstantExpression stringConstant = (IEdmStringConstantExpression)annotation.Value;
|
|
return stringConstant.Value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static IEnumerable<string> GetCollection(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term)
|
|
{
|
|
Debug.Assert(model != null);
|
|
Debug.Assert(target != null);
|
|
Debug.Assert(term != null);
|
|
|
|
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
|
|
if (annotation != null && annotation.Value != null && annotation.Value.ExpressionKind == EdmExpressionKind.Collection)
|
|
{
|
|
IEdmCollectionExpression collection = (IEdmCollectionExpression)annotation.Value;
|
|
if (collection.Elements != null)
|
|
{
|
|
return collection.Elements.Select(e => ((IEdmStringConstantExpression)e).Value);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static T GetRecord<T>(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term)
|
|
where T : IRecord, new()
|
|
{
|
|
Debug.Assert(model != null);
|
|
Debug.Assert(target != null);
|
|
Debug.Assert(term != null);
|
|
|
|
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
|
|
if (annotation != null && annotation.Value != null && annotation.Value.ExpressionKind == EdmExpressionKind.Record)
|
|
{
|
|
IEdmRecordExpression recordExpression = (IEdmRecordExpression)annotation.Value;
|
|
T newRecord = new T();
|
|
newRecord.Initialize(recordExpression);
|
|
return newRecord;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
private static IEnumerable<T> GetCollection<T>(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term)
|
|
where T : IRecord, new()
|
|
{
|
|
Debug.Assert(model != null);
|
|
Debug.Assert(target != null);
|
|
Debug.Assert(term != null);
|
|
|
|
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
|
|
if (annotation != null && annotation.Value != null && annotation.Value.ExpressionKind == EdmExpressionKind.Collection)
|
|
{
|
|
IEdmCollectionExpression collection = (IEdmCollectionExpression)annotation.Value;
|
|
if (collection.Elements != null)
|
|
{
|
|
return collection.Elements.Select(e =>
|
|
{
|
|
Debug.Assert(e.ExpressionKind == EdmExpressionKind.Record);
|
|
|
|
IEdmRecordExpression recordExpression = (IEdmRecordExpression)e;
|
|
T newRecord = new T();
|
|
newRecord.Initialize(recordExpression);
|
|
return newRecord;
|
|
});
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
private static Type GetTypeInfo<T>(string fullTypeName) where T : IRecord
|
|
{
|
|
object[] attributes = typeof(T).GetCustomAttributes(typeof(SubTypeAttribute), false);
|
|
|
|
SubTypeAttribute subType = attributes.OfType<SubTypeAttribute>().FirstOrDefault(s => s.FullName == fullTypeName);
|
|
if (subType == null)
|
|
{
|
|
return typeof(T);
|
|
}
|
|
|
|
return subType.Type;
|
|
#if false
|
|
foreach (var item in collection.Elements)
|
|
{
|
|
Debug.Assert(item.ExpressionKind == EdmExpressionKind.Record);
|
|
|
|
IEdmRecordExpression record = (IEdmRecordExpression)item;
|
|
if (record.DeclaredType == null)
|
|
{
|
|
T newRecord = new T();
|
|
newRecord.Initialize(record);
|
|
yield return newRecord;
|
|
}
|
|
else
|
|
{
|
|
IEdmComplexType complexType = record.DeclaredType.Definition as IEdmComplexType;
|
|
Debug.Assert(complexType != null);
|
|
|
|
string fullTypeName = complexType.FullTypeName();
|
|
|
|
Type subType = GetTypeInfo<T>(fullTypeName);
|
|
|
|
IRecord sub = Activator.CreateInstance(subType) as IRecord;
|
|
Debug.Assert(typeof(T).IsAssignableFrom(subType));
|
|
sub.Initialize(record);
|
|
yield return (T)sub;
|
|
}
|
|
}
|
|
// return Activator.CreateInstance(subType.Type) as T2;
|
|
#endif
|
|
}
|
|
}
|
|
}
|