// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using GraphQL; using GraphQL.DataLoader; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents.Queries; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Json.Objects; using Squidex.Log; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public sealed class GraphQLExecutionContext : QueryExecutionContext { private static readonly List EmptyAssets = new List(); private static readonly List EmptyContents = new List(); private readonly IDataLoaderContextAccessor dataLoaderContextAccessor; private readonly DataLoaderDocumentListener dataLoaderDocumentListener; private readonly IUrlGenerator urlGenerator; private readonly ISemanticLog log; private readonly ICommandBus commandBus; private Context context; public IUrlGenerator UrlGenerator { get { return urlGenerator; } } public ICommandBus CommandBus { get { return commandBus; } } public ISemanticLog Log { get { return log; } } public override Context Context { get { return context; } } public GraphQLExecutionContext(IAssetQueryService assetQuery, IContentQueryService contentQuery, IDataLoaderContextAccessor dataLoaderContextAccessor, DataLoaderDocumentListener dataLoaderDocumentListener, ICommandBus commandBus, IUrlGenerator urlGenerator, ISemanticLog log) : base(assetQuery, contentQuery) { this.commandBus = commandBus; this.dataLoaderContextAccessor = dataLoaderContextAccessor; this.dataLoaderDocumentListener = dataLoaderDocumentListener; this.urlGenerator = urlGenerator; this.log = log; } public GraphQLExecutionContext WithContext(Context newContext) { context = newContext.WithoutCleanup().WithoutContentEnrichment(); return this; } public void Setup(ExecutionOptions execution) { execution.Listeners.Add(dataLoaderDocumentListener); execution.UserContext = this; } public override async Task FindAssetAsync(DomainId id) { var dataLoader = GetAssetsLoader(); return await dataLoader.LoadAsync(id).GetResultAsync(); } public async Task FindContentAsync(DomainId id) { var dataLoader = GetContentsLoader(); return await dataLoader.LoadAsync(id).GetResultAsync(); } public Task> GetReferencedAssetsAsync(IJsonValue value) { var ids = ParseIds(value); if (ids == null) { return Task.FromResult>(EmptyAssets); } var dataLoader = GetAssetsLoader(); return LoadManyAsync(dataLoader, ids); } public Task> GetReferencedContentsAsync(IJsonValue value) { var ids = ParseIds(value); if (ids == null) { return Task.FromResult>(EmptyContents); } var dataLoader = GetContentsLoader(); return LoadManyAsync(dataLoader, ids); } private IDataLoader GetAssetsLoader() { return dataLoaderContextAccessor.Context.GetOrAddBatchLoader(nameof(GetAssetsLoader), async batch => { var result = await GetReferencedAssetsAsync(new List(batch)); return result.ToDictionary(x => x.Id); }); } private IDataLoader GetContentsLoader() { return dataLoaderContextAccessor.Context.GetOrAddBatchLoader(nameof(GetContentsLoader), async batch => { var result = await GetReferencedContentsAsync(new List(batch)); return result.ToDictionary(x => x.Id); }); } private static async Task> LoadManyAsync(IDataLoader dataLoader, ICollection keys) where T : class { var contents = await Task.WhenAll(keys.Select(x => dataLoader.LoadAsync(x).GetResultAsync())); return contents.NotNull().ToList(); } private static ICollection? ParseIds(IJsonValue value) { try { var result = new List(); if (value is JsonArray array) { foreach (var id in array) { result.Add(DomainId.Create(id.ToString())); } } return result; } catch { return null; } } } }