using System;
namespace innoveo.Blog.Domain.Utils
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class SearchableAttribute : Attribute
}
using System.IO;
using System.Web.Hosting;
namespace innoveo.Utils.Lucene
public static class IndexPath
private static readonly FileInfo indexPath;
/// <summary>
/// Initializes the <see cref="EntityIndexer"/> class.
/// </summary>
static IndexPath()
string applicationPath;
if (HostingEnvironment.IsHosted)
applicationPath = HostingEnvironment.MapPath("~/App_Data/");
else
DirectoryInfo dirInfo = new DirectoryInfo(Environment.CurrentDirectory);
applicationPath = dirInfo.Parent.Parent.FullName;
indexPath = new FileInfo(Path.Combine(applicationPath, @"index"));
/// Gets the index full path.
/// <value>The index full path.</value>
public static string FullPath
get { return indexPath.FullName; }
/// Deletes the Lucene index.
public static void DeleteIndex()
DirectoryInfo directory = new DirectoryInfo(IndexPath.FullPath);
if (directory.Exists)
directory.Delete(true);
public static void CreateIndex()
IndexWriter writer = new IndexWriter(IndexPath.FullPath, new StandardAnalyzer(), true);
writer.Close();
/// Gets the indexed entities count.
/// <value>The indexed entities count.</value>
public static int IndexedEntitiesCount
get
IndexReader reader = IndexReader.Open(IndexPath.FullPath);
int docsCount = reader.NumDocs();
reader.Close();
return docsCount;
/// Indexes the specified entity if some of it's properties are decorated with ATTRIBUTE.
/// <typeparam name="T">The type of the entity to index</typeparam>
/// <typeparam name="ATTRIBUTE">The type of the Attribute to search for indexing the property.</typeparam>
/// <param name="entity">The entity.</param>
public static void Index<T, ATTRIBUTE>(T entity) where T : class where ATTRIBUTE : Attribute
if (IsSearchableEntity<T, ATTRIBUTE>(entity))
IndexWriter writer = new IndexWriter(IndexPath.FullPath, new StandardAnalyzer(), false);
writer.AddDocument(EntityDocument.GetDocument<T, ATTRIBUTE>(entity));
writer.Optimize();
/// Determines whether the specified entity has some properties decorated with the ATTRIBUTE.
/// <typeparam name="ATTRIBUTE">The type of the TTRIBUTE.</typeparam>
/// <returns>
/// <c>true</c> if the specified entity is searchable entity; otherwise, <c>false</c>.
/// </returns>
public static bool IsSearchableEntity<T, ATTRIBUTE>(T entity) where T : class
foreach (PropertyInfo propertyInfo in typeof (T).GetProperties())
object[] attributes = propertyInfo.GetCustomAttributes(typeof (ATTRIBUTE), true);
if (attributes.Length == 1)
return true;
return false;
/// Create a Lucene Document of the specified entity if properties are marked with the ATTRIBUTE.
/// <typeparam name="T">The business entity type</typeparam>
/// <typeparam name="ATTRIBUTE">The type of the ATTRIBUTE to search for</typeparam>
/// <param name="entity">The business entity.</param>
/// A Lucene document containing fields if the entity had properties marked as Searchable,
/// otherwise an empty document
public static Document GetDocument<T, ATTRIBUTE>(T entity) where ATTRIBUTE : Attribute
Document doc = new Document();
if (AddIdFieldToDocument(doc, entity))
AddSearchablePropertyToDocument<T, ATTRIBUTE>(doc, entity, propertyInfo);
return doc;
/// Adds the id field to the Lucene document.
/// <param name="doc">The Lucene document.</param>
/// <c>true</c> if the id field was added to the Lucene document; otherwise, <c>false</c>.
private static bool AddIdFieldToDocument<T>(Document doc, T entity)
PropertyInfo idProperty = typeof (T).GetProperty("Id");
if (idProperty == null)
string entityId = idProperty.GetValue(entity, null) as string;
if (string.IsNullOrEmpty(entityId))
doc.Add(new Field("Id", entityId, Field.Store.YES, Field.Index.UN_TOKENIZED));
/// Adds the searchable property value to the Lucene document.
/// <param name="propertyInfo">The property info.</param>
private static void AddSearchablePropertyToDocument<T, ATTRIBUTE>(Document doc, T entity,
PropertyInfo propertyInfo)
where ATTRIBUTE : Attribute
ATTRIBUTE searchableAttribute = attributes[0] as ATTRIBUTE;
//todo: add validation, the attribute must decorate a property returning a String
if (searchableAttribute != null)
string toIndex = propertyInfo.GetValue(entity, null) as string;
if (toIndex != null)
doc.Add(new Field(propertyInfo.Name, new StringReader(toIndex)));
/// Removes the entity from the index.
public static void RemoveEntityFromIndex<T, ATTRIBUTE>(T entity) where T : class
//todo: check if we can remove the "Id"
if (idProperty != null)
if (!string.IsNullOrEmpty(entityId))
reader.DeleteDocuments(new Term("Id", entityId));
/// Searches for business entities having indexed properties matching the specified query string.
/// <param name="queryString">The query string.</param>
/// <param name="queryField">Name of the Lucene field to search.</param>
/// <returns>All business entities matching</returns>
public static IEnumerable<string> Search(string queryString, string queryField)
string queryEscaped = QueryParser.Escape(queryString);
foreach (Document document in SearchLuceneDocument(queryEscaped, queryField))
yield return document.Get("Id");
/// Searches for Lucene document matching the specified query string.
/// <returns>All Lucene document matching</returns>
protected static IEnumerable<Document> SearchLuceneDocument(String queryString, string queryField)
Searcher searcher = new IndexSearcher(reader);
QueryParser parser = new QueryParser(queryField, new StandardAnalyzer());
Hits hits = searcher.Search(parser.Parse(queryString));
for (int i = 0; i < hits.Length(); i++)
yield return hits.Doc(i);