diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexContent.cs new file mode 100644 index 000000000..ec480ea70 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexContent.cs @@ -0,0 +1,172 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Text; +using Lucene.Net.Documents; +using Lucene.Net.Index; +using Lucene.Net.Search; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + internal sealed class TextIndexContent + { + public const string MetaId = "_id"; + public const string MetaKey = "_key"; + public const string MetaDraft = "_dd"; + + private readonly IndexWriter indexWriter; + private readonly IndexSearcher indexSearcher; + private readonly Guid id; + + public TextIndexContent(IndexWriter indexWriter, IndexSearcher indexSearcher, Guid id) + { + this.indexWriter = indexWriter; + this.indexSearcher = indexSearcher; + + this.id = id; + } + + public void Index(NamedContentData data, bool isDraft) + { + var converted = CreateDocument(data); + + Upsert(converted, isDraft); + } + + public void Delete() + { + indexWriter.DeleteDocuments(new Term(MetaId, id.ToString())); + } + + public void Copy(bool fromDraft) + { + var published = GetDocument(fromDraft); + + Upsert(published, !fromDraft); + } + + private Document CreateDocument(NamedContentData data) + { + var languages = new Dictionary(); + + void AppendText(string language, string text) + { + if (!string.IsNullOrWhiteSpace(text)) + { + var sb = languages.GetOrAddNew(language); + + if (sb.Length > 0) + { + sb.Append(" "); + } + + sb.Append(text); + } + } + + foreach (var field in data) + { + foreach (var fieldValue in field.Value) + { + var appendText = new Action(text => AppendText(fieldValue.Key, text)); + + AppendJsonText(fieldValue.Value, appendText); + } + } + + Document document = null; + + if (languages.Count > 0) + { + document = new Document(); + + foreach (var field in languages) + { + document.AddTextField(field.Key, field.Value.ToString(), Field.Store.NO); + } + } + + return document; + } + + private Document GetDocument(bool draft) + { + if (indexSearcher == null) + { + return null; + } + + var docs = indexSearcher.Search(new TermQuery(new Term(MetaKey, BuildKey(BuildValue(draft)))), 1); + + if (docs.ScoreDocs.Length > 0) + { + return indexSearcher.Doc(docs.ScoreDocs[0].Doc); + } + + return null; + } + + private void Upsert(Document document, bool draft) + { + if (document != null) + { + document.RemoveField(MetaId); + document.RemoveField(MetaKey); + document.RemoveField(MetaDraft); + + var docDraft = BuildValue(draft); + + var docId = id.ToString(); + var docKey = BuildKey(docDraft); + + document.AddStringField(MetaId, docId, Field.Store.YES); + document.AddStringField(MetaKey, docKey, Field.Store.YES); + document.AddStringField(MetaDraft, docDraft, Field.Store.YES); + + indexWriter.DeleteDocuments(new Term(MetaKey, docKey)); + indexWriter.AddDocument(document); + } + } + + private static string BuildValue(bool draft) + { + return draft ? "1" : "0"; + } + + private string BuildKey(string draft) + { + return $"{id}_{draft}"; + } + + private static void AppendJsonText(IJsonValue value, Action appendText) + { + if (value.Type == JsonValueType.String) + { + appendText(value.ToString()); + } + else if (value is JsonArray array) + { + foreach (var item in array) + { + AppendJsonText(item, appendText); + } + } + else if (value is JsonObject obj) + { + foreach (var item in obj.Values) + { + AppendJsonText(item, appendText); + } + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs index 5e1f0e57d..66aa6de17 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs @@ -32,13 +32,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text private const LuceneVersion Version = LuceneVersion.LUCENE_48; private const int MaxResults = 2000; private const int MaxUpdates = 100; - private const string MetaId = "_id"; - private const string MetaKey = "_key"; - private const string MetaDraft = "_dd"; private static readonly TimeSpan CommitDelay = TimeSpan.FromSeconds(30); private static readonly Analyzer Analyzer = new MultiLanguageAnalyzer(Version); - private static readonly TermsFilter DraftFilter = new TermsFilter(new Term(MetaDraft, "1")); - private static readonly TermsFilter NoDraftFilter = new TermsFilter(new Term(MetaDraft, "0")); + private static readonly TermsFilter DraftFilter = new TermsFilter(new Term(TextIndexContent.MetaDraft, "1")); + private static readonly TermsFilter NoDraftFilter = new TermsFilter(new Term(TextIndexContent.MetaDraft, "0")); private readonly SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()); private readonly IAssetStore assetStore; private IDisposable timer; @@ -84,63 +81,18 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text public Task DeleteAsync(Guid id) { - indexWriter.DeleteDocuments(new Term(MetaId, id.ToString())); + var content = new TextIndexContent(indexWriter, indexSearcher, id); + + content.Delete(); return TryFlushAsync(); } public Task IndexAsync(Guid id, J data) { - var docId = id.ToString(); - var docDraft = data.Value.IsDraft ? "1" : "0"; - var docKey = $"{docId}_{docDraft}"; - - indexWriter.DeleteDocuments(new Term(MetaKey, docKey)); - - var languages = new Dictionary(); - - void AppendText(string language, string text) - { - if (!string.IsNullOrWhiteSpace(text)) - { - var sb = languages.GetOrAddNew(language); + var content = new TextIndexContent(indexWriter, indexSearcher, id); - if (sb.Length > 0) - { - sb.Append(" "); - } - - sb.Append(text); - } - } - - foreach (var field in data.Value.Data) - { - foreach (var fieldValue in field.Value) - { - var appendText = new Action(text => AppendText(fieldValue.Key, text)); - - AppendJsonText(fieldValue.Value, appendText); - } - } - - if (languages.Count > 0) - { - var document = new Document(); - - document.AddStringField(MetaId, docId, Field.Store.YES); - document.AddStringField(MetaKey, docKey, Field.Store.YES); - document.AddStringField(MetaDraft, docDraft, Field.Store.YES); - - foreach (var field in languages) - { - var fieldName = BuildFieldName(field.Key); - - document.AddTextField(fieldName, field.Value.ToString(), Field.Store.NO); - } - - indexWriter.AddDocument(document); - } + content.Index(data.Value.Data, data.Value.IsDraft); return TryFlushAsync(); } @@ -185,7 +137,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text { var document = indexReader.Document(hit.Doc); - var idField = document.GetField(MetaId)?.GetStringValue(); + var idField = document.GetField(TextIndexContent.MetaId)?.GetStringValue(); if (idField != null && Guid.TryParse(idField, out var guid)) { @@ -203,8 +155,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text if (queryParser == null || !currentLanguages.SetEquals(context.Languages)) { var fields = - context.Languages.Select(BuildFieldName) - .Union(Enumerable.Repeat(BuildFieldName(InvariantPartitioning.Instance.Master.Key), 1)).ToArray(); + context.Languages + .Union(Enumerable.Repeat(InvariantPartitioning.Instance.Master.Key, 1)).ToArray(); queryParser = new MultiFieldQueryParser(Version, fields, Analyzer); @@ -288,10 +240,5 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text directory.Delete(true); } } - - private static string BuildFieldName(string language) - { - return language; - } } }