* compiled my own version of Lucene (2.9 RC1)

* got search working
This commit is contained in:
tmont 2011-02-25 23:36:21 +00:00
parent 94481265fa
commit 866964a764
29 changed files with 26789 additions and 5862 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -203,12 +203,6 @@
not be null. not be null.
</summary> </summary>
</member> </member>
<member name="T:Portoa.Web.Unity.ConfigureInterception">
<summary>
Configures interception to occur for any subsequent container registrations
that are not part of NHibernate
</summary>
</member>
<member name="M:Portoa.Web.FilterInfoExtensions.Flatten(System.Web.Mvc.FilterInfo)"> <member name="M:Portoa.Web.FilterInfoExtensions.Flatten(System.Web.Mvc.FilterInfo)">
<summary> <summary>
Flattens a <c>FilterInfo</c> object into a single <c>IEnumerable</c> containing Flattens a <c>FilterInfo</c> object into a single <c>IEnumerable</c> containing
@ -298,11 +292,6 @@
<param name="key">The key of the item to retrieve</param> <param name="key">The key of the item to retrieve</param>
<returns>The value stored in the specified key, or null if no such key exists</returns> <returns>The value stored in the specified key, or null if no such key exists</returns>
</member> </member>
<member name="T:Portoa.Web.Unity.UnityContainerExtensions">
<summary>
Extension methods for <c>UnityContainer</c>
</summary>
</member>
<member name="M:Portoa.Web.Unity.UnityContainerExtensions.AddExtensionOnce``1(Microsoft.Practices.Unity.IUnityContainer)"> <member name="M:Portoa.Web.Unity.UnityContainerExtensions.AddExtensionOnce``1(Microsoft.Practices.Unity.IUnityContainer)">
<summary> <summary>
Adds an extension if it hasn't already been registered with the container Adds an extension if it hasn't already been registered with the container
@ -321,6 +310,45 @@
<param name="types"></param> <param name="types"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:Portoa.Web.Unity.UnityContainerExtensions.RegisterInterfaceAndIntercept``2(Microsoft.Practices.Unity.IUnityContainer,Microsoft.Practices.Unity.LifetimeManager,Microsoft.Practices.Unity.InjectionMember[])">
<summary>
Registers the type and configures an <see cref="T:Microsoft.Practices.Unity.InterceptionExtension.InterfaceInterceptor"/> for
<typeparamref name="TFrom"/>
</summary>
<typeparam name="TFrom">This must be an interface type</typeparam>
<typeparam name="TTo">The type to resolve the interface to</typeparam>
</member>
<member name="M:Portoa.Web.Unity.UnityContainerExtensions.RegisterInterfaceAndIntercept``1(Microsoft.Practices.Unity.IUnityContainer,Microsoft.Practices.Unity.LifetimeManager,Microsoft.Practices.Unity.InjectionMember[])">
<summary>
Registers the interface type and configures interception for it
</summary>
</member>
<member name="M:Portoa.Web.Unity.UnityContainerExtensions.RegisterAndIntercept``2(Microsoft.Practices.Unity.IUnityContainer,Microsoft.Practices.Unity.LifetimeManager,Microsoft.Practices.Unity.InjectionMember[])">
<summary>
Registers the type and configures interception for it
</summary>
</member>
<member name="M:Portoa.Web.Unity.UnityContainerExtensions.RegisterAndIntercept``1(Microsoft.Practices.Unity.IUnityContainer,Microsoft.Practices.Unity.LifetimeManager,Microsoft.Practices.Unity.InjectionMember[])">
<summary>
Registers the type and configures interception for it
</summary>
</member>
<member name="M:Portoa.Web.Unity.UnityContainerExtensions.RegisterAndIntercept``1(Microsoft.Practices.Unity.IUnityContainer,``0,Microsoft.Practices.Unity.LifetimeManager)">
<summary>
Registers the instance and configures interception for it
</summary>
</member>
<member name="M:Portoa.Web.Unity.UnityContainerExtensions.RegisterInterfaceAndIntercept(Microsoft.Practices.Unity.IUnityContainer,System.Type,System.Type,Microsoft.Practices.Unity.LifetimeManager,Microsoft.Practices.Unity.InjectionMember[])">
<summary>
Registers the type and configures interception for it
</summary>
<param name="typeFrom">This must be an interface type</param>
</member>
<member name="M:Portoa.Web.Unity.UnityContainerExtensions.RegisterAndIntercept(Microsoft.Practices.Unity.IUnityContainer,System.Type,System.Type,Microsoft.Practices.Unity.LifetimeManager,Microsoft.Practices.Unity.InjectionMember[])">
<summary>
Registers the type and configures interception for it
</summary>
</member>
<member name="T:Portoa.Web.ErrorHandling.ErrorWithUserResultFactory`1"> <member name="T:Portoa.Web.ErrorHandling.ErrorWithUserResultFactory`1">
<summary> <summary>
<c cref="T:Portoa.Web.ErrorHandling.IErrorResultFactory">Error result factory</c> that returns a result suitable <c cref="T:Portoa.Web.ErrorHandling.IErrorResultFactory">Error result factory</c> that returns a result suitable
@ -518,6 +546,18 @@
does nothing does nothing
</summary> </summary>
</member> </member>
<member name="M:Portoa.Web.MvcApplicationBase`1.ConfigureUnityExtensions">
<summary>
Adds extensions to the container; default implementation does nothing
</summary>
</member>
<member name="M:Portoa.Web.MvcApplicationBase`1.OnApplicationEnd">
<summary>
Performs any needed cleanup when the application ends; default implementation
does nothing. Be aware that the <c cref="F:Portoa.Web.MvcApplicationBase`1.Container">container</c> gets disposed of
later.
</summary>
</member>
<member name="T:Portoa.Web.IInjectableControllerFactory"> <member name="T:Portoa.Web.IInjectableControllerFactory">
<summary> <summary>
<c cref="T:System.Web.Mvc.IControllerFactory">IControllerFactory</c> that provides a mechanism to perform injection <c cref="T:System.Web.Mvc.IControllerFactory">IControllerFactory</c> that provides a mechanism to perform injection
@ -624,19 +664,15 @@
Controller factory that uses a service provider to resolve controllers Controller factory that uses a service provider to resolve controllers
</summary> </summary>
</member> </member>
<member name="T:Portoa.Web.Results.StatusOverrideResult"> <member name="T:Portoa.Web.Validation.FileTypeAttribute">
<summary> <summary>
<c>ActionResult</c> decorator that enables you to override the HTTP status code Verifies that an uploaded file has a certain mimetype
</summary> </summary>
<see cref="T:Portoa.Web.Filters.OverrideStatusCodeFilter"/>
</member> </member>
<member name="M:Portoa.Web.HttpRequestBaseExtensions.Get``1(System.Web.HttpRequestBase,System.String)"> <member name="P:Portoa.Web.Validation.FileTypeAttribute.MimeType">
<summary> <summary>
Gets an object from the request variables, or its default value if Gets or sets the expected mimetype (i.e. text/plain, image/png, etc.)
the key does not exist
</summary> </summary>
<typeparam name="T">The type to convert the value to</typeparam>
<param name="key">The request key of the object to retrieve</param>
</member> </member>
<member name="T:Portoa.Web.Validation.FileLengthAttribute"> <member name="T:Portoa.Web.Validation.FileLengthAttribute">
<summary> <summary>
@ -656,14 +692,49 @@
Gets or sets the (inclusive) minimum length of the uploaded file Gets or sets the (inclusive) minimum length of the uploaded file
</summary> </summary>
</member> </member>
<member name="T:Portoa.Web.Validation.FileTypeAttribute"> <member name="T:Portoa.Web.Results.StatusOverrideResult">
<summary> <summary>
Verifies that an uploaded file has a certain mimetype <c>ActionResult</c> decorator that enables you to override the HTTP status code
</summary>
<see cref="T:Portoa.Web.Filters.OverrideStatusCodeFilter"/>
</member>
<member name="M:Portoa.Web.HttpRequestBaseExtensions.Get``1(System.Web.HttpRequestBase,System.String)">
<summary>
Gets an object from the request variables, or its default value if
the key does not exist
</summary>
<typeparam name="T">The type to convert the value to</typeparam>
<param name="key">The request key of the object to retrieve</param>
</member>
<member name="T:Portoa.Web.Unity.Matching.AlwaysMatches">
<summary>
A matching rule that always matches
</summary> </summary>
</member> </member>
<member name="P:Portoa.Web.Validation.FileTypeAttribute.MimeType"> <member name="T:Portoa.Web.Unity.Matching.InstanceOf`1">
<summary> <summary>
Gets or sets the expected mimetype (i.e. text/plain, image/png, etc.) Matching rule where a member is match if it is assignable from <typeparamref name="T"/>
</summary>
<typeparam name="T">The base class/interface to match against</typeparam>
</member>
<member name="T:Portoa.Web.Unity.Matching.Not`1">
<summary>
Negates a matching rule
</summary>
<typeparam name="T">The type of matching rule to negate</typeparam>
</member>
<member name="T:Portoa.Web.Unity.Matching.Not">
<summary>
Negates a matching rule
</summary>
</member>
<member name="M:Portoa.Web.Unity.Matching.Not.#ctor(Microsoft.Practices.Unity.InterceptionExtension.IMatchingRule)">
<param name="ruleToNegate">The matching rule to negate</param>
</member>
<member name="T:Portoa.Web.Unity.Matching.PropertyGetOrSet">
<summary>
Matching rule that matches compiler-generated methods for property
getters and setters
</summary> </summary>
</member> </member>
</members> </members>

