diff --git a/docs/Contributing-Setup.md b/docs/Contributing-Setup.md index ffb6ef1..05f883b 100644 --- a/docs/Contributing-Setup.md +++ b/docs/Contributing-Setup.md @@ -57,6 +57,17 @@ To run the example project Admin customization in development mode, add the foll } ``` +The Xperience web application requests client modules from a webpack dev server that runs parallel to the Xperience application. + +Changes to client code are immediately integrated and don’t require a restart or rebuild of the web application. + +Before you start developing, the webpack server needs to be manually started by running + +```bash +npm run start +``` +from the root of the module folder, in our case in the `/src/Kentico.Xperience.Lucene.Admin/Client` folder. + ## Development Workflow ### Prepare your Git branch and commits diff --git a/src/Kentico.Xperience.Lucene.Admin/Components/LuceneIndexConfigurationComponent.cs b/src/Kentico.Xperience.Lucene.Admin/Components/LuceneIndexConfigurationComponent.cs index e958257..f7d6152 100644 --- a/src/Kentico.Xperience.Lucene.Admin/Components/LuceneIndexConfigurationComponent.cs +++ b/src/Kentico.Xperience.Lucene.Admin/Components/LuceneIndexConfigurationComponent.cs @@ -1,97 +1,97 @@ -using CMS.DataEngine; - -using Kentico.Xperience.Admin.Base; -using Kentico.Xperience.Admin.Base.FormAnnotations; -using Kentico.Xperience.Admin.Base.Forms; -using Kentico.Xperience.Lucene.Admin; -using Kentico.Xperience.Lucene.Core.Indexing; - -[assembly: RegisterFormComponent( - identifier: LuceneIndexConfigurationComponent.IDENTIFIER, - componentType: typeof(LuceneIndexConfigurationComponent), - name: "Lucene Search Index Configuration")] - -namespace Kentico.Xperience.Lucene.Admin; - -#pragma warning disable S2094 // intentionally empty class -public class LuceneIndexConfigurationComponentProperties : FormComponentProperties -{ -} -#pragma warning restore - -public class LuceneIndexConfigurationComponentClientProperties : FormComponentClientProperties> -{ - public IEnumerable? PossibleContentTypeItems { get; set; } -} - -public sealed class LuceneIndexConfigurationComponentAttribute : FormComponentAttribute -{ -} - -[ComponentAttribute(typeof(LuceneIndexConfigurationComponentAttribute))] -public class LuceneIndexConfigurationComponent : FormComponent> -{ - public const string IDENTIFIER = "kentico.xperience-integrations-lucene-admin.lucene-index-configuration"; - - internal List? Value { get; set; } - - public override string ClientComponentName => "@kentico/xperience-integrations-lucene-admin/LuceneIndexConfiguration"; - - public override IEnumerable GetValue() => Value ?? []; - public override void SetValue(IEnumerable value) => Value = value.ToList(); - - [FormComponentCommand] - public Task> DeletePath(string path) - { - var toRemove = Value?.Find(x => Equals(x.AliasPath == path, StringComparison.OrdinalIgnoreCase)); - if (toRemove != null) - { - Value?.Remove(toRemove); - return Task.FromResult(ResponseFrom(new RowActionResult(false))); - } - return Task.FromResult(ResponseFrom(new RowActionResult(false))); - } - - [FormComponentCommand] - public Task> SavePath(LuceneIndexIncludedPath path) - { - var value = Value?.SingleOrDefault(x => Equals(x.AliasPath == path.AliasPath, StringComparison.OrdinalIgnoreCase)); - - if (value is not null) - { - Value?.Remove(value); - } - - Value?.Add(path); - - return Task.FromResult(ResponseFrom(new RowActionResult(false))); - } - - [FormComponentCommand] - public Task> AddPath(string path) - { - if (Value?.Exists(x => x.AliasPath == path) ?? false) - { - return Task.FromResult(ResponseFrom(new RowActionResult(false))); - } - else - { - Value?.Add(new LuceneIndexIncludedPath(path)); - return Task.FromResult(ResponseFrom(new RowActionResult(false))); - } - } - - protected override async Task ConfigureClientProperties(LuceneIndexConfigurationComponentClientProperties properties) - { - var allWebsiteContentTypes = DataClassInfoProvider.ProviderObject - .Get() - .WhereEquals(nameof(DataClassInfo.ClassContentTypeType), "Website") - .GetEnumerableTypedResult() - .Select(x => new LuceneIndexContentType(x.ClassName, x.ClassDisplayName, 0)); - - properties.Value = Value ?? []; - properties.PossibleContentTypeItems = allWebsiteContentTypes.ToList(); - - await base.ConfigureClientProperties(properties); - } -} +using CMS.DataEngine; + +using Kentico.Xperience.Admin.Base; +using Kentico.Xperience.Admin.Base.FormAnnotations; +using Kentico.Xperience.Admin.Base.Forms; +using Kentico.Xperience.Lucene.Admin; +using Kentico.Xperience.Lucene.Core.Indexing; + +[assembly: RegisterFormComponent( + identifier: LuceneIndexConfigurationComponent.IDENTIFIER, + componentType: typeof(LuceneIndexConfigurationComponent), + name: "Lucene Search Index Configuration")] + +namespace Kentico.Xperience.Lucene.Admin; + +#pragma warning disable S2094 // intentionally empty class +public class LuceneIndexConfigurationComponentProperties : FormComponentProperties +{ +} +#pragma warning restore + +public class LuceneIndexConfigurationComponentClientProperties : FormComponentClientProperties> +{ + public IEnumerable? PossibleContentTypeItems { get; set; } +} + +public sealed class LuceneIndexConfigurationComponentAttribute : FormComponentAttribute +{ +} + +[ComponentAttribute(typeof(LuceneIndexConfigurationComponentAttribute))] +public class LuceneIndexConfigurationComponent : FormComponent> +{ + public const string IDENTIFIER = "kentico.xperience-integrations-lucene-admin.lucene-index-configuration"; + + internal List? Value { get; set; } + + public override string ClientComponentName => "@kentico/xperience-integrations-lucene-admin/LuceneIndexConfiguration"; + + public override IEnumerable GetValue() => Value ?? []; + public override void SetValue(IEnumerable value) => Value = value.ToList(); + + [FormComponentCommand] + public Task> DeletePath(string path) + { + var toRemove = Value?.Find(x => Equals(x.AliasPath == path, StringComparison.OrdinalIgnoreCase)); + if (toRemove != null) + { + Value?.Remove(toRemove); + return Task.FromResult(ResponseFrom(new RowActionResult(false))); + } + return Task.FromResult(ResponseFrom(new RowActionResult(false))); + } + + [FormComponentCommand] + public Task> SavePath(LuceneIndexIncludedPath path) + { + var value = Value?.SingleOrDefault(x => Equals(x.AliasPath == path.AliasPath, StringComparison.OrdinalIgnoreCase)); + + if (value is not null) + { + Value?.Remove(value); + } + + Value?.Add(path); + + return Task.FromResult(ResponseFrom(new RowActionResult(false))); + } + + [FormComponentCommand] + public Task> AddPath(string path) + { + if (Value?.Exists(x => x.AliasPath == path) ?? false) + { + return Task.FromResult(ResponseFrom(new RowActionResult(false))); + } + else + { + Value?.Add(new LuceneIndexIncludedPath(path)); + return Task.FromResult(ResponseFrom(new RowActionResult(false))); + } + } + + protected override async Task ConfigureClientProperties(LuceneIndexConfigurationComponentClientProperties properties) + { + var allWebsiteContentTypes = DataClassInfoProvider.ProviderObject + .Get() + .WhereEquals(nameof(DataClassInfo.ClassContentTypeType), "Website") + .GetEnumerableTypedResult() + .Select(x => new LuceneIndexContentType(x.ClassName, x.ClassDisplayName, 0)); + + properties.Value = Value ?? []; + properties.PossibleContentTypeItems = allWebsiteContentTypes.ToList(); + + await base.ConfigureClientProperties(properties); + } +} diff --git a/src/Kentico.Xperience.Lucene.Admin/LuceneAdminModule.cs b/src/Kentico.Xperience.Lucene.Admin/LuceneAdminModule.cs index 0bd3985..27714a3 100644 --- a/src/Kentico.Xperience.Lucene.Admin/LuceneAdminModule.cs +++ b/src/Kentico.Xperience.Lucene.Admin/LuceneAdminModule.cs @@ -1,49 +1,49 @@ -using CMS; -using CMS.Base; -using CMS.Core; - -using Kentico.Xperience.Admin.Base; -using Kentico.Xperience.Lucene.Admin; -using Kentico.Xperience.Lucene.Core; -using Kentico.Xperience.Lucene.Core.Scaling; - -using Microsoft.Extensions.DependencyInjection; - -[assembly: RegisterModule(typeof(LuceneAdminModule))] - -namespace Kentico.Xperience.Lucene.Admin; - -/// -/// Manages administration features and integration. -/// -internal class LuceneAdminModule : AdminModule -{ - private LuceneModuleInstaller installer = null!; - private IWebFarmService webFarmService = null!; - public LuceneAdminModule() : base(nameof(LuceneAdminModule)) { } - - protected override void OnInit(ModuleInitParameters parameters) - { - base.OnInit(parameters); - - RegisterClientModule("kentico", "xperience-integrations-lucene-admin"); - - var services = parameters.Services; - - installer = services.GetRequiredService(); - webFarmService = services.GetRequiredService(); - - ApplicationEvents.Initialized.Execute += InitializeModule; - ApplicationEvents.Initialized.Execute += RegisterLuceneWebFarmTasks; - } - - private void InitializeModule(object? sender, EventArgs e) => - installer.Install(); - - private void RegisterLuceneWebFarmTasks(object? sender, EventArgs e) - { - webFarmService.RegisterTask(); - webFarmService.RegisterTask(); - webFarmService.RegisterTask(); - } -} +using CMS; +using CMS.Base; +using CMS.Core; + +using Kentico.Xperience.Admin.Base; +using Kentico.Xperience.Lucene.Admin; +using Kentico.Xperience.Lucene.Core; +using Kentico.Xperience.Lucene.Core.Scaling; + +using Microsoft.Extensions.DependencyInjection; + +[assembly: RegisterModule(typeof(LuceneAdminModule))] + +namespace Kentico.Xperience.Lucene.Admin; + +/// +/// Manages administration features and integration. +/// +internal class LuceneAdminModule : AdminModule +{ + private LuceneModuleInstaller installer = null!; + private IWebFarmService webFarmService = null!; + public LuceneAdminModule() : base(nameof(LuceneAdminModule)) { } + + protected override void OnInit(ModuleInitParameters parameters) + { + base.OnInit(parameters); + + RegisterClientModule("kentico", "xperience-integrations-lucene-admin"); + + var services = parameters.Services; + + installer = services.GetRequiredService(); + webFarmService = services.GetRequiredService(); + + ApplicationEvents.Initialized.Execute += InitializeModule; + ApplicationEvents.Initialized.Execute += RegisterLuceneWebFarmTasks; + } + + private void InitializeModule(object? sender, EventArgs e) => + installer.Install(); + + private void RegisterLuceneWebFarmTasks(object? sender, EventArgs e) + { + webFarmService.RegisterTask(); + webFarmService.RegisterTask(); + webFarmService.RegisterTask(); + } +} diff --git a/src/Kentico.Xperience.Lucene.Admin/LuceneIndexPermissions.cs b/src/Kentico.Xperience.Lucene.Admin/LuceneIndexPermissions.cs index 27646c0..384ad92 100644 --- a/src/Kentico.Xperience.Lucene.Admin/LuceneIndexPermissions.cs +++ b/src/Kentico.Xperience.Lucene.Admin/LuceneIndexPermissions.cs @@ -1,6 +1,6 @@ -namespace Kentico.Xperience.Lucene.Admin; - -internal static class LuceneIndexPermissions -{ - public const string REBUILD = "Rebuild"; -} +namespace Kentico.Xperience.Lucene.Admin; + +internal static class LuceneIndexPermissions +{ + public const string REBUILD = "Rebuild"; +} diff --git a/src/Kentico.Xperience.Lucene.Admin/Providers/AnalyzerOptionsProvider.cs b/src/Kentico.Xperience.Lucene.Admin/Providers/AnalyzerOptionsProvider.cs index 4376931..b8df57c 100644 --- a/src/Kentico.Xperience.Lucene.Admin/Providers/AnalyzerOptionsProvider.cs +++ b/src/Kentico.Xperience.Lucene.Admin/Providers/AnalyzerOptionsProvider.cs @@ -1,14 +1,14 @@ -using Kentico.Xperience.Admin.Base.FormAnnotations; -using Kentico.Xperience.Lucene.Core.Indexing; - -namespace Kentico.Xperience.Lucene.Admin.Providers; - -internal class AnalyzerOptionsProvider : IDropDownOptionsProvider -{ - public Task> GetOptionItems() => - Task.FromResult(AnalyzerStorage.Analyzers.Keys.Select(x => new DropDownOptionItem() - { - Value = x, - Text = x - })); -} +using Kentico.Xperience.Admin.Base.FormAnnotations; +using Kentico.Xperience.Lucene.Core.Indexing; + +namespace Kentico.Xperience.Lucene.Admin.Providers; + +internal class AnalyzerOptionsProvider : IDropDownOptionsProvider +{ + public Task> GetOptionItems() => + Task.FromResult(AnalyzerStorage.Analyzers.Keys.Select(x => new DropDownOptionItem() + { + Value = x, + Text = x + })); +} diff --git a/src/Kentico.Xperience.Lucene.Admin/Providers/ChannelOptionsProvider.cs b/src/Kentico.Xperience.Lucene.Admin/Providers/ChannelOptionsProvider.cs index 43552db..617367d 100644 --- a/src/Kentico.Xperience.Lucene.Admin/Providers/ChannelOptionsProvider.cs +++ b/src/Kentico.Xperience.Lucene.Admin/Providers/ChannelOptionsProvider.cs @@ -1,23 +1,23 @@ -using CMS.ContentEngine; -using CMS.DataEngine; - -using Kentico.Xperience.Admin.Base.FormAnnotations; - -namespace Kentico.Xperience.Lucene.Admin; - -internal class ChannelOptionsProvider : IDropDownOptionsProvider -{ - private readonly IInfoProvider channelInfoProvider; - - public ChannelOptionsProvider(IInfoProvider channelInfoProvider) => this.channelInfoProvider = channelInfoProvider; - - public async Task> GetOptionItems() => - (await channelInfoProvider.Get() - .WhereEquals(nameof(ChannelInfo.ChannelType), nameof(ChannelType.Website)) - .GetEnumerableTypedResultAsync()) - .Select(x => new DropDownOptionItem() - { - Value = x.ChannelName, - Text = x.ChannelDisplayName - }); -} +using CMS.ContentEngine; +using CMS.DataEngine; + +using Kentico.Xperience.Admin.Base.FormAnnotations; + +namespace Kentico.Xperience.Lucene.Admin; + +internal class ChannelOptionsProvider : IDropDownOptionsProvider +{ + private readonly IInfoProvider channelInfoProvider; + + public ChannelOptionsProvider(IInfoProvider channelInfoProvider) => this.channelInfoProvider = channelInfoProvider; + + public async Task> GetOptionItems() => + (await channelInfoProvider.Get() + .WhereEquals(nameof(ChannelInfo.ChannelType), nameof(ChannelType.Website)) + .GetEnumerableTypedResultAsync()) + .Select(x => new DropDownOptionItem() + { + Value = x.ChannelName, + Text = x.ChannelDisplayName + }); +} diff --git a/src/Kentico.Xperience.Lucene.Admin/Providers/IndexingStrategyOptionsProvider.cs b/src/Kentico.Xperience.Lucene.Admin/Providers/IndexingStrategyOptionsProvider.cs index a6fe230..4adef86 100644 --- a/src/Kentico.Xperience.Lucene.Admin/Providers/IndexingStrategyOptionsProvider.cs +++ b/src/Kentico.Xperience.Lucene.Admin/Providers/IndexingStrategyOptionsProvider.cs @@ -1,14 +1,14 @@ -using Kentico.Xperience.Admin.Base.FormAnnotations; -using Kentico.Xperience.Lucene.Core.Indexing; - -namespace Kentico.Xperience.Lucene.Admin; - -internal class IndexingStrategyOptionsProvider : IDropDownOptionsProvider -{ - public Task> GetOptionItems() => - Task.FromResult(StrategyStorage.Strategies.Keys.Select(x => new DropDownOptionItem() - { - Value = x, - Text = x - })); -} +using Kentico.Xperience.Admin.Base.FormAnnotations; +using Kentico.Xperience.Lucene.Core.Indexing; + +namespace Kentico.Xperience.Lucene.Admin; + +internal class IndexingStrategyOptionsProvider : IDropDownOptionsProvider +{ + public Task> GetOptionItems() => + Task.FromResult(StrategyStorage.Strategies.Keys.Select(x => new DropDownOptionItem() + { + Value = x, + Text = x + })); +} diff --git a/src/Kentico.Xperience.Lucene.Admin/Providers/LanguageOptionsProvider.cs b/src/Kentico.Xperience.Lucene.Admin/Providers/LanguageOptionsProvider.cs index 9d6116c..3caf10f 100644 --- a/src/Kentico.Xperience.Lucene.Admin/Providers/LanguageOptionsProvider.cs +++ b/src/Kentico.Xperience.Lucene.Admin/Providers/LanguageOptionsProvider.cs @@ -1,69 +1,69 @@ -using CMS.ContentEngine; -using CMS.DataEngine; - -using Kentico.Xperience.Admin.Base.FormAnnotations; -using Kentico.Xperience.Admin.Base.Forms; - -namespace Kentico.Xperience.Lucene.Admin; - -internal class LanguageOptionsProvider : IGeneralSelectorDataProvider -{ - private readonly IInfoProvider contentLanguageInfoProvider; - - public LanguageOptionsProvider(IInfoProvider contentLanguageInfoProvider) => this.contentLanguageInfoProvider = contentLanguageInfoProvider; - - public async Task> GetItemsAsync(string searchTerm, int pageIndex, CancellationToken cancellationToken) - { - // Prepares a query for retrieving user objects - var itemQuery = contentLanguageInfoProvider.Get(); - // If a search term is entered, only loads users users whose first name starts with the term - if (!string.IsNullOrEmpty(searchTerm)) - { - itemQuery.WhereStartsWith(nameof(ContentLanguageInfo.ContentLanguageDisplayName), searchTerm); - } - - // Ensures paging of items - itemQuery.Page(pageIndex, 20); - - // Retrieves the users and converts them into ObjectSelectorListItem options - var items = (await itemQuery.GetEnumerableTypedResultAsync()).Select(x => new ObjectSelectorListItem() - { - Value = x.ContentLanguageName, - Text = x.ContentLanguageDisplayName, - IsValid = true - }); - - return new PagedSelectListItems() - { - NextPageAvailable = itemQuery.NextPageAvailable, - Items = items - }; - } - - // Returns ObjectSelectorListItem options for all item values that are currently selected - public async Task>> GetSelectedItemsAsync(IEnumerable selectedValues, CancellationToken cancellationToken) - { - var itemQuery = contentLanguageInfoProvider.Get().Page(0, 20); - var items = (await itemQuery.GetEnumerableTypedResultAsync()).Select(x => new ObjectSelectorListItem() - { - Value = x.ContentLanguageName, - Text = x.ContentLanguageDisplayName, - IsValid = true - }); - - var selectedItems = new List>(); - if (selectedValues is not null) - { - foreach (string? value in selectedValues) - { - var item = items.FirstOrDefault(x => x.Value == value); - - if (item != default) - { - selectedItems.Add(item); - } - } - } - return selectedItems; - } -} +using CMS.ContentEngine; +using CMS.DataEngine; + +using Kentico.Xperience.Admin.Base.FormAnnotations; +using Kentico.Xperience.Admin.Base.Forms; + +namespace Kentico.Xperience.Lucene.Admin; + +internal class LanguageOptionsProvider : IGeneralSelectorDataProvider +{ + private readonly IInfoProvider contentLanguageInfoProvider; + + public LanguageOptionsProvider(IInfoProvider contentLanguageInfoProvider) => this.contentLanguageInfoProvider = contentLanguageInfoProvider; + + public async Task> GetItemsAsync(string searchTerm, int pageIndex, CancellationToken cancellationToken) + { + // Prepares a query for retrieving user objects + var itemQuery = contentLanguageInfoProvider.Get(); + // If a search term is entered, only loads users users whose first name starts with the term + if (!string.IsNullOrEmpty(searchTerm)) + { + itemQuery.WhereStartsWith(nameof(ContentLanguageInfo.ContentLanguageDisplayName), searchTerm); + } + + // Ensures paging of items + itemQuery.Page(pageIndex, 20); + + // Retrieves the users and converts them into ObjectSelectorListItem options + var items = (await itemQuery.GetEnumerableTypedResultAsync()).Select(x => new ObjectSelectorListItem() + { + Value = x.ContentLanguageName, + Text = x.ContentLanguageDisplayName, + IsValid = true + }); + + return new PagedSelectListItems() + { + NextPageAvailable = itemQuery.NextPageAvailable, + Items = items + }; + } + + // Returns ObjectSelectorListItem options for all item values that are currently selected + public async Task>> GetSelectedItemsAsync(IEnumerable selectedValues, CancellationToken cancellationToken) + { + var itemQuery = contentLanguageInfoProvider.Get().Page(0, 20); + var items = (await itemQuery.GetEnumerableTypedResultAsync()).Select(x => new ObjectSelectorListItem() + { + Value = x.ContentLanguageName, + Text = x.ContentLanguageDisplayName, + IsValid = true + }); + + var selectedItems = new List>(); + if (selectedValues is not null) + { + foreach (string? value in selectedValues) + { + var item = items.FirstOrDefault(x => x.Value == value); + + if (item != default) + { + selectedItems.Add(item); + } + } + } + return selectedItems; + } +} diff --git a/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexCreatePage.cs b/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexCreatePage.cs index 4e522ea..ef5b4e3 100644 --- a/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexCreatePage.cs +++ b/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexCreatePage.cs @@ -1,68 +1,68 @@ -using CMS.Membership; - -using Kentico.Xperience.Admin.Base; -using Kentico.Xperience.Admin.Base.Forms; -using Kentico.Xperience.Lucene.Admin; -using Kentico.Xperience.Lucene.Core.Indexing; - -using IFormItemCollectionProvider = Kentico.Xperience.Admin.Base.Forms.Internal.IFormItemCollectionProvider; - -[assembly: UIPage( - parentType: typeof(IndexListingPage), - slug: "create", - uiPageType: typeof(IndexCreatePage), - name: "Create index", - templateName: TemplateNames.EDIT, - order: UIPageOrder.NoOrder)] - -namespace Kentico.Xperience.Lucene.Admin; - -[UIEvaluatePermission(SystemPermissions.CREATE)] -internal class IndexCreatePage : BaseIndexEditPage -{ - private readonly IPageUrlGenerator pageUrlGenerator; - private readonly ILuceneIndexManager indexManager; - private LuceneConfigurationModel? model = null; - - public IndexCreatePage( - IFormItemCollectionProvider formItemCollectionProvider, - IFormDataBinder formDataBinder, - ILuceneConfigurationStorageService storageService, - ILuceneIndexManager indexManager, - IPageUrlGenerator pageUrlGenerator) - : base(formItemCollectionProvider, formDataBinder, storageService, indexManager) - { - this.pageUrlGenerator = pageUrlGenerator; - this.indexManager = indexManager; - } - - protected override LuceneConfigurationModel Model - { - get - { - model ??= new(); - - return model; - } - } - - protected override async Task ProcessFormData(LuceneConfigurationModel model, ICollection formItems) - { - var result = await ValidateAndProcess(model); - - if (result == IndexModificationResult.Success) - { - var index = indexManager.GetRequiredIndex(model.IndexName); - - var successResponse = NavigateTo(pageUrlGenerator.GenerateUrl(index.Identifier.ToString())) - .AddSuccessMessage("Index created."); - - return await Task.FromResult(successResponse); - } - - var errorResponse = ResponseFrom(new FormSubmissionResult(FormSubmissionStatus.ValidationFailure)) - .AddErrorMessage("Could not create index."); - - return await Task.FromResult(errorResponse); - } -} +using CMS.Membership; + +using Kentico.Xperience.Admin.Base; +using Kentico.Xperience.Admin.Base.Forms; +using Kentico.Xperience.Lucene.Admin; +using Kentico.Xperience.Lucene.Core.Indexing; + +using IFormItemCollectionProvider = Kentico.Xperience.Admin.Base.Forms.Internal.IFormItemCollectionProvider; + +[assembly: UIPage( + parentType: typeof(IndexListingPage), + slug: "create", + uiPageType: typeof(IndexCreatePage), + name: "Create index", + templateName: TemplateNames.EDIT, + order: UIPageOrder.NoOrder)] + +namespace Kentico.Xperience.Lucene.Admin; + +[UIEvaluatePermission(SystemPermissions.CREATE)] +internal class IndexCreatePage : BaseIndexEditPage +{ + private readonly IPageUrlGenerator pageUrlGenerator; + private readonly ILuceneIndexManager indexManager; + private LuceneConfigurationModel? model = null; + + public IndexCreatePage( + IFormItemCollectionProvider formItemCollectionProvider, + IFormDataBinder formDataBinder, + ILuceneConfigurationStorageService storageService, + ILuceneIndexManager indexManager, + IPageUrlGenerator pageUrlGenerator) + : base(formItemCollectionProvider, formDataBinder, storageService, indexManager) + { + this.pageUrlGenerator = pageUrlGenerator; + this.indexManager = indexManager; + } + + protected override LuceneConfigurationModel Model + { + get + { + model ??= new(); + + return model; + } + } + + protected override async Task ProcessFormData(LuceneConfigurationModel model, ICollection formItems) + { + var result = await ValidateAndProcess(model); + + if (result == IndexModificationResult.Success) + { + var index = indexManager.GetRequiredIndex(model.IndexName); + + var successResponse = NavigateTo(pageUrlGenerator.GenerateUrl(index.Identifier.ToString())) + .AddSuccessMessage("Index created."); + + return await Task.FromResult(successResponse); + } + + var errorResponse = ResponseFrom(new FormSubmissionResult(FormSubmissionStatus.ValidationFailure)) + .AddErrorMessage("Could not create index."); + + return await Task.FromResult(errorResponse); + } +} diff --git a/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexEditPage.cs b/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexEditPage.cs index 891ddea..3b18910 100644 --- a/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexEditPage.cs +++ b/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexEditPage.cs @@ -1,57 +1,57 @@ -using CMS.Membership; - -using Kentico.Xperience.Admin.Base; -using Kentico.Xperience.Admin.Base.Forms; -using Kentico.Xperience.Lucene.Admin; -using Kentico.Xperience.Lucene.Core.Indexing; - -[assembly: UIPage( - parentType: typeof(IndexListingPage), - slug: PageParameterConstants.PARAMETERIZED_SLUG, - uiPageType: typeof(IndexEditPage), - name: "Edit index", - templateName: TemplateNames.EDIT, - order: UIPageOrder.NoOrder)] - -namespace Kentico.Xperience.Lucene.Admin; - -[UIEvaluatePermission(SystemPermissions.UPDATE)] -internal class IndexEditPage : BaseIndexEditPage -{ - private LuceneConfigurationModel? model = null; - - [PageParameter(typeof(IntPageModelBinder))] - public int IndexIdentifier { get; set; } - - public IndexEditPage(Xperience.Admin.Base.Forms.Internal.IFormItemCollectionProvider formItemCollectionProvider, - IFormDataBinder formDataBinder, - ILuceneConfigurationStorageService storageService, - ILuceneIndexManager indexManager) - : base(formItemCollectionProvider, formDataBinder, storageService, indexManager) { } - - protected override LuceneConfigurationModel Model - { - get - { - model ??= new LuceneConfigurationModel(StorageService.GetIndexDataOrNullAsync(IndexIdentifier).Result ?? new()); - - return model; - } - } - - protected override async Task ProcessFormData(LuceneConfigurationModel model, ICollection formItems) - { - var result = await ValidateAndProcess(model); - - var response = ResponseFrom(new FormSubmissionResult( - result == IndexModificationResult.Success - ? FormSubmissionStatus.ValidationSuccess - : FormSubmissionStatus.ValidationFailure)); - - _ = result == IndexModificationResult.Success - ? response.AddSuccessMessage("Index edited") - : response.AddErrorMessage("Could not update index"); - - return await Task.FromResult(response); - } -} +using CMS.Membership; + +using Kentico.Xperience.Admin.Base; +using Kentico.Xperience.Admin.Base.Forms; +using Kentico.Xperience.Lucene.Admin; +using Kentico.Xperience.Lucene.Core.Indexing; + +[assembly: UIPage( + parentType: typeof(IndexListingPage), + slug: PageParameterConstants.PARAMETERIZED_SLUG, + uiPageType: typeof(IndexEditPage), + name: "Edit index", + templateName: TemplateNames.EDIT, + order: UIPageOrder.NoOrder)] + +namespace Kentico.Xperience.Lucene.Admin; + +[UIEvaluatePermission(SystemPermissions.UPDATE)] +internal class IndexEditPage : BaseIndexEditPage +{ + private LuceneConfigurationModel? model = null; + + [PageParameter(typeof(IntPageModelBinder))] + public int IndexIdentifier { get; set; } + + public IndexEditPage(Xperience.Admin.Base.Forms.Internal.IFormItemCollectionProvider formItemCollectionProvider, + IFormDataBinder formDataBinder, + ILuceneConfigurationStorageService storageService, + ILuceneIndexManager indexManager) + : base(formItemCollectionProvider, formDataBinder, storageService, indexManager) { } + + protected override LuceneConfigurationModel Model + { + get + { + model ??= new LuceneConfigurationModel(StorageService.GetIndexDataOrNullAsync(IndexIdentifier).Result ?? new()); + + return model; + } + } + + protected override async Task ProcessFormData(LuceneConfigurationModel model, ICollection formItems) + { + var result = await ValidateAndProcess(model); + + var response = ResponseFrom(new FormSubmissionResult( + result == IndexModificationResult.Success + ? FormSubmissionStatus.ValidationSuccess + : FormSubmissionStatus.ValidationFailure)); + + _ = result == IndexModificationResult.Success + ? response.AddSuccessMessage("Index edited") + : response.AddErrorMessage("Could not update index"); + + return await Task.FromResult(response); + } +} diff --git a/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs b/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs index 039137f..22af594 100644 --- a/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs +++ b/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs @@ -1,205 +1,205 @@ -using CMS.Core; -using CMS.Membership; - -using Kentico.Xperience.Admin.Base; -using Kentico.Xperience.Lucene.Admin; -using Kentico.Xperience.Lucene.Core; -using Kentico.Xperience.Lucene.Core.Indexing; -using Kentico.Xperience.Lucene.Core.Scaling; - -[assembly: UIPage( - parentType: typeof(LuceneApplicationPage), - slug: "indexes", - uiPageType: typeof(IndexListingPage), - name: "List of registered Lucene indices", - templateName: TemplateNames.LISTING, - order: UIPageOrder.NoOrder)] - -namespace Kentico.Xperience.Lucene.Admin; - -/// -/// An admin UI page that displays statistics about the registered Lucene indexes. -/// -[UIEvaluatePermission(SystemPermissions.VIEW)] -internal class IndexListingPage : ListingPage -{ - private readonly ILuceneClient luceneClient; - private readonly IPageUrlGenerator pageUrlGenerator; - private readonly ILuceneConfigurationStorageService configurationStorageService; - private readonly IConversionService conversionService; - private readonly ILuceneIndexManager indexManager; - private readonly IWebFarmService webFarmService; - - protected override string ObjectType => LuceneIndexItemInfo.OBJECT_TYPE; - - /// - /// Initializes a new instance of the class. - /// - public IndexListingPage( - ILuceneClient luceneClient, - IPageUrlGenerator pageUrlGenerator, - ILuceneConfigurationStorageService configurationStorageService, - ILuceneIndexManager indexManager, - IConversionService conversionService, - IWebFarmService webFarmService) - { - this.luceneClient = luceneClient; - this.pageUrlGenerator = pageUrlGenerator; - this.configurationStorageService = configurationStorageService; - this.conversionService = conversionService; - this.indexManager = indexManager; - this.webFarmService = webFarmService; - } - - /// - public override async Task ConfigurePage() - { - if (!indexManager.GetAllIndices().Any()) - { - PageConfiguration.Callouts = - [ - new() - { - Headline = "No indexes", - Content = "No Lucene indexes registered. See our instructions to read more about creating and registering Lucene indexes.", - ContentAsHtml = true, - Type = CalloutType.FriendlyWarning, - Placement = CalloutPlacement.OnDesk - } - ]; - } - - PageConfiguration.ColumnConfigurations - .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemId), "ID", defaultSortDirection: SortTypeEnum.Asc, sortable: true) - .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemIndexName), "Name", sortable: true, searchable: true) - .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemChannelName), "Channel", searchable: true, sortable: true) - .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemStrategyName), "Index Strategy", searchable: true, sortable: true) - .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemAnalyzerName), "Lucene Analyzer", searchable: true, sortable: true) - // Placeholder field which will be replaced with a customized value - .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemId), "Entries", sortable: true) - .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemId), "Last Updated", sortable: true); - - PageConfiguration.AddEditRowAction(); - PageConfiguration.TableActions.AddCommand("Rebuild", nameof(Rebuild), icon: Icons.RotateRight); - PageConfiguration.TableActions.AddDeleteAction(nameof(Delete), "Delete"); - PageConfiguration.HeaderActions.AddLink("Create"); - - await base.ConfigurePage(); - } - - protected override async Task LoadData(LoadDataSettings settings, CancellationToken cancellationToken) - { - var result = await base.LoadData(settings, cancellationToken); - - var statistics = await luceneClient.GetStatistics(default); - // Add statistics for indexes that are registered but not created in Lucene - AddMissingStatistics(ref statistics, indexManager); - - if (PageConfiguration.ColumnConfigurations is not List columns) - { - return result; - } - - int entriesColIndex = columns.FindIndex(c => c.Caption == "Entries"); - int updatedColIndex = columns.FindIndex(c => c.Caption == "Last Updated"); - - foreach (var row in result.Rows) - { - if (row.Cells is not List cells) - { - continue; - } - - var stats = GetStatistic(row, statistics); - - if (stats is null) - { - continue; - } - - if (cells[entriesColIndex] is StringCell entriesCell) - { - entriesCell.Value = stats.Entries.ToString(); - } - if (cells[updatedColIndex] is StringCell updatedCell) - { - updatedCell.Value = stats.UpdatedAt.ToLocalTime().ToString(); - } - } - - return result; - } - - private LuceneIndexStatisticsModel? GetStatistic(Row row, ICollection statistics) - { - int indexID = conversionService.GetInteger(row.Identifier, 0); - string indexName = indexManager.GetIndex(indexID) is LuceneIndex index - ? index.IndexName - : ""; - - return statistics.FirstOrDefault(s => string.Equals(s.Name, indexName, StringComparison.OrdinalIgnoreCase)); - } - - /// - /// A page command which rebuilds an Lucene index. Runs the rebuild on all application instances in case of vertical scaling. - /// - /// The ID of the row whose action was performed, which corresponds with the internal - /// to rebuild. - /// The cancellation token for the action. - [PageCommand(Permission = LuceneIndexPermissions.REBUILD)] - public async Task> Rebuild(int id, CancellationToken cancellationToken) - { - var result = new RowActionResult(false); - var index = indexManager.GetIndex(id); - if (index is null) - { - return ResponseFrom(result) - .AddErrorMessage(string.Format("Error loading Lucene index with identifier {0}.", id)); - } - try - { - webFarmService.CreateTask(new RebuildWebFarmTask() - { - IndexName = index.IndexName, - CreatorName = webFarmService.ServerName - }); - - await luceneClient.Rebuild(index.IndexName, cancellationToken); - - return ResponseFrom(result) - .AddSuccessMessage("Indexing in progress. Visit your Lucene dashboard for details about the indexing process."); - } - catch (Exception ex) - { - EventLogService.LogException(nameof(IndexListingPage), nameof(Rebuild), ex); - return ResponseFrom(result) - .AddErrorMessage(string.Format("Errors occurred while rebuilding the '{0}' index. Please check the Event Log for more details.", index.IndexName)); - } - } - - [PageCommand(Permission = SystemPermissions.DELETE)] - public Task Delete(int id, CancellationToken _) - { - configurationStorageService.TryDeleteIndex(id); - - var response = NavigateTo(pageUrlGenerator.GenerateUrl()); - - return Task.FromResult(response); - } - - private static void AddMissingStatistics(ref ICollection statistics, ILuceneIndexManager indexManager) - { - foreach (string indexName in indexManager.GetAllIndices().Select(i => i.IndexName)) - { - if (!statistics.Any(stat => stat.Name?.Equals(indexName, StringComparison.OrdinalIgnoreCase) ?? false)) - { - statistics.Add(new LuceneIndexStatisticsModel - { - Name = indexName, - Entries = 0, - UpdatedAt = DateTime.MinValue - }); - } - } - } -} +using CMS.Core; +using CMS.Membership; + +using Kentico.Xperience.Admin.Base; +using Kentico.Xperience.Lucene.Admin; +using Kentico.Xperience.Lucene.Core; +using Kentico.Xperience.Lucene.Core.Indexing; +using Kentico.Xperience.Lucene.Core.Scaling; + +[assembly: UIPage( + parentType: typeof(LuceneApplicationPage), + slug: "indexes", + uiPageType: typeof(IndexListingPage), + name: "List of registered Lucene indices", + templateName: TemplateNames.LISTING, + order: UIPageOrder.NoOrder)] + +namespace Kentico.Xperience.Lucene.Admin; + +/// +/// An admin UI page that displays statistics about the registered Lucene indexes. +/// +[UIEvaluatePermission(SystemPermissions.VIEW)] +internal class IndexListingPage : ListingPage +{ + private readonly ILuceneClient luceneClient; + private readonly IPageUrlGenerator pageUrlGenerator; + private readonly ILuceneConfigurationStorageService configurationStorageService; + private readonly IConversionService conversionService; + private readonly ILuceneIndexManager indexManager; + private readonly IWebFarmService webFarmService; + + protected override string ObjectType => LuceneIndexItemInfo.OBJECT_TYPE; + + /// + /// Initializes a new instance of the class. + /// + public IndexListingPage( + ILuceneClient luceneClient, + IPageUrlGenerator pageUrlGenerator, + ILuceneConfigurationStorageService configurationStorageService, + ILuceneIndexManager indexManager, + IConversionService conversionService, + IWebFarmService webFarmService) + { + this.luceneClient = luceneClient; + this.pageUrlGenerator = pageUrlGenerator; + this.configurationStorageService = configurationStorageService; + this.conversionService = conversionService; + this.indexManager = indexManager; + this.webFarmService = webFarmService; + } + + /// + public override async Task ConfigurePage() + { + if (!indexManager.GetAllIndices().Any()) + { + PageConfiguration.Callouts = + [ + new() + { + Headline = "No indexes", + Content = "No Lucene indexes registered. See our instructions to read more about creating and registering Lucene indexes.", + ContentAsHtml = true, + Type = CalloutType.FriendlyWarning, + Placement = CalloutPlacement.OnDesk + } + ]; + } + + PageConfiguration.ColumnConfigurations + .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemId), "ID", defaultSortDirection: SortTypeEnum.Asc, sortable: true) + .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemIndexName), "Name", sortable: true, searchable: true) + .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemChannelName), "Channel", searchable: true, sortable: true) + .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemStrategyName), "Index Strategy", searchable: true, sortable: true) + .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemAnalyzerName), "Lucene Analyzer", searchable: true, sortable: true) + // Placeholder field which will be replaced with a customized value + .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemId), "Entries", sortable: true) + .AddColumn(nameof(LuceneIndexItemInfo.LuceneIndexItemId), "Last Updated", sortable: true); + + PageConfiguration.AddEditRowAction(); + PageConfiguration.TableActions.AddCommand("Rebuild", nameof(Rebuild), icon: Icons.RotateRight); + PageConfiguration.TableActions.AddDeleteAction(nameof(Delete), "Delete"); + PageConfiguration.HeaderActions.AddLink("Create"); + + await base.ConfigurePage(); + } + + protected override async Task LoadData(LoadDataSettings settings, CancellationToken cancellationToken) + { + var result = await base.LoadData(settings, cancellationToken); + + var statistics = await luceneClient.GetStatistics(default); + // Add statistics for indexes that are registered but not created in Lucene + AddMissingStatistics(ref statistics, indexManager); + + if (PageConfiguration.ColumnConfigurations is not List columns) + { + return result; + } + + int entriesColIndex = columns.FindIndex(c => c.Caption == "Entries"); + int updatedColIndex = columns.FindIndex(c => c.Caption == "Last Updated"); + + foreach (var row in result.Rows) + { + if (row.Cells is not List cells) + { + continue; + } + + var stats = GetStatistic(row, statistics); + + if (stats is null) + { + continue; + } + + if (cells[entriesColIndex] is StringCell entriesCell) + { + entriesCell.Value = stats.Entries.ToString(); + } + if (cells[updatedColIndex] is StringCell updatedCell) + { + updatedCell.Value = stats.UpdatedAt.ToLocalTime().ToString(); + } + } + + return result; + } + + private LuceneIndexStatisticsModel? GetStatistic(Row row, ICollection statistics) + { + int indexID = conversionService.GetInteger(row.Identifier, 0); + string indexName = indexManager.GetIndex(indexID) is LuceneIndex index + ? index.IndexName + : ""; + + return statistics.FirstOrDefault(s => string.Equals(s.Name, indexName, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// A page command which rebuilds an Lucene index. Runs the rebuild on all application instances in case of vertical scaling. + /// + /// The ID of the row whose action was performed, which corresponds with the internal + /// to rebuild. + /// The cancellation token for the action. + [PageCommand(Permission = LuceneIndexPermissions.REBUILD)] + public async Task> Rebuild(int id, CancellationToken cancellationToken) + { + var result = new RowActionResult(false); + var index = indexManager.GetIndex(id); + if (index is null) + { + return ResponseFrom(result) + .AddErrorMessage(string.Format("Error loading Lucene index with identifier {0}.", id)); + } + try + { + webFarmService.CreateTask(new RebuildWebFarmTask() + { + IndexName = index.IndexName, + CreatorName = webFarmService.ServerName + }); + + await luceneClient.Rebuild(index.IndexName, cancellationToken); + + return ResponseFrom(result) + .AddSuccessMessage("Indexing in progress. Visit your Lucene dashboard for details about the indexing process."); + } + catch (Exception ex) + { + EventLogService.LogException(nameof(IndexListingPage), nameof(Rebuild), ex); + return ResponseFrom(result) + .AddErrorMessage(string.Format("Errors occurred while rebuilding the '{0}' index. Please check the Event Log for more details.", index.IndexName)); + } + } + + [PageCommand(Permission = SystemPermissions.DELETE)] + public Task Delete(int id, CancellationToken _) + { + configurationStorageService.TryDeleteIndex(id); + + var response = NavigateTo(pageUrlGenerator.GenerateUrl()); + + return Task.FromResult(response); + } + + private static void AddMissingStatistics(ref ICollection statistics, ILuceneIndexManager indexManager) + { + foreach (string indexName in indexManager.GetAllIndices().Select(i => i.IndexName)) + { + if (!statistics.Any(stat => stat.Name?.Equals(indexName, StringComparison.OrdinalIgnoreCase) ?? false)) + { + statistics.Add(new LuceneIndexStatisticsModel + { + Name = indexName, + Entries = 0, + UpdatedAt = DateTime.MinValue + }); + } + } + } +} diff --git a/src/Kentico.Xperience.Lucene.Admin/UIPages/LuceneApplication.cs b/src/Kentico.Xperience.Lucene.Admin/UIPages/LuceneApplication.cs index 92afb4f..a1238cd 100644 --- a/src/Kentico.Xperience.Lucene.Admin/UIPages/LuceneApplication.cs +++ b/src/Kentico.Xperience.Lucene.Admin/UIPages/LuceneApplication.cs @@ -1,29 +1,29 @@ -using CMS.Membership; - -using Kentico.Xperience.Admin.Base; -using Kentico.Xperience.Admin.Base.UIPages; -using Kentico.Xperience.Lucene.Admin; - -[assembly: UIApplication( - identifier: LuceneApplicationPage.IDENTIFIER, - type: typeof(LuceneApplicationPage), - slug: "lucene", - name: "Search", - category: BaseApplicationCategories.DEVELOPMENT, - icon: Icons.Magnifier, - templateName: TemplateNames.SECTION_LAYOUT)] - -namespace Kentico.Xperience.Lucene.Admin; - -/// -/// The root application page for the Lucene integration. -/// -[UIPermission(SystemPermissions.VIEW)] -[UIPermission(SystemPermissions.CREATE)] -[UIPermission(SystemPermissions.UPDATE)] -[UIPermission(SystemPermissions.DELETE)] -[UIPermission(LuceneIndexPermissions.REBUILD, "Rebuild")] -internal class LuceneApplicationPage : ApplicationPage -{ - public const string IDENTIFIER = "Kentico.Xperience.Integrations.Lucene.Admin"; -} +using CMS.Membership; + +using Kentico.Xperience.Admin.Base; +using Kentico.Xperience.Admin.Base.UIPages; +using Kentico.Xperience.Lucene.Admin; + +[assembly: UIApplication( + identifier: LuceneApplicationPage.IDENTIFIER, + type: typeof(LuceneApplicationPage), + slug: "lucene", + name: "Search", + category: BaseApplicationCategories.DEVELOPMENT, + icon: Icons.Magnifier, + templateName: TemplateNames.SECTION_LAYOUT)] + +namespace Kentico.Xperience.Lucene.Admin; + +/// +/// The root application page for the Lucene integration. +/// +[UIPermission(SystemPermissions.VIEW)] +[UIPermission(SystemPermissions.CREATE)] +[UIPermission(SystemPermissions.UPDATE)] +[UIPermission(SystemPermissions.DELETE)] +[UIPermission(LuceneIndexPermissions.REBUILD, "Rebuild")] +internal class LuceneApplicationPage : ApplicationPage +{ + public const string IDENTIFIER = "Kentico.Xperience.Integrations.Lucene.Admin"; +} diff --git a/src/Kentico.Xperience.Lucene.Core/BaseDocumentProperties.cs b/src/Kentico.Xperience.Lucene.Core/BaseDocumentProperties.cs index c90352c..3246840 100644 --- a/src/Kentico.Xperience.Lucene.Core/BaseDocumentProperties.cs +++ b/src/Kentico.Xperience.Lucene.Core/BaseDocumentProperties.cs @@ -1,25 +1,25 @@ -using Kentico.Xperience.Lucene.Core.Indexing; - -using Lucene.Net.Documents; - -namespace Kentico.Xperience.Lucene.Core; - -/// -/// Properties automatically added to each indexed item -/// -public static class BaseDocumentProperties -{ - public const string ID = "ID"; - public const string CONTENT_TYPE_NAME = "ContentTypeName"; - public const string ITEM_GUID = "ItemGuid"; - public const string LANGUAGE_NAME = "LanguageName"; - /// - /// By default this field on the is populated with a web page's relative path - /// if the indexed item is a web page. The field is not added to a document for reusable content items. - /// - /// If a field with this name has already been added to the document by - /// custom it will not be overridden. - /// This enables a developer to choose if they want to use relative or absolute URLs - /// - public const string URL = "Url"; -} +using Kentico.Xperience.Lucene.Core.Indexing; + +using Lucene.Net.Documents; + +namespace Kentico.Xperience.Lucene.Core; + +/// +/// Properties automatically added to each indexed item +/// +public static class BaseDocumentProperties +{ + public const string ID = "ID"; + public const string CONTENT_TYPE_NAME = "ContentTypeName"; + public const string ITEM_GUID = "ItemGuid"; + public const string LANGUAGE_NAME = "LanguageName"; + /// + /// By default this field on the is populated with a web page's relative path + /// if the indexed item is a web page. The field is not added to a document for reusable content items. + /// + /// If a field with this name has already been added to the document by + /// custom it will not be overridden. + /// This enables a developer to choose if they want to use relative or absolute URLs + /// + public const string URL = "Url"; +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/AnalyzerStorage.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/AnalyzerStorage.cs index f606b43..d36b6d1 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/AnalyzerStorage.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/AnalyzerStorage.cs @@ -1,25 +1,25 @@ -using Lucene.Net.Analysis; -using Lucene.Net.Analysis.Standard; -using Lucene.Net.Util; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -internal static class AnalyzerStorage -{ - public static Dictionary Analyzers { get; private set; } - public static LuceneVersion AnalyzerLuceneVersion { get; private set; } - static AnalyzerStorage() => Analyzers = []; - - - public static void SetAnalyzerLuceneVersion(LuceneVersion matchVersion) => AnalyzerLuceneVersion = matchVersion; - - - public static void AddAnalyzer(string analyzerName) where TAnalyzer : Analyzer - => Analyzers.Add(analyzerName, typeof(TAnalyzer)); - - - public static Type GetOrDefault(string analyzerName) => - Analyzers.TryGetValue(analyzerName, out var type) - ? type - : typeof(StandardAnalyzer); -} +using Lucene.Net.Analysis; +using Lucene.Net.Analysis.Standard; +using Lucene.Net.Util; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +internal static class AnalyzerStorage +{ + public static Dictionary Analyzers { get; private set; } + public static LuceneVersion AnalyzerLuceneVersion { get; private set; } + static AnalyzerStorage() => Analyzers = []; + + + public static void SetAnalyzerLuceneVersion(LuceneVersion matchVersion) => AnalyzerLuceneVersion = matchVersion; + + + public static void AddAnalyzer(string analyzerName) where TAnalyzer : Analyzer + => Analyzers.Add(analyzerName, typeof(TAnalyzer)); + + + public static Type GetOrDefault(string analyzerName) => + Analyzers.TryGetValue(analyzerName, out var type) + ? type + : typeof(StandardAnalyzer); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexManager.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexManager.cs index 6fac358..24d797e 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexManager.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexManager.cs @@ -1,115 +1,115 @@ -using CMS.Helpers; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -/// -/// Manages adding and getting of Lucene indexes. -/// Uses progressive caching for faster index operations. -/// -internal class DefaultLuceneIndexManager : ILuceneIndexManager -{ - private readonly ILuceneConfigurationStorageService storageService; - private readonly IProgressiveCache progressiveCache; - - public DefaultLuceneIndexManager(ILuceneConfigurationStorageService storageService, IProgressiveCache progressiveCache) - { - this.storageService = storageService; - this.progressiveCache = progressiveCache; - } - - /// - public IEnumerable GetAllIndices() - { - var indices = (CacheSettings cs) => - { - var luceneIndices = storageService.GetAllIndexDataAsync().Result.Select(x => new LuceneIndex(x, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion)); - - cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); - - return luceneIndices; - }; - - return progressiveCache.Load(cs => indices(cs), new CacheSettings(10, $"customdatasource|index|all")); - } - - /// - public LuceneIndex? GetIndex(string indexName) - { - var index = (CacheSettings cs) => - { - var indexConfiguration = storageService.GetIndexDataOrNullAsync(indexName).Result; - - if (indexConfiguration == null) - { - return default; - } - cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); - - return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); - }; - - return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|name|{indexName}")); - } - - /// - public LuceneIndex? GetIndex(int identifier) - { - var index = (CacheSettings cs) => - { - var indexConfiguration = storageService.GetIndexDataOrNullAsync(identifier).Result; - - if (indexConfiguration == null) - { - return default; - } - cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); - - return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); - }; - - return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|identifier|{identifier}")); - } - - /// - public LuceneIndex GetRequiredIndex(string indexName) - { - var index = (CacheSettings cs) => - { - var indexConfiguration = storageService.GetIndexDataOrNullAsync(indexName).Result ?? throw new InvalidOperationException($"The index '{indexName}' does not exist."); - cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); - - return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); - }; - - return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|identifier|{indexName}")); - } - - /// - public void AddIndex(LuceneIndexModel indexConfiguration) - { - if (indexConfiguration == null) - { - throw new ArgumentNullException(nameof(indexConfiguration)); - } - - if (GetAllIndices().Any(i => i.IndexName.Equals(indexConfiguration.IndexName, StringComparison.OrdinalIgnoreCase) || indexConfiguration.Id == i.Identifier)) - { - throw new InvalidOperationException($"Attempted to register Lucene index with identifer [{indexConfiguration.Id}] and name [{indexConfiguration.IndexName}] but it is already registered."); - } - - storageService.TryCreateIndex(indexConfiguration); - } - - private ISet GetLuceneDependencyCacheKeys() - { - var dependencyCacheKeys = new HashSet - { - CacheHelper.BuildCacheItemName(new[] { LuceneIndexItemInfo.OBJECT_TYPE,"all" }, lowerCase: false), - CacheHelper.BuildCacheItemName(new[] { LuceneIndexLanguageItemInfo.OBJECT_TYPE,"all" }, lowerCase: false), - CacheHelper.BuildCacheItemName(new[] { LuceneIncludedPathItemInfo.OBJECT_TYPE,"all" }, lowerCase: false), - CacheHelper.BuildCacheItemName(new[] { LuceneContentTypeItemInfo.OBJECT_TYPE,"all" }, lowerCase: false) - }; - return dependencyCacheKeys; - } -} - +using CMS.Helpers; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +/// +/// Manages adding and getting of Lucene indexes. +/// Uses progressive caching for faster index operations. +/// +internal class DefaultLuceneIndexManager : ILuceneIndexManager +{ + private readonly ILuceneConfigurationStorageService storageService; + private readonly IProgressiveCache progressiveCache; + + public DefaultLuceneIndexManager(ILuceneConfigurationStorageService storageService, IProgressiveCache progressiveCache) + { + this.storageService = storageService; + this.progressiveCache = progressiveCache; + } + + /// + public IEnumerable GetAllIndices() + { + var indices = (CacheSettings cs) => + { + var luceneIndices = storageService.GetAllIndexDataAsync().Result.Select(x => new LuceneIndex(x, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion)); + + cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); + + return luceneIndices; + }; + + return progressiveCache.Load(cs => indices(cs), new CacheSettings(10, $"customdatasource|index|all")); + } + + /// + public LuceneIndex? GetIndex(string indexName) + { + var index = (CacheSettings cs) => + { + var indexConfiguration = storageService.GetIndexDataOrNullAsync(indexName).Result; + + if (indexConfiguration == null) + { + return default; + } + cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); + + return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); + }; + + return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|name|{indexName}")); + } + + /// + public LuceneIndex? GetIndex(int identifier) + { + var index = (CacheSettings cs) => + { + var indexConfiguration = storageService.GetIndexDataOrNullAsync(identifier).Result; + + if (indexConfiguration == null) + { + return default; + } + cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); + + return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); + }; + + return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|identifier|{identifier}")); + } + + /// + public LuceneIndex GetRequiredIndex(string indexName) + { + var index = (CacheSettings cs) => + { + var indexConfiguration = storageService.GetIndexDataOrNullAsync(indexName).Result ?? throw new InvalidOperationException($"The index '{indexName}' does not exist."); + cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); + + return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); + }; + + return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|identifier|{indexName}")); + } + + /// + public void AddIndex(LuceneIndexModel indexConfiguration) + { + if (indexConfiguration == null) + { + throw new ArgumentNullException(nameof(indexConfiguration)); + } + + if (GetAllIndices().Any(i => i.IndexName.Equals(indexConfiguration.IndexName, StringComparison.OrdinalIgnoreCase) || indexConfiguration.Id == i.Identifier)) + { + throw new InvalidOperationException($"Attempted to register Lucene index with identifer [{indexConfiguration.Id}] and name [{indexConfiguration.IndexName}] but it is already registered."); + } + + storageService.TryCreateIndex(indexConfiguration); + } + + private ISet GetLuceneDependencyCacheKeys() + { + var dependencyCacheKeys = new HashSet + { + CacheHelper.BuildCacheItemName(new[] { LuceneIndexItemInfo.OBJECT_TYPE,"all" }, lowerCase: false), + CacheHelper.BuildCacheItemName(new[] { LuceneIndexLanguageItemInfo.OBJECT_TYPE,"all" }, lowerCase: false), + CacheHelper.BuildCacheItemName(new[] { LuceneIncludedPathItemInfo.OBJECT_TYPE,"all" }, lowerCase: false), + CacheHelper.BuildCacheItemName(new[] { LuceneContentTypeItemInfo.OBJECT_TYPE,"all" }, lowerCase: false) + }; + return dependencyCacheKeys; + } +} + diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexService.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexService.cs index c61f129..01722dc 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexService.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexService.cs @@ -1,52 +1,52 @@ -using Lucene.Net.Facet.Taxonomy; -using Lucene.Net.Facet.Taxonomy.Directory; -using Lucene.Net.Index; -using Lucene.Net.Store; - -using LuceneDirectory = Lucene.Net.Store.Directory; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public class DefaultLuceneIndexService : ILuceneIndexService -{ - public T UseIndexAndTaxonomyWriter(LuceneIndex index, Func useIndexWriter, IndexStorageModel storage, OpenMode openMode = OpenMode.CREATE_OR_APPEND) - { - using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); - - var analyzer = index.LuceneAnalyzer; - - //Create an index writer - var indexConfig = new IndexWriterConfig(AnalyzerStorage.AnalyzerLuceneVersion, analyzer) - { - OpenMode = openMode // create/overwrite index - }; - using var writer = new IndexWriter(indexDir, indexConfig); - - using LuceneDirectory taxonomyDir = FSDirectory.Open(storage.TaxonomyPath); - using var taxonomyWriter = new DirectoryTaxonomyWriter(taxonomyDir); - - return useIndexWriter(writer, taxonomyWriter); - } - - public TResult UseWriter(LuceneIndex index, Func useIndexWriter, IndexStorageModel storage, OpenMode openMode = OpenMode.CREATE_OR_APPEND) - { - using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); - - var analyzer = index.LuceneAnalyzer; - - //Create an index writer - var indexConfig = new IndexWriterConfig(AnalyzerStorage.AnalyzerLuceneVersion, analyzer) - { - OpenMode = openMode // create/overwrite index - }; - using var writer = new IndexWriter(indexDir, indexConfig); - - return useIndexWriter(writer); - } - - public void ResetIndex(LuceneIndex index) - { - index.StorageContext.EnforceRetentionPolicy(); - UseWriter(index, (IndexWriter writer) => true, index.StorageContext.GetNextGeneration(), OpenMode.CREATE); - } -} +using Lucene.Net.Facet.Taxonomy; +using Lucene.Net.Facet.Taxonomy.Directory; +using Lucene.Net.Index; +using Lucene.Net.Store; + +using LuceneDirectory = Lucene.Net.Store.Directory; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public class DefaultLuceneIndexService : ILuceneIndexService +{ + public T UseIndexAndTaxonomyWriter(LuceneIndex index, Func useIndexWriter, IndexStorageModel storage, OpenMode openMode = OpenMode.CREATE_OR_APPEND) + { + using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); + + var analyzer = index.LuceneAnalyzer; + + //Create an index writer + var indexConfig = new IndexWriterConfig(AnalyzerStorage.AnalyzerLuceneVersion, analyzer) + { + OpenMode = openMode // create/overwrite index + }; + using var writer = new IndexWriter(indexDir, indexConfig); + + using LuceneDirectory taxonomyDir = FSDirectory.Open(storage.TaxonomyPath); + using var taxonomyWriter = new DirectoryTaxonomyWriter(taxonomyDir); + + return useIndexWriter(writer, taxonomyWriter); + } + + public TResult UseWriter(LuceneIndex index, Func useIndexWriter, IndexStorageModel storage, OpenMode openMode = OpenMode.CREATE_OR_APPEND) + { + using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); + + var analyzer = index.LuceneAnalyzer; + + //Create an index writer + var indexConfig = new IndexWriterConfig(AnalyzerStorage.AnalyzerLuceneVersion, analyzer) + { + OpenMode = openMode // create/overwrite index + }; + using var writer = new IndexWriter(indexDir, indexConfig); + + return useIndexWriter(writer); + } + + public void ResetIndex(LuceneIndex index) + { + index.StorageContext.EnforceRetentionPolicy(); + UseWriter(index, (IndexWriter writer) => true, index.StorageContext.GetNextGeneration(), OpenMode.CREATE); + } +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexingStrategy.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexingStrategy.cs index 1b69022..6828510 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexingStrategy.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexingStrategy.cs @@ -1,41 +1,41 @@ -using Lucene.Net.Documents; -using Lucene.Net.Facet; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -/// -/// Default indexing strategy that provides simple indexing. -/// -public class DefaultLuceneIndexingStrategy : ILuceneIndexingStrategy -{ - /// - /// Called when indexing a search model. Enables overriding of multiple fields with custom data. - /// By default, no custom content item fields or secured items are indexed, only the contents of and fields defined in - /// - /// The currently being indexed. - /// Modified Lucene document. - public virtual Task MapToLuceneDocumentOrNull(IIndexEventItemModel item) - { - if (item.IsSecured) - { - return Task.FromResult(null); - } - - var indexDocument = new Document() - { - new TextField(nameof(item.Name), item.Name, Field.Store.YES), - }; - - return Task.FromResult(indexDocument); - } - - /// - public virtual FacetsConfig? FacetsConfigFactory() => null; - - /// - public virtual async Task> FindItemsToReindex(IndexEventWebPageItemModel changedItem) => await Task.FromResult(new List() { changedItem }); - - /// - public virtual async Task> FindItemsToReindex(IndexEventReusableItemModel changedItem) => await Task.FromResult(new List()); -} - +using Lucene.Net.Documents; +using Lucene.Net.Facet; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +/// +/// Default indexing strategy that provides simple indexing. +/// +public class DefaultLuceneIndexingStrategy : ILuceneIndexingStrategy +{ + /// + /// Called when indexing a search model. Enables overriding of multiple fields with custom data. + /// By default, no custom content item fields or secured items are indexed, only the contents of and fields defined in + /// + /// The currently being indexed. + /// Modified Lucene document. + public virtual Task MapToLuceneDocumentOrNull(IIndexEventItemModel item) + { + if (item.IsSecured) + { + return Task.FromResult(null); + } + + var indexDocument = new Document() + { + new TextField(nameof(item.Name), item.Name, Field.Store.YES), + }; + + return Task.FromResult(indexDocument); + } + + /// + public virtual FacetsConfig? FacetsConfigFactory() => null; + + /// + public virtual async Task> FindItemsToReindex(IndexEventWebPageItemModel changedItem) => await Task.FromResult(new List() { changedItem }); + + /// + public virtual async Task> FindItemsToReindex(IndexEventReusableItemModel changedItem) => await Task.FromResult(new List()); +} + diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneClient.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneClient.cs index fb23910..6ae63ba 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneClient.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneClient.cs @@ -1,56 +1,56 @@ -using Lucene.Net.Documents; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -/// -/// Contains methods to interface with the Lucene API. -/// -public interface ILuceneClient -{ - /// - /// Removes records from the Lucene index. - /// - /// The Lucene internal IDs of the records to delete. - /// The index containing the objects to delete. - /// - /// - /// - /// - /// - /// The number of records deleted. - Task DeleteRecords(IEnumerable itemGuids, string indexName); - - /// - /// Gets the indices of the Lucene application with basic statistics. - /// - /// The cancellation token for the task. - /// - /// - Task> GetStatistics(CancellationToken cancellationToken); - - /// - /// Updates the Lucene index with the dynamic data in each object of the passed . - /// - /// Logs an error if there are issues loading the node data. - /// The document to upsert into Lucene. - /// The index to upsert the data to. - /// The cancellation token for the task. - /// - /// - /// - /// - /// The number of objects processed. - Task UpsertRecords(IEnumerable documents, string indexName, CancellationToken cancellationToken); - - /// - /// Rebuilds the Lucene index by removing existing data from Lucene and indexing all - /// pages in the content tree included in the index. - /// - /// The index to rebuild. - /// The cancellation token for the task. - /// - /// - /// - /// - Task Rebuild(string indexName, CancellationToken? cancellationToken); -} +using Lucene.Net.Documents; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +/// +/// Contains methods to interface with the Lucene API. +/// +public interface ILuceneClient +{ + /// + /// Removes records from the Lucene index. + /// + /// The Lucene internal IDs of the records to delete. + /// The index containing the objects to delete. + /// + /// + /// + /// + /// + /// The number of records deleted. + Task DeleteRecords(IEnumerable itemGuids, string indexName); + + /// + /// Gets the indices of the Lucene application with basic statistics. + /// + /// The cancellation token for the task. + /// + /// + Task> GetStatistics(CancellationToken cancellationToken); + + /// + /// Updates the Lucene index with the dynamic data in each object of the passed . + /// + /// Logs an error if there are issues loading the node data. + /// The document to upsert into Lucene. + /// The index to upsert the data to. + /// The cancellation token for the task. + /// + /// + /// + /// + /// The number of objects processed. + Task UpsertRecords(IEnumerable documents, string indexName, CancellationToken cancellationToken); + + /// + /// Rebuilds the Lucene index by removing existing data from Lucene and indexing all + /// pages in the content tree included in the index. + /// + /// The index to rebuild. + /// The cancellation token for the task. + /// + /// + /// + /// + Task Rebuild(string indexName, CancellationToken? cancellationToken); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneConfigurationStorageService.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneConfigurationStorageService.cs index 2136dee..66ff7d9 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneConfigurationStorageService.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneConfigurationStorageService.cs @@ -1,14 +1,14 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public interface ILuceneConfigurationStorageService -{ - bool TryCreateIndex(LuceneIndexModel configuration); - Task TryEditIndexAsync(LuceneIndexModel configuration); - bool TryDeleteIndex(LuceneIndexModel configuration); - bool TryDeleteIndex(int id); - Task GetIndexDataOrNullAsync(int indexId); - Task GetIndexDataOrNullAsync(string indexName); - List GetExistingIndexNames(); - List GetIndexIds(); - Task> GetAllIndexDataAsync(); -} +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public interface ILuceneConfigurationStorageService +{ + bool TryCreateIndex(LuceneIndexModel configuration); + Task TryEditIndexAsync(LuceneIndexModel configuration); + bool TryDeleteIndex(LuceneIndexModel configuration); + bool TryDeleteIndex(int id); + Task GetIndexDataOrNullAsync(int indexId); + Task GetIndexDataOrNullAsync(string indexName); + List GetExistingIndexNames(); + List GetIndexIds(); + Task> GetAllIndexDataAsync(); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexManager.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexManager.cs index 5bf2bc7..6ba7382 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexManager.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexManager.cs @@ -1,46 +1,46 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -/// -/// Manages adding and getting of Lucene indexes. -/// -public interface ILuceneIndexManager -{ - /// - /// Gets all existing indexes. - /// - IEnumerable GetAllIndices(); - - /// - /// Gets a registered with the specified , - /// or null. - /// - /// The name of the index to retrieve. - /// - /// - LuceneIndex? GetIndex(string indexName); - - /// - /// Gets a registered with the specified , - /// or null. - /// - /// The identifier of the index to retrieve. - /// - /// - LuceneIndex? GetIndex(int identifier); - - /// - /// Gets a registered with the specified . If no index is found, a is thrown. - /// - /// The name of the index to retrieve. - /// - /// - LuceneIndex GetRequiredIndex(string indexName); - - /// - /// Adds an index to the store. - /// - /// The index to add. - /// - /// - void AddIndex(LuceneIndexModel indexConfiguration); -} +namespace Kentico.Xperience.Lucene.Core.Indexing; + +/// +/// Manages adding and getting of Lucene indexes. +/// +public interface ILuceneIndexManager +{ + /// + /// Gets all existing indexes. + /// + IEnumerable GetAllIndices(); + + /// + /// Gets a registered with the specified , + /// or null. + /// + /// The name of the index to retrieve. + /// + /// + LuceneIndex? GetIndex(string indexName); + + /// + /// Gets a registered with the specified , + /// or null. + /// + /// The identifier of the index to retrieve. + /// + /// + LuceneIndex? GetIndex(int identifier); + + /// + /// Gets a registered with the specified . If no index is found, a is thrown. + /// + /// The name of the index to retrieve. + /// + /// + LuceneIndex GetRequiredIndex(string indexName); + + /// + /// Adds an index to the store. + /// + /// The index to add. + /// + /// + void AddIndex(LuceneIndexModel indexConfiguration); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexService.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexService.cs index e73b87c..7fc4acc 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexService.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexService.cs @@ -1,13 +1,13 @@ -using Lucene.Net.Facet.Taxonomy; -using Lucene.Net.Index; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public interface ILuceneIndexService -{ - T UseIndexAndTaxonomyWriter(LuceneIndex index, Func useIndexWriter, IndexStorageModel storage, OpenMode openMode = OpenMode.CREATE_OR_APPEND); - - T UseWriter(LuceneIndex index, Func useIndexWriter, IndexStorageModel storage, OpenMode openMode = OpenMode.CREATE_OR_APPEND); - - void ResetIndex(LuceneIndex index); -} +using Lucene.Net.Facet.Taxonomy; +using Lucene.Net.Index; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public interface ILuceneIndexService +{ + T UseIndexAndTaxonomyWriter(LuceneIndex index, Func useIndexWriter, IndexStorageModel storage, OpenMode openMode = OpenMode.CREATE_OR_APPEND); + + T UseWriter(LuceneIndex index, Func useIndexWriter, IndexStorageModel storage, OpenMode openMode = OpenMode.CREATE_OR_APPEND); + + void ResetIndex(LuceneIndex index); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexStorageStrategy.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexStorageStrategy.cs index 55f2057..d6969ab 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexStorageStrategy.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexStorageStrategy.cs @@ -1,210 +1,210 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Text.RegularExpressions; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public interface ILuceneIndexStorageStrategy -{ - IEnumerable GetExistingIndices(string indexStoragePath); - string FormatPath(string indexRoot, int generation, bool isPublished); - string FormatTaxonomyPath(string indexRoot, int generation, bool isPublished); - void PublishIndex(IndexStorageModel storage); - bool ScheduleRemoval(IndexStorageModel storage); - bool PerformCleanup(string indexStoragePath); -} - -internal class GenerationStorageStrategy : ILuceneIndexStorageStrategy -{ - private const string IndexDeletionDirectoryName = ".trash"; - - public IEnumerable GetExistingIndices(string indexStoragePath) - { - if (!Directory.Exists(indexStoragePath)) - { - yield break; - } - - var grouped = Directory.GetDirectories(indexStoragePath) - .Select(ParseIndexStorageModel) - .Where(x => x.Success) - .GroupBy(x => x.Result?.Generation ?? -1); - - - foreach (var result in grouped) - { - var indexDir = result.FirstOrDefault(x => string.IsNullOrWhiteSpace(x.Result?.TaxonomyName)); - var taxonomyDir = result.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Result?.TaxonomyName)); - - if (indexDir is { Success: true, Result: var (indexPath, generation, published, _) }) - { - string taxonomyPath = taxonomyDir?.Result?.Path ?? FormatTaxonomyPath(indexStoragePath, generation, false); - yield return new IndexStorageModel(indexPath, taxonomyPath, generation, published); - } - } - } - - public string FormatPath(string indexRoot, int generation, bool isPublished) => Path.Combine(indexRoot, $"i-g{generation:0000000}-p_{isPublished}"); - public string FormatTaxonomyPath(string indexRoot, int generation, bool isPublished) => Path.Combine(indexRoot, $"i-g{generation:0000000}-p_{isPublished}_taxonomy"); - - public void PublishIndex(IndexStorageModel storage) - { - string root = Path.Combine(storage.Path, ".."); - var published = storage with - { - IsPublished = true, - Path = FormatPath(root, storage.Generation, true), - TaxonomyPath = FormatTaxonomyPath(root, storage.Generation, true) - }; - - Directory.Move(storage.Path, published.Path); - - if (Directory.Exists(storage.TaxonomyPath)) - { - Directory.Move(storage.TaxonomyPath, published.TaxonomyPath); - } - } - - public bool ScheduleRemoval(IndexStorageModel storage) - { - (string? path, string? taxonomyPath, int generation, bool _) = storage; - - string delBase = Path.Combine(path, "..", IndexDeletionDirectoryName); - Directory.CreateDirectory(delBase); - - string delPath = Path.Combine(path, "..", IndexDeletionDirectoryName, $"{generation:0000000}"); - try - { - Directory.Move(path, delPath); - Trace.WriteLine($"OP={path} NP={delPath}: removal scheduled", $"GenerationStorageStrategy.ScheduleRemoval"); - } - catch (IOException ioex) - { - Trace.WriteLine($"OP={path} NP={delPath}: {ioex}", $"GenerationStorageStrategy.ScheduleRemoval"); - // fail, directory is possibly locked by reader - return false; - } - - if (!string.IsNullOrWhiteSpace(taxonomyPath) && Directory.Exists(taxonomyPath)) - { - string delPathTaxon = Path.Combine(path, "..", IndexDeletionDirectoryName, $"{generation:0000000}_taxon"); - try - { - Directory.Move(taxonomyPath, delPathTaxon); - Trace.WriteLine($"OP={taxonomyPath} NP={delPathTaxon}: removal scheduled", $"GenerationStorageStrategy.ScheduleRemoval"); - } - catch (IOException ioex) - { - // fail, directory is possibly locked by reader - Trace.WriteLine($"OP={taxonomyPath} NP={delPathTaxon}: {ioex}", $"GenerationStorageStrategy.ScheduleRemoval"); - - // restore index - Directory.Move(delPath, path); - return false; - } - } - - return true; - } - - public bool PerformCleanup(string indexStoragePath) - { - string toDeleteDir = Path.Combine(indexStoragePath, IndexDeletionDirectoryName); - var thrashDir = new DirectoryInfo(toDeleteDir); - try - { - if (!thrashDir.Exists) - { - return true; - } - - foreach (var file in thrashDir.GetFiles()) - { - try - { - Trace.WriteLine($"F={file.Name}: delete", $"GenerationStorageStrategy.PerformCleanup"); - file.Delete(); - } - catch - { - // ignored, can't do anything about resource - next iteration will pick resource to delete again - } - } - - foreach (var dir in thrashDir.GetDirectories()) - { - try - { - Trace.WriteLine($"D={dir.Name}: delete *.*", $"GenerationStorageStrategy.PerformCleanup"); - dir.Delete(true); - } - catch - { - // ignored, can't do anything about resource - next iteration will pick resource to delete again - } - } - } - catch (IOException) - { - // directory might be destroyed or inaccessible - - return false; - } - - return true; - } - - internal record IndexStorageModelParseResult(string Path, int Generation, bool IsPublished, string? TaxonomyName); - private sealed record IndexStorageModelParsingResult( - bool Success, - [property: MemberNotNullWhen(true, "Success")] IndexStorageModelParseResult? Result - ); - - private IndexStorageModelParsingResult ParseIndexStorageModel(string directoryPath) - { - if (string.IsNullOrWhiteSpace(directoryPath)) - { - return new IndexStorageModelParsingResult(false, null); - } - - try - { - var dirInfo = new DirectoryInfo(directoryPath); - if (dirInfo.Name is { Length: > 0 } directoryName) - { - var matchResult = Regex.Match(directoryName, "i-g(?[0-9]*)-p_(?(true)|(false))(_(?[a-z0-9]*)){0,1}", RegexOptions.IgnoreCase | RegexOptions.Singleline); - switch (matchResult) - { - case { Success: true } r - when r.Groups["generation"] is { Success: true, Value: { Length: > 0 } gen } && - r.Groups["published"] is { Success: true, Value: { Length: > 0 } pub }: - { - string? taxonomyName = null; - if (r.Groups["taxonomy"] is { Success: true, Value: { Length: > 0 } taxonomy }) - { - taxonomyName = taxonomy; - } - - if (int.TryParse(gen, out int generation) && bool.TryParse(pub, out bool published)) - { - return new IndexStorageModelParsingResult(true, new IndexStorageModelParseResult(directoryPath, generation, published, taxonomyName)); - } - - break; - } - default: - { - return new IndexStorageModelParsingResult(false, null); - } - } - } - } - catch - { - // low priority, if path cannot be parsed, it is possibly not generated index - // ignored - } - - return new IndexStorageModelParsingResult(false, null); - } -} +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public interface ILuceneIndexStorageStrategy +{ + IEnumerable GetExistingIndices(string indexStoragePath); + string FormatPath(string indexRoot, int generation, bool isPublished); + string FormatTaxonomyPath(string indexRoot, int generation, bool isPublished); + void PublishIndex(IndexStorageModel storage); + bool ScheduleRemoval(IndexStorageModel storage); + bool PerformCleanup(string indexStoragePath); +} + +internal class GenerationStorageStrategy : ILuceneIndexStorageStrategy +{ + private const string IndexDeletionDirectoryName = ".trash"; + + public IEnumerable GetExistingIndices(string indexStoragePath) + { + if (!Directory.Exists(indexStoragePath)) + { + yield break; + } + + var grouped = Directory.GetDirectories(indexStoragePath) + .Select(ParseIndexStorageModel) + .Where(x => x.Success) + .GroupBy(x => x.Result?.Generation ?? -1); + + + foreach (var result in grouped) + { + var indexDir = result.FirstOrDefault(x => string.IsNullOrWhiteSpace(x.Result?.TaxonomyName)); + var taxonomyDir = result.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Result?.TaxonomyName)); + + if (indexDir is { Success: true, Result: var (indexPath, generation, published, _) }) + { + string taxonomyPath = taxonomyDir?.Result?.Path ?? FormatTaxonomyPath(indexStoragePath, generation, false); + yield return new IndexStorageModel(indexPath, taxonomyPath, generation, published); + } + } + } + + public string FormatPath(string indexRoot, int generation, bool isPublished) => Path.Combine(indexRoot, $"i-g{generation:0000000}-p_{isPublished}"); + public string FormatTaxonomyPath(string indexRoot, int generation, bool isPublished) => Path.Combine(indexRoot, $"i-g{generation:0000000}-p_{isPublished}_taxonomy"); + + public void PublishIndex(IndexStorageModel storage) + { + string root = Path.Combine(storage.Path, ".."); + var published = storage with + { + IsPublished = true, + Path = FormatPath(root, storage.Generation, true), + TaxonomyPath = FormatTaxonomyPath(root, storage.Generation, true) + }; + + Directory.Move(storage.Path, published.Path); + + if (Directory.Exists(storage.TaxonomyPath)) + { + Directory.Move(storage.TaxonomyPath, published.TaxonomyPath); + } + } + + public bool ScheduleRemoval(IndexStorageModel storage) + { + (string? path, string? taxonomyPath, int generation, bool _) = storage; + + string delBase = Path.Combine(path, "..", IndexDeletionDirectoryName); + Directory.CreateDirectory(delBase); + + string delPath = Path.Combine(path, "..", IndexDeletionDirectoryName, $"{generation:0000000}"); + try + { + Directory.Move(path, delPath); + Trace.WriteLine($"OP={path} NP={delPath}: removal scheduled", $"GenerationStorageStrategy.ScheduleRemoval"); + } + catch (IOException ioex) + { + Trace.WriteLine($"OP={path} NP={delPath}: {ioex}", $"GenerationStorageStrategy.ScheduleRemoval"); + // fail, directory is possibly locked by reader + return false; + } + + if (!string.IsNullOrWhiteSpace(taxonomyPath) && Directory.Exists(taxonomyPath)) + { + string delPathTaxon = Path.Combine(path, "..", IndexDeletionDirectoryName, $"{generation:0000000}_taxon"); + try + { + Directory.Move(taxonomyPath, delPathTaxon); + Trace.WriteLine($"OP={taxonomyPath} NP={delPathTaxon}: removal scheduled", $"GenerationStorageStrategy.ScheduleRemoval"); + } + catch (IOException ioex) + { + // fail, directory is possibly locked by reader + Trace.WriteLine($"OP={taxonomyPath} NP={delPathTaxon}: {ioex}", $"GenerationStorageStrategy.ScheduleRemoval"); + + // restore index + Directory.Move(delPath, path); + return false; + } + } + + return true; + } + + public bool PerformCleanup(string indexStoragePath) + { + string toDeleteDir = Path.Combine(indexStoragePath, IndexDeletionDirectoryName); + var thrashDir = new DirectoryInfo(toDeleteDir); + try + { + if (!thrashDir.Exists) + { + return true; + } + + foreach (var file in thrashDir.GetFiles()) + { + try + { + Trace.WriteLine($"F={file.Name}: delete", $"GenerationStorageStrategy.PerformCleanup"); + file.Delete(); + } + catch + { + // ignored, can't do anything about resource - next iteration will pick resource to delete again + } + } + + foreach (var dir in thrashDir.GetDirectories()) + { + try + { + Trace.WriteLine($"D={dir.Name}: delete *.*", $"GenerationStorageStrategy.PerformCleanup"); + dir.Delete(true); + } + catch + { + // ignored, can't do anything about resource - next iteration will pick resource to delete again + } + } + } + catch (IOException) + { + // directory might be destroyed or inaccessible + + return false; + } + + return true; + } + + internal record IndexStorageModelParseResult(string Path, int Generation, bool IsPublished, string? TaxonomyName); + private sealed record IndexStorageModelParsingResult( + bool Success, + [property: MemberNotNullWhen(true, "Success")] IndexStorageModelParseResult? Result + ); + + private IndexStorageModelParsingResult ParseIndexStorageModel(string directoryPath) + { + if (string.IsNullOrWhiteSpace(directoryPath)) + { + return new IndexStorageModelParsingResult(false, null); + } + + try + { + var dirInfo = new DirectoryInfo(directoryPath); + if (dirInfo.Name is { Length: > 0 } directoryName) + { + var matchResult = Regex.Match(directoryName, "i-g(?[0-9]*)-p_(?(true)|(false))(_(?[a-z0-9]*)){0,1}", RegexOptions.IgnoreCase | RegexOptions.Singleline); + switch (matchResult) + { + case { Success: true } r + when r.Groups["generation"] is { Success: true, Value: { Length: > 0 } gen } && + r.Groups["published"] is { Success: true, Value: { Length: > 0 } pub }: + { + string? taxonomyName = null; + if (r.Groups["taxonomy"] is { Success: true, Value: { Length: > 0 } taxonomy }) + { + taxonomyName = taxonomy; + } + + if (int.TryParse(gen, out int generation) && bool.TryParse(pub, out bool published)) + { + return new IndexStorageModelParsingResult(true, new IndexStorageModelParseResult(directoryPath, generation, published, taxonomyName)); + } + + break; + } + default: + { + return new IndexStorageModelParsingResult(false, null); + } + } + } + } + catch + { + // low priority, if path cannot be parsed, it is possibly not generated index + // ignored + } + + return new IndexStorageModelParsingResult(false, null); + } +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexingStrategy.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexingStrategy.cs index dc4e2c7..cf259a0 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexingStrategy.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneIndexingStrategy.cs @@ -1,34 +1,34 @@ -using Lucene.Net.Documents; -using Lucene.Net.Facet; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public interface ILuceneIndexingStrategy -{ - /// - /// Called when indexing a search model. Enables overriding of multiple fields with custom data. - /// - /// The currently being indexed. - /// Modified Lucene document. - Task MapToLuceneDocumentOrNull(IIndexEventItemModel item); - - /// - /// When overriden and configuration supplied, indexing will also create taxonomy index for facet search - /// - /// Facet configuration for the index thta is used in indexing and querying - FacetsConfig? FacetsConfigFactory(); - - /// - /// Triggered by modifications to a web page item, which is provided to determine what other items should be included for indexing - /// - /// The web page item that was modified - /// Items that should be passed to for indexing - Task> FindItemsToReindex(IndexEventWebPageItemModel changedItem); - - /// - /// Triggered by modifications to a reusable content item, which is provided to determine what other items should be included for indexing - /// - /// The reusable content item that was modified - /// Items that should be passed to for indexing - Task> FindItemsToReindex(IndexEventReusableItemModel changedItem); -} +using Lucene.Net.Documents; +using Lucene.Net.Facet; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public interface ILuceneIndexingStrategy +{ + /// + /// Called when indexing a search model. Enables overriding of multiple fields with custom data. + /// + /// The currently being indexed. + /// Modified Lucene document. + Task MapToLuceneDocumentOrNull(IIndexEventItemModel item); + + /// + /// When overriden and configuration supplied, indexing will also create taxonomy index for facet search + /// + /// Facet configuration for the index thta is used in indexing and querying + FacetsConfig? FacetsConfigFactory(); + + /// + /// Triggered by modifications to a web page item, which is provided to determine what other items should be included for indexing + /// + /// The web page item that was modified + /// Items that should be passed to for indexing + Task> FindItemsToReindex(IndexEventWebPageItemModel changedItem); + + /// + /// Triggered by modifications to a reusable content item, which is provided to determine what other items should be included for indexing + /// + /// The reusable content item that was modified + /// Items that should be passed to for indexing + Task> FindItemsToReindex(IndexEventReusableItemModel changedItem); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneTaskLogger.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneTaskLogger.cs index 5705d80..115d0a6 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneTaskLogger.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneTaskLogger.cs @@ -1,24 +1,24 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -/// -/// Contains methods for logging s and s -/// for processing by and . -/// -public interface ILuceneTaskLogger -{ - /// - /// Logs an for each registered crawler. Then, loops - /// through all registered Lucene indexes and logs a task if the passed is indexed. - /// - /// The that triggered the event. - /// The name of the Xperience event that was triggered. - Task HandleEvent(IndexEventWebPageItemModel webpageItem, string eventName); - - Task HandleReusableItemEvent(IndexEventReusableItemModel reusableItem, string eventName); - - /// - /// Logs a single . - /// - /// The task to log. - void LogIndexTask(LuceneQueueItem task); -} +namespace Kentico.Xperience.Lucene.Core.Indexing; + +/// +/// Contains methods for logging s and s +/// for processing by and . +/// +public interface ILuceneTaskLogger +{ + /// + /// Logs an for each registered crawler. Then, loops + /// through all registered Lucene indexes and logs a task if the passed is indexed. + /// + /// The that triggered the event. + /// The name of the Xperience event that was triggered. + Task HandleEvent(IndexEventWebPageItemModel webpageItem, string eventName); + + Task HandleReusableItemEvent(IndexEventReusableItemModel reusableItem, string eventName); + + /// + /// Logs a single . + /// + /// The task to log. + void LogIndexTask(LuceneQueueItem task); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneTaskProcessor.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneTaskProcessor.cs index bf03406..3ad10da 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneTaskProcessor.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneTaskProcessor.cs @@ -1,20 +1,20 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -/// -/// Processes tasks from . -/// -public interface ILuceneTaskProcessor -{ - /// - /// Processes multiple queue items from all Lucene indexes in batches. Lucene - /// automatically applies batching in multiples of 1,000 when using their API, - /// so all queue items are forwarded to the API. - /// - /// The items to process. - /// The cancellation token for the task. - /// - /// The number of items processed. - - Task ProcessLuceneTasks(IEnumerable queueItems, CancellationToken cancellationToken, int maximumBatchSize = 100); - -} +namespace Kentico.Xperience.Lucene.Core.Indexing; + +/// +/// Processes tasks from . +/// +public interface ILuceneTaskProcessor +{ + /// + /// Processes multiple queue items from all Lucene indexes in batches. Lucene + /// automatically applies batching in multiples of 1,000 when using their API, + /// so all queue items are forwarded to the API. + /// + /// The items to process. + /// The cancellation token for the task. + /// + /// The number of items processed. + + Task ProcessLuceneTasks(IEnumerable queueItems, CancellationToken cancellationToken, int maximumBatchSize = 100); + +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/IndexStorageContext.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/IndexStorageContext.cs index c1cd157..809732f 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/IndexStorageContext.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/IndexStorageContext.cs @@ -1,151 +1,151 @@ -using System.Diagnostics; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public record IndexRetentionPolicy(int NumberOfKeptPublishedGenerations); - -public class IndexStorageContext -{ - private readonly ILuceneIndexStorageStrategy storageStrategy; - private readonly string indexStoragePathRoot; - private readonly IndexRetentionPolicy retentionPolicy; - - public IndexStorageContext(ILuceneIndexStorageStrategy selectedStorageStrategy, string indexStoragePathRoot, IndexRetentionPolicy retentionPolicy) - { - storageStrategy = selectedStorageStrategy; - this.indexStoragePathRoot = indexStoragePathRoot; - this.retentionPolicy = retentionPolicy; - } - - public IndexStorageModel GetPublishedIndex() - { - - var published = storageStrategy - .GetExistingIndices(indexStoragePathRoot) - .Where(x => x.IsPublished) - .MaxBy(x => x.Generation); - - if (published == null) - { - string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, 1, false); - string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, 1, false); - published = new IndexStorageModel(indexPath, taxonomyPath, 1, true); - } - - return published; - } - - /// - /// Gets next generation of index - /// - public IndexStorageModel GetNextGeneration() - { - var lastIndex = storageStrategy - .GetExistingIndices(indexStoragePathRoot) - .MaxBy(x => x.Generation); - - IndexStorageModel? newIndex; - switch (lastIndex) - { - case var (_, _, generation, published): - int nextGeneration = published ? generation + 1 : generation; - string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, nextGeneration, false); - string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, nextGeneration, false); - newIndex = new IndexStorageModel(indexPath, taxonomyPath, nextGeneration, false); - break; - default: - newIndex = new IndexStorageModel("", "", 1, false); - break; - } - - return newIndex with { Path = storageStrategy.FormatPath(indexStoragePathRoot, newIndex.Generation, newIndex.IsPublished) }; - } - - public IndexStorageModel GetLastGeneration(bool defaultPublished) - { - var model = storageStrategy - .GetExistingIndices(indexStoragePathRoot) - .MaxBy(x => x.Generation); - if (model == null) - { - string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, 1, defaultPublished); - string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, 1, defaultPublished); - model = new IndexStorageModel(indexPath, taxonomyPath, 1, defaultPublished); - } - return model; - } - - /// - /// method returns last writable index storage model - /// - /// Storage model with information about writable index - /// thrown when unexpected model occurs - public IndexStorageModel GetNextOrOpenNextGeneration() - { - var lastIndex = storageStrategy - .GetExistingIndices(indexStoragePathRoot) - .MaxBy(x => x.Generation); - - switch (lastIndex) - { - case { IsPublished: false }: - return lastIndex; - case (_, _, var generation, true): - { - string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, generation + 1, false); - string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, generation + 1, false); - return new IndexStorageModel(indexPath, taxonomyPath, generation + 1, false); - } - case null: - { - string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, 1, false); - string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, 1, false); - // no existing index, lets create new one - return new IndexStorageModel(indexPath, taxonomyPath, 1, false); - } - default: - throw new ArgumentException($"Non-null last index storage with invalid settings '{lastIndex}'"); - } - } - - public void PublishIndex(IndexStorageModel storage) => storageStrategy.PublishIndex(storage); - - public void EnforceRetentionPolicy() - { - int kept = retentionPolicy.NumberOfKeptPublishedGenerations; - - var ordered = storageStrategy - .GetExistingIndices(indexStoragePathRoot) - .OrderByDescending(s => s.Generation) - .ToArray(); - - Trace.WriteLine($"C={ordered.Length}", $"IndexStorageContext.EnforceRetentionPolicy"); - - for (int i = 0; i < ordered.Length; i++) - { - var current = ordered[i]; - if (kept > 0) - { - if (current.IsPublished) - { - Trace.WriteLine($"I={i} K={kept} G={current.Generation} P={current.IsPublished}: keep", $"IndexStorageContext.EnforceRetentionPolicy"); - kept -= 1; - continue; - } - else - { - // ignoring last unpublished, might be just regeneration in progress - Trace.WriteLine($"I={i} K={kept} G={current.Generation} P={current.IsPublished}: keep", $"IndexStorageContext.EnforceRetentionPolicy"); - } - } - - if (kept < 1) - { - Trace.WriteLine($"I={i} K={kept} G={current.Generation} P={current.IsPublished}: remove", $"IndexStorageContext.EnforceRetentionPolicy"); - storageStrategy.ScheduleRemoval(current); - } - } - - storageStrategy.PerformCleanup(indexStoragePathRoot); - } -} +using System.Diagnostics; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public record IndexRetentionPolicy(int NumberOfKeptPublishedGenerations); + +public class IndexStorageContext +{ + private readonly ILuceneIndexStorageStrategy storageStrategy; + private readonly string indexStoragePathRoot; + private readonly IndexRetentionPolicy retentionPolicy; + + public IndexStorageContext(ILuceneIndexStorageStrategy selectedStorageStrategy, string indexStoragePathRoot, IndexRetentionPolicy retentionPolicy) + { + storageStrategy = selectedStorageStrategy; + this.indexStoragePathRoot = indexStoragePathRoot; + this.retentionPolicy = retentionPolicy; + } + + public IndexStorageModel GetPublishedIndex() + { + + var published = storageStrategy + .GetExistingIndices(indexStoragePathRoot) + .Where(x => x.IsPublished) + .MaxBy(x => x.Generation); + + if (published == null) + { + string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, 1, false); + string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, 1, false); + published = new IndexStorageModel(indexPath, taxonomyPath, 1, true); + } + + return published; + } + + /// + /// Gets next generation of index + /// + public IndexStorageModel GetNextGeneration() + { + var lastIndex = storageStrategy + .GetExistingIndices(indexStoragePathRoot) + .MaxBy(x => x.Generation); + + IndexStorageModel? newIndex; + switch (lastIndex) + { + case var (_, _, generation, published): + int nextGeneration = published ? generation + 1 : generation; + string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, nextGeneration, false); + string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, nextGeneration, false); + newIndex = new IndexStorageModel(indexPath, taxonomyPath, nextGeneration, false); + break; + default: + newIndex = new IndexStorageModel("", "", 1, false); + break; + } + + return newIndex with { Path = storageStrategy.FormatPath(indexStoragePathRoot, newIndex.Generation, newIndex.IsPublished) }; + } + + public IndexStorageModel GetLastGeneration(bool defaultPublished) + { + var model = storageStrategy + .GetExistingIndices(indexStoragePathRoot) + .MaxBy(x => x.Generation); + if (model == null) + { + string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, 1, defaultPublished); + string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, 1, defaultPublished); + model = new IndexStorageModel(indexPath, taxonomyPath, 1, defaultPublished); + } + return model; + } + + /// + /// method returns last writable index storage model + /// + /// Storage model with information about writable index + /// thrown when unexpected model occurs + public IndexStorageModel GetNextOrOpenNextGeneration() + { + var lastIndex = storageStrategy + .GetExistingIndices(indexStoragePathRoot) + .MaxBy(x => x.Generation); + + switch (lastIndex) + { + case { IsPublished: false }: + return lastIndex; + case (_, _, var generation, true): + { + string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, generation + 1, false); + string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, generation + 1, false); + return new IndexStorageModel(indexPath, taxonomyPath, generation + 1, false); + } + case null: + { + string indexPath = storageStrategy.FormatPath(indexStoragePathRoot, 1, false); + string taxonomyPath = storageStrategy.FormatTaxonomyPath(indexStoragePathRoot, 1, false); + // no existing index, lets create new one + return new IndexStorageModel(indexPath, taxonomyPath, 1, false); + } + default: + throw new ArgumentException($"Non-null last index storage with invalid settings '{lastIndex}'"); + } + } + + public void PublishIndex(IndexStorageModel storage) => storageStrategy.PublishIndex(storage); + + public void EnforceRetentionPolicy() + { + int kept = retentionPolicy.NumberOfKeptPublishedGenerations; + + var ordered = storageStrategy + .GetExistingIndices(indexStoragePathRoot) + .OrderByDescending(s => s.Generation) + .ToArray(); + + Trace.WriteLine($"C={ordered.Length}", $"IndexStorageContext.EnforceRetentionPolicy"); + + for (int i = 0; i < ordered.Length; i++) + { + var current = ordered[i]; + if (kept > 0) + { + if (current.IsPublished) + { + Trace.WriteLine($"I={i} K={kept} G={current.Generation} P={current.IsPublished}: keep", $"IndexStorageContext.EnforceRetentionPolicy"); + kept -= 1; + continue; + } + else + { + // ignoring last unpublished, might be just regeneration in progress + Trace.WriteLine($"I={i} K={kept} G={current.Generation} P={current.IsPublished}: keep", $"IndexStorageContext.EnforceRetentionPolicy"); + } + } + + if (kept < 1) + { + Trace.WriteLine($"I={i} K={kept} G={current.Generation} P={current.IsPublished}: remove", $"IndexStorageContext.EnforceRetentionPolicy"); + storageStrategy.ScheduleRemoval(current); + } + } + + storageStrategy.PerformCleanup(indexStoragePathRoot); + } +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/IndexStorageModel.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/IndexStorageModel.cs index 21f5554..e03a68a 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/IndexStorageModel.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/IndexStorageModel.cs @@ -1,3 +1,3 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public record IndexStorageModel(string Path, string TaxonomyPath, int Generation, bool IsPublished); +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public record IndexStorageModel(string Path, string TaxonomyPath, int Generation, bool IsPublished); diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexContentType.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexContentType.cs index ac88b27..aed1b72 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexContentType.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexContentType.cs @@ -1,29 +1,29 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public class LuceneIndexContentType -{ - /// - /// Name of the indexed content type for an indexed path - /// - public string ContentTypeName { get; set; } = ""; - - /// - /// Displayed name of the indexed content type for an indexed path which will be shown in admin UI - /// - public string ContentTypeDisplayName { get; set; } = ""; - - /// - /// Id of the associated Lucene path item - /// - public int LucenePathItemId { get; set; } - - public LuceneIndexContentType() - { } - - public LuceneIndexContentType(string className, string classDisplayName, int lucenePathItemId) - { - ContentTypeName = className; - ContentTypeDisplayName = classDisplayName; - LucenePathItemId = lucenePathItemId; - } -} +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public class LuceneIndexContentType +{ + /// + /// Name of the indexed content type for an indexed path + /// + public string ContentTypeName { get; set; } = ""; + + /// + /// Displayed name of the indexed content type for an indexed path which will be shown in admin UI + /// + public string ContentTypeDisplayName { get; set; } = ""; + + /// + /// Id of the associated Lucene path item + /// + public int LucenePathItemId { get; set; } + + public LuceneIndexContentType() + { } + + public LuceneIndexContentType(string className, string classDisplayName, int lucenePathItemId) + { + ContentTypeName = className; + ContentTypeDisplayName = classDisplayName; + LucenePathItemId = lucenePathItemId; + } +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexIncludedPath.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexIncludedPath.cs index 245ecf5..4003a58 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexIncludedPath.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexIncludedPath.cs @@ -1,37 +1,37 @@ -using System.Text.Json.Serialization; - -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public class LuceneIndexIncludedPath -{ - /// - /// The node alias pattern that will be used to match pages in the content tree for indexing. - /// - /// For example, "/Blogs/Products/" will index all pages under the "Products" page. - public string AliasPath { get; } - - /// - /// A list of content types under the specified that will be indexed. - /// - public List ContentTypes { get; set; } = []; - - /// - /// The internal identifier of the included path. - /// - public int? Identifier { get; set; } - - [JsonConstructor] - public LuceneIndexIncludedPath(string aliasPath) => AliasPath = aliasPath; - - /// - /// - /// - /// - /// - public LuceneIndexIncludedPath(LuceneIncludedPathItemInfo indexPath, IEnumerable contentTypes) - { - AliasPath = indexPath.LuceneIncludedPathItemAliasPath; - ContentTypes = contentTypes.ToList(); - Identifier = indexPath.LuceneIncludedPathItemId; - } -} +using System.Text.Json.Serialization; + +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public class LuceneIndexIncludedPath +{ + /// + /// The node alias pattern that will be used to match pages in the content tree for indexing. + /// + /// For example, "/Blogs/Products/" will index all pages under the "Products" page. + public string AliasPath { get; } + + /// + /// A list of content types under the specified that will be indexed. + /// + public List ContentTypes { get; set; } = []; + + /// + /// The internal identifier of the included path. + /// + public int? Identifier { get; set; } + + [JsonConstructor] + public LuceneIndexIncludedPath(string aliasPath) => AliasPath = aliasPath; + + /// + /// + /// + /// + /// + public LuceneIndexIncludedPath(LuceneIncludedPathItemInfo indexPath, IEnumerable contentTypes) + { + AliasPath = indexPath.LuceneIncludedPathItemAliasPath; + ContentTypes = contentTypes.ToList(); + Identifier = indexPath.LuceneIncludedPathItemId; + } +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexStatisticsModel.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexStatisticsModel.cs index 15c420d..a764674 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexStatisticsModel.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexStatisticsModel.cs @@ -1,20 +1,20 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -public class LuceneIndexStatisticsModel -{ - // - // Summary: - // Index name. - public string? Name { get; set; } - - // - // Summary: - // Date of last update. - public DateTime UpdatedAt { get; set; } - - // - // Summary: - // Number of records contained in the index - public int Entries { get; set; } - -} +namespace Kentico.Xperience.Lucene.Core.Indexing; + +public class LuceneIndexStatisticsModel +{ + // + // Summary: + // Index name. + public string? Name { get; set; } + + // + // Summary: + // Date of last update. + public DateTime UpdatedAt { get; set; } + + // + // Summary: + // Number of records contained in the index + public int Entries { get; set; } + +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneQueueItem.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneQueueItem.cs index 5f8f71f..444bab3 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneQueueItem.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneQueueItem.cs @@ -1,46 +1,46 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -/// -/// A queued item to be processed by which -/// represents a recent change made to an indexed which is a representation of a . -/// -public sealed class LuceneQueueItem -{ - /// - /// The that was changed. - /// - public IIndexEventItemModel ItemToIndex { get; } - - /// - /// The type of the Lucene task. - /// - public LuceneTaskType TaskType { get; } - - /// - /// The code name of the Lucene index to be updated. - /// - public string IndexName { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The that was changed. - /// The type of the Lucene task. - /// The code name of the Lucene index to be updated. - /// - public LuceneQueueItem(IIndexEventItemModel itemToIndex, LuceneTaskType taskType, string indexName) - { - if (string.IsNullOrEmpty(indexName)) - { - throw new ArgumentNullException(nameof(indexName)); - } - if (taskType != LuceneTaskType.PUBLISH_INDEX && itemToIndex == null) - { - throw new ArgumentNullException(nameof(itemToIndex)); - } - - ItemToIndex = itemToIndex; - TaskType = taskType; - IndexName = indexName; - } -} +namespace Kentico.Xperience.Lucene.Core.Indexing; + +/// +/// A queued item to be processed by which +/// represents a recent change made to an indexed which is a representation of a . +/// +public sealed class LuceneQueueItem +{ + /// + /// The that was changed. + /// + public IIndexEventItemModel ItemToIndex { get; } + + /// + /// The type of the Lucene task. + /// + public LuceneTaskType TaskType { get; } + + /// + /// The code name of the Lucene index to be updated. + /// + public string IndexName { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The that was changed. + /// The type of the Lucene task. + /// The code name of the Lucene index to be updated. + /// + public LuceneQueueItem(IIndexEventItemModel itemToIndex, LuceneTaskType taskType, string indexName) + { + if (string.IsNullOrEmpty(indexName)) + { + throw new ArgumentNullException(nameof(indexName)); + } + if (taskType != LuceneTaskType.PUBLISH_INDEX && itemToIndex == null) + { + throw new ArgumentNullException(nameof(itemToIndex)); + } + + ItemToIndex = itemToIndex; + TaskType = taskType; + IndexName = indexName; + } +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneTaskType.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneTaskType.cs index 276ad36..7469cda 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneTaskType.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneTaskType.cs @@ -1,26 +1,26 @@ -using Kentico.Xperience.Lucene.Core.Indexing; - -namespace Kentico.Xperience.Lucene; - -/// -/// Represents the type of a . -/// -public enum LuceneTaskType -{ - /// - /// Unsupported task type. - /// - UNKNOWN, - - /// - /// A task for a page which should be removed from the index. - /// - DELETE, - - /// - /// Task marks the end of indexed items, index is published after this task occurs - /// - PUBLISH_INDEX, - - UPDATE -} +using Kentico.Xperience.Lucene.Core.Indexing; + +namespace Kentico.Xperience.Lucene; + +/// +/// Represents the type of a . +/// +public enum LuceneTaskType +{ + /// + /// Unsupported task type. + /// + UNKNOWN, + + /// + /// A task for a page which should be removed from the index. + /// + DELETE, + + /// + /// Task marks the end of indexed items, index is published after this task occurs + /// + PUBLISH_INDEX, + + UPDATE +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/StrategyStorage.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/StrategyStorage.cs index ec8b567..0b3f63d 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/StrategyStorage.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/StrategyStorage.cs @@ -1,13 +1,13 @@ -namespace Kentico.Xperience.Lucene.Core.Indexing; - -internal static class StrategyStorage -{ - public static Dictionary Strategies { get; private set; } - static StrategyStorage() => Strategies = []; - - public static void AddStrategy(string strategyName) where TStrategy : ILuceneIndexingStrategy => Strategies.Add(strategyName, typeof(TStrategy)); - public static Type GetOrDefault(string strategyName) => - Strategies.TryGetValue(strategyName, out var type) - ? type - : typeof(DefaultLuceneIndexingStrategy); -} +namespace Kentico.Xperience.Lucene.Core.Indexing; + +internal static class StrategyStorage +{ + public static Dictionary Strategies { get; private set; } + static StrategyStorage() => Strategies = []; + + public static void AddStrategy(string strategyName) where TStrategy : ILuceneIndexingStrategy => Strategies.Add(strategyName, typeof(TStrategy)); + public static Type GetOrDefault(string strategyName) => + Strategies.TryGetValue(strategyName, out var type) + ? type + : typeof(DefaultLuceneIndexingStrategy); +} diff --git a/src/Kentico.Xperience.Lucene.Core/LuceneQueueWorker.cs b/src/Kentico.Xperience.Lucene.Core/LuceneQueueWorker.cs index 3f4b5d4..f0aa34e 100644 --- a/src/Kentico.Xperience.Lucene.Core/LuceneQueueWorker.cs +++ b/src/Kentico.Xperience.Lucene.Core/LuceneQueueWorker.cs @@ -1,71 +1,71 @@ -using CMS.Base; -using CMS.Core; - -using Kentico.Xperience.Lucene.Core.Indexing; - -namespace Kentico.Xperience.Lucene.Core; - -/// -/// Thread worker which enqueues recently updated or deleted nodes indexed -/// by Lucene and processes the tasks in the background thread. -/// -internal class LuceneQueueWorker : ThreadQueueWorker -{ - private readonly ILuceneTaskProcessor luceneTaskProcessor; - - - /// - protected override int DefaultInterval => 10000; - - - /// - /// Initializes a new instance of the class. - /// Should not be called directly- the worker should be initialized during startup using - /// . - /// - public LuceneQueueWorker() => luceneTaskProcessor = Service.Resolve() ?? throw new InvalidOperationException($"{nameof(ILuceneTaskProcessor)} is not registered."); - - - /// - /// Adds an to the worker queue to be processed. - /// - /// The item to be added to the queue. - /// - public static void EnqueueLuceneQueueItem(LuceneQueueItem queueItem) - { - if (queueItem == null || (queueItem.ItemToIndex == null && queueItem.TaskType != LuceneTaskType.PUBLISH_INDEX) || string.IsNullOrEmpty(queueItem.IndexName)) - { - return; - } - - if (queueItem.TaskType == LuceneTaskType.UNKNOWN) - { - return; - } - - var indexManager = Service.Resolve() ?? throw new InvalidOperationException($"{nameof(ILuceneIndexManager)} is not registered."); - - if (indexManager.GetIndex(queueItem.IndexName) == null) - { - throw new InvalidOperationException($"Attempted to log task for Lucene index '{queueItem.IndexName},' but it is not registered."); - } - - Current.Enqueue(queueItem, false); - } - - - /// - protected override void Finish() => RunProcess(); - - - /// - protected override void ProcessItem(LuceneQueueItem item) - { - } - - - /// - protected override int ProcessItems(IEnumerable items) => - luceneTaskProcessor.ProcessLuceneTasks(items, CancellationToken.None).GetAwaiter().GetResult(); - -} +using CMS.Base; +using CMS.Core; + +using Kentico.Xperience.Lucene.Core.Indexing; + +namespace Kentico.Xperience.Lucene.Core; + +/// +/// Thread worker which enqueues recently updated or deleted nodes indexed +/// by Lucene and processes the tasks in the background thread. +/// +internal class LuceneQueueWorker : ThreadQueueWorker +{ + private readonly ILuceneTaskProcessor luceneTaskProcessor; + + + /// + protected override int DefaultInterval => 10000; + + + /// + /// Initializes a new instance of the class. + /// Should not be called directly- the worker should be initialized during startup using + /// . + /// + public LuceneQueueWorker() => luceneTaskProcessor = Service.Resolve() ?? throw new InvalidOperationException($"{nameof(ILuceneTaskProcessor)} is not registered."); + + + /// + /// Adds an to the worker queue to be processed. + /// + /// The item to be added to the queue. + /// + public static void EnqueueLuceneQueueItem(LuceneQueueItem queueItem) + { + if (queueItem == null || (queueItem.ItemToIndex == null && queueItem.TaskType != LuceneTaskType.PUBLISH_INDEX) || string.IsNullOrEmpty(queueItem.IndexName)) + { + return; + } + + if (queueItem.TaskType == LuceneTaskType.UNKNOWN) + { + return; + } + + var indexManager = Service.Resolve() ?? throw new InvalidOperationException($"{nameof(ILuceneIndexManager)} is not registered."); + + if (indexManager.GetIndex(queueItem.IndexName) == null) + { + throw new InvalidOperationException($"Attempted to log task for Lucene index '{queueItem.IndexName},' but it is not registered."); + } + + Current.Enqueue(queueItem, false); + } + + + /// + protected override void Finish() => RunProcess(); + + + /// + protected override void ProcessItem(LuceneQueueItem item) + { + } + + + /// + protected override int ProcessItems(IEnumerable items) => + luceneTaskProcessor.ProcessLuceneTasks(items, CancellationToken.None).GetAwaiter().GetResult(); + +} diff --git a/src/Kentico.Xperience.Lucene.Core/LuceneStartupExtensions.cs b/src/Kentico.Xperience.Lucene.Core/LuceneStartupExtensions.cs index 029cc70..021f924 100644 --- a/src/Kentico.Xperience.Lucene.Core/LuceneStartupExtensions.cs +++ b/src/Kentico.Xperience.Lucene.Core/LuceneStartupExtensions.cs @@ -1,171 +1,171 @@ -using Kentico.Xperience.Lucene.Core; -using Kentico.Xperience.Lucene.Core.Indexing; -using Kentico.Xperience.Lucene.Core.Search; - -using Lucene.Net.Analysis; -using Lucene.Net.Analysis.Standard; -using Lucene.Net.Util; - -namespace Microsoft.Extensions.DependencyInjection; - -public static class LuceneStartupExtensions -{ - /// - /// Adds Lucene services and custom module to application using the and for all indexes - /// - /// the which will be modified - /// Returns this instance of , allowing for further configuration in a fluent manner. - public static IServiceCollection AddKenticoLucene(this IServiceCollection serviceCollection) - { - serviceCollection.AddLuceneServicesInternal(); - - StrategyStorage.AddStrategy("Default"); - AnalyzerStorage.AddAnalyzer("Standard"); - - return serviceCollection; - } - - - /// - /// Adds Lucene services and custom module to application with customized options provided by the - /// in the action. - /// - /// the which will be modified - /// which will configure the - /// Returns this instance of , allowing for further configuration in a fluent manner. - public static IServiceCollection AddKenticoLucene(this IServiceCollection serviceCollection, Action configure) - { - serviceCollection.AddLuceneServicesInternal(); - - var builder = new LuceneBuilder(serviceCollection); - - configure(builder); - - if (builder.IncludeDefaultStrategy) - { - builder.RegisterStrategy("Default"); - } - - if (builder.IncludeDefaultAnalyzer) - { - builder.RegisterAnalyzer("Standard"); - } - - return serviceCollection; - } - - - private static IServiceCollection AddLuceneServicesInternal(this IServiceCollection services) => - services - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddTransient(); -} - - -public interface ILuceneBuilder -{ - /// - /// Registers the given as a transient service under - /// - /// The custom type of - /// Used internally to enable dynamic assignment of strategies to search indexes. Names must be unique. - /// - /// Thrown if a strategy has already been registered with the given - /// - /// Returns this instance of , allowing for further configuration in a fluent manner. - ILuceneBuilder RegisterStrategy(string strategyName) where TStrategy : class, ILuceneIndexingStrategy; - - - /// - /// Registers the given and - /// as a selectable analyzer in the Admin UI - /// - /// The type of - /// Used internally to enable dynamic assignment of analyzers to search indexes. Names must be unique. - /// - /// Thrown if an analyzer has already been registered with the given - /// - /// Returns this instance of , allowing for further configuration in a fluent manner. - ILuceneBuilder RegisterAnalyzer(string analyzerName) where TAnalyzer : Analyzer; - - - /// - /// Sets the lucene version which will be used by for search indexes. - /// Defaults to - /// - /// to be used by the - /// Returns this instance of , allowing for further configuration in a fluent manner. - ILuceneBuilder SetAnalyzerLuceneVersion(LuceneVersion matchVersion); -} - - -internal class LuceneBuilder : ILuceneBuilder -{ - private readonly IServiceCollection serviceCollection; - - /// - /// If true, the will be available as an explicitly selectable indexing strategy - /// within the Admin UI. Defaults to true - /// - public bool IncludeDefaultStrategy { get; set; } = true; - - /// - /// If true, the will be available as an explicitly selectable analyzer - /// within the Admin UI. Defaults to true - /// - public bool IncludeDefaultAnalyzer { get; set; } = true; - - public LuceneBuilder(IServiceCollection serviceCollection) => this.serviceCollection = serviceCollection; - - - /// - /// Registers the strategy in DI and - /// as a selectable strategy in the Admin UI - /// - /// The custom type of - /// Used internally to enable dynamic assignment of strategies to search indexes. Names must be unique. - /// Returns this instance of , allowing for further configuration in a fluent manner. - public ILuceneBuilder RegisterStrategy(string strategyName) where TStrategy : class, ILuceneIndexingStrategy - { - StrategyStorage.AddStrategy(strategyName); - serviceCollection.AddTransient(); - - return this; - } - - - /// - /// Registers the analyzer - /// as a selectable analyzer in the Admin UI. When selected this analyzer will be used to process indexed items. - /// - /// The type of - /// Used internally to enable dynamic assignment of analyzers to search indexes. Names must be unique. - /// Returns this instance of , allowing for further configuration in a fluent manner. - public ILuceneBuilder RegisterAnalyzer(string analyzerName) where TAnalyzer : Analyzer - { - AnalyzerStorage.AddAnalyzer(analyzerName); - - return this; - } - - - /// - /// Sets the lucene version which will be used by for indexing. - /// Defaults to - /// - /// to be used by the - /// Returns this instance of , allowing for further configuration in a fluent manner. - public ILuceneBuilder SetAnalyzerLuceneVersion(LuceneVersion matchVersion) - { - AnalyzerStorage.SetAnalyzerLuceneVersion(matchVersion); - - return this; - } -} +using Kentico.Xperience.Lucene.Core; +using Kentico.Xperience.Lucene.Core.Indexing; +using Kentico.Xperience.Lucene.Core.Search; + +using Lucene.Net.Analysis; +using Lucene.Net.Analysis.Standard; +using Lucene.Net.Util; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class LuceneStartupExtensions +{ + /// + /// Adds Lucene services and custom module to application using the and for all indexes + /// + /// the which will be modified + /// Returns this instance of , allowing for further configuration in a fluent manner. + public static IServiceCollection AddKenticoLucene(this IServiceCollection serviceCollection) + { + serviceCollection.AddLuceneServicesInternal(); + + StrategyStorage.AddStrategy("Default"); + AnalyzerStorage.AddAnalyzer("Standard"); + + return serviceCollection; + } + + + /// + /// Adds Lucene services and custom module to application with customized options provided by the + /// in the action. + /// + /// the which will be modified + /// which will configure the + /// Returns this instance of , allowing for further configuration in a fluent manner. + public static IServiceCollection AddKenticoLucene(this IServiceCollection serviceCollection, Action configure) + { + serviceCollection.AddLuceneServicesInternal(); + + var builder = new LuceneBuilder(serviceCollection); + + configure(builder); + + if (builder.IncludeDefaultStrategy) + { + builder.RegisterStrategy("Default"); + } + + if (builder.IncludeDefaultAnalyzer) + { + builder.RegisterAnalyzer("Standard"); + } + + return serviceCollection; + } + + + private static IServiceCollection AddLuceneServicesInternal(this IServiceCollection services) => + services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddTransient(); +} + + +public interface ILuceneBuilder +{ + /// + /// Registers the given as a transient service under + /// + /// The custom type of + /// Used internally to enable dynamic assignment of strategies to search indexes. Names must be unique. + /// + /// Thrown if a strategy has already been registered with the given + /// + /// Returns this instance of , allowing for further configuration in a fluent manner. + ILuceneBuilder RegisterStrategy(string strategyName) where TStrategy : class, ILuceneIndexingStrategy; + + + /// + /// Registers the given and + /// as a selectable analyzer in the Admin UI + /// + /// The type of + /// Used internally to enable dynamic assignment of analyzers to search indexes. Names must be unique. + /// + /// Thrown if an analyzer has already been registered with the given + /// + /// Returns this instance of , allowing for further configuration in a fluent manner. + ILuceneBuilder RegisterAnalyzer(string analyzerName) where TAnalyzer : Analyzer; + + + /// + /// Sets the lucene version which will be used by for search indexes. + /// Defaults to + /// + /// to be used by the + /// Returns this instance of , allowing for further configuration in a fluent manner. + ILuceneBuilder SetAnalyzerLuceneVersion(LuceneVersion matchVersion); +} + + +internal class LuceneBuilder : ILuceneBuilder +{ + private readonly IServiceCollection serviceCollection; + + /// + /// If true, the will be available as an explicitly selectable indexing strategy + /// within the Admin UI. Defaults to true + /// + public bool IncludeDefaultStrategy { get; set; } = true; + + /// + /// If true, the will be available as an explicitly selectable analyzer + /// within the Admin UI. Defaults to true + /// + public bool IncludeDefaultAnalyzer { get; set; } = true; + + public LuceneBuilder(IServiceCollection serviceCollection) => this.serviceCollection = serviceCollection; + + + /// + /// Registers the strategy in DI and + /// as a selectable strategy in the Admin UI + /// + /// The custom type of + /// Used internally to enable dynamic assignment of strategies to search indexes. Names must be unique. + /// Returns this instance of , allowing for further configuration in a fluent manner. + public ILuceneBuilder RegisterStrategy(string strategyName) where TStrategy : class, ILuceneIndexingStrategy + { + StrategyStorage.AddStrategy(strategyName); + serviceCollection.AddTransient(); + + return this; + } + + + /// + /// Registers the analyzer + /// as a selectable analyzer in the Admin UI. When selected this analyzer will be used to process indexed items. + /// + /// The type of + /// Used internally to enable dynamic assignment of analyzers to search indexes. Names must be unique. + /// Returns this instance of , allowing for further configuration in a fluent manner. + public ILuceneBuilder RegisterAnalyzer(string analyzerName) where TAnalyzer : Analyzer + { + AnalyzerStorage.AddAnalyzer(analyzerName); + + return this; + } + + + /// + /// Sets the lucene version which will be used by for indexing. + /// Defaults to + /// + /// to be used by the + /// Returns this instance of , allowing for further configuration in a fluent manner. + public ILuceneBuilder SetAnalyzerLuceneVersion(LuceneVersion matchVersion) + { + AnalyzerStorage.SetAnalyzerLuceneVersion(matchVersion); + + return this; + } +} diff --git a/src/Kentico.Xperience.Lucene.Core/Search/DefaultLuceneSearchService.cs b/src/Kentico.Xperience.Lucene.Core/Search/DefaultLuceneSearchService.cs index 1a61ed1..fdba1e3 100644 --- a/src/Kentico.Xperience.Lucene.Core/Search/DefaultLuceneSearchService.cs +++ b/src/Kentico.Xperience.Lucene.Core/Search/DefaultLuceneSearchService.cs @@ -1,81 +1,81 @@ -using Kentico.Xperience.Lucene.Core.Indexing; - -using Lucene.Net.Facet; -using Lucene.Net.Facet.Taxonomy; -using Lucene.Net.Facet.Taxonomy.Directory; -using Lucene.Net.Index; -using Lucene.Net.Search; -using Lucene.Net.Store; - -using Microsoft.Extensions.DependencyInjection; - -using LuceneDirectory = Lucene.Net.Store.Directory; - -namespace Kentico.Xperience.Lucene.Core.Search; - -internal class DefaultLuceneSearchService : ILuceneSearchService -{ - private readonly ILuceneIndexService indexService; - private readonly IServiceProvider serviceProvider; - - public DefaultLuceneSearchService(ILuceneIndexService indexService, IServiceProvider serviceProvider) - { - this.indexService = indexService; - this.serviceProvider = serviceProvider; - } - - public TResult UseSearcher(LuceneIndex index, Func useIndexSearcher) - { - var storage = index.StorageContext.GetPublishedIndex(); - if (!System.IO.Directory.Exists(storage.Path)) - { - // ensure index - indexService.UseWriter(index, (writer) => - { - writer.Commit(); - return true; - }, storage); - } - - using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); - using var reader = DirectoryReader.Open(indexDir); - var searcher = new IndexSearcher(reader); - return useIndexSearcher(searcher); - } - - public TResult UseSearcherWithFacets(LuceneIndex index, Query query, int n, Func useIndexSearcher) - { - var storage = index.StorageContext.GetPublishedIndex(); - if (!System.IO.Directory.Exists(storage.Path)) - { - // ensure index - indexService.UseIndexAndTaxonomyWriter(index, (writer, tw) => - { - writer.Commit(); - tw.Commit(); - return true; - }, storage); - } - - using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); - using var reader = DirectoryReader.Open(indexDir); - var searcher = new IndexSearcher(reader); - - using var taxonomyDir = FSDirectory.Open(storage.TaxonomyPath); - - using var taxonomyReader = new DirectoryTaxonomyReader(taxonomyDir); - var facetsCollector = new FacetsCollector(); - Dictionary facetsMap = []; - FacetsCollector.Search(searcher, query, n, facetsCollector); - var strategy = serviceProvider.GetRequiredStrategy(index); - var config = strategy?.FacetsConfigFactory() ?? new FacetsConfig(); - OrdinalsReader ordinalsReader = new DocValuesOrdinalsReader(FacetsConfig.DEFAULT_INDEX_FIELD_NAME); - var facetCounts = new TaxonomyFacetCounts(ordinalsReader, taxonomyReader, config, facetsCollector); - var facets = new MultiFacets(facetsMap, facetCounts); - - var results = useIndexSearcher(searcher, facets); - - return results; - - } -} +using Kentico.Xperience.Lucene.Core.Indexing; + +using Lucene.Net.Facet; +using Lucene.Net.Facet.Taxonomy; +using Lucene.Net.Facet.Taxonomy.Directory; +using Lucene.Net.Index; +using Lucene.Net.Search; +using Lucene.Net.Store; + +using Microsoft.Extensions.DependencyInjection; + +using LuceneDirectory = Lucene.Net.Store.Directory; + +namespace Kentico.Xperience.Lucene.Core.Search; + +internal class DefaultLuceneSearchService : ILuceneSearchService +{ + private readonly ILuceneIndexService indexService; + private readonly IServiceProvider serviceProvider; + + public DefaultLuceneSearchService(ILuceneIndexService indexService, IServiceProvider serviceProvider) + { + this.indexService = indexService; + this.serviceProvider = serviceProvider; + } + + public TResult UseSearcher(LuceneIndex index, Func useIndexSearcher) + { + var storage = index.StorageContext.GetPublishedIndex(); + if (!System.IO.Directory.Exists(storage.Path)) + { + // ensure index + indexService.UseWriter(index, (writer) => + { + writer.Commit(); + return true; + }, storage); + } + + using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); + using var reader = DirectoryReader.Open(indexDir); + var searcher = new IndexSearcher(reader); + return useIndexSearcher(searcher); + } + + public TResult UseSearcherWithFacets(LuceneIndex index, Query query, int n, Func useIndexSearcher) + { + var storage = index.StorageContext.GetPublishedIndex(); + if (!System.IO.Directory.Exists(storage.Path)) + { + // ensure index + indexService.UseIndexAndTaxonomyWriter(index, (writer, tw) => + { + writer.Commit(); + tw.Commit(); + return true; + }, storage); + } + + using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); + using var reader = DirectoryReader.Open(indexDir); + var searcher = new IndexSearcher(reader); + + using var taxonomyDir = FSDirectory.Open(storage.TaxonomyPath); + + using var taxonomyReader = new DirectoryTaxonomyReader(taxonomyDir); + var facetsCollector = new FacetsCollector(); + Dictionary facetsMap = []; + FacetsCollector.Search(searcher, query, n, facetsCollector); + var strategy = serviceProvider.GetRequiredStrategy(index); + var config = strategy?.FacetsConfigFactory() ?? new FacetsConfig(); + OrdinalsReader ordinalsReader = new DocValuesOrdinalsReader(FacetsConfig.DEFAULT_INDEX_FIELD_NAME); + var facetCounts = new TaxonomyFacetCounts(ordinalsReader, taxonomyReader, config, facetsCollector); + var facets = new MultiFacets(facetsMap, facetCounts); + + var results = useIndexSearcher(searcher, facets); + + return results; + + } +} diff --git a/src/Kentico.Xperience.Lucene.Core/Search/ILuceneSearchService.cs b/src/Kentico.Xperience.Lucene.Core/Search/ILuceneSearchService.cs index 7246870..777da0b 100644 --- a/src/Kentico.Xperience.Lucene.Core/Search/ILuceneSearchService.cs +++ b/src/Kentico.Xperience.Lucene.Core/Search/ILuceneSearchService.cs @@ -1,16 +1,16 @@ -using Kentico.Xperience.Lucene.Core.Indexing; - -using Lucene.Net.Facet; -using Lucene.Net.Search; - -namespace Kentico.Xperience.Lucene.Core.Search; - -/// -/// Primary service used for querying lucene indexes -/// -public interface ILuceneSearchService -{ - TResult UseSearcher(LuceneIndex index, Func useIndexSearcher); - - TResult UseSearcherWithFacets(LuceneIndex index, Query query, int n, Func useIndexSearcher); -} +using Kentico.Xperience.Lucene.Core.Indexing; + +using Lucene.Net.Facet; +using Lucene.Net.Search; + +namespace Kentico.Xperience.Lucene.Core.Search; + +/// +/// Primary service used for querying lucene indexes +/// +public interface ILuceneSearchService +{ + TResult UseSearcher(LuceneIndex index, Func useIndexSearcher); + + TResult UseSearcherWithFacets(LuceneIndex index, Query query, int n, Func useIndexSearcher); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Search/LuceneSearchResultModel.cs b/src/Kentico.Xperience.Lucene.Core/Search/LuceneSearchResultModel.cs index b417e2c..1d084dc 100644 --- a/src/Kentico.Xperience.Lucene.Core/Search/LuceneSearchResultModel.cs +++ b/src/Kentico.Xperience.Lucene.Core/Search/LuceneSearchResultModel.cs @@ -1,16 +1,16 @@ -using Lucene.Net.Facet; - -namespace Kentico.Xperience.Lucene.Core.Search; - -public class LuceneSearchResultModel -{ - public string Query { get; set; } = ""; - public IEnumerable Hits { get; set; } = []; - public int TotalHits { get; set; } - public int TotalPages { get; set; } - public int PageSize { get; set; } - public int Page { get; set; } - - public string? Facet { get; set; } - public LabelAndValue[]? Facets { get; set; } -} +using Lucene.Net.Facet; + +namespace Kentico.Xperience.Lucene.Core.Search; + +public class LuceneSearchResultModel +{ + public string Query { get; set; } = ""; + public IEnumerable Hits { get; set; } = []; + public int TotalHits { get; set; } + public int TotalPages { get; set; } + public int PageSize { get; set; } + public int Page { get; set; } + + public string? Facet { get; set; } + public LabelAndValue[]? Facets { get; set; } +} diff --git a/src/Kentico.Xperience.Lucene.Core/ServiceProviderExtensions.cs b/src/Kentico.Xperience.Lucene.Core/ServiceProviderExtensions.cs index 23f21cf..3ec0eec 100644 --- a/src/Kentico.Xperience.Lucene.Core/ServiceProviderExtensions.cs +++ b/src/Kentico.Xperience.Lucene.Core/ServiceProviderExtensions.cs @@ -1,24 +1,24 @@ -using Kentico.Xperience.Lucene.Core.Indexing; - -namespace Microsoft.Extensions.DependencyInjection; - -public static class ServiceProviderExtensions -{ - /// - /// Used to generate instances of a service type that can change at runtime. - /// - /// - /// - /// - /// Thrown if the assigned cannot be instantiated. - /// This shouldn't normally occur because we fallback to if no custom strategy is specified. - /// However, incorrect dependency management in user-code could cause issues. - /// - /// Returns an instance of the assigned to the given . - internal static ILuceneIndexingStrategy GetRequiredStrategy(this IServiceProvider serviceProvider, LuceneIndex index) - { - var strategy = serviceProvider.GetRequiredService(index.LuceneIndexingStrategyType) as ILuceneIndexingStrategy; - - return strategy!; - } -} +using Kentico.Xperience.Lucene.Core.Indexing; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ServiceProviderExtensions +{ + /// + /// Used to generate instances of a service type that can change at runtime. + /// + /// + /// + /// + /// Thrown if the assigned cannot be instantiated. + /// This shouldn't normally occur because we fallback to if no custom strategy is specified. + /// However, incorrect dependency management in user-code could cause issues. + /// + /// Returns an instance of the assigned to the given . + internal static ILuceneIndexingStrategy GetRequiredStrategy(this IServiceProvider serviceProvider, LuceneIndex index) + { + var strategy = serviceProvider.GetRequiredService(index.LuceneIndexingStrategyType) as ILuceneIndexingStrategy; + + return strategy!; + } +} diff --git a/tests/Kentico.Xperience.Lucene.Tests/Data/MockDataProvider.cs b/tests/Kentico.Xperience.Lucene.Tests/Data/MockDataProvider.cs index 1d7a4be..8336f03 100644 --- a/tests/Kentico.Xperience.Lucene.Tests/Data/MockDataProvider.cs +++ b/tests/Kentico.Xperience.Lucene.Tests/Data/MockDataProvider.cs @@ -1,75 +1,75 @@ -using CMS.DataEngine; - -using DancingGoat.Models; - -using Kentico.Xperience.Lucene.Core.Indexing; - -using Lucene.Net.Util; - -namespace Kentico.Xperience.Lucene.Tests.Base; - -internal static class MockDataProvider -{ - public static IndexEventWebPageItemModel WebModel => new( - itemID: 0, - itemGuid: Guid.NewGuid(), - languageName: CzechLanguageName, - contentTypeName: ArticlePage.CONTENT_TYPE_NAME, - name: "Name", - isSecured: false, - contentTypeID: 1, - contentLanguageID: 1, - websiteChannelName: DefaultChannel, - webPageItemTreePath: "/", - order: 0 - ); - - public static LuceneIndexIncludedPath Path => new("/%") - { - ContentTypes = [new LuceneIndexContentType(ArticlePage.CONTENT_TYPE_NAME, - DataClassInfoProvider.ProviderObject - .Get() - .WhereEquals(nameof(DataClassInfo.ClassName), ArticlePage.CONTENT_TYPE_NAME) - .FirstOrDefault()? - .ClassDisplayName ?? "", 0 - ) - ] - }; - - - public static LuceneIndex Index => new( - new LuceneIndexModel() - { - IndexName = DefaultIndex, - ChannelName = DefaultChannel, - LanguageNames = [EnglishLanguageName, CzechLanguageName], - Paths = [Path], - AnalyzerName = DefaultAnalyzer - }, - [], - [], - LuceneVersion.LUCENE_48 - ); - - public static readonly string DefaultIndex = "SimpleIndex"; - public static readonly string DefaultChannel = "DefaultChannel"; - public static readonly string DefaultAnalyzer = "StandardAnalyzer"; - public static readonly string EnglishLanguageName = "en"; - public static readonly string CzechLanguageName = "cz"; - public static readonly int IndexId = 1; - public static readonly string EventName = "publish"; - - public static LuceneIndex GetIndex(string indexName, int id) => new( - new LuceneIndexModel() - { - Id = id, - IndexName = indexName, - ChannelName = DefaultChannel, - LanguageNames = [EnglishLanguageName, CzechLanguageName], - Paths = [Path], - }, - [], - [], - LuceneVersion.LUCENE_48 - ); -} +using CMS.DataEngine; + +using DancingGoat.Models; + +using Kentico.Xperience.Lucene.Core.Indexing; + +using Lucene.Net.Util; + +namespace Kentico.Xperience.Lucene.Tests.Base; + +internal static class MockDataProvider +{ + public static IndexEventWebPageItemModel WebModel => new( + itemID: 0, + itemGuid: Guid.NewGuid(), + languageName: CzechLanguageName, + contentTypeName: ArticlePage.CONTENT_TYPE_NAME, + name: "Name", + isSecured: false, + contentTypeID: 1, + contentLanguageID: 1, + websiteChannelName: DefaultChannel, + webPageItemTreePath: "/", + order: 0 + ); + + public static LuceneIndexIncludedPath Path => new("/%") + { + ContentTypes = [new LuceneIndexContentType(ArticlePage.CONTENT_TYPE_NAME, + DataClassInfoProvider.ProviderObject + .Get() + .WhereEquals(nameof(DataClassInfo.ClassName), ArticlePage.CONTENT_TYPE_NAME) + .FirstOrDefault()? + .ClassDisplayName ?? "", 0 + ) + ] + }; + + + public static LuceneIndex Index => new( + new LuceneIndexModel() + { + IndexName = DefaultIndex, + ChannelName = DefaultChannel, + LanguageNames = [EnglishLanguageName, CzechLanguageName], + Paths = [Path], + AnalyzerName = DefaultAnalyzer + }, + [], + [], + LuceneVersion.LUCENE_48 + ); + + public static readonly string DefaultIndex = "SimpleIndex"; + public static readonly string DefaultChannel = "DefaultChannel"; + public static readonly string DefaultAnalyzer = "StandardAnalyzer"; + public static readonly string EnglishLanguageName = "en"; + public static readonly string CzechLanguageName = "cz"; + public static readonly int IndexId = 1; + public static readonly string EventName = "publish"; + + public static LuceneIndex GetIndex(string indexName, int id) => new( + new LuceneIndexModel() + { + Id = id, + IndexName = indexName, + ChannelName = DefaultChannel, + LanguageNames = [EnglishLanguageName, CzechLanguageName], + Paths = [Path], + }, + [], + [], + LuceneVersion.LUCENE_48 + ); +}