2018-05-31 22:03:12 +01:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using Examine ;
2020-05-19 14:02:12 +02:00
using Microsoft.AspNetCore.Http.Features ;
using Microsoft.AspNetCore.Mvc ;
2018-05-31 22:03:12 +01:00
using Umbraco.Core ;
using Umbraco.Core.Cache ;
2019-11-19 08:52:39 +01:00
using Umbraco.Core.IO ;
2018-05-31 22:03:12 +01:00
using Umbraco.Core.Logging ;
2018-11-28 21:02:45 +11:00
using Umbraco.Examine ;
2020-05-19 14:02:12 +02:00
using Umbraco.Extensions ;
using Umbraco.Web.Common.Attributes ;
using Umbraco.Web.Common.Exceptions ;
2018-12-04 14:25:37 +11:00
using Umbraco.Web.Models.ContentEditing ;
2018-05-31 22:03:12 +01:00
using Umbraco.Web.Search ;
2018-12-04 14:25:37 +11:00
using SearchResult = Umbraco . Web . Models . ContentEditing . SearchResult ;
2018-05-31 22:03:12 +01:00
2020-06-03 17:17:30 +02:00
namespace Umbraco.Web.BackOffice.Controllers
2018-05-31 22:03:12 +01:00
{
2020-06-09 13:01:05 +10:00
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
2018-05-31 22:03:12 +01:00
public class ExamineManagementController : UmbracoAuthorizedJsonController
{
private readonly IExamineManager _examineManager ;
private readonly ILogger _logger ;
2019-11-19 08:52:39 +01:00
private readonly IIOHelper _ioHelper ;
2020-01-29 12:24:57 +11:00
private readonly IIndexDiagnosticsFactory _indexDiagnosticsFactory ;
2019-01-18 07:56:38 +01:00
private readonly IAppPolicyCache _runtimeCache ;
2018-12-03 23:15:18 +11:00
private readonly IndexRebuilder _indexRebuilder ;
2018-05-31 22:03:12 +01:00
2020-01-29 12:24:57 +11:00
public ExamineManagementController ( IExamineManager examineManager , ILogger logger , IIOHelper ioHelper , IIndexDiagnosticsFactory indexDiagnosticsFactory ,
2019-01-17 11:19:06 +01:00
AppCaches appCaches ,
2018-12-03 23:15:18 +11:00
IndexRebuilder indexRebuilder )
2018-05-31 22:03:12 +01:00
{
_examineManager = examineManager ;
_logger = logger ;
2019-11-19 08:52:39 +01:00
_ioHelper = ioHelper ;
2020-01-29 12:24:57 +11:00
_indexDiagnosticsFactory = indexDiagnosticsFactory ;
2019-01-17 11:19:06 +01:00
_runtimeCache = appCaches . RuntimeCache ;
2018-12-03 23:15:18 +11:00
_indexRebuilder = indexRebuilder ;
2018-05-31 22:03:12 +01:00
}
/// <summary>
/// Get the details for indexers
/// </summary>
/// <returns></returns>
2018-11-28 21:02:45 +11:00
public IEnumerable < ExamineIndexModel > GetIndexerDetails ( )
2018-05-31 22:03:12 +01:00
{
2018-12-03 22:10:56 +11:00
return _examineManager . Indexes . Select ( CreateModel ) . OrderBy ( x = > x . Name . TrimEnd ( "Indexer" ) ) ;
2018-05-31 22:03:12 +01:00
}
/// <summary>
/// Get the details for searchers
/// </summary>
/// <returns></returns>
public IEnumerable < ExamineSearcherModel > GetSearcherDetails ( )
{
var model = new List < ExamineSearcherModel > (
2018-12-04 14:25:37 +11:00
_examineManager . RegisteredSearchers . Select ( searcher = > new ExamineSearcherModel { Name = searcher . Name } )
2018-12-03 22:10:56 +11:00
. OrderBy ( x = > x . Name . TrimEnd ( "Searcher" ) ) ) ; //order by name , but strip the "Searcher" from the end if it exists
2018-05-31 22:03:12 +01:00
return model ;
}
2018-12-04 14:25:37 +11:00
public SearchResults GetSearchResults ( string searcherName , string query , int pageIndex = 0 , int pageSize = 20 )
2018-05-31 22:03:12 +01:00
{
if ( query . IsNullOrWhiteSpace ( ) )
2018-12-04 14:25:37 +11:00
return SearchResults . Empty ( ) ;
var msg = ValidateSearcher ( searcherName , out var searcher ) ;
2020-05-19 14:02:12 +02:00
if ( ! msg . IsSuccessStatusCode ( ) )
2018-12-04 14:25:37 +11:00
throw new HttpResponseException ( msg ) ;
2020-05-19 14:02:12 +02:00
// NativeQuery will work for a single word/phrase too (but depends on the implementation) the lucene one will work.
2020-01-29 12:24:57 +11:00
var results = searcher . CreateQuery ( ) . NativeQuery ( query ) . Execute ( maxResults : pageSize * ( pageIndex + 1 ) ) ;
2018-12-04 14:25:37 +11:00
var pagedResults = results . Skip ( pageIndex * pageSize ) ;
2018-05-31 22:03:12 +01:00
2018-12-04 14:25:37 +11:00
return new SearchResults
2018-05-31 22:03:12 +01:00
{
2018-12-04 14:25:37 +11:00
TotalRecords = results . TotalItemCount ,
Results = pagedResults . Select ( x = > new SearchResult
2018-05-31 22:03:12 +01:00
{
2018-12-04 14:25:37 +11:00
Id = x . Id ,
Score = x . Score ,
2018-12-05 16:53:25 +11:00
//order the values by key
Values = new Dictionary < string , string > ( x . Values . OrderBy ( y = > y . Key ) . ToDictionary ( y = > y . Key , y = > y . Value ) )
2018-12-04 14:25:37 +11:00
} )
} ;
}
2018-05-31 22:03:12 +01:00
2019-07-08 08:48:49 +01:00
2018-05-31 22:03:12 +01:00
/// <summary>
/// Check if the index has been rebuilt
/// </summary>
2018-11-30 12:22:23 +11:00
/// <param name="indexName"></param>
2018-05-31 22:03:12 +01:00
/// <returns></returns>
/// <remarks>
/// This is kind of rudimentary since there's no way we can know that the index has rebuilt, we
/// have a listener for the index op complete so we'll just check if that key is no longer there in the runtime cache
/// </remarks>
2020-05-19 14:02:12 +02:00
public ActionResult < ExamineIndexModel > PostCheckRebuildIndex ( string indexName )
2018-05-31 22:03:12 +01:00
{
2018-11-30 12:22:23 +11:00
var validate = ValidateIndex ( indexName , out var index ) ;
2020-05-19 14:02:12 +02:00
if ( ! validate . IsSuccessStatusCode ( ) )
2018-11-30 12:22:23 +11:00
throw new HttpResponseException ( validate ) ;
2018-05-31 22:03:12 +01:00
2018-12-13 23:17:58 +11:00
validate = ValidatePopulator ( index ) ;
2020-05-19 14:02:12 +02:00
if ( ! validate . IsSuccessStatusCode ( ) )
2018-11-30 12:22:23 +11:00
throw new HttpResponseException ( validate ) ;
var cacheKey = "temp_indexing_op_" + indexName ;
2020-05-19 14:02:12 +02:00
var found = _runtimeCache . Get ( cacheKey ) ;
2018-11-30 12:22:23 +11:00
//if its still there then it's not done
return found ! = null
? null
2018-12-03 22:10:56 +11:00
: CreateModel ( index ) ;
2018-05-31 22:03:12 +01:00
}
/// <summary>
2018-11-30 12:22:23 +11:00
/// Rebuilds the index
2018-05-31 22:03:12 +01:00
/// </summary>
2018-11-30 12:22:23 +11:00
/// <param name="indexName"></param>
2018-05-31 22:03:12 +01:00
/// <returns></returns>
2020-05-19 14:02:12 +02:00
public IActionResult PostRebuildIndex ( string indexName )
2018-05-31 22:03:12 +01:00
{
2018-11-30 12:22:23 +11:00
var validate = ValidateIndex ( indexName , out var index ) ;
2020-05-19 14:02:12 +02:00
if ( ! validate . IsSuccessStatusCode ( ) )
throw new HttpResponseException ( validate ) ;
2018-05-31 22:03:12 +01:00
2018-12-13 23:17:58 +11:00
validate = ValidatePopulator ( index ) ;
2020-05-19 14:02:12 +02:00
if ( ! validate . IsSuccessStatusCode ( ) )
throw new HttpResponseException ( validate ) ;
2018-05-31 22:03:12 +01:00
2018-11-30 12:22:23 +11:00
_logger . Info < ExamineManagementController > ( "Rebuilding index '{IndexName}'" , indexName ) ;
2018-05-31 22:03:12 +01:00
2019-01-26 10:52:19 -05:00
//remove it in case there's a handler there already
2018-11-30 12:22:23 +11:00
index . IndexOperationComplete - = Indexer_IndexOperationComplete ;
2018-05-31 22:03:12 +01:00
2018-11-30 12:22:23 +11:00
//now add a single handler
index . IndexOperationComplete + = Indexer_IndexOperationComplete ;
2018-05-31 22:03:12 +01:00
2018-11-30 12:22:23 +11:00
try
{
2018-12-07 15:06:07 +11:00
var cacheKey = "temp_indexing_op_" + index . Name ;
//put temp val in cache which is used as a rudimentary way to know when the indexing is done
2020-05-19 14:02:12 +02:00
_runtimeCache . Insert ( cacheKey , ( ) = > "tempValue" , TimeSpan . FromMinutes ( 5 ) ) ;
2018-12-07 15:06:07 +11:00
2018-12-03 23:15:18 +11:00
_indexRebuilder . RebuildIndex ( indexName ) ;
////populate it
//foreach (var populator in _populators.Where(x => x.IsRegistered(indexName)))
// populator.Populate(index);
2018-05-31 22:03:12 +01:00
2020-05-19 14:02:12 +02:00
return new OkResult ( ) ;
2018-11-30 12:22:23 +11:00
}
catch ( Exception ex )
{
//ensure it's not listening
index . IndexOperationComplete - = Indexer_IndexOperationComplete ;
2020-09-14 09:10:53 +02:00
_logger . LogError < ExamineManagementController > ( ex , "An error occurred rebuilding index" ) ;
2020-05-19 14:02:12 +02:00
var response = new ConflictObjectResult ( "The index could not be rebuilt at this time, most likely there is another thread currently writing to the index. Error: {ex}" ) ;
2020-06-09 13:48:50 +02:00
HttpContext . SetReasonPhrase ( "Could Not Rebuild" ) ;
2018-11-30 12:22:23 +11:00
return response ;
}
2018-05-31 22:03:12 +01:00
}
2018-12-03 22:10:56 +11:00
private ExamineIndexModel CreateModel ( IIndex index )
2018-05-31 22:03:12 +01:00
{
2018-12-03 22:10:56 +11:00
var indexName = index . Name ;
2018-05-31 22:03:12 +01:00
2020-01-29 12:24:57 +11:00
var indexDiag = _indexDiagnosticsFactory . Create ( index ) ;
2018-05-31 22:03:12 +01:00
2018-11-28 21:02:45 +11:00
var isHealth = indexDiag . IsHealthy ( ) ;
var properties = new Dictionary < string , object >
2018-05-31 22:03:12 +01:00
{
2018-11-28 21:02:45 +11:00
[nameof(IIndexDiagnostics.DocumentCount)] = indexDiag . DocumentCount ,
[nameof(IIndexDiagnostics.FieldCount)] = indexDiag . FieldCount ,
} ;
foreach ( var p in indexDiag . Metadata )
properties [ p . Key ] = p . Value ;
2018-05-31 22:03:12 +01:00
2018-11-28 21:02:45 +11:00
var indexerModel = new ExamineIndexModel
{
Name = indexName ,
HealthStatus = isHealth . Success ? ( isHealth . Result ? ? "Healthy" ) : ( isHealth . Result ? ? "Unhealthy" ) ,
2018-11-30 12:22:23 +11:00
ProviderProperties = properties ,
2018-12-13 23:17:58 +11:00
CanRebuild = _indexRebuilder . CanRebuild ( index )
2018-11-28 21:02:45 +11:00
} ;
2018-12-04 14:25:37 +11:00
2018-05-31 22:03:12 +01:00
return indexerModel ;
}
2020-05-19 14:02:12 +02:00
private ActionResult ValidateSearcher ( string searcherName , out ISearcher searcher )
2018-05-31 22:03:12 +01:00
{
2018-12-04 14:25:37 +11:00
//try to get the searcher from the indexes
if ( _examineManager . TryGetIndex ( searcherName , out var index ) )
2018-05-31 22:03:12 +01:00
{
2018-12-04 14:25:37 +11:00
searcher = index . GetSearcher ( ) ;
2020-05-19 14:02:12 +02:00
return new OkResult ( ) ;
2018-05-31 22:03:12 +01:00
}
2018-12-04 14:25:37 +11:00
//if we didn't find anything try to find it by an explicitly declared searcher
if ( _examineManager . TryGetSearcher ( searcherName , out searcher ) )
2020-05-19 14:02:12 +02:00
return new OkResult ( ) ;
2018-05-31 22:03:12 +01:00
2020-05-19 14:02:12 +02:00
var response1 = new BadRequestObjectResult ( $"No searcher found with name = {searcherName}" ) ;
2020-06-09 13:48:50 +02:00
HttpContext . SetReasonPhrase ( "Searcher Not Found" ) ;
2018-05-31 22:03:12 +01:00
return response1 ;
}
2020-05-19 14:02:12 +02:00
private ActionResult ValidatePopulator ( IIndex index )
2018-11-30 12:22:23 +11:00
{
2018-12-13 23:17:58 +11:00
if ( _indexRebuilder . CanRebuild ( index ) )
2020-05-19 14:02:12 +02:00
return new OkResult ( ) ;
2018-11-30 12:22:23 +11:00
2020-05-19 14:02:12 +02:00
var response = new BadRequestObjectResult ( $"The index {index.Name} cannot be rebuilt because it does not have an associated {typeof(IIndexPopulator)}" ) ;
2020-06-09 13:48:50 +02:00
HttpContext . SetReasonPhrase ( "Index cannot be rebuilt" ) ;
2018-11-30 12:22:23 +11:00
return response ;
}
2020-05-19 14:02:12 +02:00
private ActionResult ValidateIndex ( string indexName , out IIndex index )
2018-05-31 22:03:12 +01:00
{
2018-11-30 12:22:23 +11:00
index = null ;
2018-05-31 22:03:12 +01:00
2018-12-03 22:10:56 +11:00
if ( _examineManager . TryGetIndex ( indexName , out index ) )
2018-05-31 22:03:12 +01:00
{
//return Ok!
2020-05-19 14:02:12 +02:00
return new OkResult ( ) ;
2018-05-31 22:03:12 +01:00
}
2020-05-19 14:02:12 +02:00
var response = new BadRequestObjectResult ( $"No index found with name = {indexName}" ) ;
2020-06-09 13:48:50 +02:00
HttpContext . SetReasonPhrase ( "Index Not Found" ) ;
2018-05-31 22:03:12 +01:00
return response ;
}
private void Indexer_IndexOperationComplete ( object sender , EventArgs e )
{
2019-07-08 08:48:49 +01:00
var indexer = ( IIndex ) sender ;
2018-05-31 22:03:12 +01:00
2018-12-07 15:06:07 +11:00
_logger . Debug < ExamineManagementController > ( "Logging operation completed for index {IndexName}" , indexer . Name ) ;
2018-05-31 22:03:12 +01:00
//ensure it's not listening anymore
indexer . IndexOperationComplete - = Indexer_IndexOperationComplete ;
_logger
. Info < ExamineManagementController
2019-07-08 08:48:49 +01:00
> ( $"Rebuilding index '{indexer.Name}' done." ) ;
2018-05-31 22:03:12 +01:00
var cacheKey = "temp_indexing_op_" + indexer . Name ;
2019-01-17 11:19:06 +01:00
_runtimeCache . Clear ( cacheKey ) ;
2018-05-31 22:03:12 +01:00
}
}
}