diff --git a/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj b/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj index 1d07df81b..28deec8b0 100644 --- a/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj +++ b/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj @@ -104,8 +104,6 @@ - - diff --git a/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs b/src/Orchard.Tests.Modules/Indexing/LuceneIndexProviderTests.cs similarity index 85% rename from src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs rename to src/Orchard.Tests.Modules/Indexing/LuceneIndexProviderTests.cs index 7b2501149..675a47e11 100644 --- a/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs +++ b/src/Orchard.Tests.Modules/Indexing/LuceneIndexProviderTests.cs @@ -3,18 +3,14 @@ using System.IO; using System.Linq; using Autofac; using NUnit.Framework; -using Orchard.Environment; using Orchard.Environment.Configuration; using Orchard.FileSystems.AppData; -using Orchard.FileSystems.VirtualPath; using Orchard.Indexing; -using Orchard.Core.Indexing.Lucene; -using Orchard.Services; -using Orchard.Tests.Environment.Configuration; +using Orchard.Indexing.Services; using Orchard.Tests.FileSystems.AppData; -namespace Orchard.Tests.Indexing { - public class DefaultIndexProviderTests { +namespace Orchard.Tests.Modules.Indexing { + public class LuceneIndexProviderTests { private IContainer _container; private IIndexProvider _provider; private IAppDataFolder _appDataFolder; @@ -36,7 +32,7 @@ namespace Orchard.Tests.Indexing { _appDataFolder = AppDataFolderTests.CreateAppDataFolder(_basePath); var builder = new ContainerBuilder(); - builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterInstance(_appDataFolder).As(); // setting up a ShellSettings instance @@ -111,7 +107,7 @@ namespace Orchard.Tests.Indexing { var hit = searchBuilder.Get(42); Assert.IsNotNull(hit); - Assert.That(hit.Id, Is.EqualTo(42)); + Assert.That(hit.ContentItemId, Is.EqualTo(42)); hit = searchBuilder.Get(1); Assert.IsNull(hit); @@ -120,12 +116,12 @@ namespace Orchard.Tests.Indexing { [Test] public void PropertiesShouldNotBeLost() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(42).Add("prop1", "value1")); + _provider.Store("default", _provider.New(42).Add("prop1", "value1").Store()); var hit = _provider.CreateSearchBuilder("default").Get(42); Assert.IsNotNull(hit); - Assert.That(hit.Id, Is.EqualTo(42)); + Assert.That(hit.ContentItemId, Is.EqualTo(42)); Assert.That(hit.GetString("prop1"), Is.EqualTo("value1")); } @@ -160,21 +156,21 @@ namespace Orchard.Tests.Indexing { var searchBuilder = _provider.CreateSearchBuilder("default"); - Assert.That(searchBuilder.Get(1).Id, Is.EqualTo(1)); - Assert.That(searchBuilder.Get(11).Id, Is.EqualTo(11)); - Assert.That(searchBuilder.Get(111).Id, Is.EqualTo(111)); + Assert.That(searchBuilder.Get(1).ContentItemId, Is.EqualTo(1)); + Assert.That(searchBuilder.Get(11).ContentItemId, Is.EqualTo(11)); + Assert.That(searchBuilder.Get(111).ContentItemId, Is.EqualTo(111)); } [Test] public void TagsShouldBeRemoved() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(1).Add("body", "some content")); - _provider.Store("default", _provider.New(2).Add("body", "some content", true)); + _provider.Store("default", _provider.New(1).Add("body", "some content").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "some content", true).Analyze()); var searchBuilder = _provider.CreateSearchBuilder("default"); Assert.That(searchBuilder.WithField("body", "hr").Search().Count(), Is.EqualTo(1)); - Assert.That(searchBuilder.WithField("body", "hr").Search().First().Id, Is.EqualTo(1)); + Assert.That(searchBuilder.WithField("body", "hr").Search().First().ContentItemId, Is.EqualTo(1)); } [Test] public void ShouldAllowNullOrEmptyStrings() { @@ -185,21 +181,21 @@ namespace Orchard.Tests.Indexing { var searchBuilder = _provider.CreateSearchBuilder("default"); - Assert.That(searchBuilder.Get(1).Id, Is.EqualTo(1)); - Assert.That(searchBuilder.Get(2).Id, Is.EqualTo(2)); - Assert.That(searchBuilder.Get(3).Id, Is.EqualTo(3)); + Assert.That(searchBuilder.Get(1).ContentItemId, Is.EqualTo(1)); + Assert.That(searchBuilder.Get(2).ContentItemId, Is.EqualTo(2)); + Assert.That(searchBuilder.Get(3).ContentItemId, Is.EqualTo(3)); } [Test] public void ProviderShouldStoreSettings() { _provider.CreateIndex("default"); - Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime)); + Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(LuceneIndexProvider.DefaultMinDateTime)); _provider.SetLastIndexUtc("default", new DateTime(2010, 1, 1, 1, 1, 1, 1)); Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(new DateTime(2010, 1, 1, 1, 1, 1, 0))); _provider.SetLastIndexUtc("default", new DateTime(1901, 1, 1, 1, 1, 1, 1)); - Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime)); + Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(LuceneIndexProvider.DefaultMinDateTime)); } [Test] @@ -223,14 +219,14 @@ namespace Orchard.Tests.Indexing { [Test] public void IsDirtyShouldBeFalseForNewDocuments() { - IIndexDocument doc = _provider.New(1); + IDocumentIndex doc = _provider.New(1); Assert.That(doc.IsDirty, Is.False); } [Test] public void IsDirtyShouldBeTrueWhenIndexIsModified() { - IIndexDocument doc; + IDocumentIndex doc; doc = _provider.New(1); doc.Add("foo", "value"); diff --git a/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs b/src/Orchard.Tests.Modules/Indexing/LuceneSearchBuilderTests.cs similarity index 67% rename from src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs rename to src/Orchard.Tests.Modules/Indexing/LuceneSearchBuilderTests.cs index a9b56e16f..a3c74db38 100644 --- a/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs +++ b/src/Orchard.Tests.Modules/Indexing/LuceneSearchBuilderTests.cs @@ -6,11 +6,11 @@ using NUnit.Framework; using Orchard.Environment.Configuration; using Orchard.FileSystems.AppData; using Orchard.Indexing; -using Orchard.Core.Indexing.Lucene; +using Orchard.Indexing.Services; using Orchard.Tests.FileSystems.AppData; -namespace Orchard.Core.Tests.Indexing { - public class DefaultSearchBuilderTests { +namespace Orchard.Tests.Modules.Indexing { + public class LuceneSearchBuilderTests { private IContainer _container; private IIndexProvider _provider; private IAppDataFolder _appDataFolder; @@ -32,7 +32,7 @@ namespace Orchard.Core.Tests.Indexing { _appDataFolder = AppDataFolderTests.CreateAppDataFolder(_basePath); var builder = new ContainerBuilder(); - builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterInstance(_appDataFolder).As(); // setting up a ShellSettings instance @@ -50,7 +50,7 @@ namespace Orchard.Core.Tests.Indexing { _provider.CreateIndex("default"); _provider.Store("default", _provider.New(42) - .Add("title", "title1 title2 title3") + .Add("title", "title1 title2 title3").Analyze() .Add("date", new DateTime(2010, 05, 28, 14, 13, 56, 123)) ); @@ -60,7 +60,6 @@ namespace Orchard.Core.Tests.Indexing { Assert.IsNotNull(_provider.CreateSearchBuilder("default").WithField("title", "title2").Search().FirstOrDefault()); Assert.IsNotNull(_provider.CreateSearchBuilder("default").WithField("title", "title3").Search().FirstOrDefault()); Assert.IsNull(_provider.CreateSearchBuilder("default").WithField("title", "title4").Search().FirstOrDefault()); - Assert.IsNotNull(_provider.CreateSearchBuilder("default").WithField("title", "title").Search().FirstOrDefault()); } @@ -72,9 +71,9 @@ namespace Orchard.Core.Tests.Indexing { _provider.Store("default", _provider.New(3)); - Assert.That(_searchBuilder.Get(1).Id, Is.EqualTo(1)); - Assert.That(_searchBuilder.Get(2).Id, Is.EqualTo(2)); - Assert.That(_searchBuilder.Get(3).Id, Is.EqualTo(3)); + Assert.That(_searchBuilder.Get(1).ContentItemId, Is.EqualTo(1)); + Assert.That(_searchBuilder.Get(2).ContentItemId, Is.EqualTo(2)); + Assert.That(_searchBuilder.Get(3).ContentItemId, Is.EqualTo(3)); } [Test] @@ -86,7 +85,7 @@ namespace Orchard.Core.Tests.Indexing { Assert.That(_searchBuilder.WithField("title", "cat").Search().Count(), Is.EqualTo(2)); - Assert.That(_searchBuilder.WithField("title", "cat").Search().Any(hit => new[] { 1, 3 }.Contains(hit.Id)), Is.True); + Assert.That(_searchBuilder.WithField("title", "cat").Search().Any(hit => new[] { 1, 3 }.Contains(hit.ContentItemId)), Is.True); } @@ -99,8 +98,6 @@ namespace Orchard.Core.Tests.Indexing { Assert.That(_searchBuilder.WithField("title", "dog").Count(), Is.EqualTo(1)); Assert.That(_searchBuilder.WithField("title", "cat").Count(), Is.EqualTo(2)); - Assert.That(_searchBuilder.WithField("title", "c").Count(), Is.EqualTo(2)); - } [Test] @@ -110,12 +107,12 @@ namespace Orchard.Core.Tests.Indexing { _provider.Store("default", _provider.New(2).Add("date", new DateTime(2010, 05, 28, 12, 30, 30))); _provider.Store("default", _provider.New(3).Add("date", new DateTime(2010, 05, 28, 12, 30, 45))); - Assert.That(_searchBuilder.After("date", new DateTime(2010, 05, 28, 12, 30, 15)).Count(), Is.EqualTo(3)); - Assert.That(_searchBuilder.Before("date", new DateTime(2010, 05, 28, 12, 30, 45)).Count(), Is.EqualTo(3)); - Assert.That(_searchBuilder.After("date", new DateTime(2010, 05, 28, 12, 30, 15)).Before("date", new DateTime(2010, 05, 28, 12, 30, 45)).Count(), Is.EqualTo(3)); - Assert.That(_searchBuilder.After("date", new DateTime(2010, 05, 28, 12, 30, 16)).Before("date", new DateTime(2010, 05, 28, 12, 30, 44)).Count(), Is.EqualTo(1)); - Assert.That(_searchBuilder.After("date", new DateTime(2010, 05, 28, 12, 30, 46)).Count(), Is.EqualTo(0)); - Assert.That(_searchBuilder.Before("date", new DateTime(2010, 05, 28, 12, 30, 1)).Count(), Is.EqualTo(0)); + Assert.That(_searchBuilder.WithinRange("date", new DateTime(2010, 05, 28, 12, 30, 15), DateTime.MaxValue).Count(), Is.EqualTo(3)); + Assert.That(_searchBuilder.WithinRange("date", DateTime.MinValue, new DateTime(2010, 05, 28, 12, 30, 45)).Count(), Is.EqualTo(3)); + Assert.That(_searchBuilder.WithinRange("date", new DateTime(2010, 05, 28, 12, 30, 15), new DateTime(2010, 05, 28, 12, 30, 45)).Count(), Is.EqualTo(3)); + Assert.That(_searchBuilder.WithinRange("date", new DateTime(2010, 05, 28, 12, 30, 16), new DateTime(2010, 05, 28, 12, 30, 44)).Count(), Is.EqualTo(1)); + Assert.That(_searchBuilder.WithinRange("date", new DateTime(2010, 05, 28, 12, 30, 46), DateTime.MaxValue).Count(), Is.EqualTo(0)); + Assert.That(_searchBuilder.WithinRange("date", DateTime.MinValue, new DateTime(2010, 05, 28, 12, 30, 1)).Count(), Is.EqualTo(0)); } [Test] @@ -143,28 +140,28 @@ namespace Orchard.Core.Tests.Indexing { [Test] public void ShouldSortByRelevance() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(1).Add("body", "michaelson is in the kitchen")); - _provider.Store("default", _provider.New(2).Add("body", "michael as a cousin named michael")); - _provider.Store("default", _provider.New(3).Add("body", "speak inside the mic")); - _provider.Store("default", _provider.New(4).Add("body", "a dog is pursuing a cat")); - _provider.Store("default", _provider.New(5).Add("body", "the elephant can't catch up the dog")); + _provider.Store("default", _provider.New(1).Add("body", "michael is in the kitchen").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "michael as a cousin named michel").Analyze()); + _provider.Store("default", _provider.New(3).Add("body", "speak inside the mic").Analyze()); + _provider.Store("default", _provider.New(4).Add("body", "a dog is pursuing a cat").Analyze()); + _provider.Store("default", _provider.New(5).Add("body", "the elephant can't catch up the dog").Analyze()); - var michael = _searchBuilder.WithField("body", "mic").Search().ToList(); - Assert.That(michael.Count(), Is.EqualTo(3)); + var michael = _searchBuilder.WithField("body", "michael").Search().ToList(); + Assert.That(michael.Count(), Is.EqualTo(2)); Assert.That(michael[0].Score >= michael[1].Score, Is.True); // Sorting on score is always descending - michael = _searchBuilder.WithField("body", "mic").Ascending().Search().ToList(); - Assert.That(michael.Count(), Is.EqualTo(3)); + michael = _searchBuilder.WithField("body", "michael").Ascending().Search().ToList(); + Assert.That(michael.Count(), Is.EqualTo(2)); Assert.That(michael[0].Score >= michael[1].Score, Is.True); } [Test] public void ShouldSortByDate() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(1).Add("date", new DateTime(2010, 05, 28, 12, 30, 15))); - _provider.Store("default", _provider.New(2).Add("date", new DateTime(2010, 05, 28, 12, 30, 30))); - _provider.Store("default", _provider.New(3).Add("date", new DateTime(2010, 05, 28, 12, 30, 45))); + _provider.Store("default", _provider.New(1).Add("date", new DateTime(2010, 05, 28, 12, 30, 15)).Store()); + _provider.Store("default", _provider.New(2).Add("date", new DateTime(2010, 05, 28, 12, 30, 30)).Store()); + _provider.Store("default", _provider.New(3).Add("date", new DateTime(2010, 05, 28, 12, 30, 45)).Store()); var date = _searchBuilder.SortBy("date").Search().ToList(); Assert.That(date.Count(), Is.EqualTo(3)); @@ -180,13 +177,13 @@ namespace Orchard.Core.Tests.Indexing { [Test] public void ShouldEscapeSpecialChars() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#")); - _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++")); + _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++").Analyze()); - var cs = _searchBuilder.WithField("body", "C#").Search().ToList(); + var cs = _searchBuilder.Parse("body", "C#").Search().ToList(); Assert.That(cs.Count(), Is.EqualTo(2)); - var cpp = _searchBuilder.WithField("body", "C++").Search().ToList(); + var cpp = _searchBuilder.Parse("body", "C++").Search().ToList(); Assert.That(cpp.Count(), Is.EqualTo(2)); } @@ -194,42 +191,42 @@ namespace Orchard.Core.Tests.Indexing { [Test] public void ShouldHandleMandatoryFields() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#")); - _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++")); + _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++").Analyze()); Assert.That(_searchBuilder.WithField("body", "develop").Search().ToList().Count(), Is.EqualTo(2)); Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Search().ToList().Count(), Is.EqualTo(2)); Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Mandatory().Search().ToList().Count(), Is.EqualTo(1)); - Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Mandatory().Search().First().Id, Is.EqualTo(1)); + Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Mandatory().Search().First().ContentItemId, Is.EqualTo(1)); } [Test] public void ShouldHandleForbiddenFields() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#")); - _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++")); + _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++").Analyze()); - Assert.That(_searchBuilder.WithField("body", "develop").Search().ToList().Count(), Is.EqualTo(2)); - Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Search().ToList().Count(), Is.EqualTo(2)); - Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Forbidden().Search().ToList().Count(), Is.EqualTo(1)); - Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Forbidden().Search().First().Id, Is.EqualTo(2)); + Assert.That(_searchBuilder.WithField("body", "developped").Search().ToList().Count(), Is.EqualTo(2)); + Assert.That(_searchBuilder.WithField("body", "developped").WithField("body", "Orchard").Search().ToList().Count(), Is.EqualTo(2)); + Assert.That(_searchBuilder.WithField("body", "developped").WithField("body", "Orchard").Forbidden().Search().ToList().Count(), Is.EqualTo(1)); + Assert.That(_searchBuilder.WithField("body", "developped").WithField("body", "Orchard").Forbidden().Search().First().ContentItemId, Is.EqualTo(2)); } [Test] public void ShouldHandleWeight() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#")); - _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++")); + _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++").Analyze()); - Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Weighted(2).Search().First().Id, Is.EqualTo(1)); + Assert.That(_searchBuilder.WithField("body", "developped").WithField("body", "Orchard").Weighted(2).Search().First().ContentItemId, Is.EqualTo(1)); } [Test] public void ShouldParseLuceneQueries() { _provider.CreateIndex("default"); - _provider.Store("default", _provider.New(1).Add("body", "Bradley is in the kitchen.").Add("title", "Beer and takos")); - _provider.Store("default", _provider.New(2).Add("body", "Renaud is also in the kitchen.").Add("title", "A love affair")); - _provider.Store("default", _provider.New(3).Add("body", "Bertrand is a little bit jealous.").Add("title", "Soap opera")); + _provider.Store("default", _provider.New(1).Add("body", "Bradley is in the kitchen.").Analyze().Add("title", "Beer and takos").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "Renaud is also in the kitchen.").Analyze().Add("title", "A love affair").Analyze()); + _provider.Store("default", _provider.New(3).Add("body", "Bertrand is a little bit jealous.").Analyze().Add("title", "Soap opera").Analyze()); Assert.That(_searchBuilder.Parse(new[] { "body" }, "kitchen").Count(), Is.EqualTo(2)); Assert.That(_searchBuilder.Parse(new[] { "body" }, "kitchen bertrand").Count(), Is.EqualTo(3)); @@ -238,9 +235,24 @@ namespace Orchard.Core.Tests.Indexing { Assert.That(_searchBuilder.Parse(new[] { "body" }, "kit").Count(), Is.EqualTo(0)); Assert.That(_searchBuilder.Parse(new[] { "body" }, "kit*").Count(), Is.EqualTo(2)); Assert.That(_searchBuilder.Parse(new[] { "body", "title" }, "bradley love^3 soap").Count(), Is.EqualTo(3)); - Assert.That(_searchBuilder.Parse(new[] { "body", "title" }, "bradley love^3 soap").Search().First().Id, Is.EqualTo(2)); + Assert.That(_searchBuilder.Parse(new[] { "body", "title" }, "bradley love^3 soap").Search().First().ContentItemId, Is.EqualTo(2)); } + [Test] + public void ShouldFilterIntValues() { + _provider.CreateIndex("default"); + _provider.Store("default", _provider.New(1).Add("field", 1)); + _provider.Store("default", _provider.New(2).Add("field", 22)); + _provider.Store("default", _provider.New(3).Add("field", 333)); + + Assert.That(_searchBuilder.WithField("field", 1).ExactMatch().Count(), Is.EqualTo(1)); + Assert.That(_searchBuilder.WithField("field", 22).ExactMatch().Count(), Is.EqualTo(1)); + Assert.That(_searchBuilder.WithField("field", 333).ExactMatch().Count(), Is.EqualTo(1)); + + Assert.That(_searchBuilder.WithField("field", 0).ExactMatch().Count(), Is.EqualTo(0)); + Assert.That(_searchBuilder.WithField("field", 2).ExactMatch().Count(), Is.EqualTo(0)); + Assert.That(_searchBuilder.WithField("field", 3).ExactMatch().Count(), Is.EqualTo(0)); + } } } diff --git a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj index adec09cb8..d683f24ca 100644 --- a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj +++ b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj @@ -108,6 +108,8 @@ + + @@ -128,6 +130,10 @@ {9916839C-39FC-4CEB-A5AF-89CA7E87119F} Orchard.Core + + {EA2B9121-EF54-40A6-A53E-6593C86EE696} + Orchard.Indexing + {4A9C04A6-0986-4A92-A610-5F59FF273FB9} Orchard.Pages diff --git a/src/Orchard.Web/Core/Common/Handlers/BodyAspectHandler.cs b/src/Orchard.Web/Core/Common/Handlers/BodyAspectHandler.cs index c566e124f..f18300b44 100644 --- a/src/Orchard.Web/Core/Common/Handlers/BodyAspectHandler.cs +++ b/src/Orchard.Web/Core/Common/Handlers/BodyAspectHandler.cs @@ -9,9 +9,9 @@ namespace Orchard.Core.Common.Handlers { public BodyAspectHandler(IRepository bodyRepository) { Filters.Add(StorageFilter.For(bodyRepository)); - OnIndexing((context, bodyAspect) => context.IndexDocument - .Add("body", bodyAspect.Record.Text, true).Store(false) - .Add("format", bodyAspect.Record.Format).Analyze(false)); + OnIndexing((context, bodyAspect) => context.DocumentIndex + .Add("body", bodyAspect.Record.Text, true).Analyze() + .Add("format", bodyAspect.Record.Format).Store()); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/Handlers/CommonAspectHandler.cs b/src/Orchard.Web/Core/Common/Handlers/CommonAspectHandler.cs index 8a95691bf..9bcf65f08 100644 --- a/src/Orchard.Web/Core/Common/Handlers/CommonAspectHandler.cs +++ b/src/Orchard.Web/Core/Common/Handlers/CommonAspectHandler.cs @@ -53,12 +53,12 @@ namespace Orchard.Core.Common.Handlers { //OnGetEditorViewModel(GetEditor); //OnUpdateEditorViewModel(UpdateEditor); - OnIndexing((context, commonAspect) => context.IndexDocument - .Add("type", commonAspect.ContentItem.ContentType).Analyze(false) - .Add("author", commonAspect.Owner.UserName).Analyze(false) - .Add("created", commonAspect.CreatedUtc ?? _clock.UtcNow).Analyze(false) - .Add("published", commonAspect.PublishedUtc ?? _clock.UtcNow).Analyze(false) - .Add("modified", commonAspect.ModifiedUtc ?? _clock.UtcNow).Analyze(false) + OnIndexing((context, commonAspect) => context.DocumentIndex + .Add("type", commonAspect.ContentItem.ContentType).Store() + .Add("author", commonAspect.Owner.UserName).Store() + .Add("created", commonAspect.CreatedUtc ?? _clock.UtcNow).Store() + .Add("published", commonAspect.PublishedUtc ?? _clock.UtcNow).Store() + .Add("modified", commonAspect.ModifiedUtc ?? _clock.UtcNow).Store() ); } diff --git a/src/Orchard.Web/Core/Common/Handlers/RoutableAspectHandler.cs b/src/Orchard.Web/Core/Common/Handlers/RoutableAspectHandler.cs index 85d510a9a..193f98e48 100644 --- a/src/Orchard.Web/Core/Common/Handlers/RoutableAspectHandler.cs +++ b/src/Orchard.Web/Core/Common/Handlers/RoutableAspectHandler.cs @@ -36,8 +36,8 @@ namespace Orchard.Core.Common.Handlers { OnCreated((context, ra) => routableService.ProcessSlug(ra)); - OnIndexing((context, part) => context.IndexDocument - .Add("slug", part.Slug).Analyze(false) + OnIndexing((context, part) => context.DocumentIndex + .Add("slug", part.Slug) .Add("title", part.Title) ); } diff --git a/src/Orchard.Web/Core/Localization/Handlers/LocalizedHandler.cs b/src/Orchard.Web/Core/Localization/Handlers/LocalizedHandler.cs index ff65bbf3c..b8a35dd42 100644 --- a/src/Orchard.Web/Core/Localization/Handlers/LocalizedHandler.cs +++ b/src/Orchard.Web/Core/Localization/Handlers/LocalizedHandler.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Globalization; +using JetBrains.Annotations; using Orchard.Core.Localization.Models; using Orchard.Data; using Orchard.Localization; @@ -23,7 +24,10 @@ namespace Orchard.Core.Localization.Handlers { OnLoaded(LazyLoadHandlers); - OnIndexed((context, localized) => context.IndexDocument.Add("culture", localized.Culture != null ? localized.Culture.Culture : _cultureManager.GetSiteCulture()).Store(false).Analyze(false)); + OnIndexed((context, localized) => context.DocumentIndex + .Add("culture", CultureInfo.GetCultureInfo(localized.Culture != null ? localized.Culture.Culture : _cultureManager.GetSiteCulture()).LCID) + .Store() + ); } public Localizer T { get; set; } diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index 63b8f57d6..9bbdd2d32 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -39,10 +39,6 @@ AllRules.ruleset - - False - ..\..\..\lib\lucene.net\Lucene.Net.dll - 3.5 @@ -136,16 +132,6 @@ - - - - - - - - - - @@ -232,7 +218,6 @@ - diff --git a/src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Indexing/AdminMenu.cs similarity index 82% rename from src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/AdminMenu.cs index 7033c2bf4..7ab7230e4 100644 --- a/src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/AdminMenu.cs @@ -1,7 +1,7 @@ using Orchard.Localization; using Orchard.UI.Navigation; -namespace Orchard.Search { +namespace Orchard.Indexing { public class AdminMenu : INavigationProvider { public Localizer T { get; set; } public string MenuName { get { return "admin"; } } @@ -9,7 +9,7 @@ namespace Orchard.Search { public void GetNavigation(NavigationBuilder builder) { builder.Add(T("Site Configuration"), "11", menu => menu - .Add(T("Search Index"), "10.0", item => item.Action("Index", "Admin", new {area = "Orchard.Search"}) + .Add(T("Search Index"), "10.0", item => item.Action("Index", "Admin", new {area = "Orchard.Indexing"}) .Permission(Permissions.ManageSearchIndex))); } } diff --git a/src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Commands/IndexingCommands.cs similarity index 97% rename from src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Commands/IndexingCommands.cs index 02c33923d..0799cdb97 100644 --- a/src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Commands/IndexingCommands.cs @@ -7,7 +7,7 @@ using Orchard.Indexing; using Orchard.Security; using Orchard.Tasks.Indexing; -namespace Orchard.Core.Indexing.Commands { +namespace Orchard.Indexing.Commands { public class IndexingCommands : DefaultOrchardCommandHandler { private readonly IEnumerable _indexNotifierHandlers; private readonly IIndexManager _indexManager; diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Controllers/AdminController.cs new file mode 100644 index 000000000..de92346cf --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Controllers/AdminController.cs @@ -0,0 +1,50 @@ +using System.Web.Mvc; +using Orchard.Indexing.Services; +using Orchard.Localization; +using Orchard.UI.Notify; +using Orchard.Indexing.ViewModels; + +namespace Orchard.Indexing.Controllers { + public class AdminController : Controller { + private readonly IIndexingService _indexingService; + + public AdminController(IIndexingService indexingService, IOrchardServices services) { + _indexingService = indexingService; + Services = services; + T = NullLocalizer.Instance; + } + + public IOrchardServices Services { get; private set; } + public Localizer T { get; set; } + + public ActionResult Index() { + var viewModel = new IndexViewModel {HasIndexToManage = _indexingService.HasIndexToManage, IndexUpdatedUtc = _indexingService.GetIndexUpdatedUtc()}; + + if (!viewModel.HasIndexToManage) + Services.Notifier.Information(T("There is no search index to manage for this site.")); + + return View(viewModel); + } + + [HttpPost] + public ActionResult Update() { + if ( !Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index.")) ) + return new HttpUnauthorizedResult(); + + _indexingService.UpdateIndex(); + + return RedirectToAction("Index"); + } + + [HttpPost] + public ActionResult Rebuild() { + if ( !Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index.")) ) + return new HttpUnauthorizedResult(); + + _indexingService.RebuildIndex(); + _indexingService.UpdateIndex(); + + return RedirectToAction("Index"); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Indexing/Services/CreateIndexingTaskHandler.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Handlers/CreateIndexingTaskHandler.cs similarity index 94% rename from src/Orchard.Web/Core/Indexing/Services/CreateIndexingTaskHandler.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Handlers/CreateIndexingTaskHandler.cs index 4da37eddc..ae6d18bbe 100644 --- a/src/Orchard.Web/Core/Indexing/Services/CreateIndexingTaskHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Handlers/CreateIndexingTaskHandler.cs @@ -3,7 +3,7 @@ using Orchard.ContentManagement; using Orchard.Core.Common.Models; using Orchard.Tasks.Indexing; -namespace Orchard.Core.Indexing.Services { +namespace Orchard.Indexing.Handlers { /// /// Intercepts the ContentHandler events to create indexing tasks when a content item /// is published, and to delete them when the content item is unpublished. diff --git a/src/Orchard.Web/Core/Indexing/Models/IndexingTask.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Models/IndexingTask.cs similarity index 94% rename from src/Orchard.Web/Core/Indexing/Models/IndexingTask.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Models/IndexingTask.cs index 8ceabded0..259647698 100644 --- a/src/Orchard.Web/Core/Indexing/Models/IndexingTask.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Models/IndexingTask.cs @@ -2,7 +2,7 @@ using System; using Orchard.ContentManagement; using Orchard.Tasks.Indexing; -namespace Orchard.Core.Indexing.Models { +namespace Orchard.Indexing.Models { public class IndexingTask : IIndexingTask { private readonly IContentManager _contentManager; private readonly IndexingTaskRecord _record; diff --git a/src/Orchard.Web/Core/Indexing/Models/IndexingTaskRecord.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Models/IndexingTaskRecord.cs similarity index 87% rename from src/Orchard.Web/Core/Indexing/Models/IndexingTaskRecord.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Models/IndexingTaskRecord.cs index 3cab96601..ed9cd88b3 100644 --- a/src/Orchard.Web/Core/Indexing/Models/IndexingTaskRecord.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Models/IndexingTaskRecord.cs @@ -1,7 +1,7 @@ using System; using Orchard.ContentManagement.Records; -namespace Orchard.Core.Indexing.Models { +namespace Orchard.Indexing.Models { public class IndexingTaskRecord { public const int Update = 0; diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexDocument.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Models/LuceneDocumentIndex.cs similarity index 50% rename from src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexDocument.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Models/LuceneDocumentIndex.cs index 571a06cc0..43afff85e 100644 --- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexDocument.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Models/LuceneDocumentIndex.cs @@ -1,21 +1,18 @@ using System; using System.Collections.Generic; -using System.Web.Mvc; using Lucene.Net.Documents; -using Orchard.Indexing; -using Orchard.Mvc.Html; using Orchard.Utility.Extensions; -namespace Orchard.Core.Indexing.Lucene { +namespace Orchard.Indexing.Models { - public class DefaultIndexDocument : IIndexDocument { + public class LuceneDocumentIndex : IDocumentIndex { public List Fields { get; private set; } private AbstractField _previousField; public int Id { get; private set; } - public DefaultIndexDocument(int documentId) { + public LuceneDocumentIndex(int documentId) { Fields = new List(); SetContentItemId(documentId); IsDirty = false; @@ -23,11 +20,11 @@ namespace Orchard.Core.Indexing.Lucene { public bool IsDirty { get; private set; } - public IIndexDocument Add(string name, string value) { + public IDocumentIndex Add(string name, string value) { return Add(name, value, false); } - public IIndexDocument Add(string name, string value, bool removeTags) { + public IDocumentIndex Add(string name, string value, bool removeTags) { AppendPreviousField(); if(value == null) { @@ -38,68 +35,54 @@ namespace Orchard.Core.Indexing.Lucene { value = value.RemoveTags(); } - _previousField = new Field(name, value, Field.Store.YES, Field.Index.ANALYZED); + _previousField = new Field(name, value, Field.Store.NO, Field.Index.NOT_ANALYZED); IsDirty = true; return this; } - public IIndexDocument Add(string name, DateTime value) { + public IDocumentIndex Add(string name, DateTime value) { + return Add(name, DateTools.DateToString(value, DateTools.Resolution.SECOND)); + } + + public IDocumentIndex Add(string name, int value) { AppendPreviousField(); - _previousField = new Field(name, DateTools.DateToString(value, DateTools.Resolution.SECOND), Field.Store.YES, Field.Index.NOT_ANALYZED); + _previousField = new NumericField(name, Field.Store.NO, true).SetIntValue(value); IsDirty = true; return this; } - public IIndexDocument Add(string name, int value) { + public IDocumentIndex Add(string name, bool value) { + return Add(name, value.ToString()); + } + + public IDocumentIndex Add(string name, float value) { AppendPreviousField(); - _previousField = new NumericField(name, Field.Store.YES, true).SetIntValue(value); + _previousField = new NumericField(name, Field.Store.NO, true).SetFloatValue(value); IsDirty = true; return this; } - public IIndexDocument Add(string name, bool value) { - AppendPreviousField(); - _previousField = new Field(name, value.ToString().ToLower(), Field.Store.YES, Field.Index.NOT_ANALYZED); - IsDirty = true; - return this; + public IDocumentIndex Add(string name, object value) { + return Add(name, value.ToString()); } - public IIndexDocument Add(string name, float value) { - AppendPreviousField(); - _previousField = new NumericField(name, Field.Store.YES, true).SetFloatValue(value); - IsDirty = true; - return this; - } - - public IIndexDocument Add(string name, object value) { - AppendPreviousField(); - _previousField = new Field(name, value.ToString(), Field.Store.NO, Field.Index.NOT_ANALYZED); - IsDirty = true; - return this; - } - - public IIndexDocument Store(bool store) { + public IDocumentIndex Store() { EnsurePreviousField(); - if(store != _previousField.IsStored()) { - var index = _previousField.IsTokenized() ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED; - _previousField = new Field(_previousField.Name(), _previousField.StringValue(), store ? Field.Store.YES : Field.Store.NO, index); - } + var index = _previousField.IsTokenized() ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED; + _previousField = new Field(_previousField.Name(), _previousField.StringValue(), Field.Store.YES, index); return this; } - public IIndexDocument Analyze(bool analyze) { + public IDocumentIndex Analyze() { EnsurePreviousField(); - if (_previousField.IsTokenized() == analyze) { - return this; - } - var index = analyze ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED; + var index = Field.Index.ANALYZED; var store = _previousField.IsStored() ? Field.Store.YES : Field.Store.NO; _previousField = new Field(_previousField.Name(), _previousField.StringValue(), store, index); return this; } - public IIndexDocument SetContentItemId(int id) { + public IDocumentIndex SetContentItemId(int id) { Id = id; Fields.Add(new Field("id", id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); return this; diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchHit.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Models/LuceneSearchHit.cs similarity index 76% rename from src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchHit.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Models/LuceneSearchHit.cs index e98a0c1bf..2946b6553 100644 --- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchHit.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Models/LuceneSearchHit.cs @@ -1,21 +1,20 @@ using Lucene.Net.Documents; using System.Globalization; using Lucene.Net.Util; -using Orchard.Indexing; -namespace Orchard.Core.Indexing.Lucene { - public class DefaultSearchHit : ISearchHit { +namespace Orchard.Indexing.Models { + public class LuceneSearchHit : ISearchHit { private readonly Document _doc; private readonly float _score; public float Score { get { return _score; } } - public DefaultSearchHit(Document document, float score) { + public LuceneSearchHit(Document document, float score) { _doc = document; _score = score; } - public int Id { get { return int.Parse(GetString("id")); } } + public int ContentItemId { get { return int.Parse(GetString("id")); } } public int GetInt(string name) { return NumericUtils.PrefixCodedToInt(_doc.GetField(name).StringValue()); diff --git a/src/Orchard.Web/Core/Indexing/Module.txt b/src/Orchard.Web/Modules/Orchard.Indexing/Module.txt similarity index 88% rename from src/Orchard.Web/Core/Indexing/Module.txt rename to src/Orchard.Web/Modules/Orchard.Indexing/Module.txt index e18a54668..aa8c0044f 100644 --- a/src/Orchard.Web/Core/Indexing/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Module.txt @@ -6,6 +6,6 @@ version: 0.1 orchardversion: 0.1.2010.0312 description: The Indexing module enables the site to be indexed. The index generated by this module can then be used by the search module to provide an integrated full-text search experience to a web site. features: - Indexing: + Orchard.Indexing: Description: Indexing services based on Lucene. - Category: Core \ No newline at end of file + Category: Search \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj b/src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj new file mode 100644 index 000000000..ce47de856 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj @@ -0,0 +1,123 @@ + + + + Debug + AnyCPU + + + 2.0 + {EA2B9121-EF54-40A6-A53E-6593C86EE696} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.Indexing + Orchard.Indexing + v4.0 + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\..\..\..\lib\lucene.net\Lucene.Net.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + + + + + + + + + + False + True + 35644 + / + + + False + False + + + False + + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Permissions.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Permissions.cs similarity index 76% rename from src/Orchard.Web/Modules/Orchard.Search/Permissions.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Permissions.cs index 901f4ae65..33f8f86d1 100644 --- a/src/Orchard.Web/Modules/Orchard.Search/Permissions.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Permissions.cs @@ -1,27 +1,25 @@ using System.Collections.Generic; using Orchard.Security.Permissions; -namespace Orchard.Search { +namespace Orchard.Indexing { public class Permissions : IPermissionProvider { public static readonly Permission ManageSearchIndex = new Permission { Description = "Manage Search Index", Name = "ManageSearchIndex" }; public string ModuleName { get { - return "Search"; + return "Indexing"; } } public IEnumerable GetPermissions() { - return new Permission[] { - ManageSearchIndex, - }; + return new[] { ManageSearchIndex }; } public IEnumerable GetDefaultStereotypes() { return new[] { new PermissionStereotype { Name = "Administrator", - Permissions = new[] {ManageSearchIndex} + Permissions = new [] { ManageSearchIndex } }, }; } diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d8f3a7be2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.Indexing")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Orchard.Indexing")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ab8e1272-1749-4e27-a123-83c8d47e321a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Services/IIndexService.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IIndexService.cs new file mode 100644 index 000000000..c34e4494d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IIndexService.cs @@ -0,0 +1,10 @@ +using System; + +namespace Orchard.Indexing.Services { + public interface IIndexingService : IDependency { + bool HasIndexToManage { get; } + void RebuildIndex(); + void UpdateIndex(); + DateTime GetIndexUpdatedUtc(); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexService.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexService.cs new file mode 100644 index 000000000..e38e7a4ae --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexService.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using Orchard.Localization; +using Orchard.Localization.Services; +using Orchard.UI.Notify; + +namespace Orchard.Indexing.Services +{ + public class IndexingService : IIndexingService + { + private const string SearchIndexName = "Search"; + private readonly IIndexManager _indexManager; + private readonly IEnumerable _indexNotifierHandlers; + + public IndexingService(IOrchardServices services, IIndexManager indexManager, IEnumerable indexNotifierHandlers, ICultureManager cultureManager) { + Services = services; + _indexManager = indexManager; + _indexNotifierHandlers = indexNotifierHandlers; + T = NullLocalizer.Instance; + } + + public IOrchardServices Services { get; set; } + public Localizer T { get; set; } + + public bool HasIndexToManage { + get { return _indexManager.HasIndexProvider(); } + } + + void IIndexingService.RebuildIndex() { + if (!_indexManager.HasIndexProvider()) { + Services.Notifier.Warning(T("There is no search index to rebuild.")); + return; + } + + var searchProvider = _indexManager.GetSearchIndexProvider(); + if (searchProvider.Exists(SearchIndexName)) + searchProvider.DeleteIndex(SearchIndexName); + + searchProvider.CreateIndex(SearchIndexName); // or just reset the updated date and let the background process recreate the index + + Services.Notifier.Information(T("The search index has been rebuilt.")); + } + + void IIndexingService.UpdateIndex() { + + foreach(var handler in _indexNotifierHandlers) { + handler.UpdateIndex(SearchIndexName); + } + + Services.Notifier.Information(T("The search index has been updated.")); + } + + DateTime IIndexingService.GetIndexUpdatedUtc() { + return !HasIndexToManage + ? DateTime.MinValue + : _indexManager.GetSearchIndexProvider().GetLastIndexUtc(SearchIndexName); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexSynLock.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexSynLock.cs new file mode 100644 index 000000000..772929322 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexSynLock.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Orchard.Indexing.Services { + public interface IIndexSynLock : ISingletonDependency { + object GetSynLock(string indexName); + } + + public class IndexSynLock : IIndexSynLock { + private readonly Dictionary _synLocks; + private readonly object _synLock = new object(); + + public IndexSynLock() { + _synLocks =new Dictionary(); + } + + public object GetSynLock(string indexName) { + lock(_synLock) { + if(!_synLocks.ContainsKey(indexName)) { + _synLocks[indexName] = new object(); + } + return _synLocks[indexName]; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingBackgroundTask.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingBackgroundTask.cs new file mode 100644 index 000000000..84f2666ad --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingBackgroundTask.cs @@ -0,0 +1,26 @@ +using JetBrains.Annotations; +using Orchard.Logging; +using Orchard.Tasks; + +namespace Orchard.Indexing.Services { + /// + /// Regularly fires IIndexNotifierHandler events + /// + [UsedImplicitly] + public class IndexingBackgroundTask : IBackgroundTask { + private readonly IIndexNotifierHandler _indexNotifierHandler; + private const string SearchIndexName = "Search"; + + public IndexingBackgroundTask( + IIndexNotifierHandler indexNotifierHandler) { + _indexNotifierHandler = indexNotifierHandler; + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public void Sweep() { + _indexNotifierHandler.UpdateIndex(SearchIndexName); + } + } +} diff --git a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskExecutor.cs similarity index 64% rename from src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskExecutor.cs index 2cf986227..27e48f6bd 100644 --- a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskExecutor.cs @@ -3,60 +3,49 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Orchard.ContentManagement; -using Orchard.ContentManagement.Handlers; using Orchard.Data; -using Orchard.Indexing; +using Orchard.Indexing.Models; using Orchard.Logging; using Orchard.Services; -using Orchard.Tasks; -using Orchard.Core.Indexing.Models; using Orchard.Tasks.Indexing; -using Orchard.Indexing; -namespace Orchard.Core.Indexing.Services { +namespace Orchard.Indexing.Services { /// /// Contains the logic which is regularly executed to retrieve index information from multiple content handlers. /// [UsedImplicitly] - public class IndexingTaskExecutor : IBackgroundTask, IIndexNotifierHandler { + public class IndexingTaskExecutor : IIndexNotifierHandler { private readonly IClock _clock; private readonly IRepository _repository; - private readonly IEnumerable _handlers; private IIndexProvider _indexProvider; private readonly IIndexManager _indexManager; private readonly IIndexingTaskManager _indexingTaskManager; private readonly IContentManager _contentManager; + private readonly IIndexSynLock _indexSynLock; private const string SearchIndexName = "Search"; - private readonly object _synLock = new object(); - public IndexingTaskExecutor( IClock clock, IRepository repository, - IEnumerable handlers, IIndexManager indexManager, IIndexingTaskManager indexingTaskManager, - IContentManager contentManager) { + IContentManager contentManager, + IIndexSynLock indexSynLock) { _clock = clock; _repository = repository; _indexManager = indexManager; - _handlers = handlers; _indexingTaskManager = indexingTaskManager; _contentManager = contentManager; + _indexSynLock = indexSynLock; Logger = NullLogger.Instance; } public ILogger Logger { get; set; } public void UpdateIndex(string indexName) { - if (indexName == SearchIndexName) { - Sweep(); - } - } + var synLock = _indexSynLock.GetSynLock(SearchIndexName); - public void Sweep() { - - if ( !System.Threading.Monitor.TryEnter(_synLock) ) { + if ( !System.Threading.Monitor.TryEnter(synLock) ) { Logger.Information("Index was requested but was already running"); return; } @@ -68,8 +57,8 @@ namespace Orchard.Core.Indexing.Services { } _indexProvider = _indexManager.GetSearchIndexProvider(); - var updateIndexDocuments = new List(); - var lastIndexing = DateTime.UtcNow; + var updateIndexDocuments = new List(); + DateTime lastIndexing; // Do we need to rebuild the full index (first time module is used, or rebuild index requested) ? if (_indexProvider.IsEmpty(SearchIndexName)) { @@ -81,22 +70,11 @@ namespace Orchard.Core.Indexing.Services { // get every existing content item to index it foreach (var contentItem in _contentManager.Query(VersionOptions.Published).List()) { try { - var context = new IndexContentContext { - ContentItem = contentItem, - IndexDocument = _indexProvider.New(contentItem.Id) - }; + var documentIndex = _indexProvider.New(contentItem.Id); - // dispatch to handlers to retrieve index information - foreach (var handler in _handlers) { - handler.Indexing(context); - } - - if ( context.IndexDocument.IsDirty ) { - updateIndexDocuments.Add(context.IndexDocument); - - foreach ( var handler in _handlers ) { - handler.Indexed(context); - } + _contentManager.Index(contentItem, documentIndex); + if(documentIndex.IsDirty) { + updateIndexDocuments.Add(documentIndex); } } catch (Exception ex) { @@ -139,22 +117,11 @@ namespace Orchard.Core.Indexing.Services { var task = new IndexingTask(_contentManager, taskRecord); try { - var context = new IndexContentContext { - ContentItem = task.ContentItem, - IndexDocument = _indexProvider.New(task.ContentItem.Id) - }; + var documentIndex = _indexProvider.New(task.ContentItem.Id); - // dispatch to handlers to retrieve index information - foreach (var handler in _handlers) { - handler.Indexing(context); - } - - if ( context.IndexDocument.IsDirty ) { - updateIndexDocuments.Add(context.IndexDocument); - - foreach (var handler in _handlers) { - handler.Indexed(context); - } + _contentManager.Index(task.ContentItem, documentIndex); + if ( documentIndex.IsDirty ) { + updateIndexDocuments.Add(documentIndex); } } @@ -173,7 +140,7 @@ namespace Orchard.Core.Indexing.Services { } } finally { - System.Threading.Monitor.Exit(_synLock); + System.Threading.Monitor.Exit(synLock); } } } diff --git a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskManager.cs similarity index 86% rename from src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskManager.cs index 9969920c3..9c2cb0de6 100644 --- a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/IndexingTaskManager.cs @@ -1,20 +1,16 @@ using System; -using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Orchard.ContentManagement; using Orchard.Data; +using Orchard.Indexing.Models; using Orchard.Logging; -using Orchard.Tasks.Scheduling; -using Orchard.Utility.Extensions; using Orchard.Tasks.Indexing; -using Orchard.Core.Indexing.Models; using Orchard.Services; -namespace Orchard.Core.Indexing.Services { +namespace Orchard.Indexing.Services { [UsedImplicitly] public class IndexingTaskManager : IIndexingTaskManager { - private readonly IContentManager _contentManager; private readonly IRepository _repository; private readonly IClock _clock; @@ -24,7 +20,6 @@ namespace Orchard.Core.Indexing.Services { IClock clock) { _clock = clock; _repository = repository; - _contentManager = contentManager; Logger = NullLogger.Instance; } diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/LuceneIndexProvider.cs similarity index 86% rename from src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Services/LuceneIndexProvider.cs index 4cf99ff3a..7149e14d5 100644 --- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/LuceneIndexProvider.cs @@ -9,17 +9,17 @@ using Lucene.Net.Index; using Lucene.Net.Store; using Orchard.Environment.Configuration; using Orchard.FileSystems.AppData; -using Orchard.Indexing; -using Directory = Lucene.Net.Store.Directory; -using Version = Lucene.Net.Util.Version; +using Orchard.Indexing.Models; using Orchard.Logging; using System.Xml.Linq; +using Directory = Lucene.Net.Store.Directory; +using Version = Lucene.Net.Util.Version; -namespace Orchard.Core.Indexing.Lucene { +namespace Orchard.Indexing.Services { /// /// Represents the default implementation of an IIndexProvider, based on Lucene /// - public class DefaultIndexProvider : IIndexProvider { + public class LuceneIndexProvider : IIndexProvider { private readonly IAppDataFolder _appDataFolder; private readonly ShellSettings _shellSettings; public static readonly Version LuceneVersion = Version.LUCENE_29; @@ -31,7 +31,7 @@ namespace Orchard.Core.Indexing.Lucene { public ILogger Logger { get; set; } - public DefaultIndexProvider(IAppDataFolder appDataFolder, ShellSettings shellSettings) { + public LuceneIndexProvider(IAppDataFolder appDataFolder, ShellSettings shellSettings) { _appDataFolder = appDataFolder; _shellSettings = shellSettings; _analyzer = CreateAnalyzer(); @@ -62,7 +62,7 @@ namespace Orchard.Core.Indexing.Lucene { return FSDirectory.Open(directoryInfo); } - private static Document CreateDocument(DefaultIndexDocument indexDocument) { + private static Document CreateDocument(LuceneDocumentIndex indexDocument) { var doc = new Document(); indexDocument.PrepareForIndexing(); @@ -123,21 +123,21 @@ namespace Orchard.Core.Indexing.Lucene { } } - public void Store(string indexName, IIndexDocument indexDocument) { - Store(indexName, new [] { (DefaultIndexDocument)indexDocument }); + public void Store(string indexName, IDocumentIndex indexDocument) { + Store(indexName, new [] { (LuceneDocumentIndex)indexDocument }); } - public void Store(string indexName, IEnumerable indexDocuments) { - Store(indexName, indexDocuments.Cast()); + public void Store(string indexName, IEnumerable indexDocuments) { + Store(indexName, indexDocuments.Cast()); } - public void Store(string indexName, IEnumerable indexDocuments) { + public void Store(string indexName, IEnumerable indexDocuments) { if(indexDocuments.AsQueryable().Count() == 0) { return; } var writer = new IndexWriter(GetDirectory(indexName), _analyzer, false, IndexWriter.MaxFieldLength.UNLIMITED); - DefaultIndexDocument current = null; + LuceneDocumentIndex current = null; try { foreach ( var indexDocument in indexDocuments ) { @@ -188,12 +188,12 @@ namespace Orchard.Core.Indexing.Lucene { } } - public IIndexDocument New(int documentId) { - return new DefaultIndexDocument(documentId); + public IDocumentIndex New(int documentId) { + return new LuceneDocumentIndex(documentId); } public ISearchBuilder CreateSearchBuilder(string indexName) { - return new DefaultSearchBuilder(GetDirectory(indexName)); + return new LuceneSearchBuilder(GetDirectory(indexName)); } private string GetSettingsFileName(string indexName) { diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs b/src/Orchard.Web/Modules/Orchard.Indexing/Services/LuceneSearchBuilder.cs similarity index 61% rename from src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/Services/LuceneSearchBuilder.cs index a9878c268..85a2d5ad5 100644 --- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Services/LuceneSearchBuilder.cs @@ -7,19 +7,19 @@ using Lucene.Net.Analysis.Tokenattributes; using Lucene.Net.Index; using Lucene.Net.Search; using Lucene.Net.Store; +using Orchard.Indexing.Models; using Orchard.Logging; using Lucene.Net.Documents; -using Orchard.Indexing; using Lucene.Net.QueryParsers; -namespace Orchard.Core.Indexing.Lucene { - public class DefaultSearchBuilder : ISearchBuilder { +namespace Orchard.Indexing.Services { + public class LuceneSearchBuilder : ISearchBuilder { private const int MaxResults = Int16.MaxValue; private readonly Directory _directory; - private readonly Dictionary _fields; + private readonly List _clauses; private int _count; private int _skip; private readonly Dictionary _before; @@ -31,16 +31,14 @@ namespace Orchard.Core.Indexing.Lucene { private string[] _defaultFields; // pending clause attributes - private string _field; - private string _terms; private BooleanClause.Occur _occur; - private bool _prefix; - private bool _stem; + private bool _exactMatch; private float _boost; + private Query _query; public ILogger Logger { get; set; } - public DefaultSearchBuilder(Directory directory) { + public LuceneSearchBuilder(Directory directory) { _directory = directory; Logger = NullLogger.Instance; @@ -48,15 +46,18 @@ namespace Orchard.Core.Indexing.Lucene { _skip = 0; _before = new Dictionary(); _after = new Dictionary(); - _fields = new Dictionary(); + _clauses = new List(); _sort = String.Empty; _sortDescending = true; _parse = String.Empty; - _analyzer = DefaultIndexProvider.CreateAnalyzer(); + _analyzer = LuceneIndexProvider.CreateAnalyzer(); InitPendingClause(); } - + public ISearchBuilder Parse(string defaultField, string query) { + return Parse(new string[] {defaultField}, query); + } + public ISearchBuilder Parse(string[] defaultFields, string query) { if ( defaultFields.Length == 0 ) { throw new ArgumentException("Default field can't be empty"); @@ -71,12 +72,61 @@ namespace Orchard.Core.Indexing.Lucene { return this; } + public ISearchBuilder WithField(string field, int value) { + CreatePendingClause(); + _query = NumericRangeQuery.NewIntRange(field, value, value, true, true); + return this; + } + + public ISearchBuilder WithinRange(string field, int min, int max) { + CreatePendingClause(); + _query = NumericRangeQuery.NewIntRange(field, min, max, true, true); + return this; + } + + public ISearchBuilder WithField(string field, float value) { + CreatePendingClause(); + _query = NumericRangeQuery.NewFloatRange(field, value, value, true, true); + return this; + } + + public ISearchBuilder WithinRange(string field, float min, float max) { + CreatePendingClause(); + _query = NumericRangeQuery.NewFloatRange(field, min, max, true, true); + return this; + } + + public ISearchBuilder WithField(string field, bool value) { + CreatePendingClause(); + _query = new TermQuery(new Term(field, value.ToString())); + return this; + } + + public ISearchBuilder WithField(string field, DateTime value) { + CreatePendingClause(); + _query = new TermQuery(new Term(field, DateTools.DateToString(value, DateTools.Resolution.SECOND))); + return this; + } + + public ISearchBuilder WithinRange(string field, DateTime min, DateTime max) { + CreatePendingClause(); + _query = new TermRangeQuery(field, DateTools.DateToString(min, DateTools.Resolution.SECOND), DateTools.DateToString(max, DateTools.Resolution.SECOND), true, true); + return this; + } + + public ISearchBuilder WithinRange(string field, string min, string max) { + CreatePendingClause(); + _query = new TermRangeQuery(field, QueryParser.Escape(min.ToLower()), QueryParser.Escape(min.ToLower()), true, true); + return this; + } + public ISearchBuilder WithField(string field, string value) { CreatePendingClause(); - _field = field; - _terms = value; - + if ( !String.IsNullOrWhiteSpace(value) ) { + _query = new TermQuery(new Term(field, QueryParser.Escape(value.ToLower()))); + } + return this; } @@ -91,8 +141,7 @@ namespace Orchard.Core.Indexing.Lucene { } public ISearchBuilder ExactMatch() { - _prefix = false; - _stem = false; + _exactMatch = true; return this; } @@ -102,58 +151,29 @@ namespace Orchard.Core.Indexing.Lucene { } private void InitPendingClause() { - _field = String.Empty; - _terms = String.Empty; _occur = BooleanClause.Occur.SHOULD; - _prefix = true; - _stem = true; + _exactMatch = false; + _query = null; _boost = 0; } private void CreatePendingClause() { - if(String.IsNullOrWhiteSpace(_field) || String.IsNullOrWhiteSpace(_terms)) { + if(_query == null) { return; } - var tokens = new List(); - using ( var sr = new System.IO.StringReader(_terms) ) { - var stream = _analyzer.TokenStream(_field, sr); - - if(_stem) { - stream = new PorterStemFilter(stream); - } - - while ( stream.IncrementToken() ) { - tokens.Add(( (TermAttribute)stream.GetAttribute(typeof(TermAttribute)) ).Term()); + if (_boost != 0) { + _query.SetBoost(_boost); + } + + if(!_exactMatch) { + var termQuery = _query as TermQuery; + if(termQuery != null) { + _query = new PrefixQuery(termQuery.GetTerm()); } } - var clauses = tokens - .Where(k => !String.IsNullOrWhiteSpace(k)) // remove empty strings - .Select(QueryParser.Escape) // escape special chars (e.g. C#) - .Select(k => new Term(_field, k)) // creates the Term instance - .Select(t => _prefix ? new PrefixQuery(t) as Query : new TermQuery(t) as Query) // apply the corresponding Query - .Select(q => { - if (_boost != 0) q.SetBoost(_boost); - return q; - }) - .Select(q => new BooleanClause(q, _occur)); // apply the corresponding clause - - if ( !_fields.ContainsKey(_field) ) { - _fields[_field] = new BooleanClause[0]; - } - - _fields[_field] = _fields[_field].Union(clauses).ToArray(); - } - - public ISearchBuilder After(string name, DateTime date) { - _after[name] = date; - return this; - } - - public ISearchBuilder Before(string name, DateTime date) { - _before[name] = date; - return this; + _clauses.Add(new BooleanClause(_query, _occur)); } public ISearchBuilder SortBy(string name) { @@ -189,29 +209,13 @@ namespace Orchard.Core.Indexing.Lucene { if(!String.IsNullOrWhiteSpace(_parse)) { foreach ( var defaultField in _defaultFields ) { - var clause = new BooleanClause(new QueryParser(DefaultIndexProvider.LuceneVersion, defaultField, DefaultIndexProvider.CreateAnalyzer()).Parse(_parse), BooleanClause.Occur.SHOULD); + var clause = new BooleanClause(new QueryParser(LuceneIndexProvider.LuceneVersion, defaultField, LuceneIndexProvider.CreateAnalyzer()).Parse(_parse), BooleanClause.Occur.SHOULD); query.Add(clause); } } - - if ( _fields.Keys.Count > 0 ) { // apply specific filters if defined - foreach ( var clauses in _fields.Values ) { - foreach( var clause in clauses) - query.Add(clause); - } - } - - // apply date range filter ? - foreach(string name in _before.Keys.Concat(_after.Keys)) { - if ((_before.ContainsKey(name) && _before[name] != DateTime.MaxValue) || (_after.ContainsKey(name) && _after[name] != DateTime.MinValue)) { - var filter = new TermRangeQuery("date", - DateTools.DateToString(_after.ContainsKey(name) ? _after[name] : DateTime.MinValue, DateTools.Resolution.SECOND), - DateTools.DateToString(_before.ContainsKey(name) ? _before[name] : DateTime.MaxValue, DateTools.Resolution.SECOND), - true, true); - query.Add(filter, BooleanClause.Occur.MUST); - } - } + foreach( var clause in _clauses) + query.Add(clause); if ( query.Clauses().Count == 0 ) { // get all documents ? query.Add(new TermRangeQuery("id", "0", "9", true, true), BooleanClause.Occur.SHOULD); @@ -223,7 +227,7 @@ namespace Orchard.Core.Indexing.Lucene { public IEnumerable Search() { var query = CreateQuery(); - + IndexSearcher searcher; try { @@ -247,15 +251,16 @@ namespace Orchard.Core.Indexing.Lucene { false, true); + Logger.Debug("Searching: {0}", query.ToString()); searcher.Search(query, collector); - var results = new List(); + var results = collector.TopDocs().scoreDocs + .Skip(_skip) + .Select(scoreDoc => new LuceneSearchHit(searcher.Doc(scoreDoc.doc), scoreDoc.score)) + .ToList(); - foreach ( var scoreDoc in collector.TopDocs().scoreDocs.Skip(_skip) ) { - results.Add(new DefaultSearchHit(searcher.Doc(scoreDoc.doc), scoreDoc.score)); - } + Logger.Debug("Search results: {0}", results.Count); - Logger.Information("Search results: {0}", results.Count); return results; } finally { @@ -297,7 +302,7 @@ namespace Orchard.Core.Indexing.Lucene { var hits = searcher.Search(query, 1); Logger.Information("Search results: {0}", hits.scoreDocs.Length); if ( hits.scoreDocs.Length > 0 ) { - return new DefaultSearchHit(searcher.Doc(hits.scoreDocs[0].doc), hits.scoreDocs[0].score); + return new LuceneSearchHit(searcher.Doc(hits.scoreDocs[0].doc), hits.scoreDocs[0].score); } else { return null; diff --git a/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs b/src/Orchard.Web/Modules/Orchard.Indexing/ViewModels/IndexViewModel.cs similarity index 73% rename from src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs rename to src/Orchard.Web/Modules/Orchard.Indexing/ViewModels/IndexViewModel.cs index 2a0637cda..6c1f4caff 100644 --- a/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.Indexing/ViewModels/IndexViewModel.cs @@ -1,8 +1,8 @@ using System; using Orchard.Mvc.ViewModels; -namespace Orchard.Search.ViewModels { - public class SearchIndexViewModel : BaseViewModel { +namespace Orchard.Indexing.ViewModels { + public class IndexViewModel : BaseViewModel { public bool HasIndexToManage { get; set; } //todo: hang the index updated date off here to show in the admin UI (e.g. -> index updated: June 4, 2010 [update index]) public DateTime IndexUpdatedUtc { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx b/src/Orchard.Web/Modules/Orchard.Indexing/Views/Admin/Index.ascx similarity index 90% rename from src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx rename to src/Orchard.Web/Modules/Orchard.Indexing/Views/Admin/Index.ascx index 53c1cbd2a..ce3678a95 100644 --- a/src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Views/Admin/Index.ascx @@ -1,8 +1,8 @@ -<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> <%@ Import Namespace="Orchard.Mvc.Html" %><% Html.RegisterStyle("admin.css"); %> <%:Html.TitleForPage(T("Search Index Management").ToString()) %><% -using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %> +using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.Indexing"})) { %> <%:T("The search index was last updated {0}.", Html.DateTimeRelative(Model.IndexUpdatedUtc, T))%> " class="primaryAction"><%:T("Update")%> <%:Html.AntiForgeryTokenOrchard() %> diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Views/Web.config b/src/Orchard.Web/Modules/Orchard.Indexing/Views/Web.config new file mode 100644 index 000000000..e065d8735 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Views/Web.config @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Web.config b/src/Orchard.Web/Modules/Orchard.Indexing/Web.config new file mode 100644 index 000000000..c654951f1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Web.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs deleted file mode 100644 index 5d98de72c..000000000 --- a/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Web.Mvc; -using Orchard.Localization; -using Orchard.Search.Services; -using Orchard.Search.ViewModels; -using Orchard.UI.Notify; - -namespace Orchard.Search.Controllers { - public class AdminController : Controller { - private readonly ISearchService _searchService; - - public AdminController(ISearchService searchService, IOrchardServices services) { - _searchService = searchService; - Services = services; - T = NullLocalizer.Instance; - } - - public IOrchardServices Services { get; private set; } - public Localizer T { get; set; } - - public ActionResult Index() { - var viewModel = new SearchIndexViewModel {HasIndexToManage = _searchService.HasIndexToManage, IndexUpdatedUtc = _searchService.GetIndexUpdatedUtc()}; - - if (!viewModel.HasIndexToManage) - Services.Notifier.Information(T("There is no search index to manage for this site.")); - - return View(viewModel); - } - - [HttpPost] - public ActionResult Update() { - if (!Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index."))) - return new HttpUnauthorizedResult(); - - _searchService.UpdateIndex(); - - return RedirectToAction("Index"); - } - - [HttpPost] - public ActionResult Rebuild() { - if (!Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index."))) - return new HttpUnauthorizedResult(); - - _searchService.RebuildIndex(); - _searchService.UpdateIndex(); - - return RedirectToAction("Index"); - } - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs b/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs index f7e4d4f98..53d2ce88c 100644 --- a/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs +++ b/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs @@ -18,7 +18,7 @@ namespace Orchard.Search.Controllers { Query = q, DefaultPageSize = 10, // <- yeah, I know :| PageOfResults = _searchService.Query(q, page, pageSize, searchHit => new SearchResultViewModel { - Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.Id), "SummaryForSearch"), + Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.ContentItemId), "SummaryForSearch"), SearchHit = searchHit }) }; diff --git a/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj b/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj index 5ee797d6f..6c6232cbf 100644 --- a/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj +++ b/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj @@ -65,15 +65,11 @@ - - - - @@ -88,7 +84,6 @@ - diff --git a/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs b/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs index d21d4f991..dba953f48 100644 --- a/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs +++ b/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Orchard.Collections; using Orchard.Indexing; @@ -39,9 +40,11 @@ namespace Orchard.Search.Services var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(SearchIndexName) .Parse(new [] {"title", "body"}, query); - if(HttpContext.Current != null) { - searchBuilder.WithField("culture", _cultureManager.GetCurrentCulture(HttpContext.Current)).Mandatory(); - } + var culture = _cultureManager.GetSiteCulture(); + + // use LCID as the text representation gets analyzed by the query parser + searchBuilder + .WithField("culture", CultureInfo.GetCultureInfo(culture).LCID); var totalCount = searchBuilder.Count(); if (pageSize != null) diff --git a/src/Orchard.Web/Orchard.Web.csproj b/src/Orchard.Web/Orchard.Web.csproj index 2da25214d..91ecbb3a2 100644 --- a/src/Orchard.Web/Orchard.Web.csproj +++ b/src/Orchard.Web/Orchard.Web.csproj @@ -131,6 +131,10 @@ {67C1D3AF-A0EC-46B2-BAE1-DF1DA8E0B890} Orchard.DevTools + + {EA2B9121-EF54-40A6-A53E-6593C86EE696} + Orchard.Indexing + {D9A7B330-CD22-4DA1-A95A-8DE1982AD8EB} Orchard.Media diff --git a/src/Orchard.sln b/src/Orchard.sln index c1b3aa847..a9604af83 100644 --- a/src/Orchard.sln +++ b/src/Orchard.sln @@ -67,6 +67,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.MetaData", "Orchard EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Search", "Orchard.Web\Modules\Orchard.Search\Orchard.Search.csproj", "{4BE4EB01-AC56-4048-924E-2CA77F509ABA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Indexing", "Orchard.Web\Modules\Orchard.Indexing\Orchard.Indexing.csproj", "{EA2B9121-EF54-40A6-A53E-6593C86EE696}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -189,6 +191,10 @@ Global {4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Release|Any CPU.Build.0 = Release|Any CPU + {EA2B9121-EF54-40A6-A53E-6593C86EE696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA2B9121-EF54-40A6-A53E-6593C86EE696}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA2B9121-EF54-40A6-A53E-6593C86EE696}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA2B9121-EF54-40A6-A53E-6593C86EE696}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -210,6 +216,7 @@ Global {17F86780-9A1F-4AA1-86F1-875EEC2730C7} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {23E04990-2A8D-41B8-9908-6DDB71EA3B23} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {4BE4EB01-AC56-4048-924E-2CA77F509ABA} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} + {EA2B9121-EF54-40A6-A53E-6593C86EE696} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {ABC826D4-2FA1-4F2F-87DE-E6095F653810} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {F112851D-B023-4746-B6B1-8D2E5AD8F7AA} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {6CB3EB30-F725-45C0-9742-42599BA8E8D2} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} diff --git a/src/Orchard/ContentManagement/DefaultContentManager.cs b/src/Orchard/ContentManagement/DefaultContentManager.cs index 8c518bf06..6b2619be6 100644 --- a/src/Orchard/ContentManagement/DefaultContentManager.cs +++ b/src/Orchard/ContentManagement/DefaultContentManager.cs @@ -7,6 +7,7 @@ using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.MetaData.Builders; using Orchard.ContentManagement.Records; using Orchard.Data; +using Orchard.Indexing; using Orchard.Mvc.ViewModels; namespace Orchard.ContentManagement { @@ -429,5 +430,18 @@ namespace Orchard.ContentManagement { } return contentTypeRecord; } + + public void Index(ContentItem contentItem, IDocumentIndex documentIndex) { + var indexContentContext = new IndexContentContext(contentItem, documentIndex); + + // dispatch to handlers to retrieve index information + foreach ( var handler in Handlers ) { + handler.Indexing(indexContentContext); + } + + foreach ( var handler in Handlers ) { + handler.Indexed(indexContentContext); + } + } } } diff --git a/src/Orchard/ContentManagement/Handlers/IndexContentContext.cs b/src/Orchard/ContentManagement/Handlers/IndexContentContext.cs index 4e5c8f443..dbe9724a6 100644 --- a/src/Orchard/ContentManagement/Handlers/IndexContentContext.cs +++ b/src/Orchard/ContentManagement/Handlers/IndexContentContext.cs @@ -1,8 +1,13 @@ -using Orchard.Indexing; +using Orchard.Indexing; namespace Orchard.ContentManagement.Handlers { - public class IndexContentContext { - public ContentItem ContentItem { get; set; } - public IIndexDocument IndexDocument { get; set; } + public class IndexContentContext : ContentContextBase { + + public IDocumentIndex DocumentIndex { get; private set; } + + public IndexContentContext(ContentItem contentItem, IDocumentIndex documentIndex) + : base(contentItem) { + DocumentIndex = documentIndex; + } } -} +} \ No newline at end of file diff --git a/src/Orchard/ContentManagement/IContentManager.cs b/src/Orchard/ContentManagement/IContentManager.cs index e9d755b23..eaff2512d 100644 --- a/src/Orchard/ContentManagement/IContentManager.cs +++ b/src/Orchard/ContentManagement/IContentManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Orchard.Indexing; using Orchard.Mvc.ViewModels; namespace Orchard.ContentManagement { @@ -17,6 +18,7 @@ namespace Orchard.ContentManagement { void Publish(ContentItem contentItem); void Unpublish(ContentItem contentItem); void Remove(ContentItem contentItem); + void Index(ContentItem contentItem, IDocumentIndex documentIndex); void Flush(); IContentQuery Query(); diff --git a/src/Orchard/Indexing/IIndexDocument.cs b/src/Orchard/Indexing/IIndexDocument.cs index 574eb612b..fdab1649c 100644 --- a/src/Orchard/Indexing/IIndexDocument.cs +++ b/src/Orchard/Indexing/IIndexDocument.cs @@ -3,26 +3,26 @@ using System.Collections.Generic; namespace Orchard.Indexing { - public interface IIndexDocument { + public interface IDocumentIndex { - IIndexDocument SetContentItemId(int documentId); + IDocumentIndex SetContentItemId(int documentId); - IIndexDocument Add(string name, string value); - IIndexDocument Add(string name, string value, bool removeTags); - IIndexDocument Add(string name, DateTime value); - IIndexDocument Add(string name, int value); - IIndexDocument Add(string name, bool value); - IIndexDocument Add(string name, float value); + IDocumentIndex Add(string name, string value); + IDocumentIndex Add(string name, string value, bool removeTags); + IDocumentIndex Add(string name, DateTime value); + IDocumentIndex Add(string name, int value); + IDocumentIndex Add(string name, bool value); + IDocumentIndex Add(string name, float value); /// - /// Whether to store the original value to the index. + /// Stores the original value to the index. /// - IIndexDocument Store(bool store); + IDocumentIndex Store(); /// - /// Whether the content should be tokenized or not. If not, value will be taken as a whole. + /// Content is analyzed and tokenized. /// - IIndexDocument Analyze(bool analyze); + IDocumentIndex Analyze(); /// /// Whether some property have been added to this document, or otherwise if it's empty diff --git a/src/Orchard/Indexing/IIndexProvider.cs b/src/Orchard/Indexing/IIndexProvider.cs index d873b1a13..a8d22dd32 100644 --- a/src/Orchard/Indexing/IIndexProvider.cs +++ b/src/Orchard/Indexing/IIndexProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Orchard.ContentManagement; namespace Orchard.Indexing { public interface IIndexProvider : IDependency { @@ -32,17 +33,17 @@ namespace Orchard.Indexing { /// Creates an empty document /// /// - IIndexDocument New(int documentId); + IDocumentIndex New(int documentId); /// /// Adds a new document to the index /// - void Store(string indexName, IIndexDocument indexDocument); + void Store(string indexName, IDocumentIndex indexDocument); /// /// Adds a set of new document to the index /// - void Store(string indexName, IEnumerable indexDocuments); + void Store(string indexName, IEnumerable indexDocuments); /// /// Removes an existing document from the index diff --git a/src/Orchard/Indexing/ISearchBuilder.cs b/src/Orchard/Indexing/ISearchBuilder.cs index ebbf3f890..eb6136fcf 100644 --- a/src/Orchard/Indexing/ISearchBuilder.cs +++ b/src/Orchard/Indexing/ISearchBuilder.cs @@ -3,16 +3,24 @@ using System.Collections.Generic; namespace Orchard.Indexing { public interface ISearchBuilder { + ISearchBuilder Parse(string defaultField, string query); ISearchBuilder Parse(string[] defaultFields, string query); + ISearchBuilder WithField(string field, bool value); + ISearchBuilder WithField(string field, DateTime value); ISearchBuilder WithField(string field, string value); + ISearchBuilder WithField(string field, int value); + ISearchBuilder WithField(string field, float value); + ISearchBuilder WithinRange(string field, int min, int max); + ISearchBuilder WithinRange(string field, float min, float max); + ISearchBuilder WithinRange(string field, DateTime min, DateTime max); + ISearchBuilder WithinRange(string field, string min, string max); + ISearchBuilder Mandatory(); ISearchBuilder Forbidden(); ISearchBuilder ExactMatch(); ISearchBuilder Weighted(float weight); - ISearchBuilder After(string name, DateTime date); - ISearchBuilder Before(string name, DateTime date); ISearchBuilder SortBy(string name); ISearchBuilder Ascending(); diff --git a/src/Orchard/Indexing/ISearchHit.cs b/src/Orchard/Indexing/ISearchHit.cs index 0382e3d0e..4555a1f8b 100644 --- a/src/Orchard/Indexing/ISearchHit.cs +++ b/src/Orchard/Indexing/ISearchHit.cs @@ -1,7 +1,7 @@ using System; namespace Orchard.Indexing { public interface ISearchHit { - int Id { get; } + int ContentItemId { get; } float Score { get; } int GetInt(string name); diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index a71212ea6..dcc3ac677 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -227,6 +227,7 @@ Code + Code @@ -413,7 +414,6 @@ -
<%:T("The search index was last updated {0}.", Html.DateTimeRelative(Model.IndexUpdatedUtc, T))%> " class="primaryAction"><%:T("Update")%>