Complete refactoring on Indexing module

Changed Parse() to use lucene syntax
Moved everything to Orchard.Indexing
New filters for search

--HG--
branch : dev
This commit is contained in:
Sebastien Ros
2010-06-17 16:21:29 -07:00
parent a1cc5d579b
commit 40769d6f2a
47 changed files with 775 additions and 413 deletions

View File

@@ -104,8 +104,6 @@
<Compile Include="Common\Providers\CommonAspectProviderTests.cs" />
<Compile Include="Common\Services\RoutableServiceTests.cs" />
<Compile Include="Feeds\Controllers\FeedControllerTests.cs" />
<Compile Include="Indexing\DefaultIndexProviderTests.cs" />
<Compile Include="Indexing\DefaultSearchBuilderTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scheduling\ScheduledTaskManagerTests.cs" />
<Compile Include="Scheduling\ScheduledTaskExecutorTests.cs" />

View File

@@ -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<DefaultIndexProvider>().As<IIndexProvider>();
builder.RegisterType<LuceneIndexProvider>().As<IIndexProvider>();
builder.RegisterInstance(_appDataFolder).As<IAppDataFolder>();
// 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", "<hr>some content</hr>"));
_provider.Store("default", _provider.New(2).Add("body", "<hr>some content</hr>", true));
_provider.Store("default", _provider.New(1).Add("body", "<hr>some content</hr>").Analyze());
_provider.Store("default", _provider.New(2).Add("body", "<hr>some content</hr>", 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");

View File

@@ -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<DefaultIndexProvider>().As<IIndexProvider>();
builder.RegisterType<LuceneIndexProvider>().As<IIndexProvider>();
builder.RegisterInstance(_appDataFolder).As<IAppDataFolder>();
// 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));
}
}
}

View File

@@ -108,6 +108,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="DatabaseEnabledTestsBase.cs" />
<Compile Include="Indexing\LuceneIndexProviderTests.cs" />
<Compile Include="Indexing\LuceneSearchBuilderTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Roles\Controllers\AdminControllerTests.cs" />
<Compile Include="Roles\Services\RoleServiceTests.cs" />
@@ -128,6 +130,10 @@
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.Indexing\Orchard.Indexing.csproj">
<Project>{EA2B9121-EF54-40A6-A53E-6593C86EE696}</Project>
<Name>Orchard.Indexing</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.Pages\Orchard.Pages.csproj">
<Project>{4A9C04A6-0986-4A92-A610-5F59FF273FB9}</Project>
<Name>Orchard.Pages</Name>

View File

@@ -9,9 +9,9 @@ namespace Orchard.Core.Common.Handlers {
public BodyAspectHandler(IRepository<BodyRecord> bodyRepository) {
Filters.Add(StorageFilter.For(bodyRepository));
OnIndexing<BodyAspect>((context, bodyAspect) => context.IndexDocument
.Add("body", bodyAspect.Record.Text, true).Store(false)
.Add("format", bodyAspect.Record.Format).Analyze(false));
OnIndexing<BodyAspect>((context, bodyAspect) => context.DocumentIndex
.Add("body", bodyAspect.Record.Text, true).Analyze()
.Add("format", bodyAspect.Record.Format).Store());
}
}
}

View File

