Working on #U4-1356 - Moving UmbracoExamine to core

This commit is contained in:
Shannon Deminick
2013-01-05 03:01:29 +03:00
parent eece4c194a
commit 027f76af96
29 changed files with 3300 additions and 3 deletions

View File

@@ -42,7 +42,7 @@
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\</OutputPath>
<OutputPath>bin\Debug\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
@@ -66,14 +66,14 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\</OutputPath>
<OutputPath>bin\Release\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE</DefineConstants>
<DocumentationFile>bin\umbraco.xml</DocumentationFile>
<DocumentationFile>bin\Release\umbraco.xml</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>

View File

@@ -0,0 +1,349 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security;
using System.Text;
using System.Threading;
using System.Web;
using Examine.LuceneEngine.Providers;
using Lucene.Net.Analysis;
using umbraco.BasePages;
using umbraco.BusinessLogic;
using UmbracoExamine.DataServices;
using Examine;
using System.IO;
using System.Xml.Linq;
namespace UmbracoExamine
{
/// <summary>
/// An abstract provider containing the basic functionality to be able to query against
/// Umbraco data.
/// </summary>
public abstract class BaseUmbracoIndexer : LuceneIndexer
{
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
protected BaseUmbracoIndexer()
: base()
{
}
/// <summary>
/// Constructor to allow for creating an indexer at runtime
/// </summary>
/// <param name="indexerData"></param>
/// <param name="indexPath"></param>
/// <param name="dataService"></param>
/// <param name="analyzer"></param>
[SecuritySafeCritical]
protected BaseUmbracoIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async)
: base(indexerData, indexPath, analyzer, async)
{
DataService = dataService;
}
[SecuritySafeCritical]
protected BaseUmbracoIndexer(IIndexCriteria indexerData, Lucene.Net.Store.Directory luceneDirectory, IDataService dataService, Analyzer analyzer, bool async)
: base(indexerData, luceneDirectory, analyzer, async)
{
DataService = dataService;
}
#endregion
#region Properties
/// <summary>
/// If true, the IndexingActionHandler will be run to keep the default index up to date.
/// </summary>
public bool EnableDefaultEventHandler { get; protected set; }
/// <summary>
/// Determines if the manager will call the indexing methods when content is saved or deleted as
/// opposed to cache being updated.
/// </summary>
public bool SupportUnpublishedContent { get; protected set; }
/// <summary>
/// The data service used for retreiving and submitting data to the cms
/// </summary>
public IDataService DataService { get; protected internal set; }
/// <summary>
/// the supported indexable types
/// </summary>
protected abstract IEnumerable<string> SupportedTypes { get; }
#endregion
#region Initialize
/// <summary>
/// Setup the properties for the indexer from the provider settings
/// </summary>
/// <param name="name"></param>
/// <param name="config"></param>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
if (config["dataService"] != null && !string.IsNullOrEmpty(config["dataService"]))
{
//this should be a fully qualified type
var serviceType = Type.GetType(config["dataService"]);
DataService = (IDataService)Activator.CreateInstance(serviceType);
}
else if (DataService == null)
{
//By default, we will be using the UmbracoDataService
//generally this would only need to be set differently for unit testing
DataService = new UmbracoDataService();
}
DataService.LogService.LogLevel = LoggingLevel.Normal;
if (config["logLevel"] != null && !string.IsNullOrEmpty(config["logLevel"]))
{
try
{
var logLevel = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), config["logLevel"]);
DataService.LogService.LogLevel = logLevel;
}
catch (ArgumentException)
{
//FAILED
DataService.LogService.LogLevel = LoggingLevel.Normal;
}
}
DataService.LogService.ProviderName = name;
EnableDefaultEventHandler = true; //set to true by default
bool enabled;
if (bool.TryParse(config["enableDefaultEventHandler"], out enabled))
{
EnableDefaultEventHandler = enabled;
}
DataService.LogService.AddVerboseLog(-1, string.Format("{0} indexer initializing", name));
base.Initialize(name, config);
}
#endregion
//public override void RebuildIndex()
//{
// //we can make the indexing rebuilding operation happen asynchronously in a web context by calling an http handler.
// //we should only do this when async='true', the current request is running in a web context and the current user is authenticated.
// if (RunAsync && HttpContext.Current != null)
// {
// if (UmbracoEnsuredPage.CurrentUser != null)
// {
// RebuildIndexAsync();
// }
// else
// {
// //don't rebuild, user is not authenticated and if async is set then we shouldn't be generating the index files non-async either
// }
// }
// else
// {
// base.RebuildIndex();
// }
//}
#region Protected
/////<summary>
///// Calls a web request in a worker thread to rebuild the indexes
/////</summary>
//protected void RebuildIndexAsync()
//{
// if (HttpContext.Current != null && UmbracoEnsuredPage.CurrentUser != null)
// {
// var handler = VirtualPathUtility.ToAbsolute(ExamineHandlerPath);
// var fullPath = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + handler + "?index=" + Name;
// var userContext = BasePage.umbracoUserContextID;
// var userContextCookie = HttpContext.Current.Request.Cookies["UserContext"];
// var thread = new Thread(() =>
// {
// var request = (HttpWebRequest)WebRequest.Create(fullPath);
// request.CookieContainer = new CookieContainer();
// request.CookieContainer.Add(new Cookie("UserContext", userContext, userContextCookie.Path,
// string.IsNullOrEmpty(userContextCookie.Domain) ? "localhost" : userContextCookie.Domain));
// request.Timeout = Timeout.Infinite;
// request.UseDefaultCredentials = true;
// request.Method = "GET";
// request.Proxy = null;
// HttpWebResponse response;
// try
// {
// response = (HttpWebResponse)request.GetResponse();
// if (response.StatusCode != HttpStatusCode.OK)
// {
// Log.Add(LogTypes.Custom, -1, "[UmbracoExamine] ExamineHandler request ended with an error: " + response.StatusDescription);
// }
// }
// catch (WebException ex)
// {
// Log.Add(LogTypes.Custom, -1, "[UmbracoExamine] ExamineHandler request threw an exception: " + ex.Message);
// }
// }) { IsBackground = true, Name = "ExamineAsyncHandler" };
// thread.Start();
// }
//}
/// <summary>
/// Ensures that the node being indexed is of a correct type and is a descendent of the parent id specified.
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override bool ValidateDocument(XElement node)
{
//check if this document is a descendent of the parent
if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0)
if (!((string)node.Attribute("path")).Contains("," + IndexerData.ParentNodeId.Value.ToString() + ","))
return false;
return base.ValidateDocument(node);
}
/// <summary>
/// Reindexes all supported types
/// </summary>
protected override void PerformIndexRebuild()
{
foreach (var t in SupportedTypes)
{
IndexAll(t);
}
}
public override void ReIndexNode(XElement node, string type)
{
if (!SupportedTypes.Contains(type))
return;
base.ReIndexNode(node, type);
}
/// <summary>
/// Builds an xpath statement to query against Umbraco data for the index type specified, then
/// initiates the re-indexing of the data matched.
/// </summary>
/// <param name="type"></param>
protected override void PerformIndexAll(string type)
{
if (!SupportedTypes.Contains(type))
return;
var xPath = "//*[(number(@id) > 0 and (@isDoc or @nodeTypeAlias)){0}]"; //we'll add more filters to this below if needed
var sb = new StringBuilder();
//create the xpath statement to match node type aliases if specified
if (IndexerData.IncludeNodeTypes.Count() > 0)
{
sb.Append("(");
foreach (var field in IndexerData.IncludeNodeTypes)
{
//this can be used across both schemas
const string nodeTypeAlias = "(@nodeTypeAlias='{0}' or (count(@nodeTypeAlias)=0 and name()='{0}'))";
sb.Append(string.Format(nodeTypeAlias, field));
sb.Append(" or ");
}
sb.Remove(sb.Length - 4, 4); //remove last " or "
sb.Append(")");
}
//create the xpath statement to match all children of the current node.
if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0)
{
if (sb.Length > 0)
sb.Append(" and ");
sb.Append("(");
sb.Append("contains(@path, '," + IndexerData.ParentNodeId.Value + ",')"); //if the path contains comma - id - comma then the nodes must be a child
sb.Append(")");
}
//create the full xpath statement to match the appropriate nodes. If there is a filter
//then apply it, otherwise just select all nodes.
var filter = sb.ToString();
xPath = string.Format(xPath, filter.Length > 0 ? " and " + filter : "");
//raise the event and set the xpath statement to the value returned
var args = new IndexingNodesEventArgs(IndexerData, xPath, type);
OnNodesIndexing(args);
if (args.Cancel)
{
return;
}
xPath = args.XPath;
DataService.LogService.AddVerboseLog(-1, string.Format("({0}) PerformIndexAll with XPATH: {1}", this.Name, xPath));
AddNodesToIndex(xPath, type);
}
/// <summary>
/// Returns an XDocument for the entire tree stored for the IndexType specified.
/// </summary>
/// <param name="xPath">The xpath to the node.</param>
/// <param name="type">The type of data to request from the data service.</param>
/// <returns>Either the Content or Media xml. If the type is not of those specified null is returned</returns>
protected virtual XDocument GetXDocument(string xPath, string type)
{
if (type == IndexTypes.Content)
{
if (this.SupportUnpublishedContent)
{
return DataService.ContentService.GetLatestContentByXPath(xPath);
}
else
{
return DataService.ContentService.GetPublishedContentByXPath(xPath);
}
}
else if (type == IndexTypes.Media)
{
return DataService.MediaService.GetLatestMediaByXpath(xPath);
}
return null;
}
#endregion
#region Private
/// <summary>
/// Adds all nodes with the given xPath root.
/// </summary>
/// <param name="xPath">The x path.</param>
/// <param name="type">The type.</param>
private void AddNodesToIndex(string xPath, string type)
{
// Get all the nodes of nodeTypeAlias == nodeTypeAlias
XDocument xDoc = GetXDocument(xPath, type);
if (xDoc != null)
{
XElement rootNode = xDoc.Root;
IEnumerable<XElement> children = rootNode.Elements();
AddNodesToIndex(children, type);
}
}
#endregion
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Examine;
using UmbracoExamine.DataServices;
using Examine.LuceneEngine.Config;
namespace UmbracoExamine.Config
{
/// <summary>
/// Extension methods for IndexSet
/// </summary>
public static class IndexSetExtensions
{
private static readonly object Locker = new object();
/// <summary>
/// Convert the indexset to indexerdata.
/// This detects if there are no user/system fields specified and if not, uses the data service to look them
/// up and update the in memory IndexSet.
/// </summary>
/// <param name="set"></param>
/// <param name="svc"></param>
/// <returns></returns>
public static IIndexCriteria ToIndexCriteria(this IndexSet set, IDataService svc)
{
if (set.IndexUserFields.Count == 0)
{
lock (Locker)
{
//we need to add all user fields to the collection if it is empty (this is the default if none are specified)
var userFields = svc.ContentService.GetAllUserPropertyNames();
foreach (var u in userFields)
{
set.IndexUserFields.Add(new IndexField() { Name = u });
}
}
}
if (set.IndexAttributeFields.Count == 0)
{
lock (Locker)
{
//we need to add all system fields to the collection if it is empty (this is the default if none are specified)
var sysFields = svc.ContentService.GetAllSystemPropertyNames();
foreach (var s in sysFields)
{
set.IndexAttributeFields.Add(new IndexField() { Name = s });
}
}
}
return new IndexCriteria(
set.IndexAttributeFields.Cast<IIndexField>().ToArray(),
set.IndexUserFields.Cast<IIndexField>().ToArray(),
set.IncludeNodeTypes.ToList().Select(x => x.Name).ToArray(),
set.ExcludeNodeTypes.ToList().Select(x => x.Name).ToArray(),
set.IndexParentId);
}
}
}

