* compiled my own version of Lucene (2.9 RC1)
* got search working
This commit is contained in:
		
							parent
							
								
									94481265fa
								
							
						
					
					
						commit
						866964a764
					
				
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										32134
									
								
								Lib/Lucene.Net.xml
									
									
									
									
									
								
							
							
						
						
									
										32134
									
								
								Lib/Lucene.Net.xml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -203,12 +203,6 @@ | ||||
|             not be null. | ||||
|             </summary> | ||||
|         </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)"> | ||||
|             <summary> | ||||
|             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> | ||||
|             <returns>The value stored in the specified key, or null if no such key exists</returns> | ||||
|         </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)"> | ||||
|             <summary> | ||||
|             Adds an extension if it hasn't already been registered with the container | ||||
| @ -321,6 +310,45 @@ | ||||
|             <param name="types"></param> | ||||
|             <returns></returns> | ||||
|         </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"> | ||||
|             <summary> | ||||
|             <c cref="T:Portoa.Web.ErrorHandling.IErrorResultFactory">Error result factory</c> that returns a result suitable | ||||
| @ -518,6 +546,18 @@ | ||||
|             does nothing | ||||
|             </summary> | ||||
|         </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"> | ||||
|             <summary> | ||||
|             <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 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Portoa.Web.Results.StatusOverrideResult"> | ||||
|         <member name="T:Portoa.Web.Validation.FileTypeAttribute"> | ||||
|             <summary> | ||||
|             <c>ActionResult</c> decorator that enables you to override the HTTP status code | ||||
|             Verifies that an uploaded file has a certain mimetype | ||||
|             </summary> | ||||
|             <see cref="T:Portoa.Web.Filters.OverrideStatusCodeFilter"/> | ||||
|         </member> | ||||
|         <member name="M:Portoa.Web.HttpRequestBaseExtensions.Get``1(System.Web.HttpRequestBase,System.String)"> | ||||
|         <member name="P:Portoa.Web.Validation.FileTypeAttribute.MimeType"> | ||||
|             <summary> | ||||
|             Gets an object from the request variables, or its default value if | ||||
|             the key does not exist | ||||
|             Gets or sets the expected mimetype (i.e. text/plain, image/png, etc.) | ||||
|             </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.Validation.FileLengthAttribute"> | ||||
|             <summary> | ||||
| @ -656,14 +692,49 @@ | ||||
|             Gets or sets the (inclusive) minimum length of the uploaded file | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Portoa.Web.Validation.FileTypeAttribute"> | ||||
|         <member name="T:Portoa.Web.Results.StatusOverrideResult"> | ||||
|             <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> | ||||
|         </member> | ||||
|         <member name="P:Portoa.Web.Validation.FileTypeAttribute.MimeType"> | ||||
|         <member name="T:Portoa.Web.Unity.Matching.InstanceOf`1"> | ||||
|             <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> | ||||
|         </member> | ||||
|     </members> | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								Lib/Portoa.dll
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Lib/Portoa.dll
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -72,12 +72,11 @@ | ||||
|             to the <paramref name="source"/> collection | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Portoa.Persistence.Entity`2"> | ||||
|         <member name="T:Portoa.Persistence.IdentifiableDto"> | ||||
|             <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> | ||||
|             <typeparam name="T">The entity type</typeparam> | ||||
|             <typeparam name="TId">The entity's identifier type</typeparam> | ||||
|         </member> | ||||
|         <member name="T:Portoa.Persistence.IIdentifiable`1"> | ||||
|             <summary> | ||||
| @ -90,6 +89,13 @@ | ||||
|             The unique identifier of this object | ||||
|             </summary> | ||||
|         </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)"> | ||||
|             <summary> | ||||
|             Implodes an enumeration given a selector function and a separator | ||||
| @ -627,11 +633,5 @@ | ||||
|             Signifies that this object should not be logged | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Portoa.Persistence.IdentifiableDto"> | ||||
|             <summary> | ||||
|             Convenience class for data transfer objects that have an integral | ||||
|             identifier | ||||
|             </summary> | ||||
|         </member> | ||||
|     </members> | ||||
| </doc> | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										32
									
								
								Src/VideoGameQuotes.Api/Persistence/SearchService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Src/VideoGameQuotes.Api/Persistence/SearchService.cs
									
									
									
									
									
										Normal 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; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -4,7 +4,6 @@ using JetBrains.Annotations; | ||||
| using Portoa.Persistence; | ||||
| 
 | ||||