@@ -53,12 +53,12 @@ namespace Orchard.Core.Common.Handlers {
//OnGetEditorViewModel<CommonAspect>(GetEditor);
//OnUpdateEditorViewModel<CommonAspect>(UpdateEditor);
OnIndexing<CommonAspect>((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<CommonAspect>((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()
);
}

View File

@@ -36,8 +36,8 @@ namespace Orchard.Core.Common.Handlers {
OnCreated<RoutableAspect>((context, ra) => routableService.ProcessSlug(ra));
OnIndexing<RoutableAspect>((context, part) => context.IndexDocument
.Add("slug", part.Slug).Analyze(false)
OnIndexing<RoutableAspect>((context, part) => context.DocumentIndex
.Add("slug", part.Slug)
.Add("title", part.Title)
);
}

View File

@@ -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<Localized>(LazyLoadHandlers);
OnIndexed<Localized>((context, localized) => context.IndexDocument.Add("culture", localized.Culture != null ? localized.Culture.Culture : _cultureManager.GetSiteCulture()).Store(false).Analyze(false));
OnIndexed<Localized>((context, localized) => context.DocumentIndex
.Add("culture", CultureInfo.GetCultureInfo(localized.Culture != null ? localized.Culture.Culture : _cultureManager.GetSiteCulture()).LCID)
.Store()
);
}
public Localizer T { get; set; }

View File

@@ -39,10 +39,6 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Lucene.Net, Version=2.9.2.2, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\lucene.net\Lucene.Net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
@@ -136,16 +132,6 @@
<Compile Include="Feeds\Rss\RssResult.cs" />
<Compile Include="HomePage\Controllers\HomeController.cs" />
<Compile Include="HomePage\Routes.cs" />
<Compile Include="Indexing\Commands\IndexingCommands.cs" />
<Compile Include="Indexing\Lucene\DefaultIndexDocument.cs" />
<Compile Include="Indexing\Lucene\DefaultIndexProvider.cs" />
<Compile Include="Indexing\Lucene\DefaultSearchBuilder.cs" />
<Compile Include="Indexing\Lucene\DefaultSearchHit.cs" />
<Compile Include="Indexing\Models\IndexingTask.cs" />
<Compile Include="Indexing\Models\IndexingTaskRecord.cs" />
<Compile Include="Indexing\Services\CreateIndexingTaskHandler.cs" />
<Compile Include="Indexing\Services\IndexingTaskExecutor.cs" />
<Compile Include="Indexing\Services\IndexingTaskManager.cs" />
<Compile Include="Localization\Handlers\LocalizedHandler.cs" />
<Compile Include="Localization\Models\Localized.cs" />
<Compile Include="Localization\Models\LocalizedRecord.cs" />
@@ -232,7 +218,6 @@
<Content Include="Contents\Views\EditorTemplates\Items\Contents.Item.ascx" />
<Content Include="Contents\Views\Item\Preview.aspx" />
<Content Include="Contents\Views\Item\Display.aspx" />
<Content Include="Indexing\Module.txt" />
<Content Include="Localization\Module.txt" />
<Content Include="Routable\Module.txt" />
<Content Include="Routable\Scripts\jquery.slugify.js" />

View File

@@ -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)));
}
}

View File

@@ -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<IIndexNotifierHandler> _indexNotifierHandlers;
private readonly IIndexManager _indexManager;

View File

@@ -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");
}
}
}

View File

@@ -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 {
/// <summary>
/// Intercepts the ContentHandler events to create indexing tasks when a content item
/// is published, and to delete them when the content item is unpublished.

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<AbstractField> Fields { get; private set; }
private AbstractField _previousField;
public int Id { get; private set; }
public DefaultIndexDocument(int documentId) {
public LuceneDocumentIndex(int documentId) {
Fields = new List<AbstractField>();
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);
}
_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;

View File

@@ -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());

View File

@@ -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
Category: Search

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{EA2B9121-EF54-40A6-A53E-6593C86EE696}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Orchard.Indexing</RootNamespace>
<AssemblyName>Orchard.Indexing</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Lucene.Net">
<HintPath>..\..\..\..\lib\lucene.net\Lucene.Net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Web.Services" />
<Reference Include="System.EnterpriseServices" />
</ItemGroup>
<ItemGroup>
<Content Include="Module.txt" />
<Content Include="Views\Admin\Index.ascx" />
<Content Include="Web.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Commands\IndexingCommands.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Handlers\CreateIndexingTaskHandler.cs" />
<Compile Include="Models\IndexingTask.cs" />
<Compile Include="Models\IndexingTaskRecord.cs" />
<Compile Include="Models\LuceneDocumentIndex.cs" />
<Compile Include="Permissions.cs" />
<Compile Include="Services\IndexingBackgroundTask.cs" />
<Compile Include="Services\IndexingTaskExecutor.cs" />
<Compile Include="Services\IndexingTaskManager.cs" />
<Compile Include="Services\IIndexService.cs" />
<Compile Include="Services\IndexSynLock.cs" />
<Compile Include="Services\LuceneIndexProvider.cs" />
<Compile Include="Services\LuceneSearchBuilder.cs" />
<Compile Include="Models\LuceneSearchHit.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\IndexService.cs" />
<Compile Include="ViewModels\IndexViewModel.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Views\Web.config" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>35644</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -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<Permission> GetPermissions() {
return new Permission[] {
ManageSearchIndex,
};
return new[] { ManageSearchIndex };
}
public IEnumerable<PermissionStereotype> GetDefaultStereotypes() {
return new[] {
new PermissionStereotype {
Name = "Administrator",
Permissions = new[] {ManageSearchIndex}
Permissions = new [] { ManageSearchIndex }
},
};
}

View File

@@ -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")]

View File

@@ -0,0 +1,10 @@
using System;
namespace Orchard.Indexing.Services {
public interface IIndexingService : IDependency {
bool HasIndexToManage { get; }
void RebuildIndex();
void UpdateIndex();
DateTime GetIndexUpdatedUtc();
}
}

View File

@@ -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<IIndexNotifierHandler> _indexNotifierHandlers;
public IndexingService(IOrchardServices services, IIndexManager indexManager, IEnumerable<IIndexNotifierHandler> 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);
}
}
}

