diff --git a/docs/Managing-Indexes.md b/docs/Managing-Indexes.md index 82586982..3a7aba3b 100644 --- a/docs/Managing-Indexes.md +++ b/docs/Managing-Indexes.md @@ -19,6 +19,7 @@ Fill out the search index form, populating the fields with your custom values. - Channel Name - the index will only be triggered by web page item creation or modication in the selected website channel - Indexing Strategy - the indexing strategy specified in code during dependency registration of a custom indexing strategies. - If you want the default strategy to appear here, register it explicitly in `IServiceCollection.AddKenticoLucene()` method +- Lucene Analyzer - the Lucene analyzer which indexes use to analyze text. Now, configure the web page paths and content types that the search index depends on by clicking the Add New Path button or clicking an existing path in the table at the top of the index configuration form. @@ -33,4 +34,4 @@ or clicking an existing path in the table at the top of the index configuration All reusable content item modifications will trigger an event to generate a `IndexEventReusableItemModel` for your custom index strategy class to process, as long as the content item has a language variant matching one of the languages selected for the index. You can use this to index reusable content items in addition to web page items but returning the reusable content item content as a `IIndexEventItemModel` from the strategy `FindItemsToReindex` method. -> Note: There currently no UI to allow administrators to configure which types of reusable content items trigger indexing. This could be added in a future update. +> Note: There is currently no UI to allow administrators to configure which types of reusable content items trigger indexing. This could be added in a future update. diff --git a/docs/Text-analyzing.md b/docs/Text-analyzing.md new file mode 100644 index 00000000..9799c7d0 --- /dev/null +++ b/docs/Text-analyzing.md @@ -0,0 +1,71 @@ +# Select Lucene text Analyzer + +In the admin UI you are able to select an `Analyzer` which will be used by selected strategy to rebuild an index. By default the only available `Analyzer` is the `StandardAnalyzer`. In order to use a different analyzer you will have to register it, along with the Lucene, to the application services. + + ```csharp + // Program.cs + + // Registers all services and enables custom indexing behavior + services.AddKenticoLucene(builder => + { + // Register strategies ... + builder.RegisterAnalyzer("Czech analyzer"); + }); + ``` + +Now the `CzechAnalyzer` will be available for selection in the Admin UI under Lucene Analyzer. This analyzer will be used by selected strategy for reindexing. You can retrieve and use this analyzer for index quierying. The analyzer will be available on the instance of the `LuceneIndex` class under `LuceneAnalyzer`. + + ```csharp + public class SimpleSearchService + { + // Other class members ... + public LuceneSearchResultModel GlobalSearch( + string indexName, + string? searchText, + int pageSize = 20, + int page = 1) + { + var index = luceneIndexManager.GetRequiredIndex(indexName); + var query = GetTermQuery(searchText, index); + // ... + } + + private Query GetTermQuery(string? searchText, LuceneIndex index) + { + // Here we retrieve the analyzer instance which we can use for our queries + var analyzer = index.LuceneAnalyzer; + var queryBuilder = new QueryBuilder(analyzer); + + var booleanQuery = new BooleanQuery(); + + if (!string.IsNullOrWhiteSpace(searchText)) + { + booleanQuery = AddToTermQuery(booleanQuery, queryBuilder.CreatePhraseQuery(nameof(DancingGoatSearchResultModel.Title), searchText, PHRASE_SLOP), 5); + booleanQuery = AddToTermQuery(booleanQuery, queryBuilder.CreateBooleanQuery(nameof(DancingGoatSearchResultModel.Title), searchText, Occur.SHOULD), 0.5f); + + if (booleanQuery.GetClauses().Count() > 0) + { + return booleanQuery; + } + } + + return new MatchAllDocsQuery(); + } + + // Other class members ... + } + ``` + +The `Analyzer` uses a `LuceneVersion` to match version compatibility accross releases of Lucene. By default we use the latest `LuceneVersion.LUCENE_48`. You can override the version when registering application services as follows. + + ```csharp + // Program.cs + + // Registers all services and enables custom indexing behavior + services.AddKenticoLucene(builder => + { + // Register strategies ... + // Register analyzers + builder.SetAnalyzerLuceneVersion(LuceneVersion.LUCENE_47); + }); + ``` \ No newline at end of file diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 59cd8d8b..bff57ede 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -30,6 +30,10 @@ See [Managing search indexes](Managing-Indexes.md) See [Search index querying](Search-index-querying.md) +## Using Lucene Analyzer + +See [Text analyzing](Text-analyzing.md) + ## Implementing document decay You can score indexed items by "freshness" or "recency" using several techniques, each with different tradeoffs. diff --git a/examples/DancingGoat/Search/DancingGoatSearchStartupExtensions.cs b/examples/DancingGoat/Search/DancingGoatSearchStartupExtensions.cs index 4364fb12..0e264676 100644 --- a/examples/DancingGoat/Search/DancingGoatSearchStartupExtensions.cs +++ b/examples/DancingGoat/Search/DancingGoatSearchStartupExtensions.cs @@ -1,5 +1,7 @@ using DancingGoat.Search.Services; +using Lucene.Net.Analysis.Cz; + namespace DancingGoat.Search; public static class DancingGoatSearchStartupExtensions @@ -10,6 +12,7 @@ public static IServiceCollection AddKenticoDancingGoatLuceneServices(this IServi { builder.RegisterStrategy("DancingGoatExampleStrategy"); builder.RegisterStrategy("DancingGoatMinimalExampleStrategy"); + builder.RegisterAnalyzer("Czech analyzer"); }); services.AddHttpClient(); diff --git a/examples/DancingGoat/Search/Services/AdvancedSearchService.cs b/examples/DancingGoat/Search/Services/AdvancedSearchService.cs index 2389602b..27c4b7c7 100644 --- a/examples/DancingGoat/Search/Services/AdvancedSearchService.cs +++ b/examples/DancingGoat/Search/Services/AdvancedSearchService.cs @@ -1,6 +1,6 @@ using Kentico.Xperience.Lucene.Core.Indexing; using Kentico.Xperience.Lucene.Core.Search; -using Lucene.Net.Analysis.Standard; + using Lucene.Net.Documents; using Lucene.Net.Facet; using Lucene.Net.Search; @@ -19,8 +19,9 @@ public class AdvancedSearchService public AdvancedSearchService( ILuceneSearchService luceneSearchService, - AdvancedSearchIndexingStrategy strategy, - ILuceneIndexManager luceneIndexManager) + ILuceneIndexManager luceneIndexManager, + AdvancedSearchIndexingStrategy strategy + ) { this.luceneSearchService = luceneSearchService; this.strategy = strategy; @@ -36,7 +37,7 @@ public LuceneSearchResultModel GlobalSearch( string? sortBy = null) { var index = luceneIndexManager.GetRequiredIndex(indexName); - var query = GetTermQuery(searchText); + var query = GetTermQuery(searchText, index); var combinedQuery = new BooleanQuery { @@ -105,9 +106,9 @@ private static BooleanQuery AddToTermQuery(BooleanQuery query, Query textQueryPa return query; } - private static Query GetTermQuery(string? searchText) + private Query GetTermQuery(string? searchText, LuceneIndex index) { - var analyzer = new StandardAnalyzer(LuceneVersion.LUCENE_48); + var analyzer = index.LuceneAnalyzer; var queryBuilder = new QueryBuilder(analyzer); if (string.IsNullOrEmpty(searchText)) diff --git a/examples/DancingGoat/Search/Services/SimpleSearchService.cs b/examples/DancingGoat/Search/Services/SimpleSearchService.cs index 7432d5bd..a1da20c3 100644 --- a/examples/DancingGoat/Search/Services/SimpleSearchService.cs +++ b/examples/DancingGoat/Search/Services/SimpleSearchService.cs @@ -1,7 +1,6 @@ using Kentico.Xperience.Lucene.Core.Indexing; using Kentico.Xperience.Lucene.Core.Search; -using Lucene.Net.Analysis.Standard; using Lucene.Net.Documents; using Lucene.Net.Search; using Lucene.Net.Util; @@ -16,7 +15,10 @@ public class SimpleSearchService private readonly ILuceneSearchService luceneSearchService; private readonly ILuceneIndexManager luceneIndexManager; - public SimpleSearchService(ILuceneSearchService luceneSearchService, ILuceneIndexManager luceneIndexManager) + public SimpleSearchService( + ILuceneSearchService luceneSearchService, + ILuceneIndexManager luceneIndexManager + ) { this.luceneSearchService = luceneSearchService; this.luceneIndexManager = luceneIndexManager; @@ -29,7 +31,7 @@ public LuceneSearchResultModel GlobalSearch( int page = 1) { var index = luceneIndexManager.GetRequiredIndex(indexName); - var query = GetTermQuery(searchText); + var query = GetTermQuery(searchText, index); var result = luceneSearchService.UseSearcher( index, @@ -72,9 +74,9 @@ private static BooleanQuery AddToTermQuery(BooleanQuery query, Query textQueryPa return query; } - private static Query GetTermQuery(string? searchText) + private Query GetTermQuery(string? searchText, LuceneIndex index) { - var analyzer = new StandardAnalyzer(LuceneVersion.LUCENE_48); + var analyzer = index.LuceneAnalyzer; var queryBuilder = new QueryBuilder(analyzer); var booleanQuery = new BooleanQuery(); diff --git a/images/xperience-administration-search-index-edit-form-paths-edit.jpg b/images/xperience-administration-search-index-edit-form-paths-edit.jpg index 00ed8dcb..79cd3253 100644 Binary files a/images/xperience-administration-search-index-edit-form-paths-edit.jpg and b/images/xperience-administration-search-index-edit-form-paths-edit.jpg differ diff --git a/images/xperience-administration-search-index-edit-form.jpg b/images/xperience-administration-search-index-edit-form.jpg index a0195cdc..10f9204f 100644 Binary files a/images/xperience-administration-search-index-edit-form.jpg and b/images/xperience-administration-search-index-edit-form.jpg differ diff --git a/images/xperience-administration-search-index-list.jpg b/images/xperience-administration-search-index-list.jpg index ed4f5c2b..54065025 100644 Binary files a/images/xperience-administration-search-index-list.jpg and b/images/xperience-administration-search-index-list.jpg differ diff --git a/src/Kentico.Xperience.Lucene.Admin/LuceneConfigurationModel.cs b/src/Kentico.Xperience.Lucene.Admin/LuceneConfigurationModel.cs index d77e27d0..2e984ab5 100644 --- a/src/Kentico.Xperience.Lucene.Admin/LuceneConfigurationModel.cs +++ b/src/Kentico.Xperience.Lucene.Admin/LuceneConfigurationModel.cs @@ -2,6 +2,7 @@ using Kentico.Xperience.Admin.Base.FormAnnotations; using Kentico.Xperience.Admin.Base.Forms; +using Kentico.Xperience.Lucene.Admin.Providers; using Kentico.Xperience.Lucene.Core.Indexing; namespace Kentico.Xperience.Lucene.Admin; @@ -27,6 +28,9 @@ public class LuceneConfigurationModel [DropDownComponent(Label = "Indexing Strategy", DataProviderType = typeof(IndexingStrategyOptionsProvider), Order = 4)] public string StrategyName { get; set; } = ""; + [DropDownComponent(Label = "Lucene Analyzer", DataProviderType = typeof(AnalyzerOptionsProvider), Order = 5)] + public string AnalyzerName { get; set; } = ""; + [TextInputComponent(Label = "Rebuild Hook")] public string RebuildHook { get; set; } = ""; @@ -44,6 +48,7 @@ LuceneIndexModel luceneModel LanguageNames = luceneModel.LanguageNames; ChannelName = luceneModel.ChannelName; StrategyName = luceneModel.StrategyName; + AnalyzerName = luceneModel.AnalyzerName; RebuildHook = luceneModel.RebuildHook; Paths = luceneModel.Paths; } @@ -55,6 +60,7 @@ public LuceneIndexModel ToLuceneModel() => IndexName = IndexName, LanguageNames = LanguageNames, ChannelName = ChannelName, + AnalyzerName = AnalyzerName, StrategyName = StrategyName, RebuildHook = RebuildHook, Paths = Paths diff --git a/src/Kentico.Xperience.Lucene.Admin/Providers/AnalyzerOptionsProvider.cs b/src/Kentico.Xperience.Lucene.Admin/Providers/AnalyzerOptionsProvider.cs new file mode 100644 index 00000000..b8df57c2 --- /dev/null +++ b/src/Kentico.Xperience.Lucene.Admin/Providers/AnalyzerOptionsProvider.cs @@ -0,0 +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 + })); +} diff --git a/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs b/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs index 318667b2..ae0256ef 100644 --- a/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs +++ b/src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs @@ -70,6 +70,7 @@ public override async Task ConfigurePage() .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); diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/AnalyzerStorage.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/AnalyzerStorage.cs new file mode 100644 index 00000000..090c1a21 --- /dev/null +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/AnalyzerStorage.cs @@ -0,0 +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); +} diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneConfigurationStorageService.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneConfigurationStorageService.cs index 9b67439d..aa3378a0 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneConfigurationStorageService.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneConfigurationStorageService.cs @@ -42,6 +42,7 @@ public bool TryCreateIndex(LuceneIndexModel configuration) LuceneIndexItemIndexName = configuration.IndexName ?? "", LuceneIndexItemChannelName = configuration.ChannelName ?? "", LuceneIndexItemStrategyName = configuration.StrategyName ?? "", + LuceneIndexItemAnalyzerName = configuration.AnalyzerName ?? "", LuceneIndexItemRebuildHook = configuration.RebuildHook ?? "" }; @@ -170,6 +171,7 @@ public async Task TryEditIndexAsync(LuceneIndexModel configuration) indexInfo.LuceneIndexItemRebuildHook = configuration.RebuildHook ?? ""; indexInfo.LuceneIndexItemStrategyName = configuration.StrategyName ?? ""; + indexInfo.LuceneIndexItemAnalyzerName = configuration.AnalyzerName ?? ""; indexInfo.LuceneIndexItemChannelName = configuration.ChannelName ?? ""; indexInfo.LuceneIndexItemIndexName = configuration.IndexName ?? ""; diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexManager.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexManager.cs index c514182a..afc021ef 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexManager.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexManager.cs @@ -22,7 +22,7 @@ public IEnumerable GetAllIndices() { var indices = (CacheSettings cs) => { - var luceneIndices = storageService.GetAllIndexDataAsync().Result.Select(x => new LuceneIndex(x, StrategyStorage.Strategies)); + var luceneIndices = storageService.GetAllIndexDataAsync().Result.Select(x => new LuceneIndex(x, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion)); cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); @@ -45,7 +45,7 @@ public IEnumerable GetAllIndices() } cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); - return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies); + return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); }; return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|name|{indexName}")); @@ -64,7 +64,7 @@ public IEnumerable GetAllIndices() } cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); - return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies); + return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); }; return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|identifier|{identifier}")); @@ -83,7 +83,7 @@ public LuceneIndex GetRequiredIndex(string indexName) } cs.CacheDependency = CacheHelper.GetCacheDependency(GetLuceneDependencyCacheKeys()); - return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies); + return new LuceneIndex(indexConfiguration, StrategyStorage.Strategies, AnalyzerStorage.Analyzers, AnalyzerStorage.AnalyzerLuceneVersion); }; return progressiveCache.Load(cs => index(cs), new CacheSettings(10, $"customdatasource|index|identifier|{indexName}")); diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexService.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexService.cs index fba4adf6..01722dc3 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexService.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/DefaultLuceneIndexService.cs @@ -2,7 +2,6 @@ using Lucene.Net.Facet.Taxonomy.Directory; using Lucene.Net.Index; using Lucene.Net.Store; -using Lucene.Net.Util; using LuceneDirectory = Lucene.Net.Store.Directory; @@ -10,14 +9,14 @@ namespace Kentico.Xperience.Lucene.Core.Indexing; public class DefaultLuceneIndexService : ILuceneIndexService { - private const LuceneVersion LUCENE_VERSION = LuceneVersion.LUCENE_48; - 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(LUCENE_VERSION, index.Analyzer) + var indexConfig = new IndexWriterConfig(AnalyzerStorage.AnalyzerLuceneVersion, analyzer) { OpenMode = openMode // create/overwrite index }; @@ -33,8 +32,10 @@ public TResult UseWriter(LuceneIndex index, Func { using LuceneDirectory indexDir = FSDirectory.Open(storage.Path); + var analyzer = index.LuceneAnalyzer; + //Create an index writer - var indexConfig = new IndexWriterConfig(LUCENE_VERSION, index.Analyzer) + var indexConfig = new IndexWriterConfig(AnalyzerStorage.AnalyzerLuceneVersion, analyzer) { OpenMode = openMode // create/overwrite index }; diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndex.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndex.cs index 262ac620..da5b3096 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndex.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndex.cs @@ -30,9 +30,9 @@ public sealed class LuceneIndex public List LanguageNames { get; } /// - /// Lucene Analyzer instance . + /// Lucene Analyzer used for indexing. /// - public Analyzer Analyzer { get; } + public Analyzer LuceneAnalyzer { get; } /// /// The type of the class which extends . @@ -46,9 +46,8 @@ public sealed class LuceneIndex internal IEnumerable IncludedPaths { get; set; } - internal LuceneIndex(LuceneIndexModel indexConfiguration, Dictionary strategies) + internal LuceneIndex(LuceneIndexModel indexConfiguration, Dictionary strategies, Dictionary analyzers, LuceneVersion matchVersion) { - Analyzer = new StandardAnalyzer(LuceneVersion.LUCENE_48); Identifier = indexConfiguration.Id; IndexName = indexConfiguration.IndexName; WebSiteChannelName = indexConfiguration.ChannelName; @@ -62,6 +61,21 @@ internal LuceneIndex(LuceneIndexModel indexConfiguration, Dictionary new + { + Constructor = x, + Parameters = x.GetParameters() + }); + var constructor = constructorParameters.First(x => x.Parameters.Length == 1 && x.Parameters.Single().ParameterType == typeof(LuceneVersion)).Constructor; + LuceneAnalyzer = (Analyzer)constructor.Invoke([matchVersion]); + LuceneIndexingStrategyType = strategy; string indexStoragePath = Path.Combine(Environment.CurrentDirectory, "App_Data", "LuceneSearch", indexConfiguration.IndexName); diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexModel.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexModel.cs index 61ff29a3..bd9c4bc3 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexModel.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/LuceneIndexModel.cs @@ -12,6 +12,8 @@ public class LuceneIndexModel public string StrategyName { get; set; } = ""; + public string AnalyzerName { get; set; } = ""; + public string RebuildHook { get; set; } = ""; public IEnumerable Paths { get; set; } = Enumerable.Empty(); @@ -30,6 +32,7 @@ IEnumerable contentTypes ChannelName = index.LuceneIndexItemChannelName; RebuildHook = index.LuceneIndexItemRebuildHook; StrategyName = index.LuceneIndexItemStrategyName; + AnalyzerName = index.LuceneIndexItemAnalyzerName; LanguageNames = indexLanguages .Where(l => l.LuceneIndexLanguageItemIndexItemId == index.LuceneIndexItemId) .Select(l => l.LuceneIndexLanguageItemName) diff --git a/src/Kentico.Xperience.Lucene.Core/Indexing/StrategyStorage.cs b/src/Kentico.Xperience.Lucene.Core/Indexing/StrategyStorage.cs index 8a5783dd..0b3f63d4 100644 --- a/src/Kentico.Xperience.Lucene.Core/Indexing/StrategyStorage.cs +++ b/src/Kentico.Xperience.Lucene.Core/Indexing/StrategyStorage.cs @@ -1,6 +1,6 @@ namespace Kentico.Xperience.Lucene.Core.Indexing; -public static class StrategyStorage +internal static class StrategyStorage { public static Dictionary Strategies { get; private set; } static StrategyStorage() => Strategies = []; diff --git a/src/Kentico.Xperience.Lucene.Core/InfoModels/LuceneIndexItem/LuceneIndexItemInfo.generated.cs b/src/Kentico.Xperience.Lucene.Core/InfoModels/LuceneIndexItem/LuceneIndexItemInfo.generated.cs index 0af18be5..758a9011 100644 --- a/src/Kentico.Xperience.Lucene.Core/InfoModels/LuceneIndexItem/LuceneIndexItemInfo.generated.cs +++ b/src/Kentico.Xperience.Lucene.Core/InfoModels/LuceneIndexItem/LuceneIndexItemInfo.generated.cs @@ -91,6 +91,17 @@ public virtual string LuceneIndexItemStrategyName } + /// + /// Analyzer name. + /// + [DatabaseField] + public virtual string LuceneIndexItemAnalyzerName + { + get => ValidationHelper.GetString(GetValue(nameof(LuceneIndexItemAnalyzerName)), String.Empty); + set => SetValue(nameof(LuceneIndexItemAnalyzerName), value); + } + + /// /// Rebuild hook. /// diff --git a/src/Kentico.Xperience.Lucene.Core/Kentico.Xperience.Lucene.Core.csproj b/src/Kentico.Xperience.Lucene.Core/Kentico.Xperience.Lucene.Core.csproj index 6fa66e65..ca2d1362 100644 --- a/src/Kentico.Xperience.Lucene.Core/Kentico.Xperience.Lucene.Core.csproj +++ b/src/Kentico.Xperience.Lucene.Core/Kentico.Xperience.Lucene.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Kentico.Xperience.Lucene.Core/LuceneModuleInstaller.cs b/src/Kentico.Xperience.Lucene.Core/LuceneModuleInstaller.cs index 32a9ebfe..5beff1b7 100644 --- a/src/Kentico.Xperience.Lucene.Core/LuceneModuleInstaller.cs +++ b/src/Kentico.Xperience.Lucene.Core/LuceneModuleInstaller.cs @@ -98,6 +98,18 @@ public void InstallLuceneItemInfo(ResourceInfo resource) }; formInfo.AddFormItem(formItem); + formItem = new FormFieldInfo + { + Name = nameof(LuceneIndexItemInfo.LuceneIndexItemAnalyzerName), + AllowEmpty = false, + Visible = true, + Precision = 0, + Size = 100, + DataType = "text", + Enabled = true + }; + formInfo.AddFormItem(formItem); + formItem = new FormFieldInfo { Name = nameof(LuceneIndexItemInfo.LuceneIndexItemRebuildHook), diff --git a/src/Kentico.Xperience.Lucene.Core/LuceneStartupExtensions.cs b/src/Kentico.Xperience.Lucene.Core/LuceneStartupExtensions.cs index a4e8b781..65a67af9 100644 --- a/src/Kentico.Xperience.Lucene.Core/LuceneStartupExtensions.cs +++ b/src/Kentico.Xperience.Lucene.Core/LuceneStartupExtensions.cs @@ -2,20 +2,25 @@ using Kentico.Xperience.Lucene.Core.Search; using Kentico.Xperience.Lucene.Core; +using Lucene.Net.Analysis.Standard; +using Lucene.Net.Util; +using Lucene.Net.Analysis; + namespace Microsoft.Extensions.DependencyInjection; public static class LuceneStartupExtensions { /// - /// Adds Lucene services and custom module to application using the for all indexes + /// 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; } @@ -25,9 +30,9 @@ public static IServiceCollection AddKenticoLucene(this IServiceCollection servic /// 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(); @@ -38,10 +43,14 @@ public static IServiceCollection AddKenticoLucene(this IServiceCollection servic if (builder.IncludeDefaultStrategy) { - serviceCollection.AddTransient(); builder.RegisterStrategy("Default"); } + if (builder.IncludeDefaultAnalyzer) + { + builder.RegisterAnalyzer("Standard"); + } + return serviceCollection; } @@ -68,10 +77,32 @@ public interface ILuceneBuilder /// The custom type of /// Used internally to enable dynamic assignment of strategies to search indexes. Names must be unique. /// - /// Thrown if an strategy has already been registered with the given + /// 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); } @@ -85,15 +116,22 @@ internal class LuceneBuilder : ILuceneBuilder /// 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); @@ -101,4 +139,33 @@ public ILuceneBuilder RegisterStrategy(string strategyName) where TSt 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/ServiceProviderExtensions.cs b/src/Kentico.Xperience.Lucene.Core/ServiceProviderExtensions.cs index a4cab8ad..3ec0eecb 100644 --- a/src/Kentico.Xperience.Lucene.Core/ServiceProviderExtensions.cs +++ b/src/Kentico.Xperience.Lucene.Core/ServiceProviderExtensions.cs @@ -2,21 +2,20 @@ namespace Microsoft.Extensions.DependencyInjection; -internal static class ServiceProviderExtensions +public static class ServiceProviderExtensions { /// - /// Returns an instance of the assigned to the given . /// 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 not custom strategy is specified. + /// 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. /// - /// - public static ILuceneIndexingStrategy GetRequiredStrategy(this IServiceProvider serviceProvider, LuceneIndex index) + /// 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; diff --git a/tests/Kentico.Xperience.Lucene.Tests/Data/MockDataProvider.cs b/tests/Kentico.Xperience.Lucene.Tests/Data/MockDataProvider.cs index a46ee473..8336f03b 100644 --- a/tests/Kentico.Xperience.Lucene.Tests/Data/MockDataProvider.cs +++ b/tests/Kentico.Xperience.Lucene.Tests/Data/MockDataProvider.cs @@ -1,7 +1,11 @@ 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 @@ -39,13 +43,17 @@ internal static class MockDataProvider IndexName = DefaultIndex, ChannelName = DefaultChannel, LanguageNames = [EnglishLanguageName, CzechLanguageName], - Paths = [Path] + 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; @@ -58,8 +66,10 @@ internal static class MockDataProvider IndexName = indexName, ChannelName = DefaultChannel, LanguageNames = [EnglishLanguageName, CzechLanguageName], - Paths = [Path] + Paths = [Path], }, - [] + [], + [], + LuceneVersion.LUCENE_48 ); }