Binary file not shown.

View File

@ -72,12 +72,11 @@
to the <paramref name="source"/> collection to the <paramref name="source"/> collection
</summary> </summary>
</member> </member>
<member name="T:Portoa.Persistence.Entity`2"> <member name="T:Portoa.Persistence.IdentifiableDto">
<summary> <summary>
Represents a domain object that can be persisted by a <c cref="T:Portoa.Persistence.IRepository`2">repository</c> Convenience class for data transfer objects that have an integral
identifier
</summary> </summary>
<typeparam name="T">The entity type</typeparam>
<typeparam name="TId">The entity's identifier type</typeparam>
</member> </member>
<member name="T:Portoa.Persistence.IIdentifiable`1"> <member name="T:Portoa.Persistence.IIdentifiable`1">
<summary> <summary>
@ -90,6 +89,13 @@
The unique identifier of this object The unique identifier of this object
</summary> </summary>
</member> </member>
<member name="T:Portoa.Persistence.Entity`2">
<summary>
Represents a domain object that can be persisted by a <c cref="T:Portoa.Persistence.IRepository`2">repository</c>
</summary>
<typeparam name="T">The entity type</typeparam>
<typeparam name="TId">The entity's identifier type</typeparam>
</member>
<member name="M:Portoa.Util.LinqExtensions.Implode``1(System.Collections.Generic.IEnumerable{``0},System.Func{``0,System.String},System.String)"> <member name="M:Portoa.Util.LinqExtensions.Implode``1(System.Collections.Generic.IEnumerable{``0},System.Func{``0,System.String},System.String)">
<summary> <summary>
Implodes an enumeration given a selector function and a separator Implodes an enumeration given a selector function and a separator
@ -627,11 +633,5 @@
Signifies that this object should not be logged Signifies that this object should not be logged
</summary> </summary>
</member> </member>
<member name="T:Portoa.Persistence.IdentifiableDto">
<summary>
Convenience class for data transfer objects that have an integral
identifier
</summary>
</member>
</members> </members>
</doc> </doc>

