sources/Google.Solutions.IapDesktop.Core/EntityModel/EntityContext.Builder.cs (258 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.ObjectModel; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; namespace Google.Solutions.IapDesktop.Core.EntityModel { public partial class EntityContext { /// <summary> /// Builder for EntityContext objects. /// </summary> public class Builder { private readonly List<RegisteredEntityCache> entityCaches = new List<RegisteredEntityCache>(); private readonly List<RegisteredEntityNavigator> entityNavigators = new List<RegisteredEntityNavigator>(); private readonly List<RegisteredEntitySearcher> entitySearchers = new List<RegisteredEntitySearcher>(); private readonly List<RegisteredAspectProvider> aspectProviders = new List<RegisteredAspectProvider>(); private readonly List<RegisteredAsyncAspectProvider> asyncAspectProviders = new List<RegisteredAsyncAspectProvider>(); private readonly MethodInfo addCacheCoreMethod; private readonly MethodInfo addNavigatorCoreMethod; private readonly MethodInfo addSearcherCoreMethod; private readonly MethodInfo addAspectProviderCoreMethod; private readonly MethodInfo addAsyncAspectProviderCoreMethod; public IEventQueue EventQueue { get; } public Builder(IEventQueue eventQueue) { this.EventQueue = eventQueue; this.addCacheCoreMethod = GetType().GetMethod( nameof(AddCacheCore), BindingFlags.Instance | BindingFlags.NonPublic); this.addNavigatorCoreMethod = GetType().GetMethod( nameof(AddNavigatorCore), BindingFlags.Instance | BindingFlags.NonPublic); this.addSearcherCoreMethod = GetType().GetMethod( nameof(AddSearcherCore), BindingFlags.Instance | BindingFlags.NonPublic); this.addAspectProviderCoreMethod = GetType().GetMethod( nameof(AddAspectProviderCore), BindingFlags.Instance | BindingFlags.NonPublic); this.addAsyncAspectProviderCoreMethod = GetType().GetMethod( nameof(AddAsyncAspectProviderCore), BindingFlags.Instance | BindingFlags.NonPublic); // // Enable introspection of registered searchers. // var introspector = new Introspector(this.entitySearchers); AddSearcher(introspector); AddNavigator(introspector); } /// <summary> /// Lookup a type's implemented interface based on its unbound type. /// </summary> private static IEnumerable<Type> GetGenericInterfaces( Type type, Type unboundType) { return type .GetInterfaces() .Where( i => i.IsGenericType && i.GetGenericTypeDefinition() == unboundType); } private void AddCacheCore<TLocator>(IEntityCache<TLocator> cache) where TLocator : ILocator { this.entityCaches.Add(new RegisteredEntityCache( typeof(TLocator), locator => cache.Invalidate((TLocator)locator))); } private void AddNavigatorCore<TLocator, TEntity>( IEntityNavigator<TLocator, TEntity> navigator) where TLocator : ILocator where TEntity : IEntity<ILocator> { this.entityNavigators.Add(new RegisteredEntityNavigator( typeof(TLocator), typeof(TEntity), async (locator, cancellationToken) => { Debug.Assert(locator is TLocator); var result = await navigator .ListDescendantsAsync((TLocator)locator, cancellationToken) .ConfigureAwait(false); return result.Cast<IEntity<ILocator>>().ToList(); })); } private void AddSearcherCore<TQuery, TEntity>( IEntitySearcher<TQuery, TEntity> searcher) where TEntity : IEntity<ILocator> { this.entitySearchers.Add(new RegisteredEntitySearcher( typeof(TEntity), typeof(TQuery), async (query, cancellationToken) => { Debug.Assert(query is TQuery); var result = await searcher .SearchAsync((TQuery)query, cancellationToken) .ConfigureAwait(false); return result.Cast<IEntity<ILocator>>().ToList(); })); } private void AddAspectProviderCore<TLocator, TAspect>( IEntityAspectProvider<TLocator, TAspect> provider) where TLocator : ILocator where TAspect : class { this.aspectProviders.Add(new RegisteredAspectProvider( typeof(TLocator), typeof(TAspect), locator => { Debug.Assert(locator is TLocator); return provider.QueryAspect((TLocator)locator); })); } private void AddAsyncAspectProviderCore<TLocator, TAspect>( IAsyncEntityAspectProvider<TLocator, TAspect> provider) where TLocator : ILocator where TAspect : class { this.asyncAspectProviders.Add(new RegisteredAsyncAspectProvider( typeof(TLocator), typeof(TAspect), async (locator, cancellationToken) => { Debug.Assert(locator is TLocator); var result = await provider .QueryAspectAsync((TLocator)locator, cancellationToken) .ConfigureAwait(false); Debug.Assert(result != null); return result!; })); } internal Builder AddCache(IEntityCache cache) { foreach (var genericInterface in GetGenericInterfaces( cache.GetType(), typeof(IEntityCache<>))) { this.addCacheCoreMethod .MakeGenericMethod(genericInterface.GenericTypeArguments) .Invoke(this, new object[] { cache }); } return this; } public Builder AddNavigator(IEntityNavigator navigator) { foreach (var genericInterface in GetGenericInterfaces( navigator.GetType(), typeof(IEntityNavigator<,>))) { this.addNavigatorCoreMethod .MakeGenericMethod(genericInterface.GenericTypeArguments) .Invoke(this, new object[] { navigator }); } if (navigator is IEntityCache cache) { AddCache(cache); } return this; } public Builder AddSearcher(IEntitySearcher searcher) { foreach (var genericInterface in GetGenericInterfaces( searcher.GetType(), typeof(IEntitySearcher<,>))) { this.addSearcherCoreMethod .MakeGenericMethod(genericInterface.GenericTypeArguments) .Invoke(this, new object[] { searcher }); } if (searcher is IEntityCache cache) { AddCache(cache); } return this; } public Builder AddAspectProvider(IEntityAspectProvider provider) { // // Synchrounous. // foreach (var genericInterface in GetGenericInterfaces( provider.GetType(), typeof(IEntityAspectProvider<,>))) { this.addAspectProviderCoreMethod .MakeGenericMethod(genericInterface.GenericTypeArguments) .Invoke(this, new object[] { provider }); } // // Asynchrounous. // foreach (var genericInterface in GetGenericInterfaces( provider.GetType(), typeof(IAsyncEntityAspectProvider<,>))) { this.addAsyncAspectProviderCoreMethod .MakeGenericMethod(genericInterface.GenericTypeArguments) .Invoke(this, new object[] { provider }); } if (provider is IEntityCache cache) { AddCache(cache); } return this; } public Builder AddNavigators(IEnumerable<IEntityNavigator> navigator) { foreach (var container in navigator) { AddNavigator(container); } return this; } public Builder AddSearchers(IEnumerable<IEntitySearcher> searchers) { foreach (var searcher in searchers) { AddSearcher(searcher); } return this; } public Builder AddAspectProviders(IEnumerable<IEntityAspectProvider> providers) { foreach (var provider in providers) { AddAspectProvider(provider); } return this; } internal IDictionary<Type, LocatorConfiguration> BuildLocatorConfiguration() { return Enumerable.Empty<Type>() .Concat(this.entityNavigators.Select(c => c.LocatorType)) .Concat(this.aspectProviders.Select(c => c.LocatorType)) .Concat(this.asyncAspectProviders.Select(c => c.LocatorType)) .Distinct() .ToDictionary( type => type, type => new LocatorConfiguration( type, this.entityNavigators.Where(c => c.LocatorType == type).ToList(), this.aspectProviders.Where(c => c.LocatorType == type).ToList(), this.asyncAspectProviders.Where(c => c.LocatorType == type).ToList())); } internal List<RegisteredEntityCache> BuildCaches() { return this.entityCaches; } internal IDictionary<Type, List<RegisteredEntitySearcher>> BuildSearchers() { return this.entitySearchers .GroupBy(s => s.QueryType) .ToDictionary(g => g.Key, g => g.ToList()); } public EntityContext Build() { return new EntityContext(this); } } } }