mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
377 lines
15 KiB
C#
377 lines
15 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
using System.Threading.Tasks;
|
|
using Orchard.Caching;
|
|
using Orchard.Environment.Configuration;
|
|
using Orchard.Environment.Extensions;
|
|
using Orchard.Environment.ShellBuilders;
|
|
using Orchard.Environment.State;
|
|
using Orchard.Environment.Descriptor;
|
|
using Orchard.Environment.Descriptor.Models;
|
|
using Orchard.Localization;
|
|
using Orchard.Logging;
|
|
using Orchard.Utility.Extensions;
|
|
using Orchard.Exceptions;
|
|
|
|
namespace Orchard.Environment {
|
|
// All the event handlers that DefaultOrchardHost implements have to be declared in OrchardStarter
|
|
public class DefaultOrchardHost : IOrchardHost, IShellSettingsManagerEventHandler, IShellDescriptorManagerEventHandler {
|
|
private readonly IHostLocalRestart _hostLocalRestart;
|
|
private readonly IShellSettingsManager _shellSettingsManager;
|
|
private readonly IShellContextFactory _shellContextFactory;
|
|
private readonly IRunningShellTable _runningShellTable;
|
|
private readonly IProcessingEngine _processingEngine;
|
|
private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator;
|
|
private readonly IExtensionMonitoringCoordinator _extensionMonitoringCoordinator;
|
|
private readonly ICacheManager _cacheManager;
|
|
private readonly static object _syncLock = new object();
|
|
private readonly static object _shellContextsWriteLock = new object();
|
|
|
|
private IEnumerable<ShellContext> _shellContexts;
|
|
|
|
private readonly ContextState<IList<ShellSettings>> _tenantsToRestart;
|
|
public DefaultOrchardHost(
|
|
IShellSettingsManager shellSettingsManager,
|
|
IShellContextFactory shellContextFactory,
|
|
IRunningShellTable runningShellTable,
|
|
IProcessingEngine processingEngine,
|
|
IExtensionLoaderCoordinator extensionLoaderCoordinator,
|
|
IExtensionMonitoringCoordinator extensionMonitoringCoordinator,
|
|
ICacheManager cacheManager,
|
|
IHostLocalRestart hostLocalRestart) {
|
|
_shellSettingsManager = shellSettingsManager;
|
|
_shellContextFactory = shellContextFactory;
|
|
_runningShellTable = runningShellTable;
|
|
_processingEngine = processingEngine;
|
|
_extensionLoaderCoordinator = extensionLoaderCoordinator;
|
|
_extensionMonitoringCoordinator = extensionMonitoringCoordinator;
|
|
_cacheManager = cacheManager;
|
|
_hostLocalRestart = hostLocalRestart;
|
|
|
|
_tenantsToRestart = new ContextState<IList<ShellSettings>>("DefaultOrchardHost.TenantsToRestart", () => new List<ShellSettings>());
|
|
|
|
T = NullLocalizer.Instance;
|
|
Logger = NullLogger.Instance;
|
|
}
|
|
|
|
public Localizer T { get; set; }
|
|
public ILogger Logger { get; set; }
|
|
|
|
public IList<ShellContext> Current {
|
|
get { return BuildCurrent().ToReadOnlyCollection(); }
|
|
}
|
|
|
|
public ShellContext GetShellContext(ShellSettings shellSettings) {
|
|
return BuildCurrent().SingleOrDefault(shellContext => shellContext.Settings.Name.Equals(shellSettings.Name));
|
|
}
|
|
|
|
void IOrchardHost.Initialize() {
|
|
Logger.Information("Initializing");
|
|
BuildCurrent();
|
|
Logger.Information("Initialized");
|
|
}
|
|
|
|
void IOrchardHost.ReloadExtensions() {
|
|
DisposeShellContext();
|
|
}
|
|
|
|
void IOrchardHost.BeginRequest() {
|
|
Logger.Debug("BeginRequest");
|
|
BeginRequest();
|
|
}
|
|
|
|
void IOrchardHost.EndRequest() {
|
|
Logger.Debug("EndRequest");
|
|
EndRequest();
|
|
}
|
|
|
|
IWorkContextScope IOrchardHost.CreateStandaloneEnvironment(ShellSettings shellSettings) {
|
|
Logger.Debug("Creating standalone environment for tenant {0}", shellSettings.Name);
|
|
|
|
MonitorExtensions();
|
|
BuildCurrent();
|
|
|
|
var shellContext = CreateShellContext(shellSettings);
|
|
var workContext = shellContext.LifetimeScope.CreateWorkContextScope();
|
|
return new StandaloneEnvironmentWorkContextScopeWrapper(workContext, shellContext);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures shells are activated, or re-activated if extensions have changed
|
|
/// </summary>
|
|
IEnumerable<ShellContext> BuildCurrent() {
|
|
if (_shellContexts == null) {
|
|
lock (_syncLock) {
|
|
if (_shellContexts == null) {
|
|
SetupExtensions();
|
|
MonitorExtensions();
|
|
CreateAndActivateShells();
|
|
}
|
|
}
|
|
}
|
|
|
|
return _shellContexts;
|
|
}
|
|
|
|
void StartUpdatedShells() {
|
|
while (_tenantsToRestart.GetState().Any()) {
|
|
var settings = _tenantsToRestart.GetState().First();
|
|
_tenantsToRestart.GetState().Remove(settings);
|
|
Logger.Debug("Updating shell: " + settings.Name);
|
|
lock (_syncLock) {
|
|
ActivateShell(settings);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CreateAndActivateShells() {
|
|
Logger.Information("Start creation of shells");
|
|
|
|
// is there any tenant right now ?
|
|
var allSettings = _shellSettingsManager.LoadSettings()
|
|
.Where(settings => settings.State == TenantState.Running || settings.State == TenantState.Uninitialized)
|
|
.ToArray();
|
|
|
|
// load all tenants, and activate their shell
|
|
if (allSettings.Any()) {
|
|
Parallel.ForEach(allSettings, settings => {
|
|
try {
|
|
var context = CreateShellContext(settings);
|
|
ActivateShell(context);
|
|
}
|
|
catch (Exception ex) {
|
|
if (ex.IsFatal()) {
|
|
throw;
|
|
}
|
|
Logger.Error(ex, "A tenant could not be started: " + settings.Name);
|
|
}
|
|
while (_processingEngine.AreTasksPending()) {
|
|
Logger.Debug("Processing pending task after activate Shell");
|
|
_processingEngine.ExecuteNextTask();
|
|
}
|
|
});
|
|
}
|
|
// no settings, run the Setup
|
|
else {
|
|
var setupContext = CreateSetupContext();
|
|
ActivateShell(setupContext);
|
|
}
|
|
|
|
Logger.Information("Done creating shells");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts a Shell and registers its settings in RunningShellTable
|
|
/// </summary>
|
|
private void ActivateShell(ShellContext context) {
|
|
Logger.Debug("Activating context for tenant {0}", context.Settings.Name);
|
|
context.Shell.Activate();
|
|
|
|
lock (_shellContextsWriteLock) {
|
|
_shellContexts = (_shellContexts ?? Enumerable.Empty<ShellContext>())
|
|
.Where(c => c.Settings.Name != context.Settings.Name)
|
|
.Concat(new[] { context })
|
|
.ToArray();
|
|
}
|
|
|
|
_runningShellTable.Add(context.Settings);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a transient shell for the default tenant's setup
|
|
/// </summary>
|
|
private ShellContext CreateSetupContext() {
|
|
Logger.Debug("Creating shell context for root setup");
|
|
return _shellContextFactory.CreateSetupContext(new ShellSettings { Name = ShellSettings.DefaultName });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a shell context based on shell settings
|
|
/// </summary>
|
|
private ShellContext CreateShellContext(ShellSettings settings) {
|
|
if (settings.State == TenantState.Uninitialized) {
|
|
Logger.Debug("Creating shell context for tenant {0} setup", settings.Name);
|
|
return _shellContextFactory.CreateSetupContext(settings);
|
|
}
|
|
|
|
Logger.Debug("Creating shell context for tenant {0}", settings.Name);
|
|
return _shellContextFactory.CreateShellContext(settings);
|
|
}
|
|
|
|
private void SetupExtensions() {
|
|
_extensionLoaderCoordinator.SetupExtensions();
|
|
}
|
|
|
|
private void MonitorExtensions() {
|
|
// This is a "fake" cache entry to allow the extension loader coordinator
|
|
// notify us (by resetting _current to "null") when an extension has changed
|
|
// on disk, and we need to reload new/updated extensions.
|
|
_cacheManager.Get("OrchardHost_Extensions",
|
|
ctx => {
|
|
_extensionMonitoringCoordinator.MonitorExtensions(ctx.Monitor);
|
|
_hostLocalRestart.Monitor(ctx.Monitor);
|
|
DisposeShellContext();
|
|
return "";
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Terminates all active shell contexts, and dispose their scope, forcing
|
|
/// them to be reloaded if necessary.
|
|
/// </summary>
|
|
private void DisposeShellContext() {
|
|
Logger.Information("Disposing active shell contexts");
|
|
|
|
if (_shellContexts != null) {
|
|
lock (_syncLock) {
|
|
if (_shellContexts != null) {
|
|
foreach (var shellContext in _shellContexts) {
|
|
shellContext.Shell.Terminate();
|
|
shellContext.Dispose();
|
|
}
|
|
}
|
|
}
|
|
_shellContexts = null;
|
|
}
|
|
}
|
|
|
|
protected virtual void BeginRequest() {
|
|
// Ensure all shell contexts are loaded, or need to be reloaded if
|
|
// extensions have changed
|
|
MonitorExtensions();
|
|
BuildCurrent();
|
|
StartUpdatedShells();
|
|
}
|
|
|
|
protected virtual void EndRequest() {
|
|
// Synchronously process all pending tasks. It's safe to do this at this point
|
|
// of the pipeline, as the request transaction has been closed, so creating a new
|
|
// environment and transaction for these tasks will behave as expected.)
|
|
while (_processingEngine.AreTasksPending()) {
|
|
Logger.Debug("Processing pending task");
|
|
_processingEngine.ExecuteNextTask();
|
|
}
|
|
|
|
StartUpdatedShells();
|
|
}
|
|
|
|
void IShellSettingsManagerEventHandler.Saved(ShellSettings settings) {
|
|
Logger.Debug("Shell saved: " + settings.Name);
|
|
|
|
// if a tenant has been created
|
|
if (settings.State != TenantState.Invalid) {
|
|
if (!_tenantsToRestart.GetState().Any(t => t.Name.Equals(settings.Name))) {
|
|
Logger.Debug("Adding tenant to restart: " + settings.Name + " " + settings.State);
|
|
_tenantsToRestart.GetState().Add(settings);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ActivateShell(ShellSettings settings) {
|
|
Logger.Debug("Activating shell: " + settings.Name);
|
|
|
|
// look for the associated shell context
|
|
var shellContext = _shellContexts.FirstOrDefault(c => c.Settings.Name == settings.Name);
|
|
|
|
if (shellContext == null && settings.State == TenantState.Disabled) {
|
|
return;
|
|
}
|
|
|
|
// is this is a new tenant ? or is it a tenant waiting for setup ?
|
|
if (shellContext == null || settings.State == TenantState.Uninitialized) {
|
|
// create the Shell
|
|
var context = CreateShellContext(settings);
|
|
|
|
// activate the Shell
|
|
ActivateShell(context);
|
|
}
|
|
// terminate the shell if the tenant was disabled
|
|
else if (settings.State == TenantState.Disabled) {
|
|
shellContext.Shell.Terminate();
|
|
_runningShellTable.Remove(settings);
|
|
|
|
// Forcing enumeration with ToArray() so a lazy execution isn't causing issues by accessing the disposed context.
|
|
_shellContexts = _shellContexts.Where(shell => shell.Settings.Name != settings.Name).ToArray();
|
|
|
|
shellContext.Dispose();
|
|
}
|
|
// reload the shell as its settings have changed
|
|
else {
|
|
// dispose previous context
|
|
shellContext.Shell.Terminate();
|
|
|
|
var context = _shellContextFactory.CreateShellContext(settings);
|
|
|
|
// Activate and register modified context.
|
|
// Forcing enumeration with ToArray() so a lazy execution isn't causing issues by accessing the disposed shell context.
|
|
_shellContexts = _shellContexts.Where(shell => shell.Settings.Name != settings.Name).Union(new[] { context }).ToArray();
|
|
|
|
shellContext.Dispose();
|
|
context.Shell.Activate();
|
|
|
|
_runningShellTable.Update(settings);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A feature is enabled/disabled, the tenant needs to be restarted
|
|
/// </summary>
|
|
void IShellDescriptorManagerEventHandler.Changed(ShellDescriptor descriptor, string tenant) {
|
|
if (_shellContexts == null) {
|
|
return;
|
|
}
|
|
|
|
Logger.Debug("Shell changed: " + tenant);
|
|
|
|
var context = _shellContexts.FirstOrDefault(x => x.Settings.Name == tenant);
|
|
|
|
if (context == null) {
|
|
return;
|
|
}
|
|
|
|
// don't restart when tenant is in setup
|
|
if (context.Settings.State != TenantState.Running) {
|
|
return;
|
|
}
|
|
|
|
// don't flag the tenant if already listed
|
|
if (_tenantsToRestart.GetState().Any(x => x.Name == tenant)) {
|
|
return;
|
|
}
|
|
|
|
Logger.Debug("Adding tenant to restart: " + tenant);
|
|
_tenantsToRestart.GetState().Add(context.Settings);
|
|
}
|
|
|
|
// To be used from CreateStandaloneEnvironment(), also disposes the ShellContext LifetimeScope.
|
|
private class StandaloneEnvironmentWorkContextScopeWrapper : IWorkContextScope {
|
|
private readonly ShellContext _shellContext;
|
|
private readonly IWorkContextScope _workContextScope;
|
|
|
|
public WorkContext WorkContext {
|
|
get { return _workContextScope.WorkContext; }
|
|
}
|
|
|
|
public StandaloneEnvironmentWorkContextScopeWrapper(IWorkContextScope workContextScope, ShellContext shellContext) {
|
|
_workContextScope = workContextScope;
|
|
_shellContext = shellContext;
|
|
}
|
|
|
|
public TService Resolve<TService>() {
|
|
return _workContextScope.Resolve<TService>();
|
|
}
|
|
|
|
public bool TryResolve<TService>(out TService service) {
|
|
return _workContextScope.TryResolve<TService>(out service);
|
|
}
|
|
|
|
public void Dispose() {
|
|
_workContextScope.Dispose();
|
|
_shellContext.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|