From 79cbd18e43a334149df743a1dd738c49f8b64d72 Mon Sep 17 00:00:00 2001 From: Andrey Surkov Date: Thu, 11 Jul 2024 12:41:50 +0400 Subject: [PATCH] Isolate access to IElasticClient in ElasticIndexManager Fixes #16421 --- .../Controllers/AdminController.cs | 150 ++++++++--------- .../Services/ElasticSearchService.cs | 92 +++++----- .../IElasticQueryService.cs | 21 --- .../IElasticsearchQueryService.cs | 15 -- .../Handlers/ElasticIndexingContentHandler.cs | 2 +- .../ElasticContentPickerResultProvider.cs | 78 +++------ .../Recipes/ElasticIndexRebuildStep.cs | 2 +- .../Recipes/ElasticIndexResetStep.cs | 2 +- .../Recipes/ElasticIndexStep.cs | 4 +- .../ServiceCollectionExtensions.cs | 5 +- .../ElasticIndexInitializerService.cs | 12 +- .../Services/ElasticIndexManager.cs | 157 +++++++----------- .../Services/ElasticIndexingService.cs | 29 ++-- .../Services/ElasticQueryService.cs | 92 ---------- .../Services/ElasticQuerySource.cs | 14 +- .../Services/ElasticSearchQueryService.cs | 30 ---- .../Services/IElasticIndexManager.cs | 34 ++++ 17 files changed, 264 insertions(+), 475 deletions(-) delete mode 100644 src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticQueryService.cs delete mode 100644 src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticsearchQueryService.cs delete mode 100644 src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticQueryService.cs delete mode 100644 src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticSearchQueryService.cs create mode 100644 src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/IElasticIndexManager.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Controllers/AdminController.cs index 28f1b3ff25c..e5b971baca4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Controllers/AdminController.cs @@ -45,8 +45,7 @@ public class AdminController : Controller private readonly ILiquidTemplateManager _liquidTemplateManager; private readonly IContentDefinitionManager _contentDefinitionManager; private readonly IAuthorizationService _authorizationService; - private readonly IElasticQueryService _queryService; - private readonly ElasticIndexManager _elasticIndexManager; + private readonly IElasticIndexManager _elasticIndexManager; private readonly ElasticIndexingService _elasticIndexingService; private readonly ElasticIndexSettingsService _elasticIndexSettingsService; private readonly JavaScriptEncoder _javaScriptEncoder; @@ -67,8 +66,7 @@ public AdminController( ILiquidTemplateManager liquidTemplateManager, IContentDefinitionManager contentDefinitionManager, IAuthorizationService authorizationService, - IElasticQueryService queryService, - ElasticIndexManager elasticIndexManager, + IElasticIndexManager elasticIndexManager, ElasticIndexingService elasticIndexingService, ElasticIndexSettingsService elasticIndexSettingsService, JavaScriptEncoder javaScriptEncoder, @@ -87,7 +85,6 @@ public AdminController( _liquidTemplateManager = liquidTemplateManager; _contentDefinitionManager = contentDefinitionManager; _authorizationService = authorizationService; - _queryService = queryService; _elasticIndexManager = elasticIndexManager; _elasticIndexingService = elasticIndexingService; _elasticIndexSettingsService = elasticIndexSettingsService; @@ -247,63 +244,35 @@ public async Task EditPost(ElasticIndexSettingsViewModel model, st return View(model); } + var settings = new ElasticIndexSettings + { + IndexName = model.IndexName, + AnalyzerName = model.AnalyzerName, + QueryAnalyzerName = model.IsCreate ? model.AnalyzerName : null, + IndexLatest = model.IndexLatest, + IndexedContentTypes = indexedContentTypes, + Culture = model.Culture ?? string.Empty, + StoreSourceData = model.StoreSourceData + }; + if (model.IsCreate) { - try + // We call Rebuild in order to reset the index state cursor too in case the same index + // name was also used previously. + if (await _elasticIndexingService.CreateIndexAsync(settings)) { - var settings = new ElasticIndexSettings - { - IndexName = model.IndexName, - AnalyzerName = model.AnalyzerName, - QueryAnalyzerName = model.AnalyzerName, - IndexLatest = model.IndexLatest, - IndexedContentTypes = indexedContentTypes, - Culture = model.Culture ?? string.Empty, - StoreSourceData = model.StoreSourceData - }; - - // We call Rebuild in order to reset the index state cursor too in case the same index - // name was also used previously. - await _elasticIndexingService.CreateIndexAsync(settings); + await _notifier.SuccessAsync(H["Index {0} created successfully.", model.IndexName]); } - catch (Exception e) + else { await _notifier.ErrorAsync(H["An error occurred while creating the index."]); - _logger.LogError(e, "An error occurred while creating index: {indexName}.", _elasticIndexManager.GetFullIndexName(model.IndexName)); - await PopulateMenuOptionsAsync(model); - return View(model); } - - await _notifier.SuccessAsync(H["Index {0} created successfully.", model.IndexName]); } else { - try - { - var settings = new ElasticIndexSettings - { - IndexName = model.IndexName, - AnalyzerName = model.AnalyzerName, - IndexLatest = model.IndexLatest, - IndexedContentTypes = indexedContentTypes, - Culture = model.Culture ?? string.Empty, - StoreSourceData = model.StoreSourceData - }; - - await _elasticIndexingService.UpdateIndexAsync(settings); - } - catch (Exception e) - { - await _notifier.ErrorAsync(H["An error occurred while editing the index."]); - _logger.LogError(e, "An error occurred while editing index: {indexName}.", _elasticIndexManager.GetFullIndexName(model.IndexName)); - - await PopulateMenuOptionsAsync(model); - - return View(model); - } - + await _elasticIndexingService.UpdateIndexAsync(settings); await _notifier.SuccessAsync(H["Index {0} modified successfully, please consider rebuilding the index.", model.IndexName]); } @@ -328,11 +297,15 @@ public async Task Reset(string id) return NotFound(); } - await _elasticIndexingService.ResetIndexAsync(id); - await ProcessContentItemsAsync(id); - - await _notifier.SuccessAsync(H["Index {0} reset successfully.", id]); - + if (await _elasticIndexingService.ResetIndexAsync(id)) + { + await ProcessContentItemsAsync(id); + await _notifier.SuccessAsync(H["Index {0} reset successfully.", id]); + } + else + { + await _notifier.ErrorAsync(H["An error occurred while resetting the index.", id]); + } return RedirectToAction("Index"); } @@ -356,20 +329,27 @@ public async Task Rebuild(string id) var settings = await _elasticIndexSettingsService.GetSettingsAsync(id); - await _elasticIndexingService.RebuildIndexAsync(settings); + var result = await _elasticIndexingService.RebuildIndexAsync(settings); - if (settings.QueryAnalyzerName != settings.AnalyzerName) + if (result) { - // Query Analyzer may be different until the index in rebuilt. - // Since the index is rebuilt, lets make sure we query using the same analyzer. - settings.QueryAnalyzerName = settings.AnalyzerName; + if (settings.QueryAnalyzerName != settings.AnalyzerName) + { + // Query Analyzer may be different until the index in rebuilt. + // Since the index is rebuilt, lets make sure we query using the same analyzer. + settings.QueryAnalyzerName = settings.AnalyzerName; - await _elasticIndexSettingsService.UpdateIndexAsync(settings); - } + await _elasticIndexSettingsService.UpdateIndexAsync(settings); + } - await ProcessContentItemsAsync(id); + await ProcessContentItemsAsync(id); - await _notifier.SuccessAsync(H["Index {0} rebuilt successfully.", id]); + await _notifier.SuccessAsync(H["Index {0} rebuilt successfully.", id]); + } + else + { + await _notifier.ErrorAsync(H["An error occurred while rebuilding the index."]); + } return RedirectToAction(nameof(Index)); } @@ -389,20 +369,19 @@ public async Task Delete(ElasticIndexSettingsViewModel model) if (!await _elasticIndexManager.ExistsAsync(model.IndexName)) { - await _notifier.SuccessAsync(H["Index not found on Elasticsearch server.", model.IndexName]); + await _notifier.ErrorAsync(H["Index not found on Elasticsearch server.", model.IndexName]); return RedirectToAction("Index"); } - try - { - await _elasticIndexingService.DeleteIndexAsync(model.IndexName); + var result = await _elasticIndexingService.DeleteIndexAsync(model.IndexName); + if (result) + { await _notifier.SuccessAsync(H["Index {0} deleted successfully.", model.IndexName]); } - catch (Exception e) + else { await _notifier.ErrorAsync(H["An error occurred while deleting the index."]); - _logger.LogError(e, "An error occurred while deleting the index {indexName}", _elasticIndexManager.GetFullIndexName(model.IndexName)); } return RedirectToAction("Index"); @@ -421,16 +400,15 @@ public async Task ForceDelete(ElasticIndexSettingsViewModel model) return BadRequest(); } - try - { - await _elasticIndexingService.DeleteIndexAsync(model.IndexName); + var result = await _elasticIndexingService.DeleteIndexAsync(model.IndexName); + if (result) + { await _notifier.SuccessAsync(H["Index {0} deleted successfully.", model.IndexName]); } - catch (Exception e) + else { await _notifier.ErrorAsync(H["An error occurred while deleting the index."]); - _logger.LogError(e, "An error occurred while deleting the index {indexName}", _elasticIndexManager.GetFullIndexName(model.IndexName)); } return RedirectToAction(nameof(Index)); @@ -442,7 +420,7 @@ public async Task Mappings(string indexName) var formattedJson = JNode.Parse(mappings).ToJsonString(JOptions.Indented); return View(new MappingsViewModel { - IndexName = _elasticIndexManager.GetFullIndexName(indexName), + IndexName = indexName, Mappings = formattedJson }); } @@ -522,7 +500,8 @@ public async Task Query(AdminQueryViewModel model) try { - var elasticTopDocs = await _queryService.SearchAsync(model.IndexName, tokenizedContent); + var searchDescriptor = await _elasticIndexManager.DeserializeSearchDescriptor(tokenizedContent); + var elasticTopDocs = await _elasticIndexManager.SearchAsync(model.IndexName, _ => searchDescriptor); if (elasticTopDocs != null) { @@ -580,10 +559,12 @@ public async Task IndexPost(ContentOptions options, IEnumerable{0} reset successfully.", item.IndexName]); + await _notifier.SuccessAsync(H["Index {0} reset successfully.", item.IndexName]); + } } break; case ContentsBulkAction.Rebuild: @@ -594,10 +575,11 @@ public async Task IndexPost(ContentOptions options, IEnumerable{0} rebuilt successfully.", item.IndexName]); + if (await _elasticIndexingService.RebuildIndexAsync(await _elasticIndexSettingsService.GetSettingsAsync(item.IndexName))) + { + await ProcessContentItemsAsync(item.IndexName); + await _notifier.SuccessAsync(H["Index {0} rebuilt successfully.", item.IndexName]); + } } break; default: diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs index 9f675bf3c26..86bfeaedc26 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Services/ElasticSearchService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Text; +using System.Linq; using System.Text.Encodings.Web; using System.Threading.Tasks; using Fluid.Values; @@ -16,15 +15,15 @@ namespace OrchardCore.Search.Elasticsearch.Services; +using ElasticSearchDescriptor = SearchDescriptor>; + public class ElasticsearchService : ISearchService { public const string Key = "Elasticsearch"; private readonly ISiteService _siteService; - private readonly ElasticIndexManager _elasticIndexManager; + private readonly IElasticIndexManager _elasticIndexManager; private readonly ElasticIndexSettingsService _elasticIndexSettingsService; - private readonly IElasticSearchQueryService _elasticsearchQueryService; - private readonly IElasticClient _elasticClient; private readonly JavaScriptEncoder _javaScriptEncoder; private readonly ElasticConnectionOptions _elasticConnectionOptions; private readonly ILiquidTemplateManager _liquidTemplateManager; @@ -32,10 +31,8 @@ public class ElasticsearchService : ISearchService public ElasticsearchService( ISiteService siteService, - ElasticIndexManager elasticIndexManager, + IElasticIndexManager elasticIndexManager, ElasticIndexSettingsService elasticIndexSettingsService, - IElasticSearchQueryService elasticsearchQueryService, - IElasticClient elasticClient, JavaScriptEncoder javaScriptEncoder, IOptions elasticConnectionOptions, ILiquidTemplateManager liquidTemplateManager, @@ -45,8 +42,6 @@ ILogger logger _siteService = siteService; _elasticIndexManager = elasticIndexManager; _elasticIndexSettingsService = elasticIndexSettingsService; - _elasticsearchQueryService = elasticsearchQueryService; - _elasticClient = elasticClient; _javaScriptEncoder = javaScriptEncoder; _elasticConnectionOptions = elasticConnectionOptions.Value; _liquidTemplateManager = liquidTemplateManager; @@ -87,54 +82,55 @@ public async Task SearchAsync(string indexName, string term, int s return result; } - try + var searchType = searchSettings.GetSearchType(); + Func formatSearch = null; + if (searchType == ElasticSettings.CustomSearchType && !string.IsNullOrWhiteSpace(searchSettings.DefaultQuery)) { - var searchType = searchSettings.GetSearchType(); - QueryContainer query = null; - - if (searchType == ElasticSettings.CustomSearchType && !string.IsNullOrWhiteSpace(searchSettings.DefaultQuery)) - { - var tokenizedContent = await _liquidTemplateManager.RenderStringAsync(searchSettings.DefaultQuery, _javaScriptEncoder, - new Dictionary() - { - ["term"] = new StringValue(term) - }); - - try + var tokenizedContent = await _liquidTemplateManager.RenderStringAsync(searchSettings.DefaultQuery, _javaScriptEncoder, + new Dictionary() { - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(tokenizedContent)); + ["term"] = new StringValue(term) + }); - var searchRequest = await _elasticClient.RequestResponseSerializer.DeserializeAsync(stream); - - query = searchRequest.Query; - } - catch { } - } - else if (searchType == ElasticSettings.QueryStringSearchType) + try { - query = new QueryStringQuery - { - Fields = searchSettings.DefaultSearchFields, - Analyzer = await _elasticIndexSettingsService.GetQueryAnalyzerAsync(index), - Query = term - }; + var searchDescriptor = await _elasticIndexManager.DeserializeSearchDescriptor(tokenizedContent); + formatSearch = _ => searchDescriptor; } - - query ??= new MultiMatchQuery + catch (Exception e) { - Fields = searchSettings.DefaultSearchFields, - Analyzer = await _elasticIndexSettingsService.GetQueryAnalyzerAsync(index), - Query = term - }; - - result.ContentItemIds = await _elasticsearchQueryService.ExecuteQueryAsync(index, query, null, start, pageSize); - result.Success = true; + _logger.LogError(e, "Incorrect Elasticsearch search query syntax provided in search."); + } } - catch (Exception e) + else if (searchType == ElasticSettings.QueryStringSearchType) { - _logger.LogError(e, "Incorrect Elasticsearch search query syntax provided in search."); + var analyzer = await _elasticIndexSettingsService.GetQueryAnalyzerAsync(index); + formatSearch = descriptor => + descriptor.Query(q => q.QueryString(qs => qs + .Fields(searchSettings.DefaultSearchFields) + .Analyzer(analyzer) + .Query(term) + )); } + if (formatSearch == null) + { + var analyzer = await _elasticIndexSettingsService.GetQueryAnalyzerAsync(index); + formatSearch = descriptor => + descriptor.Query(q => q.MultiMatch(qs => qs + .Fields(searchSettings.DefaultSearchFields) + .Analyzer(analyzer) + .Query(term) + )); + } + + var elasticTopDocs = await _elasticIndexManager.SearchAsync(index, descriptor => + formatSearch(descriptor) + .Size(pageSize) + .From(start)); + + result.ContentItemIds = elasticTopDocs.TopDocs.Select(item => item.GetValueOrDefault("ContentItemId").ToString()).ToList(); + result.Success = true; return result; } } diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticQueryService.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticQueryService.cs deleted file mode 100644 index 6b6a8b7e8fb..00000000000 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticQueryService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using OrchardCore.Queries; - -namespace OrchardCore.Search.Elasticsearch -{ - public interface IElasticQueryService - { - /// - /// Provides a way to execute an OC in Elasticsearch. - /// OC implementation deserializes the JSON string to an Elasticsearch SearchRequest. - /// Also provides a way to return only specific fields: - /// The fields option. - /// - /// Also provides a way to return only specific _source fields: - /// The _source option. - /// - /// - /// . - Task SearchAsync(string indexName, string query); - } -} diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticsearchQueryService.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticsearchQueryService.cs deleted file mode 100644 index 5f4dcf3c936..00000000000 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticsearchQueryService.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Nest; - -namespace OrchardCore.Search.Elasticsearch -{ - public interface IElasticSearchQueryService - { - /// - /// Provides a way to execute a search request in Elasticsearch based on a . - /// - /// IList<string>. - Task> ExecuteQueryAsync(string indexName, QueryContainer query, List sort, int start = 0, int end = 20); - } -} diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Handlers/ElasticIndexingContentHandler.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Handlers/ElasticIndexingContentHandler.cs index 73988833b3f..9676911636d 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Handlers/ElasticIndexingContentHandler.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Handlers/ElasticIndexingContentHandler.cs @@ -69,7 +69,7 @@ private static async Task IndexingAsync(ShellScope scope, IEnumerable(); var contentItemIndexHandlers = services.GetServices(); - var elasticIndexManager = services.GetRequiredService(); + var elasticIndexManager = services.GetRequiredService(); var elasticIndexSettingsService = services.GetRequiredService(); var logger = services.GetRequiredService>(); diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Providers/ElasticContentPickerResultProvider.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Providers/ElasticContentPickerResultProvider.cs index 42310d7ec50..a69736d8b7d 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Providers/ElasticContentPickerResultProvider.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Providers/ElasticContentPickerResultProvider.cs @@ -3,9 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Options; -using Nest; using OrchardCore.ContentManagement; -using OrchardCore.Environment.Shell; using OrchardCore.Search.Elasticsearch.Core.Models; using OrchardCore.Search.Elasticsearch.Core.Services; @@ -13,16 +11,13 @@ namespace OrchardCore.Search.Elasticsearch.Core.Providers { public class ElasticContentPickerResultProvider : IContentPickerResultProvider { - private readonly ElasticIndexManager _elasticIndexManager; - private readonly string _indexPrefix; + private readonly IElasticIndexManager _elasticIndexManager; private readonly ElasticConnectionOptions _elasticConnectionOptions; public ElasticContentPickerResultProvider( - ShellSettings shellSettings, IOptions elasticConnectionOptions, - ElasticIndexManager elasticIndexManager) + IElasticIndexManager elasticIndexManager) { - _indexPrefix = shellSettings.Name.ToLowerInvariant() + "_"; _elasticConnectionOptions = elasticConnectionOptions.Value; _elasticIndexManager = elasticIndexManager; } @@ -52,68 +47,37 @@ public async Task> Search(ContentPickerSearchCo var results = new List(); - await _elasticIndexManager.SearchAsync(indexName, async elasticClient => - { - ISearchResponse> searchResponse = null; - var elasticTopDocs = new ElasticTopDocs(); - - if (string.IsNullOrWhiteSpace(searchContext.Query)) - { - searchResponse = await elasticClient.SearchAsync>(s => s - .Index(_indexPrefix + indexName) - .Query(q => q - .Bool(b => b - .Filter(f => f + var elasticTopDocs = await _elasticIndexManager.SearchAsync(indexName, descriptor => + descriptor.Query(q => q + .Bool(b => + { + b = b.Filter(f => f .Terms(t => t .Field("Content.ContentItem.ContentType") .Terms(searchContext.ContentTypes.ToArray()) ) - ) - ) - ) - ); - } - else - { - searchResponse = await elasticClient.SearchAsync>(s => s - .Index(_indexPrefix + indexName) - .Query(q => q - .Bool(b => b - .Filter(f => f - .Terms(t => t - .Field("Content.ContentItem.ContentType") - .Terms(searchContext.ContentTypes.ToArray()) - ) - ) - .Should(s => s + ); + return string.IsNullOrWhiteSpace(searchContext.Query) ? b : b.Should(s => s .Wildcard(w => w .Field("Content.ContentItem.DisplayText.Normalized") .Wildcard(searchContext.Query.ToLowerInvariant() + "*") ) - ) - ) - ) - ); - } + ); + }) + )); - if (searchResponse.IsValid) - { - elasticTopDocs.TopDocs = searchResponse.Documents.ToList(); - } - - if (elasticTopDocs.TopDocs != null) + if (elasticTopDocs.TopDocs != null) + { + foreach (var doc in elasticTopDocs.TopDocs) { - foreach (var doc in elasticTopDocs.TopDocs) + results.Add(new ContentPickerResult { - results.Add(new ContentPickerResult - { - ContentItemId = doc["ContentItemId"].ToString(), - DisplayText = doc["Content.ContentItem.DisplayText.keyword"].ToString(), - HasPublished = doc["Content.ContentItem.Published"].ToString().ToLowerInvariant().Equals("true", StringComparison.Ordinal) - }); - } + ContentItemId = doc["ContentItemId"].ToString(), + DisplayText = doc["Content.ContentItem.DisplayText.keyword"].ToString(), + HasPublished = doc["Content.ContentItem.Published"].ToString().ToLowerInvariant().Equals("true", StringComparison.Ordinal) + }); } - }); + } return results.OrderBy(x => x.DisplayText); } diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexRebuildStep.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexRebuildStep.cs index 2dc3571eee9..441e42101ec 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexRebuildStep.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexRebuildStep.cs @@ -31,7 +31,7 @@ await HttpBackgroundJob.ExecuteAfterEndOfRequestAsync("elastic-index-rebuild", a { var elasticIndexingService = scope.ServiceProvider.GetService(); var elasticIndexSettingsService = scope.ServiceProvider.GetService(); - var elasticIndexManager = scope.ServiceProvider.GetRequiredService(); + var elasticIndexManager = scope.ServiceProvider.GetRequiredService(); var indexNames = model.IncludeAll ? (await elasticIndexSettingsService.GetSettingsAsync()).Select(x => x.IndexName).ToArray() : model.Indices; diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexResetStep.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexResetStep.cs index 0501bdce407..60ad68884e3 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexResetStep.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexResetStep.cs @@ -31,7 +31,7 @@ await HttpBackgroundJob.ExecuteAfterEndOfRequestAsync("elastic-index-reset", asy { var elasticIndexingService = scope.ServiceProvider.GetService(); var elasticIndexSettingsService = scope.ServiceProvider.GetService(); - var elasticIndexManager = scope.ServiceProvider.GetRequiredService(); + var elasticIndexManager = scope.ServiceProvider.GetRequiredService(); var indexNames = model.IncludeAll ? (await elasticIndexSettingsService.GetSettingsAsync()).Select(x => x.IndexName).ToArray() : model.Indices; diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexStep.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexStep.cs index fecfc4c96ce..a32d779fd49 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexStep.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Recipes/ElasticIndexStep.cs @@ -16,11 +16,11 @@ namespace OrchardCore.Search.Elasticsearch.Core.Recipes public class ElasticIndexStep : IRecipeStepHandler { private readonly ElasticIndexingService _elasticIndexingService; - private readonly ElasticIndexManager _elasticIndexManager; + private readonly IElasticIndexManager _elasticIndexManager; public ElasticIndexStep( ElasticIndexingService elasticIndexingService, - ElasticIndexManager elasticIndexManager + IElasticIndexManager elasticIndexManager ) { _elasticIndexManager = elasticIndexManager; diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/ServiceCollectionExtensions.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/ServiceCollectionExtensions.cs index db457a9a0c7..10dbb11e31e 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/ServiceCollectionExtensions.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/ServiceCollectionExtensions.cs @@ -19,11 +19,10 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddElasticServices(this IServiceCollection services) { services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); // ElasticQuerySource is registered for both the Queries module and local usage. diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexInitializerService.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexInitializerService.cs index 6428cd27c48..57972e1de42 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexInitializerService.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexInitializerService.cs @@ -19,14 +19,14 @@ namespace OrchardCore.Search.Elasticsearch public class ElasticIndexInitializerService : ModularTenantEvents { private readonly ShellSettings _shellSettings; - private readonly ElasticIndexManager _elasticIndexManager; + private readonly IElasticIndexManager _elasticIndexManager; private readonly ElasticIndexSettingsService _elasticIndexSettingsService; protected readonly IStringLocalizer S; private readonly ILogger _logger; public ElasticIndexInitializerService( ShellSettings shellSettings, - ElasticIndexManager elasticIndexManager, + IElasticIndexManager elasticIndexManager, ElasticIndexSettingsService elasticIndexSettingsService, IStringLocalizer localizer, ILogger logger) @@ -49,7 +49,7 @@ await HttpBackgroundJob.ExecuteAfterEndOfRequestAsync("elastic-initialize", asyn { var elasticIndexSettingsService = scope.ServiceProvider.GetRequiredService(); var elasticIndexingService = scope.ServiceProvider.GetRequiredService(); - var indexManager = scope.ServiceProvider.GetRequiredService(); + var indexManager = scope.ServiceProvider.GetRequiredService(); var elasticIndexSettings = await elasticIndexSettingsService.GetSettingsAsync(); var createdIndexes = new List(); @@ -58,8 +58,10 @@ await HttpBackgroundJob.ExecuteAfterEndOfRequestAsync("elastic-initialize", asyn { if (!await indexManager.ExistsAsync(settings.IndexName)) { - await elasticIndexingService.CreateIndexAsync(settings); - createdIndexes.Add(settings.IndexName); + if (await elasticIndexingService.CreateIndexAsync(settings)) + { + createdIndexes.Add(settings.IndexName); + } } } diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexManager.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexManager.cs index 2e14b5d0dc9..407aec32ba8 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexManager.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexManager.cs @@ -1,9 +1,9 @@ - using System; -using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; @@ -16,14 +16,13 @@ using OrchardCore.Indexing; using OrchardCore.Modules; using OrchardCore.Search.Elasticsearch.Core.Mappings; -using OrchardCore.Search.Elasticsearch.Core.Models; namespace OrchardCore.Search.Elasticsearch.Core.Services { /// /// Provides methods to manage Elasticsearch indices. /// - public sealed class ElasticIndexManager + public sealed class ElasticIndexManager : IElasticIndexManager { private const string _separator = "_"; @@ -32,7 +31,6 @@ public sealed class ElasticIndexManager private readonly IClock _clock; private readonly ILogger _logger; private readonly ElasticsearchOptions _elasticsearchOptions; - private readonly ConcurrentDictionary _timestamps = new(StringComparer.OrdinalIgnoreCase); private readonly string _lastTaskId = "last_task_id"; private readonly Dictionary> _analyzerGetter = new(StringComparer.OrdinalIgnoreCase) { @@ -89,10 +87,10 @@ ILogger logger /// /// /// . - public async Task CreateIndexAsync(ElasticIndexSettings elasticIndexSettings) + public async Task CreateIndexAsync(string indexName, string analyzerName, bool storeSourceData) { // Get Index name scoped by ShellName - if (await ExistsAsync(elasticIndexSettings.IndexName)) + if (await ExistsAsync(indexName)) { return true; } @@ -102,7 +100,7 @@ public async Task CreateIndexAsync(ElasticIndexSettings elasticIndexSettin var indexSettingsDescriptor = new IndexSettingsDescriptor(); // The name "standardanalyzer" is a legacy used prior OC 1.6 release. It can be removed in future releases. - var analyzerName = (elasticIndexSettings.AnalyzerName == "standardanalyzer" ? null : elasticIndexSettings.AnalyzerName) ?? "standard"; + analyzerName = (analyzerName == "standardanalyzer" ? null : analyzerName) ?? "standard"; if (_elasticsearchOptions.Analyzers.TryGetValue(analyzerName, out var analyzerProperties)) { @@ -117,12 +115,12 @@ public async Task CreateIndexAsync(ElasticIndexSettings elasticIndexSettin var IndexingState = new FluentDictionary() { { _lastTaskId, 0 } }; - var fullIndexName = GetFullIndexName(elasticIndexSettings.IndexName); + var fullIndexName = GetFullIndexName(indexName); var createIndexDescriptor = new CreateIndexDescriptor(fullIndexName) .Settings(s => indexSettingsDescriptor) .Map(m => m .SourceField(s => s - .Enabled(elasticIndexSettings.StoreSourceData) + .Enabled(storeSourceData) .Excludes([IndexingConstants.DisplayTextAnalyzedKey])) .Meta(me => IndexingState)); @@ -197,7 +195,11 @@ await _elasticClient.MapAsync(p => p ) ); - return response.Acknowledged; + if (!response.IsValid) + { + _logger.LogError("Error while creating index {indexName}: {error}", fullIndexName, response.ServerError); + } + return response.IsValid; } private IAnalyzer CreateAnalyzer(JsonObject analyzerProperties) @@ -285,18 +287,24 @@ public async Task GetIndexMappings(string indexName) /// This allows storing the last indexing task id executed on the Elasticsearch index. /// . /// - public async Task SetLastTaskId(string indexName, long lastTaskId) + public async Task SetLastTaskId(string indexName, long lastTaskId) { var IndexingState = new FluentDictionary() { { _lastTaskId, lastTaskId } }; - var putMappingRequest = new PutMappingRequest(GetFullIndexName(indexName)) + var fullIndexName = GetFullIndexName(indexName); + var putMappingRequest = new PutMappingRequest(fullIndexName) { Meta = IndexingState }; - await _elasticClient.Indices.PutMappingAsync(putMappingRequest); + var result = await _elasticClient.Indices.PutMappingAsync(putMappingRequest); + if (!result.IsValid) + { + _logger.LogError("Error while setting last TaskId for index {indexName}: {error}", fullIndexName, result.ServerError); + } + return result.IsValid; } /// @@ -403,32 +411,18 @@ public static string ToSafeIndexName(string indexName) public async Task StoreDocumentsAsync(string indexName, IEnumerable indexDocuments) { - var documents = new List>(); + var result = await _elasticClient.BulkAsync(descriptor => + descriptor + .Index(GetFullIndexName(indexName)) + .IndexMany( + indexDocuments.Select(CreateElasticDocument), + (indexDescriptor, item) => indexDescriptor.Id(item.GetValueOrDefault("ContentItemId").ToString()) + ) + ); - foreach (var indexDocument in indexDocuments) + if (result.Errors) { - documents.Add(CreateElasticDocument(indexDocument)); - } - - if (documents.Count > 0) - { - var descriptor = new BulkDescriptor(); - - foreach (var document in documents) - { - descriptor.Index>(op => op - .Id(document.GetValueOrDefault("ContentItemId").ToString()) - .Document(document) - .Index(GetFullIndexName(indexName)) - ); - } - - var result = await _elasticClient.BulkAsync(d => descriptor); - - if (result.Errors) - { - _logger.LogWarning("There were issues reported indexing the documents. {result.ServerError}", result.ServerError); - } + _logger.LogWarning("There were issues reported indexing the documents. {result.ServerError}", result.ServerError); } } @@ -436,81 +430,54 @@ public async Task StoreDocumentsAsync(string indexName, IEnumerable /// - /// - /// - /// - /// + /// /// . - public async Task SearchAsync(string indexName, QueryContainer query, List sort, int from, int size) + public async Task SearchAsync(string indexName, Func>, SearchDescriptor>> selector) { var elasticTopDocs = new ElasticTopDocs(); - if (await ExistsAsync(indexName)) + var fullIndexName = GetFullIndexName(indexName); + try { - var fullIndexName = GetFullIndexName(indexName); - - var searchRequest = new SearchRequest(fullIndexName) - { - Query = query, - From = from, - Size = size, - Sort = sort - }; - - var searchResponse = await _elasticClient.SearchAsync>(searchRequest); + var searchResponse = await _elasticClient.SearchAsync>(descriptior => selector(descriptior) + .Index(fullIndexName) + ); if (searchResponse.IsValid) { - elasticTopDocs.Count = searchResponse.Hits.Count; - - var topDocs = new List>(); - - var documents = searchResponse.Documents.GetEnumerator(); - var hits = searchResponse.Hits.GetEnumerator(); - - while (documents.MoveNext() && hits.MoveNext()) - { - var document = documents.Current; - - if (document != null) - { - topDocs.Add(document); - - continue; - } - - var hit = hits.Current; - - var topDoc = new Dictionary + elasticTopDocs.Count = searchResponse.Total; + elasticTopDocs.TopDocs = searchResponse.Hits.Select(hit => + hit.Source ?? new Dictionary { { "ContentItemId", hit.Id } - }; - - topDocs.Add(topDoc); - } - - elasticTopDocs.TopDocs = topDocs; + } + ).ToList(); + elasticTopDocs.Fields =searchResponse.Hits + .Where(hit => hit.Fields != null) + .Select(hit => hit.Fields.ToDictionary(item => item.Key, item => (object)item.Value.As())) + .ToList(); } - - _timestamps[fullIndexName] = _clock.UtcNow; + else + { + _logger.LogError("Received failure response from Elasticsearch: {ServerError}", searchResponse.ServerError); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while querying elastic with exception: {Message}", ex.Message); } + elasticTopDocs.TopDocs ??= new List>(); return elasticTopDocs; } - /// - /// Returns results from a search made with NEST Fluent DSL query. - /// - public async Task SearchAsync(string indexName, Func elasticClient) + public async Task>> DeserializeSearchDescriptor(string request) { - if (await ExistsAsync(indexName)) - { - await elasticClient(_elasticClient); - - _timestamps[GetFullIndexName(indexName)] = _clock.UtcNow; - } + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(request)); + return await _elasticClient.RequestResponseSerializer.DeserializeAsync>>(stream); } + private static Dictionary CreateElasticDocument(DocumentIndex documentIndex) { var entries = new Dictionary @@ -575,7 +542,7 @@ private static Dictionary CreateElasticDocument(DocumentIndex do return entries; } - public string GetFullIndexName(string indexName) + private string GetFullIndexName(string indexName) { ArgumentException.ThrowIfNullOrEmpty(indexName, nameof(indexName)); diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexingService.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexingService.cs index 55991703176..9a2e5e860a9 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexingService.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticIndexingService.cs @@ -29,7 +29,7 @@ public class ElasticIndexingService private readonly IShellHost _shellHost; private readonly ShellSettings _shellSettings; private readonly ElasticIndexSettingsService _elasticIndexSettingsService; - private readonly ElasticIndexManager _indexManager; + private readonly IElasticIndexManager _indexManager; private readonly IIndexingTaskManager _indexingTaskManager; private readonly ElasticConnectionOptions _elasticConnectionOptions; private readonly ISiteService _siteService; @@ -43,7 +43,7 @@ public ElasticIndexingService( IShellHost shellHost, ShellSettings shellSettings, ElasticIndexSettingsService elasticIndexSettingsService, - ElasticIndexManager indexManager, + IElasticIndexManager indexManager, IIndexingTaskManager indexingTaskManager, IOptions elasticConnectionOptions, ISiteService siteService, @@ -231,12 +231,17 @@ public async Task ProcessContentItemsAsync(params string[] indexNames) /// /// Creates a new index. /// - public async Task CreateIndexAsync(ElasticIndexSettings elasticIndexSettings) + public async Task CreateIndexAsync(ElasticIndexSettings elasticIndexSettings) { - await _elasticIndexSettingsService.UpdateIndexAsync(elasticIndexSettings); - await RebuildIndexAsync(elasticIndexSettings); + var indexCreated = await RebuildIndexAsync(elasticIndexSettings); + if (indexCreated) + { + await _elasticIndexSettingsService.UpdateIndexAsync(elasticIndexSettings); + } + return indexCreated; } + /// /// Update an existing index. /// @@ -264,22 +269,22 @@ public async Task DeleteIndexAsync(string indexName) /// Restarts the indexing process from the beginning in order to update /// current content items. It doesn't delete existing entries from the index. /// - public async Task ResetIndexAsync(string indexName) + public Task ResetIndexAsync(string indexName) { - await _indexManager.SetLastTaskId(indexName, 0); + return _indexManager.SetLastTaskId(indexName, 0); } /// /// Deletes and recreates the full index content. /// - public async Task RebuildIndexAsync(ElasticIndexSettings elasticIndexSettings) + public async Task RebuildIndexAsync(ElasticIndexSettings elasticIndexSettings) { - await _indexManager.DeleteIndex(elasticIndexSettings.IndexName); - await _indexManager.CreateIndexAsync(elasticIndexSettings); - await ResetIndexAsync(elasticIndexSettings.IndexName); + return + await _indexManager.DeleteIndex(elasticIndexSettings.IndexName) && + await _indexManager.CreateIndexAsync(elasticIndexSettings.IndexName, elasticIndexSettings.AnalyzerName, elasticIndexSettings.StoreSourceData); } - public async Task GetElasticSettingsAsync() + public async Task GetElasticSettingsAsync() => await _siteService.GetSettingsAsync() ?? new ElasticSettings(); /// diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticQueryService.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticQueryService.cs deleted file mode 100644 index 73be897aab7..00000000000 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticQueryService.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Nest; -using OrchardCore.Environment.Shell; - -namespace OrchardCore.Search.Elasticsearch.Core.Services -{ - public class ElasticQueryService : IElasticQueryService - { - private readonly string _indexPrefix; - private readonly IElasticClient _elasticClient; - private readonly ILogger _logger; - - public ElasticQueryService( - IElasticClient elasticClient, - ShellSettings shellSettings, - ILogger logger - ) - { - _indexPrefix = shellSettings.Name.ToLowerInvariant() + "_"; - _elasticClient = elasticClient; - _logger = logger; - } - - public async Task SearchAsync(string indexName, string query) - { - var elasticTopDocs = new ElasticTopDocs(); - - if (_elasticClient == null) - { - _logger.LogWarning("Elasticsearch Client is not setup, please validate your Elasticsearch Configurations"); - - return elasticTopDocs; - } - - try - { - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(query)); - var deserializedSearchRequest = _elasticClient.RequestResponseSerializer.Deserialize(stream); - - var searchRequest = new SearchRequest(_indexPrefix + indexName) - { - Query = deserializedSearchRequest.Query, - From = deserializedSearchRequest.From, - Size = deserializedSearchRequest.Size, - Fields = deserializedSearchRequest.Fields, - Sort = deserializedSearchRequest.Sort, - Source = deserializedSearchRequest.Source, - }; - - var searchResponse = await _elasticClient.SearchAsync>(searchRequest); - var hits = new List>(); - - foreach (var hit in searchResponse.Hits) - { - if (hit.Fields != null) - { - var row = new Dictionary(); - - foreach (var keyValuePair in hit.Fields) - { - row[keyValuePair.Key] = keyValuePair.Value.As(); - } - - hits.Add(row); - } - } - - if (searchResponse.IsValid) - { - elasticTopDocs.Count = searchResponse.Total; - elasticTopDocs.TopDocs = new List>(searchResponse.Documents); - elasticTopDocs.Fields = hits; - } - else - { - _logger.LogError("Received failure response from Elasticsearch: {ServerError}", searchResponse.ServerError); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error while querying elastic with exception: {Message}", ex.Message); - } - - return elasticTopDocs; - } - } -} diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticQuerySource.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticQuerySource.cs index 72ef9057373..23cb32b14ee 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticQuerySource.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticQuerySource.cs @@ -18,20 +18,20 @@ namespace OrchardCore.Search.Elasticsearch.Core.Services { public class ElasticQuerySource : IQuerySource { - private readonly IElasticQueryService _queryService; + private readonly IElasticIndexManager _elasticQueryManager; private readonly ILiquidTemplateManager _liquidTemplateManager; private readonly ISession _session; private readonly JavaScriptEncoder _javaScriptEncoder; private readonly TemplateOptions _templateOptions; public ElasticQuerySource( - IElasticQueryService queryService, + IElasticIndexManager elasticQueryManager, ILiquidTemplateManager liquidTemplateManager, ISession session, JavaScriptEncoder javaScriptEncoder, IOptions templateOptions) { - _queryService = queryService; + _elasticQueryManager = elasticQueryManager; _liquidTemplateManager = liquidTemplateManager; _session = session; _javaScriptEncoder = javaScriptEncoder; @@ -40,10 +40,7 @@ public ElasticQuerySource( public string Name => "Elasticsearch"; - public Query Create() - { - return new ElasticQuery(); - } + public Query Create() => new ElasticQuery(); public async Task ExecuteQueryAsync(Query query, IDictionary parameters) { @@ -51,7 +48,8 @@ public async Task ExecuteQueryAsync(Query query, IDictionary new KeyValuePair(x.Key, FluidValue.Create(x.Value, _templateOptions)))); - var docs = await _queryService.SearchAsync(elasticQuery.Index, tokenizedContent); + var searchDescriptor = await _elasticQueryManager.DeserializeSearchDescriptor(tokenizedContent); + var docs = await _elasticQueryManager.SearchAsync(elasticQuery.Index, _ => searchDescriptor); elasticQueryResults.Count = docs.Count; if (elasticQuery.ReturnContentItems) diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticSearchQueryService.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticSearchQueryService.cs deleted file mode 100644 index b19d54f5769..00000000000 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/ElasticSearchQueryService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Nest; - -namespace OrchardCore.Search.Elasticsearch.Core.Services -{ - public class ElasticSearchQueryService : IElasticSearchQueryService - { - private readonly ElasticIndexManager _elasticIndexManager; - - public ElasticSearchQueryService(ElasticIndexManager elasticIndexManager) - { - _elasticIndexManager = elasticIndexManager; - } - - public async Task> ExecuteQueryAsync(string indexName, QueryContainer query, List sort, int from, int size) - { - var contentItemIds = new List(); - - var results = await _elasticIndexManager.SearchAsync(indexName, query, sort, from, size); - - foreach (var item in results.TopDocs) - { - contentItemIds.Add(item.GetValueOrDefault("ContentItemId").ToString()); - } - - return contentItemIds; - } - } -} diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/IElasticIndexManager.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/IElasticIndexManager.cs new file mode 100644 index 00000000000..8647737d00a --- /dev/null +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Services/IElasticIndexManager.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nest; +using OrchardCore.Indexing; + +namespace OrchardCore.Search.Elasticsearch.Core.Services; + +using ElasticSearchDescriptor = SearchDescriptor>; + +public interface IElasticIndexManager +{ + Task CreateIndexAsync(string indexName, string analyzerName, bool storeSourceData); + + Task DeleteIndex(string indexName); + + Task ExistsAsync(string indexName); + + Task GetIndexMappings(string indexName); + + Task SearchAsync(string indexName, Func selector); + + Task DeserializeSearchDescriptor(string request); + + Task GetLastTaskId(string indexName); + + Task SetLastTaskId(string indexName, long lastTaskId); + + Task StoreDocumentsAsync(string indexName, IEnumerable indexDocuments); + + Task DeleteAllDocumentsAsync(string indexName); + + Task DeleteDocumentsAsync(string indexName, IEnumerable contentItemIds); +}