Merge integration -> 1.x

--HG--
branch : 1.x
This commit is contained in:
Suha Can
2011-03-26 12:50:51 -07:00
956 changed files with 48243 additions and 11389 deletions

View File

@@ -5,13 +5,14 @@ namespace Orchard.Indexing.Services {
public class IndexEntry {
public string IndexName { get; set; }
public int DocumentCount { get; set; }
public DateTime? LastUpdateUtc { get; set; }
public DateTime LastUpdateUtc { get; set; }
public IEnumerable<string> Fields { get; set; }
public IndexingStatus IndexingStatus { get; set; }
}
public interface IIndexingService : IDependency {
void RebuildIndex();
void UpdateIndex();
IndexEntry GetIndexEntry();
void RebuildIndex(string indexName);
void UpdateIndex(string indexName);
IndexEntry GetIndexEntry(string indexName);
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace Orchard.Indexing.Services {
public enum IndexingStatus {
Rebuilding,
Updating,
Idle
}
public interface IIndexStatisticsProvider : IDependency {
DateTime GetLastIndexedUtc(string indexName);
IndexingStatus GetIndexingStatus(string indexName);
}
}

View File

@@ -0,0 +1,6 @@
namespace Orchard.Indexing.Services {
public interface IIndexingTaskExecutor : IDependency {
bool DeleteIndex(string indexName);
bool UpdateIndexBatch(string indexName);
}
}

View File

@@ -0,0 +1,5 @@
namespace Orchard.Indexing.Services {
public interface IUpdateIndexScheduler : IDependency {
void Schedule(string indexName);
}
}

View File

@@ -1,59 +1,67 @@
using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Localization.Services;
using Orchard.UI.Notify;
namespace Orchard.Indexing.Services
{
public class IndexingService : IIndexingService {
private const string SearchIndexName = "Search";
private readonly IIndexManager _indexManager;
private readonly IEnumerable<IIndexNotifierHandler> _indexNotifierHandlers;
private readonly IIndexStatisticsProvider _indexStatisticsProvider;
private readonly IIndexingTaskExecutor _indexingTaskExecutor;
public IndexingService(IOrchardServices services, IIndexManager indexManager, IEnumerable<IIndexNotifierHandler> indexNotifierHandlers, ICultureManager cultureManager) {
public IndexingService(
IOrchardServices services,
IIndexManager indexManager,
IEnumerable<IIndexNotifierHandler> indexNotifierHandlers,
IIndexStatisticsProvider indexStatisticsProvider,
IIndexingTaskExecutor indexingTaskExecutor) {
Services = services;
_indexManager = indexManager;
_indexNotifierHandlers = indexNotifierHandlers;
_indexStatisticsProvider = indexStatisticsProvider;
_indexingTaskExecutor = indexingTaskExecutor;
T = NullLocalizer.Instance;
}
public IOrchardServices Services { get; set; }
public Localizer T { get; set; }
void IIndexingService.RebuildIndex() {
public void RebuildIndex(string indexName) {
if (!_indexManager.HasIndexProvider()) {
Services.Notifier.Warning(T("There is no search index to rebuild."));
return;
}
var searchProvider = _indexManager.GetSearchIndexProvider();
if (searchProvider.Exists(SearchIndexName))
searchProvider.DeleteIndex(SearchIndexName);
searchProvider.CreateIndex(SearchIndexName); // or just reset the updated date and let the background process recreate the index
Services.Notifier.Information(T("The search index has been rebuilt."));
if(_indexingTaskExecutor.DeleteIndex(indexName)) {
Services.Notifier.Information(T("The index {0} has been rebuilt.", indexName));
UpdateIndex(indexName);
}
else {
Services.Notifier.Warning(T("The index {0} could no ben rebuilt. It might already be in use, please try again later.", indexName));
}
}
void IIndexingService.UpdateIndex() {
public void UpdateIndex(string indexName) {
foreach(var handler in _indexNotifierHandlers) {
handler.UpdateIndex(SearchIndexName);
handler.UpdateIndex(indexName);
}
Services.Notifier.Information(T("The search index has been updated."));
}
IndexEntry IIndexingService.GetIndexEntry() {
IndexEntry IIndexingService.GetIndexEntry(string indexName) {
var provider = _indexManager.GetSearchIndexProvider();
if (provider == null)
return null;
return new IndexEntry {
IndexName = SearchIndexName,
DocumentCount = provider.NumDocs(SearchIndexName),
Fields = provider.GetFields(SearchIndexName),
LastUpdateUtc = provider.GetLastIndexUtc(SearchIndexName)
IndexName = indexName,
DocumentCount = provider.NumDocs(indexName),
Fields = provider.GetFields(indexName),
LastUpdateUtc = _indexStatisticsProvider.GetLastIndexedUtc(indexName),
IndexingStatus = _indexStatisticsProvider.GetIndexingStatus(indexName)
};
}
}

View File

@@ -1,25 +0,0 @@
using System.Collections.Generic;
namespace Orchard.Indexing.Services {
public interface IIndexSynLock : ISingletonDependency {
object GetSynLock(string indexName);
}
public class IndexSynLock : IIndexSynLock {
private readonly Dictionary<string, object> _synLocks;
private readonly object _synLock = new object();
public IndexSynLock() {
_synLocks =new Dictionary<string, object>();
}
public object GetSynLock(string indexName) {
lock(_synLock) {
if(!_synLocks.ContainsKey(indexName)) {
_synLocks[indexName] = new object();
}
return _synLocks[indexName];
}
}
}
}

View File

@@ -3,173 +3,308 @@ using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
using Orchard.Data;
using Orchard.Environment.Configuration;
using Orchard.FileSystems.AppData;
using Orchard.FileSystems.LockFile;
using Orchard.Indexing.Models;
using Orchard.Indexing.Settings;
using Orchard.Logging;
using Orchard.Services;
using Orchard.Tasks.Indexing;
namespace Orchard.Indexing.Services {
/// <summary>
/// Contains the logic which is regularly executed to retrieve index information from multiple content handlers.
/// </summary>
/// <remarks>
/// This class is synchronized using a lock file as both command line and web workers can potentially use it,
/// and singleton locks would not be shared accross those two.
/// </remarks>
[UsedImplicitly]
public class IndexingTaskExecutor : IIndexNotifierHandler {
private readonly IClock _clock;
private readonly IRepository<IndexingTaskRecord> _repository;
public class IndexingTaskExecutor : IIndexingTaskExecutor, IIndexStatisticsProvider
{
private readonly IRepository<IndexingTaskRecord> _taskRepository;
private readonly IRepository<ContentItemVersionRecord> _contentRepository;
private IIndexProvider _indexProvider;
private readonly IIndexManager _indexManager;
private readonly IIndexingTaskManager _indexingTaskManager;
private readonly IContentManager _contentManager;
private readonly IIndexSynLock _indexSynLock;
private readonly IAppDataFolder _appDataFolder;
private readonly ShellSettings _shellSettings;
private readonly ILockFileManager _lockFileManager;
private readonly IClock _clock;
private const int ContentItemsPerLoop = 50;
private IndexingStatus _indexingStatus = IndexingStatus.Idle;
public IndexingTaskExecutor(
IClock clock,
IRepository<IndexingTaskRecord> repository,
IRepository<IndexingTaskRecord> taskRepository,
IRepository<ContentItemVersionRecord> contentRepository,
IIndexManager indexManager,
IIndexingTaskManager indexingTaskManager,
IContentManager contentManager,
IIndexSynLock indexSynLock) {
_clock = clock;
_repository = repository;
IAppDataFolder appDataFolder,
ShellSettings shellSettings,
ILockFileManager lockFileManager,
IClock clock) {
_taskRepository = taskRepository;
_contentRepository = contentRepository;
_indexManager = indexManager;
_indexingTaskManager = indexingTaskManager;
_contentManager = contentManager;
_indexSynLock = indexSynLock;
_appDataFolder = appDataFolder;
_shellSettings = shellSettings;
_lockFileManager = lockFileManager;
_clock = clock;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void UpdateIndex(string indexName) {
var synLock = _indexSynLock.GetSynLock(indexName);
public bool DeleteIndex(string indexName) {
ILockFile lockFile = null;
var settingsFilename = GetSettingsFileName(indexName);
var lockFilename = settingsFilename + ".lock";
if (!System.Threading.Monitor.TryEnter(synLock)) {
Logger.Information("Index was requested but was already running");
return;
// acquire a lock file on the index
if (!_lockFileManager.TryAcquireLock(lockFilename, ref lockFile)) {
Logger.Information("Could not delete the index. Already in use.");
return false;
}
try {
using (lockFile) {
if (!_indexManager.HasIndexProvider()) {
return;
return false;
}
var searchProvider = _indexManager.GetSearchIndexProvider();
if (searchProvider.Exists(indexName)) {
searchProvider.DeleteIndex(indexName);
}
DeleteSettings(indexName);
}
return true;
}
public bool UpdateIndexBatch(string indexName) {
ILockFile lockFile = null;
var settingsFilename = GetSettingsFileName(indexName);
var lockFilename = settingsFilename + ".lock";
// acquire a lock file on the index
if (!_lockFileManager.TryAcquireLock(lockFilename, ref lockFile)) {
Logger.Information("Index was requested but is already running");
return false;
}
using (lockFile) {
if (!_indexManager.HasIndexProvider()) {
return false;
}
// load index settings to know what is the current state of indexing
var indexSettings = LoadSettings(indexName);
_indexProvider = _indexManager.GetSearchIndexProvider();
var updateIndexDocuments = new List<IDocumentIndex>();
var addedContentItemIds = new List<string>();
DateTime? lastIndexUtc;
// Do we need to rebuild the full index (first time module is used, or rebuild index requested) ?
if (_indexProvider.IsEmpty(indexName)) {
Logger.Information("Rebuild index started");
// mark current last task, as we should process older ones (in case of rebuild index only)
lastIndexUtc = _indexingTaskManager.GetLastTaskDateTime();
// get every existing content item to index it
foreach (var contentItem in _contentManager.Query(VersionOptions.Published).List()) {
try {
// skip items which are not indexed
var settings = GetTypeIndexingSettings(contentItem);
if (!settings.Included)
continue;
var documentIndex = _indexProvider.New(contentItem.Id);
_contentManager.Index(contentItem, documentIndex);
if (documentIndex.IsDirty) {
updateIndexDocuments.Add(documentIndex);
addedContentItemIds.Add(contentItem.Id.ToString());
}
}
catch (Exception ex) {
Logger.Warning(ex, "Unable to index content item #{0} during rebuild", contentItem.Id);
}
}
}
else {
// retrieve last processed index time
lastIndexUtc = _indexProvider.GetLastIndexUtc(indexName);
}
_indexProvider.SetLastIndexUtc(indexName, _clock.UtcNow);
// retrieve not yet processed tasks
var taskRecords = lastIndexUtc == null
? _repository.Fetch(x => true).ToArray()
: _repository.Fetch(x => x.CreatedUtc >= lastIndexUtc).ToArray(); // CreatedUtc and lastIndexUtc might be equal if a content item is created in a background task
// nothing to do ?)))
if (taskRecords.Length + updateIndexDocuments.Count == 0) {
Logger.Information("Index update requested, nothing to do");
return;
}
Logger.Information("Processing {0} indexing tasks", taskRecords.Length);
if (!_indexProvider.Exists(indexName)) {
if (indexSettings.Mode == IndexingMode.Rebuild && indexSettings.LastContentId == 0) {
_indexProvider.CreateIndex(indexName);
// mark the last available task at the moment the process is started.
// once the Rebuild is done, Update will start at this point of the table
indexSettings.LastIndexedId = _taskRepository
.Table
.OrderByDescending(x => x.Id)
.Select(x => x.Id)
.FirstOrDefault();
}
// process Delete tasks
try {
var deleteIds = taskRecords.Where(t => t.Action == IndexingTaskRecord.Delete).Select(t => t.ContentItemRecord.Id).ToArray();
if (deleteIds.Length > 0) {
_indexProvider.Delete(indexName, deleteIds);
Logger.Information("Deleted content items from index: {0}", String.Join(", ", deleteIds));
}
}
catch (Exception ex) {
Logger.Warning(ex, "An error occured while removing a document from the index");
}
// process Update tasks
foreach (var taskRecord in taskRecords.Where(t => t.Action == IndexingTaskRecord.Update)) {
var task = new IndexingTask(_contentManager, taskRecord);
// skip items which are not indexed
var settings = GetTypeIndexingSettings(task.ContentItem);
if (!settings.Included)
continue;
try {
var documentIndex = _indexProvider.New(task.ContentItem.Id);
_contentManager.Index(task.ContentItem, documentIndex);
if (!addedContentItemIds.Contains(task.ContentItem.Id.ToString()) && documentIndex.IsDirty) {
updateIndexDocuments.Add(documentIndex);
}
}
catch (Exception ex) {
Logger.Warning(ex, "Unable to process indexing task #{0}", taskRecord.Id);
}
}
if (updateIndexDocuments.Count > 0) {
try {
_indexProvider.Store(indexName, updateIndexDocuments);
Logger.Information("Added content items to index: {0}", String.Join(", ", addedContentItemIds));
}
catch (Exception ex) {
Logger.Warning(ex, "An error occured while adding a document to the index");
}
}
}
finally {
System.Threading.Monitor.Exit(synLock);
// execute indexing commands by batch of [ContentItemsPerLoop] content items
return BatchIndex(indexName, settingsFilename, indexSettings);
}
}
static TypeIndexing GetTypeIndexingSettings(ContentItem contentItem) {
/// <summary>
/// Indexes a batch of content items
/// </summary>
/// <returns>
/// <c>true</c> if there are more items to process; otherwise, <c>false</c>.
/// </returns>
private bool BatchIndex(string indexName, string settingsFilename, IndexSettings indexSettings) {
var addToIndex = new List<IDocumentIndex>();
var deleteFromIndex = new List<int>();
// Rebuilding the index ?
if (indexSettings.Mode == IndexingMode.Rebuild) {
Logger.Information("Rebuilding index");
_indexingStatus = IndexingStatus.Rebuilding;
// load all content items
var contentItems = _contentRepository
.Fetch(
versionRecord => versionRecord.Published && versionRecord.Id > indexSettings.LastContentId,
order => order.Asc(versionRecord => versionRecord.Id))
.Take(ContentItemsPerLoop)
.Select(versionRecord => _contentManager.Get(versionRecord.ContentItemRecord.Id, VersionOptions.VersionRecord(versionRecord.Id)))
.Distinct()
.ToList();
// if no more elements to index, switch to update mode
if (contentItems.Count == 0) {
indexSettings.Mode = IndexingMode.Update;
}
foreach (var item in contentItems) {
try {
IDocumentIndex documentIndex = ExtractDocumentIndex(item);
if (documentIndex != null && documentIndex.IsDirty) {
addToIndex.Add(documentIndex);
}
indexSettings.LastContentId = item.VersionRecord.Id;
}
catch (Exception ex) {
Logger.Warning(ex, "Unable to index content item #{0} during rebuild", item.Id);
}
}
}
if (indexSettings.Mode == IndexingMode.Update) {
Logger.Information("Updating index");
_indexingStatus = IndexingStatus.Updating;
var contentItems = _taskRepository
.Fetch(x => x.Id > indexSettings.LastIndexedId)
.OrderBy(x => x.Id)
.Take(ContentItemsPerLoop)
.GroupBy(x => x.ContentItemRecord.Id)
.Select(group => new {TaskId = group.Max(task => task.Id), ContentItem = _contentManager.Get(group.Key, VersionOptions.Published)})
.OrderBy(x => x.TaskId)
.ToArray();
foreach (var item in contentItems) {
try {
IDocumentIndex documentIndex = ExtractDocumentIndex(item.ContentItem);
if (documentIndex == null) {
deleteFromIndex.Add(item.ContentItem.Id);
}
else if (documentIndex.IsDirty) {
addToIndex.Add(documentIndex);
}
indexSettings.LastIndexedId = item.TaskId;
}
catch (Exception ex) {
Logger.Warning(ex, "Unable to index content item #{0} during update", item.ContentItem.Id);
}
}
}
// save current state of the index
indexSettings.LastIndexedUtc = _clock.UtcNow;
_appDataFolder.CreateFile(settingsFilename, indexSettings.ToXml());
if (deleteFromIndex.Count == 0 && addToIndex.Count == 0) {
// nothing more to do
_indexingStatus = IndexingStatus.Idle;
return false;
}
// save new and updated documents to the index
try {
if (addToIndex.Count > 0) {
_indexProvider.Store(indexName, addToIndex);
Logger.Information("Added content items to index: {0}", addToIndex.Count);
}
}
catch (Exception ex) {
Logger.Warning(ex, "An error occured while adding a document to the index");
}
// removing documents from the index
try {
if (deleteFromIndex.Count > 0) {
_indexProvider.Delete(indexName, deleteFromIndex);
Logger.Information("Added content items to index: {0}", addToIndex.Count);
}
}
catch (Exception ex) {
Logger.Warning(ex, "An error occured while removing a document from the index");
}
return true;
}
/// <summary>
/// Loads the settings file or create a new default one if it doesn't exist
/// </summary>
public IndexSettings LoadSettings(string indexName)
{
var indexSettings = new IndexSettings();
var settingsFilename = GetSettingsFileName(indexName);
if (_appDataFolder.FileExists(settingsFilename))
{
var content = _appDataFolder.ReadFile(settingsFilename);
indexSettings = IndexSettings.Parse(content);
}
return indexSettings;
}
/// <summary>
/// Deletes the settings file
/// </summary>
public void DeleteSettings(string indexName) {
var settingsFilename = GetSettingsFileName(indexName);
if (_appDataFolder.FileExists(settingsFilename)) {
_appDataFolder.DeleteFile(settingsFilename);
}
}
/// <summary>
/// Creates a IDocumentIndex instance for a specific content item id. If the content
/// item is no more published, it returns null.
/// </summary>
private IDocumentIndex ExtractDocumentIndex(ContentItem contentItem) {
// ignore deleted or unpublished items
if (contentItem == null || !contentItem.IsPublished()) {
return null;
}
// skip items from types which are not indexed
var settings = GetTypeIndexingSettings(contentItem);
if (!settings.Included) {
return null;
}
var documentIndex = _indexProvider.New(contentItem.Id);
// call all handlers to add content to index
_contentManager.Index(contentItem, documentIndex);
return documentIndex;
}
private static TypeIndexing GetTypeIndexingSettings(ContentItem contentItem) {
if (contentItem == null ||
contentItem.TypeDefinition == null ||
contentItem.TypeDefinition.Settings == null) {
return new TypeIndexing { Included = false };
return new TypeIndexing {Included = false};
}
return contentItem.TypeDefinition.Settings.GetModel<TypeIndexing>();
}
private string GetSettingsFileName(string indexName) {
return _appDataFolder.Combine("Sites", _shellSettings.Name, indexName + ".settings.xml");
}
public DateTime GetLastIndexedUtc(string indexName) {
var indexSettings = LoadSettings(indexName);
return indexSettings.LastIndexedUtc;
}
public IndexingStatus GetIndexingStatus(string indexName) {
return _indexingStatus;
}
}
}

View File

@@ -30,8 +30,6 @@ namespace Orchard.Indexing.Services {
throw new ArgumentNullException("contentItem");
}
DeleteTasks(contentItem);
var taskRecord = new IndexingTaskRecord {
CreatedUtc = _clock.UtcNow,
ContentItemRecord = contentItem.Record,
@@ -54,22 +52,6 @@ namespace Orchard.Indexing.Services {
Logger.Information("Deleting index task created for [{0}:{1}]", contentItem.ContentType, contentItem.Id);
}
public DateTime GetLastTaskDateTime() {
return _repository.Table.Max(t => t.CreatedUtc) ?? new DateTime(1980, 1, 1);
}
/// <summary>
/// Removes existing tasks for the specified content item
/// </summary>
public void DeleteTasks(ContentItem contentItem) {
var tasks = _repository
.Fetch(x => x.ContentItemRecord.Id == contentItem.Id)
.ToArray();
foreach (var task in tasks) {
_repository.Delete(task);
}
_repository.Flush();
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using Orchard.Environment.Configuration;
using Orchard.Environment.Descriptor;
using Orchard.Environment.State;
namespace Orchard.Indexing.Services {
public class UpdateIndexScheduler : IUpdateIndexScheduler, IIndexNotifierHandler {
private readonly IProcessingEngine _processingEngine;
private readonly ShellSettings _shellSettings;
private readonly IShellDescriptorManager _shellDescriptorManager;
private readonly Lazy<IIndexingTaskExecutor> _indexingTaskExecutor;
public UpdateIndexScheduler(
IProcessingEngine processingEngine,
ShellSettings shellSettings,
IShellDescriptorManager shellDescriptorManager,
Lazy<IIndexingTaskExecutor> indexingTaskExecutor
) {
_processingEngine = processingEngine;
_shellSettings = shellSettings;
_shellDescriptorManager = shellDescriptorManager;
_indexingTaskExecutor = indexingTaskExecutor;
}
public void Schedule(string indexName) {
var shellDescriptor = _shellDescriptorManager.GetShellDescriptor();
_processingEngine.AddTask(
_shellSettings,
shellDescriptor,
"IIndexNotifierHandler.UpdateIndex",
new Dictionary<string, object> { { "indexName", indexName } }
);
}
public void UpdateIndex(string indexName) {
if(_indexingTaskExecutor.Value.UpdateIndexBatch(indexName)) {
Schedule(indexName);
}
}
}
}