Files
Umbraco-CMS/src/Umbraco.Examine/ExamineExtensions.cs
2019-08-14 10:56:08 +10:00

210 lines
7.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Examine;
using Examine.LuceneEngine.Providers;
using Lucene.Net.Analysis;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Umbraco.Core;
using Version = Lucene.Net.Util.Version;
using Umbraco.Core.Logging;
using System.Threading;
namespace Umbraco.Examine
{
/// <summary>
/// Extension methods for the LuceneIndex
/// </summary>
public static class ExamineExtensions
{
/// <summary>
/// Matches a culture iso name suffix
/// </summary>
/// <remarks>
/// myFieldName_en-us will match the "en-us"
/// </remarks>
internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled);
private static bool _isConfigured = false;
private static object _configuredInit = null;
private static object _isConfiguredLocker = new object();
/// <summary>
/// Called on startup to configure each index.
/// </summary>
/// <remarks>
/// Configures and unlocks all Lucene based indexes registered with the <see cref="IExamineManager"/>.
/// </remarks>
internal static void ConfigureIndexes(this IExamineManager examineManager, IMainDom mainDom, ILogger logger)
{
LazyInitializer.EnsureInitialized(
ref _configuredInit,
ref _isConfigured,
ref _isConfiguredLocker,
() =>
{
examineManager.ConfigureLuceneIndexes(logger, !mainDom.IsMainDom);
return null;
});
}
//TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression
/// <summary>
/// Returns all index fields that are culture specific (suffixed)
/// </summary>
/// <param name="index"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static IEnumerable<string> GetCultureFields(this IUmbracoIndex index, string culture)
{
var allFields = index.GetFields();
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var field in allFields)
{
var match = CultureIsoCodeFieldNameMatchExpression.Match(field);
if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value))
yield return field;
}
}
/// <summary>
/// Returns all index fields that are culture specific (suffixed) or invariant
/// </summary>
/// <param name="index"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static IEnumerable<string> GetCultureAndInvariantFields(this IUmbracoIndex index, string culture)
{
var allFields = index.GetFields();
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var field in allFields)
{
var match = CultureIsoCodeFieldNameMatchExpression.Match(field);
if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value))
{
yield return field; //matches this culture field
}
else if (!match.Success)
{
yield return field; //matches no culture field (invariant)
}
}
}
internal static bool TryParseLuceneQuery(string query)
{
// TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll
// also do this rudimentary check
if (!query.Contains(":"))
return false;
try
{
//This will pass with a plain old string without any fields, need to figure out a way to have it properly parse
var parsed = new QueryParser(Version.LUCENE_30, "nodeName", new KeywordAnalyzer()).Parse(query);
return true;
}
catch (ParseException)
{
return false;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Forcibly unlocks all lucene based indexes
/// </summary>
/// <remarks>
/// This is not thread safe, use with care
/// </remarks>
internal static void ConfigureLuceneIndexes(this IExamineManager examineManager, ILogger logger, bool disableExamineIndexing)
{
foreach (var luceneIndexer in examineManager.Indexes.OfType<LuceneIndex>())
{
//We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending
//indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because
//that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems.
luceneIndexer.WaitForIndexQueueOnShutdown = false;
if (disableExamineIndexing) continue; //exit if not enabled, we don't need to unlock them if we're not maindom
//we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that
//the indexes are not operational unless MainDom is true
var dir = luceneIndexer.GetLuceneDirectory();
if (IndexWriter.IsLocked(dir))
{
logger.Info(typeof(ExamineExtensions), "Forcing index {IndexerName} to be unlocked since it was left in a locked state", luceneIndexer.Name);
IndexWriter.Unlock(dir);
}
}
}
/// <summary>
/// Checks if the index can be read/opened
/// </summary>
/// <param name="indexer"></param>
/// <param name="ex">The exception returned if there was an error</param>
/// <returns></returns>
public static bool IsHealthy(this LuceneIndex indexer, out Exception ex)
{
try
{
using (indexer.GetIndexWriter().GetReader())
{
ex = null;
return true;
}
}
catch (Exception e)
{
ex = e;
return false;
}
}
/// <summary>
/// Return the number of indexed documents in Lucene
/// </summary>
/// <param name="indexer"></param>
/// <returns></returns>
public static int GetIndexDocumentCount(this LuceneIndex indexer)
{
if (!((indexer.GetSearcher() as LuceneSearcher)?.GetLuceneSearcher() is IndexSearcher searcher))
return 0;
using (searcher)
using (var reader = searcher.IndexReader)
{
return reader.NumDocs();
}
}
/// <summary>
/// Return the total number of fields in the index
/// </summary>
/// <param name="indexer"></param>
/// <returns></returns>
public static int GetIndexFieldCount(this LuceneIndex indexer)
{
if (!((indexer.GetSearcher() as LuceneSearcher)?.GetLuceneSearcher() is IndexSearcher searcher))
return 0;
using (searcher)
using (var reader = searcher.IndexReader)
{
return reader.GetFieldNames(IndexReader.FieldOption.ALL).Count;
}
}
}
}