View File

@@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using umbraco;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.web;
using Examine.LuceneEngine;
namespace UmbracoExamine
{
/// <summary>
/// Static methods to help query umbraco xml
/// </summary>
public static class ContentExtensions
{
/// <summary>
/// Converts a content node to XDocument
/// </summary>
/// <param name="node"></param>
/// <param name="cacheOnly">true if data is going to be returned from cache</param>
/// <returns></returns>
/// <remarks>
/// If the type of node is not a Document, the cacheOnly has no effect, it will use the API to return
/// the xml.
/// </remarks>
[SecuritySafeCritical]
public static XDocument ToXDocument(this Content node, bool cacheOnly)
{
if (cacheOnly && node.GetType().Equals(typeof(Document)))
{
var umbXml = library.GetXmlNodeById(node.Id.ToString());
if (umbXml != null)
{
return umbXml.ToXDocument();
}
}
//this will also occur if umbraco hasn't cached content yet....
//if it's not a using cache and it's not cacheOnly, then retrieve the Xml using the API
return node.ToXDocument();
}
/// <summary>
/// Converts a content node to Xml
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
[SecuritySafeCritical]
private static XDocument ToXDocument(this Content node)
{
var xDoc = new XmlDocument();
var xNode = xDoc.CreateNode(XmlNodeType.Element, "node", "");
node.XmlPopulate(xDoc, ref xNode, false);
if (xNode.Attributes["nodeTypeAlias"] == null)
{
//we'll add the nodeTypeAlias ourselves
XmlAttribute d = xDoc.CreateAttribute("nodeTypeAlias");
d.Value = node.ContentType.Alias;
xNode.Attributes.Append(d);
}
return new XDocument(xNode.ToXElement());
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
namespace UmbracoExamine.DataServices
{
public interface IContentService
{
XDocument GetLatestContentByXPath(string xpath);
XDocument GetPublishedContentByXPath(string xpath);
/// <summary>
/// Returns a list of ALL properties names for all nodes defined in the data source
/// </summary>
/// <returns></returns>
IEnumerable<string> GetAllUserPropertyNames();
/// <summary>
/// Returns a list of ALL system property names for all nodes defined in the data source
/// </summary>
/// <returns></returns>
IEnumerable<string> GetAllSystemPropertyNames();
string StripHtml(string value);
bool IsProtected(int nodeId, string path);
}
}

View File

@@ -0,0 +1,14 @@
using System.Web;
namespace UmbracoExamine.DataServices
{
public interface IDataService
{
IContentService ContentService { get; }
ILogService LogService { get; }
IMediaService MediaService { get; }
string MapPath(string virtualPath);
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace UmbracoExamine.DataServices
{
public interface ILogService
{
string ProviderName { get; set; }
void AddErrorLog(int nodeId, string msg);
void AddInfoLog(int nodeId, string msg);
void AddVerboseLog(int nodeId, string msg);
LoggingLevel LogLevel { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Xml.Linq;
namespace UmbracoExamine.DataServices
{
public interface IMediaService
{
XDocument GetLatestMediaByXpath(string xpath);
}
}

View File

@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using umbraco;
using System.Xml.Linq;
using System.Xml;
using umbraco.cms.businesslogic.web;
using System.Collections;
using System.Xml.XPath;
using umbraco.DataLayer;
using umbraco.BusinessLogic;
using UmbracoExamine.Config;
using Examine.LuceneEngine;
using System.Data.SqlClient;
using System.Diagnostics;
namespace UmbracoExamine.DataServices
{
public class UmbracoContentService : UmbracoExamine.DataServices.IContentService
{
/// <summary>
/// removes html markup from a string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[SecuritySafeCritical]
public string StripHtml(string value)
{
return library.StripHtml(value);
}
/// <summary>
/// Gets published content by xpath
/// </summary>
/// <param name="xpath"></param>
/// <returns></returns>
[SecuritySafeCritical]
public XDocument GetPublishedContentByXPath(string xpath)
{
return library.GetXmlNodeByXPath(xpath).ToXDocument();
}
/// <summary>
/// This is quite an intensive operation...
/// get all root content, then get the XML structure for all children,
/// then run xpath against the navigator that's created
/// </summary>
/// <param name="xpath"></param>
/// <returns></returns>
[SecuritySafeCritical]
public XDocument GetLatestContentByXPath(string xpath)
{
var rootContent = Document.GetRootDocuments();
var xmlContent = XDocument.Parse("<content></content>");
var xDoc = new XmlDocument();
foreach (var c in rootContent)
{
var xNode = xDoc.CreateNode(XmlNodeType.Element, "node", "");
c.XmlPopulate(xDoc, ref xNode, true);
if (xNode.Attributes["nodeTypeAlias"] == null)
{
//we'll add the nodeTypeAlias ourselves
XmlAttribute d = xDoc.CreateAttribute("nodeTypeAlias");
d.Value = c.ContentType.Alias;
xNode.Attributes.Append(d);
}
xmlContent.Root.Add(xNode.ToXElement());
}
var result = ((IEnumerable)xmlContent.XPathEvaluate(xpath)).Cast<XElement>();
return result.ToXDocument();
}
/// <summary>
/// Unfortunately, we need to implement our own IsProtected method since
/// the Umbraco core code requires an HttpContext for this method and when we're running
/// async, there is no context
/// </summary>
/// <param name="documentId"></param>
/// <returns></returns>
[SecuritySafeCritical]
private XmlNode GetPage(int documentId)
{
XmlNode x = Access.AccessXml.SelectSingleNode("/access/page [@id=" + documentId.ToString() + "]");
return x;
}
/// <summary>
/// Unfortunately, we need to implement our own IsProtected method since
/// the Umbraco core code requires an HttpContext for this method and when we're running
/// async, there is no context
/// </summary>
/// <param name="nodeId"></param>
/// <param name="path"></param>
/// <returns></returns>
public bool IsProtected(int nodeId, string path)
{
foreach (string id in path.Split(','))
{
if (GetPage(int.Parse(id)) != null)
{
return true;
}
}
return false;
}
/// <summary>
/// Returns a list of all of the user defined property names in Umbraco
/// </summary>
/// <returns></returns>
[SecuritySafeCritical]
public IEnumerable<string> GetAllUserPropertyNames()
{
//this is how umb codebase 4.0 does this... booo, should be in the data layer, will fix in 4.1
var aliases = new List<string>();
var fieldSql = "select distinct alias from cmsPropertyType order by alias";
try
{
using (var dr = Application.SqlHelper.ExecuteReader(fieldSql))
{
while (dr.Read())
{
aliases.Add(dr.GetString("alias"));
}
}
}
catch (Exception ex)
{
if (ex is SqlHelperException || ex is SqlException)
{
//if this happens, it could be due to wrong connection string, or something else.
//we don't want to crash the app because of this so we'll actually swallow this
//exception... Unfortunately logging probably won't work in this situation either :(
Debug.WriteLine("EXCEPTION OCCURRED reading GetAllUserPropertyNames: " + ex.Message, "Error");
Trace.WriteLine("EXCEPTION OCCURRED reading GetAllUserPropertyNames: " + ex.Message, "Error");
}
else
{
throw ex;
}
}
return aliases;
}
/// <summary>
/// Returns a list of all system field names in Umbraco
/// </summary>
/// <returns></returns>
public IEnumerable<string> GetAllSystemPropertyNames()
{
return UmbracoContentIndexer.IndexFieldPolicies.Select(x => x.Key);
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Web;
using System.Web.Hosting;
namespace UmbracoExamine.DataServices
{
public class UmbracoDataService : IDataService
{
public UmbracoDataService()
{
ContentService = new UmbracoContentService();
MediaService = new UmbracoMediaService();
LogService = new UmbracoLogService();
}
public IContentService ContentService { get; protected set; }
public IMediaService MediaService { get; protected set; }
public ILogService LogService { get; protected set; }
public string MapPath(string virtualPath)
{
return HostingEnvironment.MapPath(virtualPath);
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using umbraco.BusinessLogic;
namespace UmbracoExamine.DataServices
{
public class UmbracoLogService : UmbracoExamine.DataServices.ILogService
{
public string ProviderName { get; set; }
[SecuritySafeCritical]
public void AddInfoLog(int nodeId, string msg)
{
Log.Add(LogTypes.Custom, nodeId, "[UmbracoExamine] (" + ProviderName + ")" + msg);
}
[SecuritySafeCritical]
public void AddErrorLog(int nodeId, string msg)
{
Log.Add(LogTypes.Error, nodeId, "[UmbracoExamine] (" + ProviderName + ")" + msg);
}
[SecuritySafeCritical]
public void AddVerboseLog(int nodeId, string msg)
{
if (LogLevel == LoggingLevel.Verbose)
Log.Add(LogTypes.Custom, nodeId, "[UmbracoExamine] (" + ProviderName + ")" + msg);
}
public LoggingLevel LogLevel { get; set; }
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using System.Xml.XPath;
using System.Xml.Linq;
using umbraco.cms.businesslogic.media;
using System.Collections;
using Examine.LuceneEngine;
namespace UmbracoExamine.DataServices
{
/// <summary>
/// Data service used to query for media
/// </summary>
public class UmbracoMediaService : UmbracoExamine.DataServices.IMediaService
{
/// <summary>
/// This is quite an intensive operation...
/// get all root media, then get the XML structure for all children,
/// then run xpath against the navigator that's created
/// </summary>
/// <param name="xpath"></param>
/// <returns></returns>
[SecuritySafeCritical]
public XDocument GetLatestMediaByXpath(string xpath)
{
Media[] rootMedia = Media.GetRootMedias();
var xmlMedia = XDocument.Parse("<media></media>");
foreach (Media media in rootMedia)
{
xmlMedia.Root.Add(GetMediaItem(media.Id));
}
var result = ((IEnumerable)xmlMedia.XPathEvaluate(xpath)).Cast<XElement>();
return result.ToXDocument();
}
[SecuritySafeCritical]
private XElement GetMediaItem(int nodeId)
{
var nodes = umbraco.library.GetMedia(nodeId, true);
return XElement.Parse(nodes.Current.OuterXml);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UmbracoExamine
{
/// <summary>
/// The index types stored in the Lucene Index
/// </summary>
public static class IndexTypes
{
/// <summary>
/// The content index type
/// </summary>
/// <remarks>
/// Is lower case because the Standard Analyzer requires lower case
/// </remarks>
public const string Content = "content";
/// <summary>
/// The media index type
/// </summary>
/// <remarks>
/// Is lower case because the Standard Analyzer requires lower case
/// </remarks>
public const string Media = "media";
/// <summary>
/// The member index type
/// </summary>
/// <remarks>
/// Is lower case because the Standard Analyzer requires lower case
/// </remarks>
public const string Member = "member";
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UmbracoExamine
{
public enum LoggingLevel
{
Verbose, Normal
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Security;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyCompany("umbraco")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyTitle("UmbracoExamine")]
[assembly: AssemblyDescription("Umbraco index & search providers based on the Examine model using Lucene.NET 2.9.2")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("UmbracoExamine")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("31c5b048-cfa8-49b4-8983-bdba0f99eef5")]
[assembly: NeutralResourcesLanguage("en-US")]
//NOTE: WE cannot make change the major version to be the same as Umbraco because of backwards compatibility, however we
// will make the minor version the same as the umbraco version
[assembly: AssemblyVersion("0.6.0.*")]
[assembly: AssemblyFileVersion("0.6.0.*")]
[assembly: AllowPartiallyTrustedCallers]
[assembly: InternalsVisibleTo("Umbraco.Tests")]

View File

@@ -0,0 +1,37 @@
using Examine.SearchCriteria;
namespace UmbracoExamine.SearchCriteria
{
internal class ExamineValue : IExamineValue
{
public ExamineValue(Examineness vagueness, string value) : this(vagueness, value, 1)
{
}
public ExamineValue(Examineness vagueness, string value, float level)
{
this.Examineness = vagueness;
this.Value = value;
this.Level = level;
}
public Examineness Examineness
{
get;
private set;
}
public string Value
{
get;
private set;
}
public float Level
{
get;
private set;
}
}
}

View File

@@ -0,0 +1,70 @@
using Examine.SearchCriteria;
using Lucene.Net.Search;
namespace UmbracoExamine.SearchCriteria
{
/// <summary>
/// An implementation of the fluent API boolean operations
/// </summary>
public class LuceneBooleanOperation : IBooleanOperation
{
private LuceneSearchCriteria search;
internal LuceneBooleanOperation(LuceneSearchCriteria search)
{
this.search = search;
}
#region IBooleanOperation Members
/// <summary>
/// Sets the next operation to be AND
/// </summary>
/// <returns></returns>
public IQuery And()
{
return new LuceneQuery(this.search, BooleanClause.Occur.MUST);
}
/// <summary>
/// Sets the next operation to be OR
/// </summary>
/// <returns></returns>
public IQuery Or()
{
return new LuceneQuery(this.search, BooleanClause.Occur.SHOULD);
}
/// <summary>
/// Sets the next operation to be NOT
/// </summary>
/// <returns></returns>
public IQuery Not()
{
return new LuceneQuery(this.search, BooleanClause.Occur.MUST_NOT);
}
/// <summary>
/// Compiles this instance for fluent API conclusion
/// </summary>
/// <returns></returns>
public ISearchCriteria Compile()
{
if (!string.IsNullOrEmpty(this.search.SearchIndexType))
{
var query = this.search.query;
this.search.query = new BooleanQuery();
this.search.query.Add(query, BooleanClause.Occur.MUST);
//this.search.query.Add(this.search.queryParser.Parse("(" + query.ToString() + ")"), BooleanClause.Occur.MUST);
this.search.FieldInternal(LuceneExamineIndexer.IndexTypeFieldName, new ExamineValue(Examineness.Explicit, this.search.SearchIndexType.ToString().ToLower()), BooleanClause.Occur.MUST);
}
return this.search;
}
#endregion
}
}

View File

@@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Examine.SearchCriteria;
using Lucene.Net.Search;
namespace UmbracoExamine.SearchCriteria
{
public class LuceneQuery : IQuery
{
private LuceneSearchCriteria search;
private BooleanClause.Occur occurance;
/// <summary>
/// Initializes a new instance of the <see cref="LuceneQuery"/> class.
/// </summary>
/// <param name="search">The search.</param>
/// <param name="occurance">The occurance.</param>
internal LuceneQuery(LuceneSearchCriteria search, BooleanClause.Occur occurance)
{
this.search = search;
this.occurance = occurance;
}
/// <summary>
/// Gets the boolean operation which this query method will be added as
/// </summary>
/// <value>The boolean operation.</value>
public BooleanOperation BooleanOperation
{
get { return occurance.ToBooleanOperation(); }
}
#region ISearch Members
/// <summary>
/// Query on the id
/// </summary>
/// <param name="id">The id.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Id(int id)
{
return this.search.IdInternal(id, this.occurance);
}
/// <summary>
/// Query on the NodeName
/// </summary>
/// <param name="nodeName">Name of the node.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation NodeName(string nodeName)
{
return this.search.NodeNameInternal(new ExamineValue(Examineness.Explicit, nodeName), occurance);
}
/// <summary>
/// Query on the NodeTypeAlias
/// </summary>
/// <param name="nodeTypeAlias">The node type alias.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation NodeTypeAlias(string nodeTypeAlias)
{
return this.search.NodeTypeAliasInternal(new ExamineValue(Examineness.Explicit, nodeTypeAlias), occurance);
}
/// <summary>
/// Query on the Parent ID
/// </summary>
/// <param name="id">The id of the parent.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation ParentId(int id)
{
return this.search.ParentIdInternal(id, occurance);
}
/// <summary>
/// Query on the specified field
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="fieldValue">The field value.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Field(string fieldName, string fieldValue)
{
return this.search.FieldInternal(fieldName, new ExamineValue(Examineness.Explicit, fieldValue), occurance);
}
/// <summary>
/// Ranges the specified field name.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Range(string fieldName, DateTime start, DateTime end)
{
return this.Range(fieldName, start, end, true, true);
}
/// <summary>
/// Ranges the specified field name.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <param name="includeLower">if set to <c>true</c> [include lower].</param>
/// <param name="includeUpper">if set to <c>true</c> [include upper].</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Range(string fieldName, DateTime start, DateTime end, bool includeLower, bool includeUpper)
{
return this.search.Range(fieldName, start, end, includeLower, includeUpper);
}
/// <summary>
/// Ranges the specified field name.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Range(string fieldName, int start, int end)
{
return this.Range(fieldName, start, end, true, true);
}
/// <summary>
/// Ranges the specified field name.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <param name="includeLower">if set to <c>true</c> [include lower].</param>
/// <param name="includeUpper">if set to <c>true</c> [include upper].</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Range(string fieldName, int start, int end, bool includeLower, bool includeUpper)
{
return this.search.RangeInternal(fieldName, start, end, includeLower, includeUpper, occurance);
}
/// <summary>
/// Ranges the specified field name.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Range(string fieldName, string start, string end)
{
return this.Range(fieldName, start, end, true, true);
}
/// <summary>
/// Ranges the specified field name.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <param name="includeLower">if set to <c>true</c> [include lower].</param>
/// <param name="includeUpper">if set to <c>true</c> [include upper].</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Range(string fieldName, string start, string end, bool includeLower, bool includeUpper)
{
return this.search.RangeInternal(fieldName, start, end, includeLower, includeUpper, occurance);
}
/// <summary>
/// Query on the NodeName
/// </summary>
/// <param name="nodeName">Name of the node.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation NodeName(IExamineValue nodeName)
{
return this.search.NodeNameInternal(nodeName, occurance);
}
/// <summary>
/// Query on the NodeTypeAlias
/// </summary>
/// <param name="nodeTypeAlias">The node type alias.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation NodeTypeAlias(IExamineValue nodeTypeAlias)
{
return this.search.NodeTypeAliasInternal(nodeTypeAlias, occurance);
}
/// <summary>
/// Query on the specified field
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="fieldValue">The field value.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Field(string fieldName, IExamineValue fieldValue)
{
return this.search.FieldInternal(fieldName, fieldValue, occurance);
}
/// <summary>
/// Queries multiple fields with each being an And boolean operation
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation GroupedAnd(IEnumerable<string> fields, params string[] query)
{
var fieldVals = new List<IExamineValue>();
foreach (var f in query)
{
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
}
return this.search.GroupedAndInternal(fields.ToArray(), fieldVals.ToArray(), this.occurance);
}
/// <summary>
/// Queries multiple fields with each being an And boolean operation
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation GroupedAnd(IEnumerable<string> fields, params IExamineValue[] query)
{
throw new NotImplementedException();
}
/// <summary>
/// Queries multiple fields with each being an Or boolean operation
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation GroupedOr(IEnumerable<string> fields, params string[] query)
{
var fieldVals = new List<IExamineValue>();
foreach (var f in query)
{
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
}
return this.search.GroupedOrInternal(fields.ToArray(), fieldVals.ToArray(), this.occurance);
}
/// <summary>
/// Queries multiple fields with each being an Or boolean operation
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation GroupedOr(IEnumerable<string> fields, params IExamineValue[] query)
{
return this.search.GroupedOrInternal(fields.ToArray(), query, this.occurance);
}
/// <summary>
/// Queries multiple fields with each being an Not boolean operation
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation GroupedNot(IEnumerable<string> fields, params string[] query)
{
var fieldVals = new List<IExamineValue>();
foreach (var f in query)
{
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
}
return this.search.GroupedNotInternal(fields.ToArray(), fieldVals.ToArray(), this.occurance);
}
/// <summary>
/// Queries multiple fields with each being an Not boolean operation
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation GroupedNot(IEnumerable<string> fields, params IExamineValue[] query)
{
return this.search.GroupedNotInternal(fields.ToArray(), query, this.occurance);
}
/// <summary>
/// Queries on multiple fields with their inclusions customly defined
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="operations">The operations.</param>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation GroupedFlexible(IEnumerable<string> fields, IEnumerable<BooleanOperation> operations, params string[] query)
{
var fieldVals = new List<IExamineValue>();
foreach (var f in query)
{
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
}
return this.search.GroupedFlexibleInternal(fields.ToArray(), operations.ToArray(), fieldVals.ToArray(), occurance);
}
/// <summary>
/// Queries on multiple fields with their inclusions customly defined
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="operations">The operations.</param>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation GroupedFlexible(IEnumerable<string> fields, IEnumerable<BooleanOperation> operations, params IExamineValue[] query)
{
return this.search.GroupedFlexibleInternal(fields.ToArray(), operations.ToArray(), query, occurance);
}
/// <summary>
/// Orders the results by the specified fields
/// </summary>
/// <param name="fieldNames">The field names.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation OrderBy(params string[] fieldNames)
{
return this.search.OrderBy(fieldNames);
}
/// <summary>
/// Orders the results by the specified fields in a descending order
/// </summary>
/// <param name="fieldNames">The field names.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation OrderByDescending(params string[] fieldNames)
{
return this.search.OrderByDescending(fieldNames);
}
#endregion
}
}

View File

@@ -0,0 +1,559 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Examine;
using Examine.SearchCriteria;
using Lucene.Net.Analysis;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Search.Spans;
using Lucene.Net.Index;
using Lucene.Net.Documents;
namespace UmbracoExamine.SearchCriteria
{
/// <summary>
/// This class is used to query against Lucene.Net
/// </summary>
public class LuceneSearchCriteria : ISearchCriteria
{
internal MultiFieldQueryParser queryParser;
internal BooleanQuery query;
internal List<SortField> sortFields = new List<SortField>();
private readonly BooleanClause.Occur occurance;
private readonly Lucene.Net.Util.Version luceneVersion = Lucene.Net.Util.Version.LUCENE_29;
internal LuceneSearchCriteria(string type, Analyzer analyzer, string[] fields, bool allowLeadingWildcards, BooleanOperation occurance)
{
Enforcer.ArgumentNotNull(fields, "fields");
SearchIndexType = type;
query = new BooleanQuery();
this.BooleanOperation = occurance;
this.queryParser = new MultiFieldQueryParser(luceneVersion, fields, analyzer);
this.queryParser.SetAllowLeadingWildcard(allowLeadingWildcards);
this.occurance = occurance.ToLuceneOccurance();
}
/// <summary>
/// Gets the boolean operation which this query method will be added as
/// </summary>
/// <value>The boolean operation.</value>
public BooleanOperation BooleanOperation
{
get;
protected set;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
return string.Format("{{ SearchIndexType: {0}, LuceneQuery: {1} }}", this.SearchIndexType, this.query.ToString());
}
private static void ValidateIExamineValue(IExamineValue v)
{
var ev = v as ExamineValue;
if (ev == null)
{
throw new ArgumentException("IExamineValue was not created from this provider. Ensure that it is created from the ISearchCriteria this provider exposes");
}
}
#region ISearchCriteria Members
public string SearchIndexType
{
get;
protected set;
}
public bool IncludeHitCount
{
get;
set;
}
public int TotalHits
{
get;
internal protected set;
}
#endregion
#region ISearch Members
/// <summary>
/// Query on the id
/// </summary>
/// <param name="id">The id.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Id(int id)
{
return IdInternal(id, occurance);
}
internal protected IBooleanOperation IdInternal(int id, BooleanClause.Occur occurance)
{
//use a query parser (which uses the analyzer) to build up the field query which we want
query.Add(this.queryParser.GetFieldQuery(LuceneExamineIndexer.IndexNodeIdFieldName, id.ToString()), occurance);
return new LuceneBooleanOperation(this);
}
/// <summary>
/// Query on the NodeName
/// </summary>
/// <param name="nodeName">Name of the node.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation NodeName(string nodeName)
{
Enforcer.ArgumentNotNull(nodeName, "nodeName");
return NodeName(new ExamineValue(Examineness.Explicit, nodeName));
}
/// <summary>
/// Query on the NodeName
/// </summary>
/// <param name="nodeName">Name of the node.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation NodeName(IExamineValue nodeName)
{
Enforcer.ArgumentNotNull(nodeName, "nodeName");
return this.NodeNameInternal(nodeName, occurance);
}
internal protected IBooleanOperation NodeNameInternal(IExamineValue examineValue, BooleanClause.Occur occurance)
{
return this.FieldInternal("nodeName", examineValue, occurance);
}
/// <summary>
/// Query on the NodeTypeAlias
/// </summary>
/// <param name="nodeTypeAlias">The node type alias.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation NodeTypeAlias(string nodeTypeAlias)
{
Enforcer.ArgumentNotNull(nodeTypeAlias, "nodeTypeAlias");
return this.NodeTypeAlias(new ExamineValue(Examineness.Explicit, nodeTypeAlias));
}
/// <summary>
/// Query on the NodeTypeAlias
/// </summary>
/// <param name="nodeTypeAlias">The node type alias.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation NodeTypeAlias(IExamineValue nodeTypeAlias)
{
Enforcer.ArgumentNotNull(nodeTypeAlias, "nodeTypeAlias");
return this.NodeTypeAliasInternal(nodeTypeAlias, occurance);
}
internal protected IBooleanOperation NodeTypeAliasInternal(IExamineValue examineValue, BooleanClause.Occur occurance)
{
return this.FieldInternal("nodeTypeAlias", examineValue, occurance);
}
/// <summary>
/// Query on the Parent ID
/// </summary>
/// <param name="id">The id of the parent.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation ParentId(int id)
{
return this.ParentIdInternal(id, occurance);
}
internal protected IBooleanOperation ParentIdInternal(int id, BooleanClause.Occur occurance)
{
query.Add(this.queryParser.GetFieldQuery("parentID", id.ToString()), occurance);
return new LuceneBooleanOperation(this);
}
/// <summary>
/// Query on the specified field
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="fieldValue">The field value.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Field(string fieldName, string fieldValue)
{
Enforcer.ArgumentNotNull(fieldName, "fieldName");
Enforcer.ArgumentNotNull(fieldValue, "fieldValue");
return this.FieldInternal(fieldName, new ExamineValue(Examineness.Explicit, fieldValue), occurance);
}
/// <summary>
/// Query on the specified field
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="fieldValue">The field value.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation Field(string fieldName, IExamineValue fieldValue)
{
Enforcer.ArgumentNotNull(fieldName, "fieldName");
Enforcer.ArgumentNotNull(fieldValue, "fieldValue");
return this.FieldInternal(fieldName, fieldValue, occurance);
}
/// <summary>
/// Returns the Lucene query object for a field given an IExamineValue
/// </summary>
/// <param name="fieldName"></param>
/// <param name="fieldValue"></param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
internal protected Query GetFieldInternalQuery(string fieldName, IExamineValue fieldValue)
{
Query queryToAdd;
switch (fieldValue.Examineness)
{
case Examineness.Fuzzy:
queryToAdd = this.queryParser.GetFuzzyQuery(fieldName, fieldValue.Value, fieldValue.Level);
break;
case Examineness.SimpleWildcard:
case Examineness.ComplexWildcard:
queryToAdd = this.queryParser.GetWildcardQuery(fieldName, fieldValue.Value);
break;
case Examineness.Boosted:
queryToAdd = this.queryParser.GetFieldQuery(fieldName, fieldValue.Value);
queryToAdd.SetBoost(fieldValue.Level);
break;
case Examineness.Proximity:
//This is how you are supposed to do this based on this doc here:
//http://lucene.apache.org/java/2_4_1/api/org/apache/lucene/search/spans/package-summary.html#package_description
//but i think that lucene.net has an issue with it's internal parser since it parses to a very strange query
//we'll just manually make it instead below
//var spans = new List<SpanQuery>();
//foreach (var s in fieldValue.Value.Split(' '))
//{
// spans.Add(new SpanTermQuery(new Term(fieldName, s)));
//}
//queryToAdd = new SpanNearQuery(spans.ToArray(), Convert.ToInt32(fieldValue.Level), true);
var proxQuery = fieldName + ":\"" + fieldValue.Value + "\"~" + Convert.ToInt32(fieldValue.Level).ToString();
queryToAdd = queryParser.Parse(proxQuery);
break;
case Examineness.Explicit:
default:
queryToAdd = this.queryParser.GetFieldQuery(fieldName, fieldValue.Value);
break;
}
return queryToAdd;
}
internal protected IBooleanOperation FieldInternal(string fieldName, IExamineValue fieldValue, BooleanClause.Occur occurance)
{
Query queryToAdd = GetFieldInternalQuery(fieldName, fieldValue);
if (queryToAdd != null)
query.Add(queryToAdd, occurance);
return new LuceneBooleanOperation(this);
}
public IBooleanOperation Range(string fieldName, DateTime start, DateTime end)
{
return this.Range(fieldName, start, end, true, true);
}
public IBooleanOperation Range(string fieldName, DateTime start, DateTime end, bool includeLower, bool includeUpper)
{
//since lucene works on string's for all searching we need to flatten the date
return this.RangeInternal(fieldName, DateTools.DateToString(start, DateTools.Resolution.MILLISECOND), DateTools.DateToString(end, DateTools.Resolution.MILLISECOND), includeLower, includeUpper, occurance);
}
public IBooleanOperation Range(string fieldName, int start, int end)
{
Enforcer.ArgumentNotNull(fieldName, "fieldName");
return this.Range(fieldName, start, end, true, true);
}
public IBooleanOperation Range(string fieldName, int start, int end, bool includeLower, bool includeUpper)
{
return this.RangeInternal(fieldName, start, end, includeLower, includeUpper, occurance);
}
protected internal IBooleanOperation RangeInternal(string fieldName, int start, int end, bool includeLower, bool includeUpper, BooleanClause.Occur occurance)
{
query.Add(NumericRangeQuery.NewIntRange(fieldName, start, end, includeLower, includeUpper), occurance);
return new LuceneBooleanOperation(this);
}
public IBooleanOperation Range(string fieldName, string start, string end)
{
Enforcer.ArgumentNotNull(fieldName, "fieldName");
Enforcer.ArgumentNotNull(start, "start");
Enforcer.ArgumentNotNull(end, "end");
return this.Range(fieldName, start, end, true, true);
}
public IBooleanOperation Range(string fieldName, string start, string end, bool includeLower, bool includeUpper)
{
Enforcer.ArgumentNotNull(fieldName, "fieldName");
Enforcer.ArgumentNotNull(start, "start");
Enforcer.ArgumentNotNull(end, "end");
return this.RangeInternal(fieldName, start, end, includeLower, includeUpper, occurance);
}
protected internal IBooleanOperation RangeInternal(string fieldName, string start, string end, bool includeLower, bool includeUpper, BooleanClause.Occur occurance)
{
query.Add(new TermRangeQuery(fieldName, start, end, includeLower, includeUpper), occurance);
return new LuceneBooleanOperation(this);
}
public IBooleanOperation GroupedAnd(IEnumerable<string> fields, params string[] query)
{
Enforcer.ArgumentNotNull(fields, "fields");
Enforcer.ArgumentNotNull(query, "query");
var fieldVals = new List<IExamineValue>();
foreach (var f in query)
{
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
}
return this.GroupedAnd(fields.ToArray(), fieldVals.ToArray());
}
public IBooleanOperation GroupedAnd(IEnumerable<string> fields, IExamineValue[] fieldVals)
{
Enforcer.ArgumentNotNull(fields, "fields");
Enforcer.ArgumentNotNull(query, "fieldVals");
return this.GroupedAndInternal(fields.ToArray(), fieldVals.ToArray(), occurance);
}
protected internal IBooleanOperation GroupedAndInternal(string[] fields, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
{
//if there's only 1 query text we want to build up a string like this:
//(+field1:query +field2:query +field3:query)
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
query.Add(GetMultiFieldQuery(fields, fieldVals, BooleanClause.Occur.MUST), occurance);
return new LuceneBooleanOperation(this);
}
public IBooleanOperation GroupedOr(IEnumerable<string> fields, params string[] query)
{
Enforcer.ArgumentNotNull(fields, "fields");
Enforcer.ArgumentNotNull(query, "query");
var fieldVals = new List<IExamineValue>();
foreach (var f in query)
{
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
}
return this.GroupedOr(fields.ToArray(), fieldVals.ToArray());
}
public IBooleanOperation GroupedOr(IEnumerable<string> fields, params IExamineValue[] fieldVals)
{
Enforcer.ArgumentNotNull(fields, "fields");
Enforcer.ArgumentNotNull(query, "query");
return this.GroupedOrInternal(fields.ToArray(), fieldVals, occurance);
}
protected internal IBooleanOperation GroupedOrInternal(string[] fields, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
{
//if there's only 1 query text we want to build up a string like this:
//(field1:query field2:query field3:query)
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
query.Add(GetMultiFieldQuery(fields, fieldVals, BooleanClause.Occur.SHOULD), occurance);
return new LuceneBooleanOperation(this);
}
public IBooleanOperation GroupedNot(IEnumerable<string> fields, params string[] query)
{
Enforcer.ArgumentNotNull(fields, "fields");
Enforcer.ArgumentNotNull(query, "query");
var fieldVals = new List<IExamineValue>();
foreach (var f in query)
{
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
}
return this.GroupedNot(fields.ToArray(), fieldVals.ToArray());
}
public IBooleanOperation GroupedNot(IEnumerable<string> fields, params IExamineValue[] query)
{
Enforcer.ArgumentNotNull(fields, "fields");
Enforcer.ArgumentNotNull(query, "query");
return this.GroupedNotInternal(fields.ToArray(), query, occurance);
}
protected internal IBooleanOperation GroupedNotInternal(string[] fields, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
{
//if there's only 1 query text we want to build up a string like this:
//(!field1:query !field2:query !field3:query)
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
query.Add(GetMultiFieldQuery(fields, fieldVals, BooleanClause.Occur.MUST_NOT), occurance);
return new LuceneBooleanOperation(this);
}
/// <summary>
/// Creates our own style 'multi field query' used internal for the grouped operations
/// </summary>
/// <param name="fields"></param>
/// <param name="fieldVals"></param>
/// <param name="occurance"></param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
protected internal BooleanQuery GetMultiFieldQuery(string[] fields, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
{
//if there's only 1 query text we want to build up a string like this:
//(!field1:query !field2:query !field3:query)
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
var queryVals = new IExamineValue[fields.Length];
if (fieldVals.Length == 1)
{
for (int i = 0; i < queryVals.Length; i++)
queryVals[i] = fieldVals[0];
}
else
{
queryVals = fieldVals;
}
var qry = new BooleanQuery();
for (int i = 0; i < fields.Length; i++)
{
qry.Add(this.GetFieldInternalQuery(fields[i], queryVals[i]), occurance);
}
return qry;
}
public IBooleanOperation GroupedFlexible(IEnumerable<string> fields, IEnumerable<BooleanOperation> operations, params string[] query)
{
Enforcer.ArgumentNotNull(fields, "fields");
Enforcer.ArgumentNotNull(query, "query");
Enforcer.ArgumentNotNull(operations, "operations");
var fieldVals = new List<IExamineValue>();
foreach (var f in query)
{
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
}
return this.GroupedFlexible(fields.ToArray(), operations.ToArray(), fieldVals.ToArray());
}
public IBooleanOperation GroupedFlexible(IEnumerable<string> fields, IEnumerable<BooleanOperation> operations, params IExamineValue[] fieldVals)
{
Enforcer.ArgumentNotNull(fields, "fields");
Enforcer.ArgumentNotNull(query, "query");
Enforcer.ArgumentNotNull(operations, "operations");
return this.GroupedFlexibleInternal(fields.ToArray(), operations.ToArray(), fieldVals, occurance);
}
protected internal IBooleanOperation GroupedFlexibleInternal(string[] fields, BooleanOperation[] operations, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
{
//if there's only 1 query text we want to build up a string like this:
//(field1:query field2:query field3:query)
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
var flags = new BooleanClause.Occur[operations.Count()];
for (int i = 0; i < flags.Length; i++)
flags[i] = operations.ElementAt(i).ToLuceneOccurance();
var queryVals = new IExamineValue[fields.Length];
if (fieldVals.Length == 1)
{
for (int i = 0; i < queryVals.Length; i++)
queryVals[i] = fieldVals[0];
}
else
{
queryVals = fieldVals;
}
var qry = new BooleanQuery();
for (int i = 0; i < fields.Length; i++)
{
qry.Add(this.GetFieldInternalQuery(fields[i], queryVals[i]), flags[i]);
}
this.query.Add(qry, occurance);
return new LuceneBooleanOperation(this);
}
/// <summary>
/// Passes a raw search query to the provider to handle
/// </summary>
/// <param name="query">The query.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public ISearchCriteria RawQuery(string query)
{
this.query.Add(this.queryParser.Parse(query), this.occurance);
return this;
}
/// <summary>
/// Orders the results by the specified fields
/// </summary>
/// <param name="fieldNames">The field names.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation OrderBy(params string[] fieldNames)
{
Enforcer.ArgumentNotNull(fieldNames, "fieldNames");
return this.OrderByInternal(false, fieldNames);
}
/// <summary>
/// Orders the results by the specified fields in a descending order
/// </summary>
/// <param name="fieldNames">The field names.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
public IBooleanOperation OrderByDescending(params string[] fieldNames)
{
Enforcer.ArgumentNotNull(fieldNames, "fieldNames");
return this.OrderByInternal(true, fieldNames);
}
/// <summary>
/// Internal operation for adding the ordered results
/// </summary>
/// <param name="descending">if set to <c>true</c> [descending].</param>
/// <param name="fieldNames">The field names.</param>
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
protected internal IBooleanOperation OrderByInternal(bool descending, params string[] fieldNames)
{
foreach (var fieldName in fieldNames)
{
this.sortFields.Add(new SortField(LuceneExamineIndexer.SortedFieldNamePrefix + fieldName, SortField.STRING, descending));
}
return new LuceneBooleanOperation(this);
}
#endregion
}
}

View File

@@ -0,0 +1,164 @@
using Examine.SearchCriteria;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;
using System;
namespace UmbracoExamine.SearchCriteria
{
public static class LuceneSearchExtensions
{
/// <summary>
/// Adds a single character wildcard to the string for Lucene wildcard matching
/// </summary>
/// <param name="s">The string to wildcard.</param>
/// <returns>An IExamineValue for the required operation</returns>
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
public static IExamineValue SingleCharacterWildcard(this string s)
{
if (System.String.IsNullOrEmpty(s))
throw new ArgumentException("Supplied string is null or empty.", "s");
return new ExamineValue(Examineness.SimpleWildcard, s + "?");
}
/// <summary>
/// Adds a multi-character wildcard to a string for Lucene wildcard matching
/// </summary>
/// <param name="s">The string to wildcard.</param>
/// <returns>An IExamineValue for the required operation</returns>
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
public static IExamineValue MultipleCharacterWildcard(this string s)
{
if (String.IsNullOrEmpty(s))
throw new ArgumentException("Supplied string is null or empty.", "s");
return new ExamineValue(Examineness.ComplexWildcard, s + "*");
}
/// <summary>
/// Configures the string for fuzzy matching in Lucene using the default fuzziness level
/// </summary>
/// <param name="s">The string to configure fuzzy matching on.</param>
/// <returns>An IExamineValue for the required operation</returns>
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
public static IExamineValue Fuzzy(this string s)
{
return Fuzzy(s, 0.5f);
}
/// <summary>
/// Configures the string for fuzzy matching in Lucene using the supplied fuzziness level
/// </summary>
/// <param name="s">The string to configure fuzzy matching on.</param>
/// <param name="fuzzieness">The fuzzieness level.</param>
/// <returns>
/// An IExamineValue for the required operation
/// </returns>
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
public static IExamineValue Fuzzy(this string s, float fuzzieness)
{
if (String.IsNullOrEmpty(s))
throw new ArgumentException("Supplied string is null or empty.", "s");
return new ExamineValue(Examineness.Fuzzy, s, fuzzieness);
}
/// <summary>
/// Configures the string for boosting in Lucene
/// </summary>
/// <param name="s">The string to wildcard.</param>
/// <param name="boost">The boost level.</param>
/// <returns>
/// An IExamineValue for the required operation
/// </returns>
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
public static IExamineValue Boost(this string s, float boost)
{
if (String.IsNullOrEmpty(s))
throw new ArgumentException("Supplied string is null or empty.", "s");
return new ExamineValue(Examineness.Boosted, s + "^", boost);
}
/// <summary>
/// Configures the string for proximity matching
/// </summary>
/// <param name="s">The string to wildcard.</param>
/// <param name="proximity">The proximity level.</param>
/// <returns>
/// An IExamineValue for the required operation
/// </returns>
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
public static IExamineValue Proximity(this string s, int proximity)
{
if (String.IsNullOrEmpty(s))
throw new ArgumentException("Supplied string is null or empty.", "s");
return new ExamineValue(Examineness.Proximity, s + "~", Convert.ToSingle(proximity));
}
/// <summary>
/// Escapes the string within Lucene
/// </summary>
/// <param name="s">The string to wildcard.</param>
/// <returns>An IExamineValue for the required operation</returns>
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
public static IExamineValue Escape(this string s)
{
if (String.IsNullOrEmpty(s))
throw new ArgumentException("Supplied string is null or empty.", "s");
return new ExamineValue(Examineness.Escaped, QueryParser.Escape(s));
}
/// <summary>
/// Sets up an <see cref="IExamineValue"/> for an additional Examiness
/// </summary>
/// <param name="examineValue">The IExamineValue to continue working with.</param>
/// <param name="s">The string to postfix.</param>
/// <returns>Combined strings</returns>
public static string Then(this IExamineValue examineValue, string s)
{
if (examineValue == null)
throw new ArgumentNullException("examineValue", "examineValue is null.");
if (String.IsNullOrEmpty(s))
throw new ArgumentException("Supplied string is null or empty.", "s");
return examineValue.Value + s;
}
/// <summary>
/// Converts an Examine boolean operation to a Lucene representation
/// </summary>
/// <param name="o">The operation.</param>
/// <returns>The translated Boolean operation</returns>
public static BooleanClause.Occur ToLuceneOccurance(this BooleanOperation o)
{
switch (o)
{
case BooleanOperation.And:
return BooleanClause.Occur.MUST;
case BooleanOperation.Not:
return BooleanClause.Occur.MUST_NOT;
case BooleanOperation.Or:
default:
return BooleanClause.Occur.SHOULD;
}
}
/// <summary>
/// Converts a Lucene boolean occurrence to an Examine representation
/// </summary>
/// <param name="o">The occurrence to translate.</param>
/// <returns>The translated boolean occurrence</returns>
public static BooleanOperation ToBooleanOperation(this BooleanClause.Occur o)
{
if (o == BooleanClause.Occur.MUST)
{
return BooleanOperation.And;
}
else if (o == BooleanClause.Occur.MUST_NOT)
{
return BooleanOperation.Not;
}
else
{
return BooleanOperation.Or;
}
}
}
}

View File

@@ -0,0 +1,375 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Web;
using System.Xml.Linq;
using Examine;
using Examine.Config;
using Examine.Providers;
using umbraco.cms.businesslogic;
using UmbracoExamine.DataServices;
using Examine.LuceneEngine;
using Examine.LuceneEngine.Config;
using UmbracoExamine.Config;
using Examine.LuceneEngine.Providers;
using Lucene.Net.Analysis;
using umbraco.BasePages;
namespace UmbracoExamine
{
/// <summary>
///
/// </summary>
public class UmbracoContentIndexer : BaseUmbracoIndexer
{
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public UmbracoContentIndexer()
: base() { }
/// <summary>
/// Constructor to allow for creating an indexer at runtime
/// </summary>
/// <param name="indexerData"></param>
/// <param name="indexPath"></param>
/// <param name="dataService"></param>
/// <param name="analyzer"></param>
[SecuritySafeCritical]
public UmbracoContentIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async)
: base(indexerData, indexPath, dataService, analyzer, async) { }
/// <summary>
/// Constructor to allow for creating an indexer at runtime
/// </summary>
/// <param name="indexerData"></param>
/// <param name="luceneDirectory"></param>
/// <param name="dataService"></param>
/// <param name="analyzer"></param>
/// <param name="async"></param>
[SecuritySafeCritical]
public UmbracoContentIndexer(IIndexCriteria indexerData, Lucene.Net.Store.Directory luceneDirectory, IDataService dataService, Analyzer analyzer, bool async)
: base(indexerData, luceneDirectory, dataService, analyzer, async) { }
#endregion
#region Constants & Fields
/// <summary>
/// Used to store the path of a content object
/// </summary>
public const string IndexPathFieldName = "__Path";
public const string NodeTypeAliasFieldName = "__NodeTypeAlias";
/// <summary>
/// A type that defines the type of index for each Umbraco field (non user defined fields)
/// Alot of standard umbraco fields shouldn't be tokenized or even indexed, just stored into lucene
/// for retreival after searching.
/// </summary>
internal static readonly Dictionary<string, FieldIndexTypes> IndexFieldPolicies
= new Dictionary<string, FieldIndexTypes>()
{
{ "id", FieldIndexTypes.NOT_ANALYZED},
{ "version", FieldIndexTypes.NOT_ANALYZED},
{ "parentID", FieldIndexTypes.NOT_ANALYZED},
{ "level", FieldIndexTypes.NOT_ANALYZED},
{ "writerID", FieldIndexTypes.NOT_ANALYZED},
{ "creatorID", FieldIndexTypes.NOT_ANALYZED},
{ "nodeType", FieldIndexTypes.NOT_ANALYZED},
{ "template", FieldIndexTypes.NOT_ANALYZED},
{ "sortOrder", FieldIndexTypes.NOT_ANALYZED},
{ "createDate", FieldIndexTypes.NOT_ANALYZED},
{ "updateDate", FieldIndexTypes.NOT_ANALYZED},
{ "nodeName", FieldIndexTypes.ANALYZED},
{ "urlName", FieldIndexTypes.NOT_ANALYZED},
{ "writerName", FieldIndexTypes.ANALYZED},
{ "creatorName", FieldIndexTypes.ANALYZED},
{ "nodeTypeAlias", FieldIndexTypes.ANALYZED},
{ "path", FieldIndexTypes.NOT_ANALYZED}
};
#endregion
#region Initialize
/// <summary>
/// Set up all properties for the indexer based on configuration information specified. This will ensure that
/// all of the folders required by the indexer are created and exist. This will also create an instruction
/// file declaring the computer name that is part taking in the indexing. This file will then be used to
/// determine the master indexer machine in a load balanced environment (if one exists).
/// </summary>
/// <param name="name">The friendly name of the provider.</param>
/// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
/// <exception cref="T:System.ArgumentNullException">
/// The name of the provider is null.
/// </exception>
/// <exception cref="T:System.ArgumentException">
/// The name of the provider has a length of zero.
/// </exception>
/// <exception cref="T:System.InvalidOperationException">
/// An attempt is made to call <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"/> on a provider after the provider has already been initialized.
/// </exception>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
//check if there's a flag specifying to support unpublished content,
//if not, set to false;
bool supportUnpublished;
if (config["supportUnpublished"] != null && bool.TryParse(config["supportUnpublished"], out supportUnpublished))
SupportUnpublishedContent = supportUnpublished;
else
SupportUnpublishedContent = false;
//check if there's a flag specifying to support protected content,
//if not, set to false;
bool supportProtected;
if (config["supportProtected"] != null && bool.TryParse(config["supportProtected"], out supportProtected))
SupportProtectedContent = supportProtected;
else
SupportProtectedContent = false;
base.Initialize(name, config);
}
#endregion
#region Properties
/// <summary>
/// By default this is false, if set to true then the indexer will include indexing content that is flagged as publicly protected.
/// This property is ignored if SupportUnpublishedContent is set to true.
/// </summary>
public bool SupportProtectedContent { get; protected internal set; }
protected override IEnumerable<string> SupportedTypes
{
get
{
return new string[] { IndexTypes.Content, IndexTypes.Media };
}
}
#endregion
#region Event handlers
protected override void OnIndexingError(IndexingErrorEventArgs e)
{
DataService.LogService.AddErrorLog(e.NodeId, string.Format("{0},{1}, IndexSet: {2}", e.Message, e.InnerException != null ? e.InnerException.Message : "", this.IndexSetName));
base.OnIndexingError(e);
}
//protected override void OnDocumentWriting(DocumentWritingEventArgs docArgs)
//{
// DataService.LogService.AddVerboseLog(docArgs.NodeId, string.Format("({0}) DocumentWriting event for node ({1})", this.Name, LuceneIndexFolder.FullName));
// base.OnDocumentWriting(docArgs);
//}
protected override void OnNodeIndexed(IndexedNodeEventArgs e)
{
DataService.LogService.AddVerboseLog(e.NodeId, string.Format("Index created for node"));
base.OnNodeIndexed(e);
}
protected override void OnIndexDeleted(DeleteIndexEventArgs e)
{
DataService.LogService.AddVerboseLog(-1, string.Format("Index deleted for term: {0} with value {1}", e.DeletedTerm.Key, e.DeletedTerm.Value));
base.OnIndexDeleted(e);
}
protected override void OnIndexOptimizing(EventArgs e)
{
DataService.LogService.AddInfoLog(-1, string.Format("Index is being optimized"));
base.OnIndexOptimizing(e);
}
#endregion
#region Public methods
/// <summary>
/// Overridden for logging
/// </summary>
/// <param name="node"></param>
/// <param name="type"></param>
public override void ReIndexNode(XElement node, string type)
{
if (!SupportedTypes.Contains(type))
return;
DataService.LogService.AddVerboseLog((int)node.Attribute("id"), string.Format("ReIndexNode with type: {0}", type));
base.ReIndexNode(node, type);
}
/// <summary>
/// Deletes a node from the index.
/// </summary>
/// <remarks>
/// When a content node is deleted, we also need to delete it's children from the index so we need to perform a
/// custom Lucene search to find all decendents and create Delete item queues for them too.
/// </remarks>
/// <param name="nodeId">ID of the node to delete</param>
public override void DeleteFromIndex(string nodeId)
{
//find all descendants based on path
var descendantPath = string.Format(@"\-1\,*{0}\,*", nodeId);
var rawQuery = string.Format("{0}:{1}", IndexPathFieldName, descendantPath);
var c = InternalSearcher.CreateSearchCriteria();
var filtered = c.RawQuery(rawQuery);
var results = InternalSearcher.Search(filtered);
DataService.LogService.AddVerboseLog(int.Parse(nodeId), string.Format("DeleteFromIndex with query: {0} (found {1} results)", rawQuery, results.Count()));
//need to create a delete queue item for each one found
foreach (var r in results)
{
EnqueueIndexOperation(new IndexOperation()
{
Operation = IndexOperationType.Delete,
Item = new IndexItem(null, "", r.Id.ToString())
});
//SaveDeleteIndexQueueItem(new KeyValuePair<string, string>(IndexNodeIdFieldName, r.Id.ToString()));
}
base.DeleteFromIndex(nodeId);
}
#endregion
#region Protected
/// <summary>
/// Overridden for logging.
/// </summary>
/// <param name="node"></param>
/// <param name="type"></param>
protected override void AddSingleNodeToIndex(XElement node, string type)
{
DataService.LogService.AddVerboseLog((int)node.Attribute("id"), string.Format("AddSingleNodeToIndex with type: {0}", type));
base.AddSingleNodeToIndex(node, type);
}
public override void RebuildIndex()
{
DataService.LogService.AddVerboseLog(-1, "Rebuilding index");
base.RebuildIndex();
}
/// <summary>
/// Override this method to strip all html from all user fields before raising the event, then after the event
/// ensure our special Path field is added to the collection
/// </summary>
/// <param name="e"></param>
protected override void OnGatheringNodeData(IndexingNodeDataEventArgs e)
{
//strip html of all users fields
// Get all user data that we want to index and store into a dictionary
foreach (var field in IndexerData.UserFields)
{
if (e.Fields.ContainsKey(field.Name))
{
e.Fields[field.Name] = DataService.ContentService.StripHtml(e.Fields[field.Name]);
}
}
base.OnGatheringNodeData(e);
//ensure the special path and node type alis fields is added to the dictionary to be saved to file
var path = e.Node.Attribute("path").Value;
if (!e.Fields.ContainsKey(IndexPathFieldName))
e.Fields.Add(IndexPathFieldName, path);
//this needs to support both schemas so get the nodeTypeAlias if it exists, otherwise the name
var nodeTypeAlias = e.Node.Attribute("nodeTypeAlias") == null ? e.Node.Name.LocalName : e.Node.Attribute("nodeTypeAlias").Value;
if (!e.Fields.ContainsKey(NodeTypeAliasFieldName))
e.Fields.Add(NodeTypeAliasFieldName, nodeTypeAlias);
}
/// <summary>
/// Called when a duplicate field is detected in the dictionary that is getting indexed.
/// </summary>
/// <param name="nodeId"></param>
/// <param name="indexSetName"></param>
/// <param name="fieldName"></param>
protected override void OnDuplicateFieldWarning(int nodeId, string indexSetName, string fieldName)
{
base.OnDuplicateFieldWarning(nodeId, indexSetName, fieldName);
this.DataService.LogService.AddInfoLog(nodeId, "Field \"" + fieldName + "\" is listed multiple times in the index set \"" + indexSetName + "\". Please ensure all names are unique");
}
/// <summary>
/// Overridden to add the path property to the special fields to index
/// </summary>
/// <param name="allValuesForIndexing"></param>
/// <returns></returns>
protected override Dictionary<string, string> GetSpecialFieldsToIndex(Dictionary<string, string> allValuesForIndexing)
{
var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing);
//adds the special path property to the index
fields.Add(IndexPathFieldName, allValuesForIndexing[IndexPathFieldName]);
//adds the special node type alias property to the index
fields.Add(NodeTypeAliasFieldName, allValuesForIndexing[NodeTypeAliasFieldName]);
return fields;
}
/// <summary>
/// Creates an IIndexCriteria object based on the indexSet passed in and our DataService
/// </summary>
/// <param name="indexSet"></param>
/// <returns></returns>
protected override IIndexCriteria GetIndexerData(IndexSet indexSet)
{
return indexSet.ToIndexCriteria(DataService);
}
/// <summary>
/// return the index policy for the field name passed in, if not found, return normal
/// </summary>
/// <param name="fieldName"></param>
/// <returns></returns>
protected override FieldIndexTypes GetPolicy(string fieldName)
{
var def = IndexFieldPolicies.Where(x => x.Key == fieldName);
return (def.Count() == 0 ? FieldIndexTypes.ANALYZED : def.Single().Value);
}
/// <summary>
/// Ensure that the content of this node is available for indexing (i.e. don't allow protected
/// content to be indexed when this is disabled).
/// <returns></returns>
/// </summary>
protected override bool ValidateDocument(XElement node)
{
var nodeId = int.Parse(node.Attribute("id").Value);
// Test for access if we're only indexing published content
// return nothing if we're not supporting protected content and it is protected, and we're not supporting unpublished content
if (!SupportUnpublishedContent
&& (!SupportProtectedContent
&& DataService.ContentService.IsProtected(nodeId, node.Attribute("path").Value)))
{
return false;
}
return base.ValidateDocument(node);
}
#endregion
}
}

View File

@@ -0,0 +1,223 @@
using System;
using System.Linq;
using System.Net;
using System.Security;
using System.Threading;
using System.Web;
using Examine.Config;
using umbraco;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.media;
using umbraco.cms.businesslogic.web;
using Examine;
using Examine.Providers;
using umbraco.cms.businesslogic.member;
using Examine.LuceneEngine;
using Examine.LuceneEngine.Providers;
using Lucene.Net.Index;
using Lucene.Net.Store;
namespace UmbracoExamine
{
/// <summary>
/// An <see cref="umbraco.BusinessLogic.ApplicationBase"/> instance for wiring up Examine to the Umbraco events system
/// </summary>
public class UmbracoEventManager : ApplicationBase
{
/// <summary>
/// Creates a new instance of the class
/// </summary>
[SecuritySafeCritical]
public UmbracoEventManager()
{
var registeredProviders = ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => x.EnableDefaultEventHandler)
.Count();
Log.Add(LogTypes.Custom, -1, "[UmbracoExamine] Adding examine event handlers for index providers: " + registeredProviders.ToString());
//don't bind event handlers if we're not suppose to listen
if (registeredProviders == 0)
return;
Media.AfterSave += Media_AfterSave;
Media.AfterDelete += Media_AfterDelete;
CMSNode.AfterMove += Media_AfterMove;
//These should only fire for providers that DONT have SupportUnpublishedContent set to true
content.AfterUpdateDocumentCache += content_AfterUpdateDocumentCache;
content.AfterClearDocumentCache += content_AfterClearDocumentCache;
//These should only fire for providers that have SupportUnpublishedContent set to true
Document.AfterSave += Document_AfterSave;
Document.AfterDelete += Document_AfterDelete;
Member.AfterSave += Member_AfterSave;
Member.AfterDelete += Member_AfterDelete;
}
//This does work, however we need to lock down the httphandler and thats an issue... so i've removed this
//if people don't want to rebuild on app startup then they can disable it and reindex manually.
[SecuritySafeCritical]
private void Member_AfterSave(Member sender, SaveEventArgs e)
{
//ensure that only the providers are flagged to listen execute
var xml = sender.ToXml(new System.Xml.XmlDocument(), false).ToXElement();
var providers = ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => x.EnableDefaultEventHandler);
ExamineManager.Instance.ReIndexNode(xml, IndexTypes.Member, providers);
}
[SecuritySafeCritical]
private void Member_AfterDelete(Member sender, DeleteEventArgs e)
{
var nodeId = sender.Id.ToString();
//ensure that only the providers are flagged to listen execute
ExamineManager.Instance.DeleteFromIndex(nodeId,
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => x.EnableDefaultEventHandler));
}
/// <summary>
/// Only index using providers that SupportUnpublishedContent
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
[SecuritySafeCritical]
private void Document_AfterSave(Document sender, SaveEventArgs e)
{
//ensure that only the providers that have unpublishing support enabled
//that are also flagged to listen
//there's a bug in 4.0.x that fires the Document saving event handler for media when media is moved,
//therefore, we need to try to figure out if this is media or content. Currently one way to do this
//is by checking the creator ID property as it will be null if it is media. We then need to try to
//create the media object, see if it exists, and pass it to the media save event handler... yeah i know,
//pretty horrible but has to be done.
try
{
var creator = sender.Creator;
if (creator != null)
{
//it's def a Document
ExamineManager.Instance.ReIndexNode(sender.ToXDocument(false).Root, IndexTypes.Content,
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => x.SupportUnpublishedContent
&& x.EnableDefaultEventHandler));
return; //exit, we've indexed the content
}
}
catch (Exception)
{
//if we get this exception, it means it's most likely media, so well do our check next.
}
//this is most likely media, not sure what kind of exception might get thrown in 4.0.x or 4.1 if accessing a null
//creator, so we catch everything.
var m = new Media(sender.Id);
if (!string.IsNullOrEmpty(m.Path))
{
//this is a media item, no exception occurred on access to path and it's not empty which means it was found
Media_AfterSave(m, e);
return;
}
}
/// <summary>
/// Only remove indexes using providers that SupportUnpublishedContent
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
[SecuritySafeCritical]
private void Document_AfterDelete(Document sender, DeleteEventArgs e)
{
var nodeId = sender.Id.ToString();
//ensure that only the providers that have unpublishing support enabled
//that are also flagged to listen
ExamineManager.Instance.DeleteFromIndex(nodeId,
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => x.SupportUnpublishedContent
&& x.EnableDefaultEventHandler));
}
[SecuritySafeCritical]
private void Media_AfterDelete(Media sender, DeleteEventArgs e)
{
var nodeId = sender.Id.ToString();
//ensure that only the providers are flagged to listen execute
ExamineManager.Instance.DeleteFromIndex(nodeId,
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => x.EnableDefaultEventHandler));
}
private void Media_AfterSave(Media sender, umbraco.cms.businesslogic.SaveEventArgs e)
{
//ensure that only the providers are flagged to listen execute
IndexMedia(sender);
}
private void IndexMedia(Media sender)
{
ExamineManager.Instance.ReIndexNode(sender.ToXDocument(true).Root, IndexTypes.Media,
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => x.EnableDefaultEventHandler));
}
/// <summary>
/// When media is moved, re-index
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Media_AfterMove(object sender, MoveEventArgs e)
{
if (sender is Media)
{
Media m = (Media)sender;
IndexMedia(m);
}
}
/// <summary>
/// Only Update indexes for providers that dont SupportUnpublishedContent
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void content_AfterUpdateDocumentCache(Document sender, umbraco.cms.businesslogic.DocumentCacheEventArgs e)
{
//ensure that only the providers that have DONT unpublishing support enabled
//that are also flagged to listen
ExamineManager.Instance.ReIndexNode(sender.ToXDocument(true).Root, IndexTypes.Content,
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => !x.SupportUnpublishedContent
&& x.EnableDefaultEventHandler));
}
/// <summary>
/// Only update indexes for providers that don't SupportUnpublishedContnet
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
[SecuritySafeCritical]
private void content_AfterClearDocumentCache(Document sender, DocumentCacheEventArgs e)
{
var nodeId = sender.Id.ToString();
//ensure that only the providers that DONT have unpublishing support enabled
//that are also flagged to listen
ExamineManager.Instance.DeleteFromIndex(nodeId,
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
.Where(x => !x.SupportUnpublishedContent
&& x.EnableDefaultEventHandler));
}
}
}

View File

@@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{07FBC26B-2927-4A22-8D96-D644C667FECC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>UmbracoExamine</RootNamespace>
<AssemblyName>UmbracoExamine</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SccProjectName>
</SccProjectName>
<SccLocalPath>
</SccLocalPath>
<SccAuxPath>
</SccAuxPath>
<SccProvider>
</SccProvider>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>SecurityRules.ruleset</CodeAnalysisRuleSet>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<DocumentationFile>
</DocumentationFile>
<RunCodeAnalysis>false</RunCodeAnalysis>
<CodeAnalysisIgnoreGeneratedCode>false</CodeAnalysisIgnoreGeneratedCode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\Release\UmbracoExamine.XML</DocumentationFile>
<CodeAnalysisIgnoreGeneratedCode>false</CodeAnalysisIgnoreGeneratedCode>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<DelaySign>true</DelaySign>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\Solution Items\TheFARM-Public.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Examine">
<HintPath>..\packages\Examine.0.1.42.2941\lib\Examine.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="Lucene.Net, Version=2.9.4.1, Culture=neutral, PublicKeyToken=85089178b9ac3181, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BaseUmbracoIndexer.cs" />
<Compile Include="Config\IndexSetExtensions.cs" />
<Compile Include="DataServices\IContentService.cs" />
<Compile Include="DataServices\IDataService.cs" />
<Compile Include="DataServices\ILogService.cs" />
<Compile Include="DataServices\IMediaService.cs" />
<Compile Include="DataServices\UmbracoDataService.cs" />
<Compile Include="DataServices\UmbracoContentService.cs" />
<Compile Include="DataServices\UmbracoMediaService.cs" />
<Compile Include="IndexTypes.cs" />
<Compile Include="LoggingLevel.cs" />
<Compile Include="UmbracoMemberIndexer.cs" />
<Compile Include="ContentExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UmbracoContentIndexer.cs" />
<Compile Include="UmbracoExamineSearcher.cs" />
<Compile Include="DataServices\UmbracoLogService.cs" />
<Compile Include="UmbracoEventManager.cs" />
<Compile Include="XsltExtensions.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
<Visible>False</Visible>
<ProductName>Windows Installer 3.1</ProductName>
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\umbraco.businesslogic\umbraco.businesslogic.csproj">
<Project>{e469a9ce-1bec-423f-ac44-713cd72457ea}</Project>
<Name>umbraco.businesslogic</Name>
</ProjectReference>
<ProjectReference Include="..\umbraco.cms\umbraco.cms.csproj">
<Project>{ccd75ec3-63db-4184-b49d-51c1dd337230}</Project>
<Name>umbraco.cms</Name>
</ProjectReference>
<ProjectReference Include="..\umbraco.datalayer\umbraco.datalayer.csproj">
<Project>{c7cb79f0-1c97-4b33-bfa7-00731b579ae2}</Project>
<Name>umbraco.datalayer</Name>
</ProjectReference>
<ProjectReference Include="..\umbraco.interfaces\umbraco.interfaces.csproj">
<Project>{511f6d8d-7717-440a-9a57-a507e9a8b27f}</Project>
<Name>umbraco.interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
<Name>Umbraco.Web</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,83 @@
using System;
using System.IO;
using System.Linq;
using System.Security;
using Examine;
using Examine.Providers;
using Examine.SearchCriteria;
using UmbracoExamine.Config;
using Examine.LuceneEngine;
using Examine.LuceneEngine.Providers;
using Examine.LuceneEngine.SearchCriteria;
using Lucene.Net.Analysis;
namespace UmbracoExamine
{
/// <summary>
/// An Examine searcher which uses Lucene.Net as the
/// </summary>
public class UmbracoExamineSearcher : LuceneSearcher
{
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public UmbracoExamineSearcher()
: base()
{
}
/// <summary>
/// Constructor to allow for creating an indexer at runtime
/// </summary>
/// <param name="indexPath"></param>
/// <param name="analyzer"></param>
[SecuritySafeCritical]
public UmbracoExamineSearcher(DirectoryInfo indexPath, Analyzer analyzer)
: base(indexPath, analyzer)
{
}
/// <summary>
/// Constructor to allow for creating an indexer at runtime
/// </summary>
/// <param name="luceneDirectory"></param>
/// <param name="analyzer"></param>
[SecuritySafeCritical]
public UmbracoExamineSearcher(Lucene.Net.Store.Directory luceneDirectory, Analyzer analyzer)
: base(luceneDirectory, analyzer)
{
}
#endregion
/// <summary>
/// Override in order to set the nodeTypeAlias field name of the underlying SearchCriteria to __NodeTypeAlias
/// </summary>
/// <param name="type"></param>
/// <param name="defaultOperation"></param>
/// <returns></returns>
public override ISearchCriteria CreateSearchCriteria(string type, BooleanOperation defaultOperation)
{
var criteria = base.CreateSearchCriteria(type, defaultOperation) as LuceneSearchCriteria;
criteria.NodeTypeAliasField = UmbracoContentIndexer.NodeTypeAliasFieldName;
return criteria;
}
/// <summary>
/// Returns a list of fields to search on, this will also exclude the IndexPathFieldName and node type alias
/// </summary>
/// <returns></returns>
protected internal override string[] GetSearchFields()
{
var fields = base.GetSearchFields();
return fields
.Where(x => x != UmbracoContentIndexer.IndexPathFieldName)
.Where(x => x != UmbracoContentIndexer.NodeTypeAliasFieldName)
.ToArray();
}
}
}

View File

@@ -0,0 +1,87 @@
using System.Collections;
using System.Linq;
using System.Security;
using System.Xml.Linq;
using System.Xml.XPath;
using umbraco.cms.businesslogic.member;
using Examine.LuceneEngine;
using System.Collections.Generic;
using Examine;
using System.IO;
using UmbracoExamine.DataServices;
using Lucene.Net.Analysis;
namespace UmbracoExamine
{
/// <summary>
///
/// </summary>
public class UmbracoMemberIndexer : UmbracoContentIndexer
{
/// <summary>
/// Default constructor
/// </summary>
public UmbracoMemberIndexer()
: base() { }
/// <summary>
/// Constructor to allow for creating an indexer at runtime
/// </summary>
/// <param name="indexerData"></param>
/// <param name="indexPath"></param>
/// <param name="dataService"></param>
/// <param name="analyzer"></param>
[SecuritySafeCritical]
public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async)
: base(indexerData, indexPath, dataService, analyzer, async) { }
/// <summary>
/// The supported types for this indexer
/// </summary>
protected override IEnumerable<string> SupportedTypes
{
get
{
return new string[] { IndexTypes.Member };
}
}
[SecuritySafeCritical]
protected override XDocument GetXDocument(string xPath, string type)
{
if (type == IndexTypes.Member)
{
Member[] rootMembers = Member.GetAll;
var xmlMember = XDocument.Parse("<member></member>");
foreach (Member member in rootMembers)
{
xmlMember.Root.Add(GetMemberItem(member.Id));
}
var result = ((IEnumerable)xmlMember.XPathEvaluate(xPath)).Cast<XElement>();
return result.ToXDocument();
}
return null;
}
protected override System.Collections.Generic.Dictionary<string, string> GetDataToIndex(XElement node, string type)
{
var data = base.GetDataToIndex(node, type);
if (data.ContainsKey("email"))
{
data.Add("__email",data["email"].Replace("."," ").Replace("@"," "));
}
return data;
}
[SecuritySafeCritical]
private XElement GetMemberItem(int nodeId)
{
var nodes = umbraco.library.GetMember(nodeId);
return XElement.Parse(nodes.Current.OuterXml);
}
}
}

View File

@@ -0,0 +1,265 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using Examine;
using Examine.LuceneEngine.Providers;
using Examine.LuceneEngine.SearchCriteria;
using Examine.SearchCriteria;
using Examine.Providers;
using UmbracoExamine.DataServices;
namespace UmbracoExamine
{
/// <summary>
/// Methods to support Umbraco XSLT extensions.
/// </summary>
/// <remarks>
/// XSLT extensions will ONLY work for provider that have a base class of BaseUmbracoIndexer
/// </remarks>
public class XsltExtensions
{
///<summary>
/// Uses the provider specified to search, returning an XPathNodeIterator
///</summary>
///<param name="searchText"></param>
///<param name="useWildcards"></param>
///<param name="provider"></param>
///<param name="indexType"></param>
///<returns></returns>
internal static XPathNodeIterator Search(string searchText, bool useWildcards, LuceneSearcher provider, string indexType)
{
if (provider == null) throw new ArgumentNullException("provider");
var results = provider.Search(searchText, useWildcards, indexType);
return GetResultsAsXml(results);
}
/// <summary>
/// Uses the provider specified to search, returning an XPathNodeIterator
/// </summary>
/// <param name="searchText">The search text.</param>
/// <param name="useWildcards">if set to <c>true</c> [use wildcards].</param>
/// <param name="providerName">Name of the provider.</param>
/// <param name="indexType">Type of the index.</param>
/// <returns></returns>
public static XPathNodeIterator Search(string searchText, bool useWildcards, string providerName, string indexType)
{
EnsureProvider(ExamineManager.Instance.SearchProviderCollection[providerName]);
var provider = ExamineManager.Instance.SearchProviderCollection[providerName] as LuceneSearcher;
return Search(searchText, useWildcards, provider, indexType);
}
/// <summary>
/// Uses the provider specified to search, returning an XPathNodeIterator
/// </summary>
/// <param name="searchText"></param>
/// <param name="useWildcards"></param>
/// <param name="providerName"></param>
/// <returns></returns>
public static XPathNodeIterator Search(string searchText, bool useWildcards, string providerName)
{
return Search(searchText, useWildcards, providerName, string.Empty);
}
/// <summary>
/// Uses the default provider specified to search, returning an XPathNodeIterator
/// </summary>
/// <param name="searchText">The search query</param>
/// <param name="useWildcards">Enable a wildcard search query</param>
/// <returns>A node-set of the search results</returns>
public static XPathNodeIterator Search(string searchText, bool useWildcards)
{
return Search(searchText, useWildcards, ExamineManager.Instance.DefaultSearchProvider.Name);
}
/// <summary>
/// Uses the default provider specified to search, returning an XPathNodeIterator
/// </summary>
/// <param name="searchText">The search query</param>
/// <returns>A node-set of the search results</returns>
public static XPathNodeIterator Search(string searchText)
{
return Search(searchText, true);
}
/// <summary>
/// Will perform a search against the media index type only
/// </summary>
/// <param name="searchText"></param>
/// <param name="useWildcards"></param>
/// <param name="providerName"></param>
/// <returns></returns>
public static XPathNodeIterator SearchMediaOnly(string searchText, bool useWildcards, string providerName)
{
return Search(searchText, useWildcards, providerName, IndexTypes.Media);
}
/// <summary>
/// Will perform a search against the media index type only
/// </summary>
/// <param name="searchText"></param>
/// <param name="useWildcards"></param>
/// <returns></returns>
public static XPathNodeIterator SearchMediaOnly(string searchText, bool useWildcards)
{
return SearchMediaOnly(searchText, useWildcards, ExamineManager.Instance.DefaultSearchProvider.Name);
}
/// <summary>
/// Will perform a search against the media index type only
/// </summary>
/// <param name="searchText"></param>
/// <returns></returns>
public static XPathNodeIterator SearchMediaOnly(string searchText)
{
return SearchMediaOnly(searchText, true);
}
/// <summary>
/// Searches the member only.
/// </summary>
/// <param name="searchText">The search text.</param>
/// <param name="useWildcards">if set to <c>true</c> [use wildcards].</param>
/// <param name="providerName">Name of the provider.</param>
/// <returns></returns>
public static XPathNodeIterator SearchMemberOnly(string searchText, bool useWildcards, string providerName)
{
return Search(searchText, useWildcards, providerName, IndexTypes.Member);
}
/// <summary>
/// Searches the member only.
/// </summary>
/// <param name="searchText">The search text.</param>
/// <param name="useWildcards">if set to <c>true</c> [use wildcards].</param>
/// <returns></returns>
public static XPathNodeIterator SearchMemberOnly(string searchText, bool useWildcards)
{
return SearchMemberOnly(searchText, useWildcards, ExamineManager.Instance.DefaultSearchProvider.Name);
}
/// <summary>
/// Searches the member only.
/// </summary>
/// <param name="searchText">The search text.</param>
/// <returns></returns>
public static XPathNodeIterator SearchMemberOnly(string searchText)
{
return SearchMemberOnly(searchText, true);
}
/// <summary>
/// Will perform a search against the content index type only
/// </summary>
/// <param name="searchText"></param>
/// <param name="useWildcards"></param>
/// <param name="providerName"></param>
/// <returns></returns>
public static XPathNodeIterator SearchContentOnly(string searchText, bool useWildcards, string providerName)
{
return Search(searchText, useWildcards, providerName, IndexTypes.Content);
}
/// <summary>
/// Will perform a search against the content index type only
/// </summary>
/// <param name="searchText"></param>
/// <param name="useWildcards"></param>
/// <returns></returns>
public static XPathNodeIterator SearchContentOnly(string searchText, bool useWildcards)
{
return SearchContentOnly(searchText, useWildcards, ExamineManager.Instance.DefaultSearchProvider.Name);
}
/// <summary>
/// Will perform a search against the content index type only
/// </summary>
/// <param name="searchText"></param>
/// <returns></returns>
public static XPathNodeIterator SearchContentOnly(string searchText)
{
return SearchContentOnly(searchText, true);
}
/// <summary>
/// Ensures the provider.
/// </summary>
/// <param name="p">The provider.</param>
private static void EnsureProvider(BaseSearchProvider p)
{
if (!(p is LuceneSearcher))
{
throw new NotSupportedException("XSLT Extensions are only support for providers of type LuceneSearcher");
}
}
/// <summary>
/// Gets the results as XML.
/// </summary>
/// <param name="results">The results.</param>
/// <returns></returns>
private static XPathNodeIterator GetResultsAsXml(ISearchResults results)
{
// create the XDocument
XDocument doc = new XDocument();
// check there are any search results
if (results.TotalItemCount > 0)
{
// create the root element
XElement root = new XElement("nodes");
// iterate through the search results
foreach (SearchResult result in results)
{
// create a new <node> element
XElement node = new XElement("node");
// create the @id attribute
XAttribute nodeId = new XAttribute("id", result.Id);
// create the @score attribute
XAttribute nodeScore = new XAttribute("score", result.Score);
// add the content
node.Add(nodeId, nodeScore);
foreach (KeyValuePair<String, String> field in result.Fields)
{
// create a new <data> element
XElement data = new XElement("data");
// create the @alias attribute
XAttribute alias = new XAttribute("alias", field.Key);
// assign the value to a CDATA section
XCData value = new XCData(field.Value);
// append the content
data.Add(alias, value);
// append the <data> element
node.Add(data);
}
// add the node
root.Add(node);
}
// add the root node
doc.Add(root);
}
else
{
doc.Add(new XElement("error", "There were no search results."));
}
return doc.CreateNavigator().Select("/");
}
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net40" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
</packages>

View File

@@ -12,4 +12,5 @@
<repository path="..\Umbraco.Tests\packages.config" />
<repository path="..\Umbraco.Web.UI\packages.config" />
<repository path="..\Umbraco.Web\packages.config" />
<repository path="..\UmbracoExamine\packages.config" />
</repositories>

View File

@@ -59,6 +59,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{66AB04
.nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UmbracoExamine", "UmbracoExamine\UmbracoExamine.csproj", "{07FBC26B-2927-4A22-8D96-D644C667FECC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UmbracoExamineLibs", "UmbracoExamineLibs", "{DD32977B-EF54-475B-9A1B-B97A502C6E58}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -125,11 +129,16 @@ Global
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Release|Any CPU.Build.0 = Release|Any CPU
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5D3B8245-ADA6-453F-A008-50ED04BFE770} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
{07FBC26B-2927-4A22-8D96-D644C667FECC} = {DD32977B-EF54-475B-9A1B-B97A502C6E58}
EndGlobalSection
EndGlobal