Binary file not shown.

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Linq;
using Portoa.Persistence;
namespace VideoGameQuotes.Api.Persistence {
public interface ISearchService<T> where T : Entity<T, int> {
IEnumerable<T> FindByIds(IEnumerable<int> ids);
IEnumerable<T> GetAllIndexableRecords();
}
public class SearchService<T> : ISearchService<T> where T : Entity<T, int> {
private readonly IRepository<T> repository;
public SearchService(IRepository<T> repository) {
this.repository = repository;
}
[UnitOfWork]
public IEnumerable<T> FindByIds(IEnumerable<int> ids) {
return repository
.Records
.Where(entity => ids.ToArray().Contains(entity.Id));
}
[UnitOfWork]
public IEnumerable<T> GetAllIndexableRecords() {
return repository.Records;
}
}
}

View File

@ -4,7 +4,6 @@ using JetBrains.Annotations;
using Portoa.Persistence; using Portoa.Persistence;
namespace VideoGameQuotes.Api.Persistence { namespace VideoGameQuotes.Api.Persistence {
public interface IUserService { public interface IUserService {
User Save(User user); User Save(User user);
[CanBeNull] [CanBeNull]

View File

@ -0,0 +1,17 @@
namespace VideoGameQuotes.Api.Search {
/// <summary>
/// Exposes an interface to build and update a search index
/// </summary>
public interface ISearchIndexBuilder<T> {
/// <summary>
/// (Re)builds the search index
/// </summary>
void BuildIndex();
/// <summary>
/// Updates the index for the specified <paramref name="indexableObject"/>
/// </summary>
/// <param name="indexableObject">The object that needs its index updated</param>
void UpdateIndex(T indexableObject);
}
}

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace VideoGameQuotes.Api.Search {
/// <summary>
/// Exposes an interface to perform a full-text search
/// </summary>
public interface ISearcher<T> {
/// <summary>
/// Searches for records based on the given search query
/// </summary>
/// <param name="query">The search term(s) to search for</param>
/// <param name="maxResults">The maximum number of results to return (<c>0</c> is unlimited); the default is <c>10</c></param>
IEnumerable<SearchResult<T>> Search(string query, int maxResults = 10);
}
}

View File

@ -0,0 +1,9 @@
using Lucene.Net.Documents;
using Lucene.Net.Index;
namespace VideoGameQuotes.Api.Search.Lucene {
public interface ILuceneDocumentHandler<T> {
Document BuildDocument(T source);
Term GetIdTerm(T source);
}
}

View File

@ -0,0 +1,45 @@
using Lucene.Net.Index;
using Portoa.Logging;
using Portoa.Persistence;
using VideoGameQuotes.Api.Persistence;
namespace VideoGameQuotes.Api.Search.Lucene {
public class LuceneEntityIndexBuilder<T> : ISearchIndexBuilder<T> where T : Entity<T, int> {
private readonly ILogger logger;
private readonly IndexWriter indexWriter;
private readonly ISearchService<T> searchService;
private readonly ILuceneDocumentHandler<T> documentHandler;
public LuceneEntityIndexBuilder(IndexWriter indexWriter, ISearchService<T> searchService, ILuceneDocumentHandler<T> documentHandler, ILogger logger) {
this.indexWriter = indexWriter;
this.searchService = searchService;
this.documentHandler = documentHandler;
this.logger = logger;
}
public void BuildIndex() {
logger.Info("Building lucene index");
foreach (var quote in searchService.GetAllIndexableRecords()) {
indexWriter.AddDocument(documentHandler.BuildDocument(quote));
}
indexWriter.Optimize();
indexWriter.Commit();
logger.Info("Finished building lucene index");
}
public void UpdateIndex(T entity) {
if (entity.IsTransient()) {
throw new SearchIndexException(string.Format("Cannot add a transient entity to the index ({0})", entity));
}
logger.Info(string.Format("Updating index for {0}", entity));
//delete current document, if it exists
indexWriter.DeleteDocuments(documentHandler.GetIdTerm(entity));
indexWriter.AddDocument(documentHandler.BuildDocument(entity));
indexWriter.Commit();
logger.Info(string.Format("Finished updating index for {0}", entity));
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Portoa.Persistence;
using VideoGameQuotes.Api.Persistence;
using Directory = Lucene.Net.Store.Directory;
namespace VideoGameQuotes.Api.Search.Lucene {
/// <summary>
/// <see cref="ISearcher{T}"/> implementation for entities based on <c>Lucene.NET</c>
/// </summary>
public class LuceneEntitySearcher<T> : ISearcher<T> where T : Entity<T, int> {
private readonly QueryParser queryParser;
private readonly Directory indexDirectory;
private readonly ISearchService<T> searchService;
public LuceneEntitySearcher(QueryParser queryParser, Directory indexDirectory, ISearchService<T> searchService) {
this.queryParser = queryParser;
this.indexDirectory = indexDirectory;
this.searchService = searchService;
}
public IEnumerable<SearchResult<T>> Search(string searchString, int maxResults = 10) {
if (string.IsNullOrWhiteSpace(searchString)) {
return Enumerable.Empty<SearchResult<T>>();
}
if (maxResults < 0) {
throw new ArgumentOutOfRangeException("maxResults", maxResults, "Maximum number of results must be greater than or equal to zero");
}
if (maxResults == 0) {
maxResults = int.MaxValue;
}
var query = queryParser.Parse(QueryParser.Escape(searchString));
var searcher = new IndexSearcher(indexDirectory, true);
try {
var docs = searcher
.Search(query, maxResults)
.scoreDocs;
var quotes = searchService.FindByIds(docs.Select(doc => int.Parse(searcher.Doc(doc.doc).GetField("id").StringValue())));
return quotes.Zip(docs, (entity, doc) => new SearchResult<T> { Entity = entity, Score = doc.score }).ToArray();
} finally {
searcher.Close();
}
}
}
}

View File

@ -0,0 +1,18 @@
using Lucene.Net.Documents;
using Lucene.Net.Index;
namespace VideoGameQuotes.Api.Search.Lucene {
public class QuoteDocumentHandler : ILuceneDocumentHandler<Quote> {
public Document BuildDocument(Quote quote) {
var document = new Document();
document.Add(new Field("id", quote.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
document.Add(new Field("text", quote.Text, Field.Store.YES, Field.Index.ANALYZED));
return document;
}
public Term GetIdTerm(Quote quote) {
return new Term("id", quote.Id.ToString());
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace VideoGameQuotes.Api.Search {
/// <summary>
/// Raised when an error occurs while reading/writing a search index
/// </summary>
public class SearchIndexException : Exception {
public SearchIndexException(string message = null, Exception innerException = null) : base(message, innerException) { }
}
}

View File

@ -2,7 +2,7 @@
/// <summary> /// <summary>
/// Represents a search result /// Represents a search result
/// </summary> /// </summary>
public class SearchResult { public class SearchResult<T> {
/// <summary> /// <summary>
/// A value (between 0 and 1, the higher the better) representing how good /// A value (between 0 and 1, the higher the better) representing how good
/// the match is between the search query and the value /// the match is between the search query and the value
@ -12,6 +12,6 @@
/// <summary> /// <summary>
/// The matched quote /// The matched quote
/// </summary> /// </summary>
public Quote Quote { get; set; } public T Entity { get; set; }
} }
} }

View File

@ -69,10 +69,13 @@
<Compile Include="Category.cs" /> <Compile Include="Category.cs" />
<Compile Include="CriterionHandler.cs" /> <Compile Include="CriterionHandler.cs" />
<Compile Include="Game.cs" /> <Compile Include="Game.cs" />
<Compile Include="Search\IQuoteSearcher.cs" /> <Compile Include="Persistence\SearchService.cs" />
<Compile Include="Search\ISearcher.cs" />
<Compile Include="Search\ISearchIndexBuilder.cs" />
<Compile Include="Search\ISearchIndexLocator.cs" /> <Compile Include="Search\ISearchIndexLocator.cs" />
<Compile Include="Search\Lucene\LuceneQuoteSearcher.cs" /> <Compile Include="Search\Lucene\ILuceneDocumentHandler.cs" />
<Compile Include="Search\Lucene\LuceneExtensions.cs" /> <Compile Include="Search\Lucene\LuceneEntityIndexBuilder.cs" />
<Compile Include="Search\Lucene\LuceneEntitySearcher.cs" />
<Compile Include="Persistence\IUserRepository.cs" /> <Compile Include="Persistence\IUserRepository.cs" />
<Compile Include="Persistence\UserService.cs" /> <Compile Include="Persistence\UserService.cs" />
<Compile Include="QuoteFlag.cs" /> <Compile Include="QuoteFlag.cs" />
@ -82,6 +85,8 @@
<Compile Include="QuoteFlagType.cs" /> <Compile Include="QuoteFlagType.cs" />
<Compile Include="Region.cs" /> <Compile Include="Region.cs" />
<Compile Include="GamingSystem.cs" /> <Compile Include="GamingSystem.cs" />
<Compile Include="Search\Lucene\QuoteDocumentHandler.cs" />
<Compile Include="Search\SearchIndexException.cs" />
<Compile Include="Search\SearchResult.cs" /> <Compile Include="Search\SearchResult.cs" />
<Compile Include="User.cs" /> <Compile Include="User.cs" />
<Compile Include="UserGroup.cs" /> <Compile Include="UserGroup.cs" />
@ -104,6 +109,7 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Mappings\Category.hbm.xml" /> <EmbeddedResource Include="Mappings\Category.hbm.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -0,0 +1,61 @@
using System;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using Portoa.Persistence;
using VideoGameQuotes.Api;
using VideoGameQuotes.Api.Search;
namespace VideoGameQuotes.Web.Configuration {
public class UpdateSearchIndex : UnityContainerExtension {
protected override void Initialize() {
Container
.Configure<Interception>()
.AddPolicy("UpdateSearchIndexPolicy")
.AddCallHandler<UpdateSearchIndexCallHandler>()
.AddMatchingRule<QuoteUpdatedMatchingRule>();
}
}
public class QuoteUpdatedMatchingRule : IMatchingRule {
private static readonly MethodBase saveMethod = typeof(IRepository<Quote, int>).GetMethod("Save", new[] { typeof(Quote) });
public bool Matches(MethodBase member) {
return member == saveMethod;
}
}
public class UpdateSearchIndexCallHandler : ICallHandler {
private readonly IUnityContainer container;
/// <remarks>
/// Can't inject ISearchIndexBuilder because it causes an infinite loop
/// while trying to instantiate the call handler. So we do a later resolve
/// on the index builder so that this shit fucking works.
/// </remarks>
public UpdateSearchIndexCallHandler(IUnityContainer container) {
this.container = container;
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) {
var returnValue = getNext()(input, getNext);
if (returnValue.Exception != null) {
//if the update failed then just return
return returnValue;
}
var quote = returnValue.ReturnValue as Quote;
if (quote == null) {
return returnValue;
}
container.Resolve<ISearchIndexBuilder<Quote>>().UpdateIndex(quote);
return returnValue;
}
public int Order { get; set; }
}
}

View File

@ -18,9 +18,9 @@ namespace VideoGameQuotes.Web.Controllers {
public class QuoteController : Controller { public class QuoteController : Controller {
private readonly IQuoteService quoteService; private readonly IQuoteService quoteService;
private readonly ICurrentUserProvider<User> currentUserProvider; private readonly ICurrentUserProvider<User> currentUserProvider;
private readonly IQuoteSearcher quoteSearcher; private readonly ISearcher<Quote> quoteSearcher;
public QuoteController(IQuoteService quoteService, ICurrentUserProvider<User> currentUserProvider, IQuoteSearcher quoteSearcher) { public QuoteController(IQuoteService quoteService, ICurrentUserProvider<User> currentUserProvider, ISearcher<Quote> quoteSearcher) {
this.quoteService = quoteService; this.quoteService = quoteService;
this.currentUserProvider = currentUserProvider; this.currentUserProvider = currentUserProvider;
this.quoteSearcher = quoteSearcher; this.quoteSearcher = quoteSearcher;

View File

@ -1,9 +1,19 @@
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Configuration; using System.Configuration;
using System.IO;
using System.Web.Mvc; using System.Web.Mvc;
using System.Web.Routing; using System.Web.Routing;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
using Microsoft.Practices.Unity; using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using Portoa.Logging; using Portoa.Logging;
using Portoa.Persistence;
using Portoa.Web; using Portoa.Web;
using Portoa.Web.Models; using Portoa.Web.Models;
using Portoa.Web.Security; using Portoa.Web.Security;
@ -13,10 +23,12 @@ using VideoGameQuotes.Api;
using VideoGameQuotes.Api.Persistence; using VideoGameQuotes.Api.Persistence;
using VideoGameQuotes.Api.Search; using VideoGameQuotes.Api.Search;
using VideoGameQuotes.Api.Search.Lucene; using VideoGameQuotes.Api.Search.Lucene;
using VideoGameQuotes.Web.Configuration;
using VideoGameQuotes.Web.Controllers; using VideoGameQuotes.Web.Controllers;
using VideoGameQuotes.Web.Models; using VideoGameQuotes.Web.Models;
using VideoGameQuotes.Web.Security; using VideoGameQuotes.Web.Security;
using VideoGameQuotes.Web.Services; using VideoGameQuotes.Web.Services;
using Directory = Lucene.Net.Store.Directory;
namespace VideoGameQuotes.Web { namespace VideoGameQuotes.Web {
public class MvcApplication : MvcApplicationBase<User> { public class MvcApplication : MvcApplicationBase<User> {
@ -28,41 +40,67 @@ namespace VideoGameQuotes.Web {
.Add<ApiModel, ApiModelBinder>(); .Add<ApiModel, ApiModelBinder>();
} }
protected override void ConfigureUnity() { protected override void ConfigureUnityExtensions() {
Container Container
.AddNewExtension<ConfigureLog4Net>() .AddNewExtension<ConfigureLog4Net>()
.Configure<ILog4NetConfigurator>() .Configure<ILog4NetConfigurator>()
.SetName("VideoGameQuotes.Web") .SetName("VideoGameQuotes.Web")
.UseXml(); .UseXml();
Container Container.AddNewExtension<LogAllMethodCalls>();
.AddNewExtension<LogAllMethodCalls>() Container.AddNewExtension<UpdateSearchIndex>();
.RegisterType<ICurrentUserProvider<User>, SessionBasedUserProvider>()
.RegisterType<VerifyUserAttribute>(new InjectionProperty<VerifyUserAttribute>(attr => attr.UserProvider))
.RegisterType<IUserService, UserService>()
.RegisterType<IAdministrationService, AdministrationService>()
.RegisterType<IQuoteService, QuoteService>()
.RegisterType<ISystemService, SystemService>()
.RegisterType<ICategoryService, CategoryService>()
.RegisterType<IPublisherService, PublisherService>()
.RegisterType<IGameService, GameService>()
.RegisterType<IApiService, ApiService>()
.RegisterType<IAuthenticationService, FormsAuthenticationService>()
.RegisterType<IQuoteSearcher, LuceneQuoteSearcher>()
.RegisterType<ISearchIndexLocator, SearchIndexLocator>(new ContainerControlledLifetimeManager(), new InjectionFactory(CreateIndexLocator))
.RegisterType<IUserRepository, UserRepository>();
} }
private static SearchIndexLocator CreateIndexLocator(IUnityContainer container) { protected override void ConfigureUnity() {
var indexDirectory = ((NameValueCollection)ConfigurationManager.GetSection("vgquotes"))["luceneIndexDirectory"]; Container
return new SearchIndexLocator(indexDirectory); .RegisterType<VerifyUserAttribute>(new InjectionProperty<VerifyUserAttribute>(attr => attr.UserProvider))
.RegisterAndIntercept<ICurrentUserProvider<User>, SessionBasedUserProvider>()
.RegisterAndIntercept<IUserService, UserService>()
.RegisterAndIntercept<IAdministrationService, AdministrationService>()
.RegisterAndIntercept<IQuoteService, QuoteService>()
.RegisterAndIntercept<ISystemService, SystemService>()
.RegisterAndIntercept<ICategoryService, CategoryService>()
.RegisterAndIntercept<IPublisherService, PublisherService>()
.RegisterAndIntercept<IGameService, GameService>()
.RegisterAndIntercept<IApiService, ApiService>()
.RegisterAndIntercept<IAuthenticationService, FormsAuthenticationService>()
.RegisterAndIntercept<IUserRepository, UserRepository>();
//search stuff
Container
.RegisterType<Directory>(new ContainerControlledLifetimeManager(), new InjectionFactory(CreateIndexDirectory))
.RegisterType<IndexWriter>(new ContainerControlledLifetimeManager(), new InjectionFactory(CreateIndexWriter))
.RegisterInstance(Version.LUCENE_29)
.RegisterType<Analyzer, StandardAnalyzer>(new InjectionConstructor(typeof(Version)))
.RegisterType<QueryParser>(new InjectionFactory(CreateQueryParser))
.RegisterAndIntercept(typeof(ISearcher<>), typeof(LuceneEntitySearcher<>))
.RegisterAndIntercept(typeof(ISearchService<>), typeof(SearchService<>))
.RegisterAndIntercept<ILuceneDocumentHandler<Quote>, QuoteDocumentHandler>()
.RegisterAndIntercept(typeof(ISearchIndexBuilder<>), typeof(LuceneEntityIndexBuilder<>));
} }
#region lucene-related factories
private static QueryParser CreateQueryParser(IUnityContainer container) {
return new QueryParser(container.Resolve<Version>(), "text", container.Resolve<Analyzer>());
}
private static Directory CreateIndexDirectory(IUnityContainer container) {
var indexDirectory = ((NameValueCollection)ConfigurationManager.GetSection("vgquotes"))["luceneIndexDirectory"];
return new SimpleFSDirectory(new DirectoryInfo(indexDirectory));
}
private static IndexWriter CreateIndexWriter(IUnityContainer container) {
return new IndexWriter(
container.Resolve<Directory>(),
new StandardAnalyzer(Version.LUCENE_29),
true,
IndexWriter.MaxFieldLength.UNLIMITED
);
}
#endregion
protected override void AfterStartUp() { protected override void AfterStartUp() {
var logger = Container.Resolve<ILogger>(); Container.Resolve<ISearchIndexBuilder<Quote>>().BuildIndex();
logger.Info("Building lucene index");
Container.Resolve<IQuoteSearcher>().BuildIndex();
logger.Info("Done building lucene index");
} }
protected override void RegisterRoutes(RouteCollection routes) { protected override void RegisterRoutes(RouteCollection routes) {

View File

@ -5,7 +5,7 @@ using VideoGameQuotes.Api.Search;
namespace VideoGameQuotes.Web.Models { namespace VideoGameQuotes.Web.Models {
public class SearchModel { public class SearchModel {
public User User { get; set; } public User User { get; set; }
public IEnumerable<SearchResult> Results { get; set; } public IEnumerable<SearchResult<Quote>> Results { get; set; }
public string SearchQuery { get; set; } public string SearchQuery { get; set; }
} }
} }

View File

@ -33,11 +33,17 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Lucene.Net, Version=2.9.2.2, Culture=neutral, processorArchitecture=MSIL" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Practices.Unity"> <Reference Include="Microsoft.Practices.Unity">
<HintPath>..\..\Lib\Microsoft.Practices.Unity.dll</HintPath> <HintPath>..\..\Lib\Microsoft.Practices.Unity.dll</HintPath>
</Reference> </Reference>
<Reference Include="Portoa"> <Reference Include="Microsoft.Practices.Unity.Interception, Version=2.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Lib\Microsoft.Practices.Unity.Interception.dll</HintPath>
</Reference>
<Reference Include="Portoa, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Lib\Portoa.dll</HintPath> <HintPath>..\..\Lib\Portoa.dll</HintPath>
</Reference> </Reference>
<Reference Include="Portoa.Log4Net"> <Reference Include="Portoa.Log4Net">
@ -84,6 +90,7 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Configuration\UpdateSearchIndexCallHandler.cs" />
<Compile Include="Controllers\AdminController.cs" /> <Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\ApiController.cs" /> <Compile Include="Controllers\ApiController.cs" />
<Compile Include="Controllers\CategoryController.cs" /> <Compile Include="Controllers\CategoryController.cs" />

View File

@ -8,7 +8,7 @@
<% <%
foreach (var result in Model.Results) { foreach (var result in Model.Results) {
Html.RenderPartial("SingleQuote", new QuoteModel {Quote = result.Quote, User = Model.User }); Html.RenderPartial("SingleQuote", new QuoteModel {Quote = result.Entity, User = Model.User });
} }
%> %>
</asp:Content> </asp:Content>

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

View File

@ -66,7 +66,6 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="LuceneTests.cs" />
<Compile Include="QuoteTests.cs" /> <Compile Include="QuoteTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="NHibernate\SchemaExporter.cs" /> <Compile Include="NHibernate\SchemaExporter.cs" />