View File

@@ -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<string, object> _synLocks;
private readonly object _synLock = new object();
public IndexSynLock() {
_synLocks =new Dictionary<string, object>();
}
public object GetSynLock(string indexName) {
lock(_synLock) {
if(!_synLocks.ContainsKey(indexName)) {
_synLocks[indexName] = new object();
}
return _synLocks[indexName];
}
}
}
}

View File

@@ -0,0 +1,26 @@
using JetBrains.Annotations;
using Orchard.Logging;
using Orchard.Tasks;
namespace Orchard.Indexing.Services {
/// <summary>
/// Regularly fires IIndexNotifierHandler events
/// </summary>
[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);
}
}
}

View File

@@ -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 {
/// <summary>
/// Contains the logic which is regularly executed to retrieve index information from multiple content handlers.
/// </summary>
[UsedImplicitly]
public class IndexingTaskExecutor : IBackgroundTask, IIndexNotifierHandler {
public class IndexingTaskExecutor : IIndexNotifierHandler {
private readonly IClock _clock;
private readonly IRepository<IndexingTaskRecord> _repository;
private readonly IEnumerable<IContentHandler> _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<IndexingTaskRecord> repository,
IEnumerable<IContentHandler> 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<IIndexDocument>();
var lastIndexing = DateTime.UtcNow;
var updateIndexDocuments = new List<IDocumentIndex>();
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);
}
}
}

View File

@@ -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<IndexingTaskRecord> _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;
}

View File

@@ -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 {
/// <summary>
/// Represents the default implementation of an IIndexProvider, based on Lucene
/// </summary>
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<IIndexDocument> indexDocuments) {
Store(indexName, indexDocuments.Cast<DefaultIndexDocument>());
public void Store(string indexName, IEnumerable<IDocumentIndex> indexDocuments) {
Store(indexName, indexDocuments.Cast<LuceneDocumentIndex>());
}
public void Store(string indexName, IEnumerable<DefaultIndexDocument> indexDocuments) {
public void Store(string indexName, IEnumerable<LuceneDocumentIndex> 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) {

View File

@@ -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<string, BooleanClause[]> _fields;
private readonly List<BooleanClause> _clauses;
private int _count;
private int _skip;
private readonly Dictionary<string, DateTime> _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,14 +46,17 @@ namespace Orchard.Core.Indexing.Lucene {
_skip = 0;
_before = new Dictionary<string, DateTime>();
_after = new Dictionary<string, DateTime>();
_fields = new Dictionary<string, BooleanClause[]>();
_clauses = new List<BooleanClause>();
_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 ) {
@@ -71,11 +72,60 @@ 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<string>();
using ( var sr = new System.IO.StringReader(_terms) ) {
var stream = _analyzer.TokenStream(_field, sr);
if(_stem) {
stream = new PorterStemFilter(stream);
if (_boost != 0) {
_query.SetBoost(_boost);
}
while ( stream.IncrementToken() ) {
tokens.Add(( (TermAttribute)stream.GetAttribute(typeof(TermAttribute)) ).Term());
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)
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);
}
}
if ( query.Clauses().Count == 0 ) { // get all documents ?
query.Add(new TermRangeQuery("id", "0", "9", true, true), BooleanClause.Occur.SHOULD);
@@ -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<DefaultSearchHit>();
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;

View File

@@ -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; }

View File

@@ -1,8 +1,8 @@
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Search.ViewModels.SearchIndexViewModel>" %>
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Indexing.ViewModels.IndexViewModel>" %>
<%@ Import Namespace="Orchard.Mvc.Html" %><%
Html.RegisterStyle("admin.css"); %>
<h1><%:Html.TitleForPage(T("Search Index Management").ToString()) %></h1><%
using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %>
using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.Indexing"})) { %>
<fieldset>
<p><%:T("The search index was last updated {0}.", Html.DateTimeRelative(Model.IndexUpdatedUtc, T))%> <button type="submit" title="<%:T("Update the search index.") %>" class="primaryAction"><%:T("Update")%></button></p>
<%:Html.AntiForgeryTokenOrchard() %>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<add path="*" verb="*"
type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<!--
Enabling request validation in view pages would cause validation to occur
after the input has already been processed by the controller. By default
MVC performs request validation before a controller processes the input.
To change this behavior apply the ValidateInputAttribute to a
controller or action.
-->
<pages
validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<controls>
<add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
</handlers>
</system.webServer>
</configuration>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Note: As an alternative to hand editing this file you can use the
web admin tool to configure settings for your application. Use
the Website->Asp.Net Configuration option in Visual Studio.
A full list of settings and comments can be found in
machine.config.comments usually located in
\Windows\Microsoft.Net\Framework\v2.x\Config
-->
<configuration>
<appSettings/>
<connectionStrings>
<add name="ApplicationServices" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/>
</connectionStrings>
<system.web>
<!--
Set compilation debug="true" to insert debugging
symbols into the compiled page. Because this
affects performance, set this value to true only
during development.
-->
<compilation debug="false" targetFramework="4.0">
<assemblies>
<add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/></assemblies>
</compilation>
<pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID">
<namespaces>
<add namespace="System.Web.Mvc"/>
<add namespace="System.Web.Mvc.Ajax"/>
<add namespace="System.Web.Mvc.Html"/>
<add namespace="System.Web.Routing"/>
<add namespace="System.Linq"/>
<add namespace="System.Collections.Generic"/>
</namespaces>
</pages>
</system.web>
<system.web.extensions/>
<!--
The system.webServer section is required for running ASP.NET AJAX under Internet
Information Services 7.0. It is not necessary for previous version of IIS.
-->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
</modules>
<handlers>
<remove name="UrlRoutingHandler"/>
</handlers>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -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");
}
}
}