| namespace VideoGameQuotes.Api.Persistence { | ||||
| 
 | ||||
| 	public interface IUserService { | ||||
| 		User Save(User user); | ||||
| 		[CanBeNull] | ||||
|  | ||||
							
								
								
									
										17
									
								
								Src/VideoGameQuotes.Api/Search/ISearchIndexBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Src/VideoGameQuotes.Api/Search/ISearchIndexBuilder.cs
									
									
									
									
									
										Normal 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); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										15
									
								
								Src/VideoGameQuotes.Api/Search/ISearcher.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Src/VideoGameQuotes.Api/Search/ISearcher.cs
									
									
									
									
									
										Normal 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); | ||||
| 	} | ||||
| } | ||||
| @ -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); | ||||
| 	} | ||||
| } | ||||
| @ -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)); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -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(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -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()); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										10
									
								
								Src/VideoGameQuotes.Api/Search/SearchIndexException.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Src/VideoGameQuotes.Api/Search/SearchIndexException.cs
									
									
									
									
									
										Normal 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) { } | ||||
| 	} | ||||
| } | ||||
| @ -2,7 +2,7 @@ | ||||
| 	/// <summary> | ||||
| 	/// Represents a search result | ||||
| 	/// </summary> | ||||
| 	public class SearchResult { | ||||
| 	public class SearchResult<T> { | ||||
| 		/// <summary> | ||||
| 		/// A value (between 0 and 1, the higher the better) representing how good | ||||
| 		/// the match is between the search query and the value | ||||
| @ -12,6 +12,6 @@ | ||||
| 		/// <summary> | ||||
| 		/// The matched quote | ||||
| 		/// </summary> | ||||
| 		public Quote Quote { get; set; } | ||||
| 		public T Entity { get; set; } | ||||
| 	} | ||||
| } | ||||
| @ -69,10 +69,13 @@ | ||||
|     <Compile Include="Category.cs" /> | ||||
|     <Compile Include="CriterionHandler.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\Lucene\LuceneQuoteSearcher.cs" /> | ||||
|     <Compile Include="Search\Lucene\LuceneExtensions.cs" /> | ||||
|     <Compile Include="Search\Lucene\ILuceneDocumentHandler.cs" /> | ||||
|     <Compile Include="Search\Lucene\LuceneEntityIndexBuilder.cs" /> | ||||
|     <Compile Include="Search\Lucene\LuceneEntitySearcher.cs" /> | ||||
|     <Compile Include="Persistence\IUserRepository.cs" /> | ||||
|     <Compile Include="Persistence\UserService.cs" /> | ||||
|     <Compile Include="QuoteFlag.cs" /> | ||||
| @ -82,6 +85,8 @@ | ||||
|     <Compile Include="QuoteFlagType.cs" /> | ||||
|     <Compile Include="Region.cs" /> | ||||
|     <Compile Include="GamingSystem.cs" /> | ||||
|     <Compile Include="Search\Lucene\QuoteDocumentHandler.cs" /> | ||||
|     <Compile Include="Search\SearchIndexException.cs" /> | ||||
|     <Compile Include="Search\SearchResult.cs" /> | ||||
|     <Compile Include="User.cs" /> | ||||
|     <Compile Include="UserGroup.cs" /> | ||||
| @ -104,6 +109,7 @@ | ||||
|   <ItemGroup> | ||||
|     <EmbeddedResource Include="Mappings\Category.hbm.xml" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup /> | ||||
|   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||
|   <!-- 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. | ||||
|  | ||||
| @ -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; } | ||||
| 	} | ||||
| } | ||||
| @ -18,9 +18,9 @@ namespace VideoGameQuotes.Web.Controllers { | ||||
| 	public class QuoteController : Controller { | ||||
| 		private readonly IQuoteService quoteService; | ||||
| 		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.currentUserProvider = currentUserProvider; | ||||
| 			this.quoteSearcher = quoteSearcher; | ||||
|  | ||||
| @ -1,9 +1,19 @@ | ||||
| using System.Collections.Specialized; | ||||
| using System.Configuration; | ||||
| using System.IO; | ||||
| using System.Web.Mvc; | ||||
| 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.InterceptionExtension; | ||||
| using Portoa.Logging; | ||||
| using Portoa.Persistence; | ||||
| using Portoa.Web; | ||||
| using Portoa.Web.Models; | ||||
| using Portoa.Web.Security; | ||||
| @ -13,10 +23,12 @@ using VideoGameQuotes.Api; | ||||
| using VideoGameQuotes.Api.Persistence; | ||||
| using VideoGameQuotes.Api.Search; | ||||
| using VideoGameQuotes.Api.Search.Lucene; | ||||
| using VideoGameQuotes.Web.Configuration; | ||||
| using VideoGameQuotes.Web.Controllers; | ||||
| using VideoGameQuotes.Web.Models; | ||||
| using VideoGameQuotes.Web.Security; | ||||
| using VideoGameQuotes.Web.Services; | ||||
| using Directory = Lucene.Net.Store.Directory; | ||||
| 
 | ||||
| namespace VideoGameQuotes.Web { | ||||
| 	public class MvcApplication : MvcApplicationBase<User> { | ||||
| @ -28,41 +40,67 @@ namespace VideoGameQuotes.Web { | ||||
| 				.Add<ApiModel, ApiModelBinder>(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected override void ConfigureUnity() { | ||||
| 		protected override void ConfigureUnityExtensions() { | ||||
| 			Container | ||||
| 				.AddNewExtension<ConfigureLog4Net>() | ||||
| 				.Configure<ILog4NetConfigurator>() | ||||
| 				.SetName("VideoGameQuotes.Web") | ||||
| 				.UseXml(); | ||||
| 				 | ||||
| 			Container | ||||
| 				.AddNewExtension<LogAllMethodCalls>() | ||||
| 				.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>(); | ||||
| 
 | ||||
| 			Container.AddNewExtension<LogAllMethodCalls>(); | ||||
| 			Container.AddNewExtension<UpdateSearchIndex>(); | ||||
| 		} | ||||
| 
 | ||||
| 		private static SearchIndexLocator CreateIndexLocator(IUnityContainer container) { | ||||
| 			var indexDirectory = ((NameValueCollection)ConfigurationManager.GetSection("vgquotes"))["luceneIndexDirectory"]; | ||||
| 			return new SearchIndexLocator(indexDirectory); | ||||
| 		protected override void ConfigureUnity() { | ||||
| 			Container | ||||
| 				.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() { | ||||
| 			var logger = Container.Resolve<ILogger>(); | ||||
| 			logger.Info("Building lucene index"); | ||||
| 			Container.Resolve<IQuoteSearcher>().BuildIndex(); | ||||
| 			logger.Info("Done building lucene index"); | ||||
| 			Container.Resolve<ISearchIndexBuilder<Quote>>().BuildIndex(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected override void RegisterRoutes(RouteCollection routes) { | ||||
|  | ||||
| @ -5,7 +5,7 @@ using VideoGameQuotes.Api.Search; | ||||
| namespace VideoGameQuotes.Web.Models { | ||||
| 	public class SearchModel { | ||||
| 		public User User { get; set; } | ||||
| 		public IEnumerable<SearchResult> Results { get; set; } | ||||
| 		public IEnumerable<SearchResult<Quote>> Results { get; set; } | ||||
| 		public string SearchQuery { get; set; } | ||||
| 	} | ||||
| } | ||||
| @ -33,11 +33,17 @@ | ||||
|     <WarningLevel>4</WarningLevel> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <Reference Include="Lucene.Net, Version=2.9.2.2, Culture=neutral, processorArchitecture=MSIL" /> | ||||
|     <Reference Include="Microsoft.CSharp" /> | ||||
|     <Reference Include="Microsoft.Practices.Unity"> | ||||
|       <HintPath>..\..\Lib\Microsoft.Practices.Unity.dll</HintPath> | ||||
|     </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> | ||||
|     </Reference> | ||||
|     <Reference Include="Portoa.Log4Net"> | ||||
| @ -84,6 +90,7 @@ | ||||
|     </Reference> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Compile Include="Configuration\UpdateSearchIndexCallHandler.cs" /> | ||||
|     <Compile Include="Controllers\AdminController.cs" /> | ||||
|     <Compile Include="Controllers\ApiController.cs" /> | ||||
|     <Compile Include="Controllers\CategoryController.cs" /> | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
| 
 | ||||
| 	<% | ||||
| 		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> | ||||
							
								
								
									
										
											BIN
										
									
								
								Src/VideoGameQuotes.Web/media/images/flag_red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Src/VideoGameQuotes.Web/media/images/flag_red.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 665 B | 
							
								
								
									
										
											BIN
										
									
								
								Src/VideoGameQuotes.Web/media/images/link.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Src/VideoGameQuotes.Web/media/images/link.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 343 B | 
| @ -66,7 +66,6 @@ | ||||
|     <Reference Include="System.Xml" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Compile Include="LuceneTests.cs" /> | ||||
|     <Compile Include="QuoteTests.cs" /> | ||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
|     <Compile Include="NHibernate\SchemaExporter.cs" /> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user