sources/Google.Solutions.IapDesktop.Core/EntityModel/EntityContext.cs (314 lines of code) (raw):

// // Copyright 2024 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Solutions.Apis.Locator; using Google.Solutions.IapDesktop.Core.EntityModel.Query; using Google.Solutions.IapDesktop.Core.ObjectModel; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Google.Solutions.IapDesktop.Core.EntityModel { /// <summary> /// Provides a unified view over data exposed by multiple /// entity navigators and aspect providers. /// </summary> public partial class EntityContext { private readonly IList<RegisteredEntityCache> caches; private readonly IDictionary<Type, List<RegisteredEntitySearcher>> searchers; private readonly IDictionary<Type, LocatorConfiguration> locators; /// <summary> /// Queue for receiving entity-related events. Used for observable results. /// </summary> internal IEventQueue EventQueue { get; } public EntityContext(IServiceCategoryProvider serviceProvider) : this( new Builder(serviceProvider.GetService<IEventQueue>()) .AddNavigators(serviceProvider.GetServicesByCategory<IEntityNavigator>()) .AddSearchers(serviceProvider.GetServicesByCategory<IEntitySearcher>()) .AddAspectProviders(serviceProvider.GetServicesByCategory<IEntityAspectProvider>())) { } private EntityContext(Builder builder) { this.EventQueue = builder.EventQueue; this.locators = builder.BuildLocatorConfiguration(); this.caches = builder.BuildCaches(); this.searchers = builder.BuildSearchers(); } //-------------------------------------------------------------------- // Publics. //-------------------------------------------------------------------- /// <summary> /// Check of there are any entity navigators for this type of locator. /// </summary> public bool SupportsDescendants(Type locatorType) { if (this.locators.TryGetValue(locatorType, out var configuration)) { return configuration.Navigators.Any(); } else { return false; } } /// <summary> /// Check of there is any entity navigators for this type of locator. /// </summary> public bool SupportsDescendants(ILocator locator) { return SupportsDescendants(locator.GetType()); } /// <summary> /// Check of there is any entity navigators for this type of locator. /// </summary> public bool SupportsDescendants<TLocator>() where TLocator : ILocator { return SupportsDescendants(typeof(TLocator)); } /// <summary> /// Check if a given aspect if supported for a type of locator. /// </summary> public bool SupportsAspect(Type locatorType, Type aspectType) { if (this.locators.TryGetValue(locatorType, out var configuration)) { // // Check synchronous providers. // if (configuration.AspectProviders .Any(p => aspectType.IsAssignableFrom(p.AspectType))) { return true; } // // Check asynchronous providers. // if (configuration.AsyncAspectProviders .Any(p => aspectType.IsAssignableFrom(p.AspectType))) { return true; } } return false; } /// <summary> /// Check if a given aspect if supported for a type of locator. /// </summary> public bool SupportsAspect<TLocator, TAspect>() where TLocator : ILocator { return SupportsAspect(typeof(TLocator), typeof(TAspect)); } /// <summary> /// Invalidate cached entities for a locator. /// </summary> public void Invalidate(ILocator locator) { foreach (var cache in this.caches.Where(c => c.LocatorType == locator.GetType())) { cache.Invalidate(locator); } } /// <summary> /// Query all navigators that support the kind of locator and /// requested entity type, or a subtype thereof. /// </summary> /// <returns> /// Empty if there is no navigator for this type of entity. /// </returns> public async Task<ICollection<TEntity>> ListDescendantsAsync<TEntity>( ILocator locator, CancellationToken cancellationToken) where TEntity : IEntity<ILocator> { if (this.locators.TryGetValue(locator.GetType(), out var configuration)) { var listTasks = configuration.Navigators .Where(c => typeof(TEntity).IsAssignableFrom(c.EntityType)) .Select(c => c.ListDescendantsAsync(locator, cancellationToken)) .ToList(); var listResults = await Task .WhenAll(listTasks) .ConfigureAwait(false); // // Flatten result and cast to requested type. // return listResults .SelectMany(r => r) .Cast<TEntity>() .ToList(); } else { return Array.Empty<TEntity>(); } } /// <summary> /// Search for entities of the requested entity type that match a query. /// </summary> /// <returns> /// Empty if there is no searcher for this type of query or entity. /// </returns> public async Task<ICollection<TEntity>> SearchAsync<TQuery, TEntity>( TQuery query, CancellationToken cancellationToken) where TEntity : IEntity<ILocator> { if (query != null && this.searchers.TryGetValue(typeof(TQuery), out var searchers)) { var searchTasks = searchers .Where(s => typeof(TEntity).IsAssignableFrom(s.EntityType)) .Select(s => s.SearchAsync(query, cancellationToken)) .ToList(); var searchResults = await Task .WhenAll(searchTasks) .ConfigureAwait(false); // // Flatten result and cast to requested type. // return searchResults .SelectMany(r => r) .Cast<TEntity>() .OrderBy(e => e.GetType().Name).ThenBy(e => e.DisplayName) .ToList(); } else { return Array.Empty<TEntity>(); } } /// <summary> /// Query an aspect from synchronous providers, ignoring /// asynchronous providers. /// </summary> /// <returns> /// Null if there is no provider for the aspect, or if /// the provider returned null. /// </returns> public TAspect? QueryAspect<TAspect>(ILocator locator) where TAspect : class { if (this.locators.TryGetValue(locator.GetType(), out var configuration)) { return configuration.AspectProviders .Where(c => typeof(TAspect).IsAssignableFrom(c.AspectType)) .Select(c => c.QueryAspect(locator)) .FirstOrDefault() as TAspect; } return null; } /// <summary> /// Query an aspect from synchronous and asynchronous providers. /// </summary> /// <returns> /// Null if there is no provider for the aspect, or if /// the provider returned null. /// </returns> public async Task<TAspect?> QueryAspectAsync<TAspect>( ILocator locator, CancellationToken cancellationToken) where TAspect : class { // // Try synchronous providers. // if (QueryAspect<TAspect>(locator) is TAspect aspect) { return aspect; } // // Try asynchronous providers. // if (this.locators.TryGetValue(locator.GetType(), out var configuration)) { var queryTask = configuration.AsyncAspectProviders .Where(c => typeof(TAspect).IsAssignableFrom(c.AspectType)) .Select(p => p.QueryAspectAsync(locator, cancellationToken)) .FirstOrDefault(); if (queryTask != null) { return (await queryTask.ConfigureAwait(false)) as TAspect; } } return null; } /// <summary> /// Create a query builder for this context. /// </summary> public EntityQuery<TEntity>.Builder Entities<TEntity>() where TEntity : class, IEntity<ILocator> { return new EntityQuery<TEntity>.Builder(this); } //-------------------------------------------------------------------- // Introspection //-------------------------------------------------------------------- /// <summary> /// Enables introspecting the entity types supported by this context. /// </summary> private class Introspector : IEntitySearcher<WildcardQuery, EntityType>, IEntityNavigator<EntityTypeLocator, IEntity<ILocator>> { private readonly ICollection<RegisteredEntitySearcher> searchers; public Introspector(ICollection<RegisteredEntitySearcher> searchers) { this.searchers = searchers; } /// <summary> /// List all searchable entity types. /// </summary> public Task<IEnumerable<EntityType>> SearchAsync( WildcardQuery query, CancellationToken cancellationToken) { return Task.FromResult<IEnumerable<EntityType>>(this.searchers .Select(s => new EntityType(s.EntityType)) .ToList()); } /// <summary> /// List entities of a certain type. /// </summary> public Task<IEnumerable<IEntity<ILocator>>> ListDescendantsAsync( EntityTypeLocator typeLocator, CancellationToken cancellationToken) { // // Look for a suitable searcher. // var searcher = this.searchers.Where(s => s.EntityType == typeLocator.Type && s.QueryType == typeof(WildcardQuery)); if (searcher.Any()) { return searcher .First() .SearchAsync(WildcardQuery.Instance, cancellationToken); } else { return Task.FromResult(Enumerable.Empty<IEntity<ILocator>>()); } } } //-------------------------------------------------------------------- // Registration data structures. //-------------------------------------------------------------------- /// <summary> /// Configuration for a type of locator. /// </summary> internal readonly struct LocatorConfiguration { public Type LocatorType { get; } public ICollection<RegisteredEntityNavigator> Navigators { get; } public ICollection<RegisteredAspectProvider> AspectProviders { get; } public ICollection<RegisteredAsyncAspectProvider> AsyncAspectProviders { get; } public LocatorConfiguration( Type locatorType, ICollection<RegisteredEntityNavigator> navigators, ICollection<RegisteredAspectProvider> aspectProviders, ICollection<RegisteredAsyncAspectProvider> asyncAspectProviders) { this.LocatorType = locatorType; this.Navigators = navigators; this.AspectProviders = aspectProviders; this.AsyncAspectProviders = asyncAspectProviders; if (Enumerable.Empty<Type>() .Concat(aspectProviders.Select(p => p.AspectType)) .Concat(asyncAspectProviders.Select(p => p.AspectType)) .GroupBy(t => t) .Where(g => g.Count() > 1) .Select(g => g.Key) .FirstOrDefault() is Type duplicateAspect) { throw new InvalidOperationException( "Only one aspect provider can be registerd for " + $"aspect {duplicateAspect}"); } } } internal readonly struct RegisteredEntityCache { public delegate void InvalidateDelegate(ILocator locator); public Type LocatorType { get; } public InvalidateDelegate Invalidate { get; } public RegisteredEntityCache(Type locatorType, InvalidateDelegate invalidate) { this.LocatorType = locatorType; this.Invalidate = invalidate; } } internal readonly struct RegisteredEntityNavigator { public delegate Task<IEnumerable<IEntity<ILocator>>> ListDescendantsAsyncDelegate( ILocator locator, CancellationToken cancellationToken); public Type LocatorType { get; } public Type EntityType { get; } public ListDescendantsAsyncDelegate ListDescendantsAsync { get; } public RegisteredEntityNavigator( Type locatorType, Type entityType, ListDescendantsAsyncDelegate listAsync) { this.LocatorType = locatorType; this.EntityType = entityType; this.ListDescendantsAsync = listAsync; } } internal readonly struct RegisteredEntitySearcher { public delegate Task<IEnumerable<IEntity<ILocator>>> SearchAsyncDelegate( object query, CancellationToken cancellationToken); public Type EntityType { get; } public Type QueryType { get; } public SearchAsyncDelegate SearchAsync { get; } public RegisteredEntitySearcher( Type entityType, Type queryType, SearchAsyncDelegate searchAsync) { this.EntityType = entityType; this.QueryType = queryType; this.SearchAsync = searchAsync; } } internal readonly struct RegisteredAspectProvider { public delegate object? QueryAspectDelegate(ILocator locator); public Type LocatorType { get; } public Type AspectType { get; } public QueryAspectDelegate QueryAspect { get; } public RegisteredAspectProvider( Type locatorType, Type aspectType, QueryAspectDelegate queryAspect) { this.LocatorType = locatorType; this.AspectType = aspectType; this.QueryAspect = queryAspect; } } internal readonly struct RegisteredAsyncAspectProvider { public delegate Task<object?> QueryAspectAsyncDelegate( ILocator locator, CancellationToken cancellationToken); public Type LocatorType { get; } public Type AspectType { get; } public QueryAspectAsyncDelegate QueryAspectAsync { get; } public RegisteredAsyncAspectProvider( Type locatorType, Type aspectType, QueryAspectAsyncDelegate queryAspectAsync) { this.LocatorType = locatorType; this.AspectType = aspectType; this.QueryAspectAsync = queryAspectAsync; } } } }