Added search fields administration

--HG--
branch : dev
This commit is contained in:
Sebastien Ros
2010-06-18 12:22:06 -07:00
parent 85f3219d37
commit 27c65e211a
15 changed files with 229 additions and 91 deletions

View File

@@ -10,7 +10,7 @@ namespace Orchard.Core.Common.Handlers {
Filters.Add(StorageFilter.For(bodyRepository));
OnIndexing<BodyAspect>((context, bodyAspect) => context.DocumentIndex
.Add("body", bodyAspect.Record.Text, true).Analyze()
.Add("body", bodyAspect.Record.Text).RemoveTags().Analyze()
.Add("format", bodyAspect.Record.Format).Store());
}
}

View File

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

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Lucene.Net.Documents;
using Orchard.Utility.Extensions;
@@ -8,34 +9,33 @@ namespace Orchard.Indexing.Models {
public class LuceneDocumentIndex : IDocumentIndex {
public List<AbstractField> Fields { get; private set; }
private AbstractField _previousField;
public int Id { get; private set; }
private string _name;
private string _stringValue;
private int _intValue;
private float _floatValue;
private bool _analyze;
private bool _store;
private bool _removeTags;
private TypeCode _typeCode;
public int ContentItemId { get; private set; }
public LuceneDocumentIndex(int documentId) {
Fields = new List<AbstractField>();
SetContentItemId(documentId);
IsDirty = false;
_typeCode = TypeCode.Empty;
}
public bool IsDirty { get; private set; }
public IDocumentIndex Add(string name, string value) {
return Add(name, value, false);
}
public IDocumentIndex Add(string name, string value, bool removeTags) {
AppendPreviousField();
if(value == null) {
value = String.Empty;
}
if(removeTags) {
value = value.RemoveTags();
}
_previousField = new Field(name, value, Field.Store.NO, Field.Index.NOT_ANALYZED);
PrepareForIndexing();
_name = name;
_stringValue = value;
_typeCode = TypeCode.String;
IsDirty = true;
return this;
}
@@ -45,8 +45,10 @@ namespace Orchard.Indexing.Models {
}
public IDocumentIndex Add(string name, int value) {
AppendPreviousField();
_previousField = new NumericField(name, Field.Store.NO, true).SetIntValue(value);
PrepareForIndexing();
_name = name;
_intValue = value;
_typeCode = TypeCode.Int32;
IsDirty = true;
return this;
}
@@ -56,8 +58,10 @@ namespace Orchard.Indexing.Models {
}
public IDocumentIndex Add(string name, float value) {
AppendPreviousField();
_previousField = new NumericField(name, Field.Store.NO, true).SetFloatValue(value);
PrepareForIndexing();
_name = name;
_floatValue = value;
_typeCode = TypeCode.Single;
IsDirty = true;
return this;
}
@@ -66,46 +70,57 @@ namespace Orchard.Indexing.Models {
return Add(name, value.ToString());
}
public IDocumentIndex RemoveTags() {
_removeTags = true;
return this;
}
public IDocumentIndex Store() {
EnsurePreviousField();
var index = _previousField.IsTokenized() ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED;
_previousField = new Field(_previousField.Name(), _previousField.StringValue(), Field.Store.YES, index);
_store = true;
return this;
}
public IDocumentIndex Analyze() {
EnsurePreviousField();
var index = Field.Index.ANALYZED;
var store = _previousField.IsStored() ? Field.Store.YES : Field.Store.NO;
_previousField = new Field(_previousField.Name(), _previousField.StringValue(), store, index);
_analyze = true;
return this;
}
public IDocumentIndex SetContentItemId(int id) {
Id = id;
Fields.Add(new Field("id", id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
public IDocumentIndex SetContentItemId(int contentItemId) {
ContentItemId = contentItemId;
Fields.Add(new Field("id", contentItemId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
return this;
}
private void AppendPreviousField() {
if (_previousField == null) {
return;
}
Fields.Add(_previousField);
_previousField = null;
}
public void PrepareForIndexing() {
AppendPreviousField();
}
private void EnsurePreviousField() {
if(_previousField == null) {
throw new ApplicationException("Operation can't be applied in this context.");
switch(_typeCode) {
case TypeCode.String:
if(_removeTags) {
_stringValue = _stringValue.RemoveTags();
}
Fields.Add(new Field(_name, _stringValue ?? String.Empty,
_store ? Field.Store.YES : Field.Store.NO,
_analyze ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED));
break;
case TypeCode.Int32:
Fields.Add(new NumericField(_name,
_store ? Field.Store.YES : Field.Store.NO,
true).SetIntValue(_intValue));
break;
case TypeCode.Single:
Fields.Add(new NumericField(_name,
_store ? Field.Store.YES : Field.Store.NO,
true).SetFloatValue(_floatValue));
break;
case TypeCode.Empty:
break;
default:
throw new OrchardException("Unexpected index type");
}
_removeTags = false;
_analyze = false;
_store = false;
_typeCode = TypeCode.Empty;
}
}
}

View File

@@ -144,11 +144,11 @@ namespace Orchard.Indexing.Services {
current = indexDocument;
var doc = CreateDocument(indexDocument);
writer.AddDocument(doc);
Logger.Debug("Document [{0}] indexed", indexDocument.Id);
Logger.Debug("Document [{0}] indexed", indexDocument.ContentItemId);
}
}
catch ( Exception ex ) {
Logger.Error(ex, "An unexpected error occured while add the document [{0}] from the index [{1}].", current.Id, indexName);
Logger.Error(ex, "An unexpected error occured while add the document [{0}] from the index [{1}].", current.ContentItemId, indexName);
}
finally {
writer.Optimize();
@@ -229,5 +229,19 @@ namespace Orchard.Indexing.Services {
doc.Save(settingsFileName);
}
public string[] GetFields(string indexName) {
if ( !Exists(indexName) ) {
return new string[0];
}
var reader = IndexReader.Open(GetDirectory(indexName), true);
try {
return reader.GetFieldNames(IndexReader.FieldOption.ALL).ToArray();
}
finally {
reader.Close();
}
}
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Tokenattributes;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
@@ -20,15 +19,12 @@ namespace Orchard.Indexing.Services {
private readonly Directory _directory;
private readonly List<BooleanClause> _clauses;
private readonly List<BooleanClause> _filters;
private int _count;
private int _skip;
private readonly Dictionary<string, DateTime> _before;
private readonly Dictionary<string, DateTime> _after;
private string _sort;
private bool _sortDescending;
private string _parse;
private readonly Analyzer _analyzer;
private string[] _defaultFields;
private bool _asFilter;
// pending clause attributes
private BooleanClause.Occur _occur;
@@ -44,13 +40,10 @@ namespace Orchard.Indexing.Services {
_count = MaxResults;
_skip = 0;
_before = new Dictionary<string, DateTime>();
_after = new Dictionary<string, DateTime>();
_clauses = new List<BooleanClause>();
_filters = new List<BooleanClause>();
_sort = String.Empty;
_sortDescending = true;
_parse = String.Empty;
_analyzer = LuceneIndexProvider.CreateAnalyzer();
InitPendingClause();
}
@@ -67,8 +60,13 @@ namespace Orchard.Indexing.Services {
throw new ArgumentException("Query can't be empty");
}
_defaultFields = defaultFields;
_parse = query;
var analyzer = LuceneIndexProvider.CreateAnalyzer();
foreach ( var defaultField in defaultFields ) {
var clause = new BooleanClause(new QueryParser(LuceneIndexProvider.LuceneVersion, defaultField, analyzer).Parse(query), BooleanClause.Occur.SHOULD);
_clauses.Add(clause);
}
_query = null;
return this;
}
@@ -155,6 +153,7 @@ namespace Orchard.Indexing.Services {
_exactMatch = false;
_query = null;
_boost = 0;
_asFilter = false;
}
private void CreatePendingClause() {
@@ -172,8 +171,12 @@ namespace Orchard.Indexing.Services {
_query = new PrefixQuery(termQuery.GetTerm());
}
}
_clauses.Add(new BooleanClause(_query, _occur));
if ( _asFilter ) {
_filters.Add(new BooleanClause(_query, _occur));
}
else {
_clauses.Add(new BooleanClause(_query, _occur));
}
}
public ISearchBuilder SortBy(string name) {
@@ -186,6 +189,11 @@ namespace Orchard.Indexing.Services {
return this;
}
public ISearchBuilder AsFilter() {
_asFilter = true;
return this;
}
public ISearchBuilder Slice(int skip, int count) {
if ( skip < 0 ) {
throw new ArgumentException("Skip must be greater or equal to zero");
@@ -206,23 +214,27 @@ namespace Orchard.Indexing.Services {
var query = new BooleanQuery();
if(!String.IsNullOrWhiteSpace(_parse)) {
foreach ( var defaultField in _defaultFields ) {
var clause = new BooleanClause(new QueryParser(LuceneIndexProvider.LuceneVersion, defaultField, LuceneIndexProvider.CreateAnalyzer()).Parse(_parse), BooleanClause.Occur.SHOULD);
query.Add(clause);
}
}
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);
}
Logger.Debug("New search query: {0}", query.ToString());
return query;
Query finalQuery = query;
if(_filters.Count > 0) {
var filter = new BooleanQuery();
foreach( var clause in _filters)
filter.Add(clause);
var queryFilter = new QueryWrapperFilter(filter);
finalQuery = new FilteredQuery(query, queryFilter);
}
Logger.Debug("New search query: {0}", finalQuery.ToString());
return finalQuery;
}
public IEnumerable<ISearchHit> Search() {

View File

@@ -5,6 +5,7 @@ using Orchard.Search.Services;
using Orchard.Search.ViewModels;
using Orchard.Settings;
using Orchard.Search.Models;
using System;
namespace Orchard.Search.Controllers {
[ValidateInput(false)]
@@ -20,13 +21,18 @@ namespace Orchard.Search.Controllers {
protected virtual ISite CurrentSite { get; [UsedImplicitly] private set; }
public ActionResult Index(string q, int page = 1, int pageSize = 10) {
var searchFields = CurrentSite.As<SearchSettings>().Record.SearchedFields.Split(new[] {',', ' '}, StringSplitOptions.RemoveEmptyEntries);
var searchViewModel = new SearchViewModel {
Query = q,
DefaultPageSize = 10, // <- yeah, I know :|
PageOfResults = _searchService.Query(q, page, pageSize, CurrentSite.As<SearchSettings>().Record.FilterCulture, searchHit => new SearchResultViewModel {
Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.ContentItemId), "SummaryForSearch"),
SearchHit = searchHit
})
PageOfResults = _searchService.Query(q, page, pageSize,
CurrentSite.As<SearchSettings>().Record.FilterCulture,
searchFields,
searchHit => new SearchResultViewModel {
Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.ContentItemId), "SummaryForSearch"),
SearchHit = searchHit
})
};
//todo: deal with page requests beyond result count

View File

@@ -3,9 +3,11 @@ using Orchard.ContentManagement.Records;
namespace Orchard.Search.Models {
public class SearchSettingsRecord : ContentPartRecord {
public virtual bool FilterCulture { get; set; }
public virtual string SearchedFields { get; set; }
public SearchSettingsRecord() {
FilterCulture = true;
SearchedFields = "body, title";
}
}
}

View File

@@ -5,7 +5,7 @@ using Orchard.Indexing;
namespace Orchard.Search.Services {
public interface ISearchService : IDependency {
bool HasIndexToManage { get; }
IPageOfItems<T> Query<T>(string query, int skip, int? take, bool filterCulture, Func<ISearchHit, T> shapeResult);
IPageOfItems<T> Query<T>(string query, int skip, int? take, bool filterCulture, string[] searchFields, Func<ISearchHit, T> shapeResult);
void RebuildIndex();
void UpdateIndex();
DateTime GetIndexUpdatedUtc();

View File

@@ -33,19 +33,20 @@ namespace Orchard.Search.Services
get { return _indexManager.HasIndexProvider(); }
}
IPageOfItems<T> ISearchService.Query<T>(string query, int page, int? pageSize, bool filterCulture, Func<ISearchHit, T> shapeResult) {
IPageOfItems<T> ISearchService.Query<T>(string query, int page, int? pageSize, bool filterCulture, string[] searchFields, Func<ISearchHit, T> shapeResult) {
if (string.IsNullOrWhiteSpace(query) || !_indexManager.HasIndexProvider())
return null;
var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(SearchIndexName)
.Parse(new[] {"title", "body"}, query);
.Parse(searchFields, query);
if ( filterCulture ) {
var culture = _cultureManager.GetSiteCulture();
// use LCID as the text representation gets analyzed by the query parser
searchBuilder
.WithField("culture", CultureInfo.GetCultureInfo(culture).LCID);
.WithField("culture", CultureInfo.GetCultureInfo(culture).LCID)
.AsFilter();
}
var totalCount = searchBuilder.Count();

View File

@@ -7,4 +7,9 @@
<label class="forcheckbox" for="SearchSettings_FilterCulture"><%: T("Search results must be filtered with current culture")%></label>
<%: Html.ValidationMessage("FilterCulture", "*")%>
</div>
<div>
<label for="SearchSettings_SearchedFields"><%: T("Searched fields")%></label>
<%: Html.EditorFor(m => m.SearchedFields)%>
<%: Html.ValidationMessage("SearchedFields", "*")%>
</div>
</fieldset>