View File

@@ -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
})
};

View File

@@ -65,15 +65,11 @@
<Reference Include="System.EnterpriseServices" />
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\SearchController.cs" />
<Compile Include="Filters\SearchFilter.cs" />
<Compile Include="Permissions.cs" />
<Compile Include="Routes.cs" />
<Compile Include="Services\ISearchService.cs" />
<Compile Include="Services\SearchService.cs" />
<Compile Include="ViewModels\SearchIndexViewModel.cs" />
<Compile Include="ViewModels\SearchResultViewModel.cs" />
<Compile Include="ViewModels\SearchViewModel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -88,7 +84,6 @@
<Content Include="Module.txt" />
<Content Include="Styles\admin.css" />
<Content Include="Styles\search.css" />
<Content Include="Views\Admin\Index.ascx" />
<Content Include="Views\SearchForm.ascx" />
<Content Include="Views\Search\Index.ascx" />
<Content Include="Views\Web.config" />

View File

@@ -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)

View File

@@ -131,6 +131,10 @@
<Project>{67C1D3AF-A0EC-46B2-BAE1-DF1DA8E0B890}</Project>
<Name>Orchard.DevTools</Name>
</ProjectReference>
<ProjectReference Include="Modules\Orchard.Indexing\Orchard.Indexing.csproj">
<Project>{EA2B9121-EF54-40A6-A53E-6593C86EE696}</Project>
<Name>Orchard.Indexing</Name>
</ProjectReference>
<ProjectReference Include="Modules\Orchard.Media\Orchard.Media.csproj">
<Project>{D9A7B330-CD22-4DA1-A95A-8DE1982AD8EB}</Project>
<Name>Orchard.Media</Name>

View File

@@ -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}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<ContentItem> Query();

View File

@@ -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);
/// <summary>
/// Whether to store the original value to the index.
/// Stores the original value to the index.
/// </summary>
IIndexDocument Store(bool store);
IDocumentIndex Store();
/// <summary>
/// Whether the content should be tokenized or not. If not, value will be taken as a whole.
/// Content is analyzed and tokenized.
/// </summary>
IIndexDocument Analyze(bool analyze);
IDocumentIndex Analyze();
/// <summary>
/// Whether some property have been added to this document, or otherwise if it's empty

View File

@@ -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
/// </summary>
/// <returns></returns>
IIndexDocument New(int documentId);
IDocumentIndex New(int documentId);
/// <summary>
/// Adds a new document to the index
/// </summary>
void Store(string indexName, IIndexDocument indexDocument);
void Store(string indexName, IDocumentIndex indexDocument);
/// <summary>
/// Adds a set of new document to the index
/// </summary>
void Store(string indexName, IEnumerable<IIndexDocument> indexDocuments);
void Store(string indexName, IEnumerable<IDocumentIndex> indexDocuments);
/// <summary>
/// Removes an existing document from the index

View File

@@ -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();

View File

@@ -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);

View File

@@ -227,6 +227,7 @@
<Compile Include="ContentManagement\Handlers\ContentItemTemplates.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="ContentManagement\Handlers\IndexContentContext.cs" />
<Compile Include="ContentManagement\Handlers\CreateContentContext.cs">
<SubType>Code</SubType>
</Compile>
@@ -413,7 +414,6 @@
<Compile Include="Indexing\ISearchHit.cs" />
<Compile Include="Tasks\Indexing\IIndexingTask.cs" />
<Compile Include="Tasks\Indexing\IIndexingTaskManager.cs" />
<Compile Include="ContentManagement\Handlers\IndexContentContext.cs" />
<Compile Include="Validation\Argument.cs" />
</ItemGroup>
<ItemGroup>