Files
Umbraco-CMS/src/Umbraco.Core/CoreRuntime.cs

365 lines
14 KiB
C#
Raw Normal View History

2016-08-25 15:09:51 +02:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using AutoMapper;
using LightInject;
using Umbraco.Core.Cache;
using Umbraco.Core.Components;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.Mapping;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Plugins;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Core.Strings;
using Umbraco.Core._Legacy.PackageActions;
namespace Umbraco.Core
{
/// <summary>
/// Represents the Core Umbraco runtime.
/// </summary>
/// <remarks>Does not handle any of the web-related aspects of Umbraco (startup, etc). It
/// should be possible to use this runtime in console apps.</remarks>
public class CoreRuntime : IRuntime
{
private BootLoader _bootLoader;
private DisposableTimer _timer;
// fixme cleanup these
2016-08-25 15:09:51 +02:00
private IServiceContainer _appStartupEvtContainer;
private bool _isInitialized;
private bool _isStarted;
private bool _isComplete;
// what the UmbracoApplication does is...
// create the container and configure for core
// create and register a logger
// then:
//GetBootManager()
// .Initialize()
// .Startup(appContext => OnApplicationStarting(sender, e))
// .Complete(appContext => OnApplicationStarted(sender, e));
//
// edit so it becomes:
//GetBootLoader()
// .Boot();
//
// merge all RegisterX into one Register
//
// WebBootLoader should
// configure the container for web BUT that should be AFTER the components have initialized? OR?
//
// Startup runs all app event handler OnApplicationStarting methods
// then triggers the OnApplicationStarting event of the app
// Complete
// freezes resolution
// ensures database connection (else?)
// tells user service it is upgrading (?)
// runs all app event handler OnApplicationStarted methods
// then triggers the OnApplicationStarted event of the app
// and sets Ready
//
// note: what's deciding whether install, upgrade, run?
private RuntimeState _state; // fixme what about web?!
/// <summary>
/// Initializes a new instance of the <see cref="CoreRuntime"/> class.
/// </summary>
/// <param name="umbracoApplication">The Umbraco HttpApplication.</param>
public CoreRuntime(UmbracoApplicationBase umbracoApplication)
{
if (umbracoApplication == null) throw new ArgumentNullException(nameof(umbracoApplication));
UmbracoApplication = umbracoApplication;
}
/// <inheritdoc/>
2016-08-25 15:09:51 +02:00
public virtual void Boot(ServiceContainer container)
{
// create and register essential stuff
Logger = container.GetInstance<ILogger>();
container.RegisterInstance(Profiler = GetProfiler());
container.RegisterInstance(ProfilingLogger = new ProfilingLogger(Current.Logger, Profiler));
container.RegisterInstance(_state = new RuntimeState());
// then compose
Compose(container);
// the boot loader boots using a container scope, so anything that is PerScope will
// be disposed after the boot loader has booted, and anything else will remain.
// note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else
// the container will fail to create a scope since there is no http context when
// the application starts.
// the boot loader is kept in the runtime for as long as Umbraco runs, and components
// are NOT disposed - which is not a big deal as long as they remain lightweight
// objects.
_bootLoader = new BootLoader(container);
_bootLoader.Boot(GetComponentTypes());
}
public virtual void Terminate()
{
_bootLoader?.Terminate();
}
public virtual void Compose(ServiceContainer container)
{
// create and register essential stuff
var cache = GetApplicationCache();
container.RegisterInstance(cache);
container.RegisterInstance(cache.RuntimeCache);
container.RegisterInstance(new PluginManager(cache.RuntimeCache, ProfilingLogger));
}
/// <summary>
/// Gets the Umbraco HttpApplication.
/// </summary>
protected UmbracoApplicationBase UmbracoApplication { get; }
2016-08-25 15:09:51 +02:00
#region Locals
protected ILogger Logger { get; private set; }
protected IProfiler Profiler { get; private set; }
protected ProfilingLogger ProfilingLogger { get; private set; }
#endregion
#region Getters
// getters can be implemented by runtimes inheriting from CoreRuntime
2016-08-25 15:09:51 +02:00
protected virtual IEnumerable<Type> GetComponentTypes() => Current.PluginManager.ResolveTypes<IUmbracoComponent>();
protected virtual IProfiler GetProfiler() => new LogProfiler(Logger);
protected virtual CacheHelper GetApplicationCache() => new CacheHelper(
// we need to have the dep clone runtime cache provider to ensure
// all entities are cached properly (cloned in and cloned out)
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()),
new StaticCacheProvider(),
// we have no request based cache when not running in web-based context
new NullCacheProvider(),
new IsolatedRuntimeCache(type =>
// we need to have the dep clone runtime cache provider to ensure
// all entities are cached properly (cloned in and cloned out)
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
#endregion
#region Core
// cannot run if the db is not there
// tries to connect to db (if configured)
private void EnsureDatabaseConnection()
{
if (Current.ApplicationContext.IsConfigured == false) return;
if (Current.ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return;
2016-08-25 15:09:51 +02:00
for (var i = 0; i < 5; i++)
{
if (Current.ApplicationContext.DatabaseContext.CanConnect) return;
2016-08-25 15:09:51 +02:00
Thread.Sleep(1000);
}
throw new UmbracoStartupFailedException("Umbraco cannot start: a connection string is configured but Umbraco could not connect to the database.");
}
/// <summary>
/// Special method to extend the use of Umbraco by enabling the consumer to overwrite
/// the absolute path to the root of an Umbraco site/solution, which is used for stuff
/// like Umbraco.Core.IO.IOHelper.MapPath etc.
/// </summary>
/// <param name="rootPath">Absolute Umbraco root path.</param>
protected virtual void InitializeApplicationRootPath(string rootPath)
{
IOHelper.SetRootDirectory(rootPath);
}
#endregion
// FIXME everything below needs to be sorted out!
protected ServiceContainer Container => Current.Container; // fixme kill
public virtual IRuntime Initialize()
{
if (_isInitialized)
throw new InvalidOperationException("The boot manager has already been initialized");
//ApplicationCache = Container.GetInstance<CacheHelper>(); //GetApplicationCache();
_timer = ProfilingLogger.TraceDuration<CoreRuntime>(
string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.GetSemanticVersion().ToSemanticString(), NetworkHelper.MachineName),
"Umbraco application startup complete");
//now we need to call the initialize methods
//Create a 'child'container which is a copy of all of the current registrations and begin a sub scope for it
// this child container will be used to manage the application event handler instances and the scope will be
// completed at the end of the boot process to allow garbage collection
// using (Container.BeginScope()) { } // fixme - throws
_appStartupEvtContainer = Container.Clone(); // fixme - but WHY clone? because *then* it has its own scope?! bah ;-(
//using (_appStartupEvtContainer.BeginScope()) // fixme - works wtf?
_appStartupEvtContainer.BeginScope(); // fixme - but then attend to end a scope before all child scopes are completed wtf?!
_appStartupEvtContainer.RegisterCollection<PerScopeLifetime>(factory => factory.GetInstance<PluginManager>().ResolveApplicationStartupHandlers());
// fixme - parallel? what about our dependencies?
Parallel.ForEach(_appStartupEvtContainer.GetAllInstances<IApplicationEventHandler>(), x =>
{
try
{
using (ProfilingLogger.DebugDuration<CoreRuntime>(
$"Executing {x.GetType()} in ApplicationInitialized",
$"Executed {x.GetType()} in ApplicationInitialized",
//only log if more than 150ms
150))
{
x.OnApplicationInitialized(UmbracoApplication, Current.ApplicationContext);
2016-08-25 15:09:51 +02:00
}
}
catch (Exception ex)
{
ProfilingLogger.Logger.Error<CoreRuntime>("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex);
throw;
}
});
_isInitialized = true;
return this;
}
/// <summary>
/// Fires after initialization and calls the callback to allow for customizations to occur &
/// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called
/// </summary>
/// <param name="afterStartup"></param>
/// <returns></returns>
public virtual IRuntime Startup(Action<ApplicationContext> afterStartup)
{
if (_isStarted)
throw new InvalidOperationException("The boot manager has already been initialized");
//call OnApplicationStarting of each application events handler
Parallel.ForEach(_appStartupEvtContainer.GetAllInstances<IApplicationEventHandler>(), x =>
{
try
{
using (ProfilingLogger.DebugDuration<CoreRuntime>(
$"Executing {x.GetType()} in ApplicationStarting",
$"Executed {x.GetType()} in ApplicationStarting",
//only log if more than 150ms
150))
{
x.OnApplicationStarting(UmbracoApplication, Current.ApplicationContext);
2016-08-25 15:09:51 +02:00
}
}
catch (Exception ex)
{
ProfilingLogger.Logger.Error<CoreRuntime>("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex);
throw;
}
});
if (afterStartup != null)
{
afterStartup(ApplicationContext.Current);
}
_isStarted = true;
return this;
}
/// <summary>
/// Fires after startup and calls the callback once customizations are locked
/// </summary>
/// <param name="afterComplete"></param>
/// <returns></returns>
public virtual IRuntime Complete(Action<ApplicationContext> afterComplete)
{
if (_isComplete)
throw new InvalidOperationException("The boot manager has already been completed");
Complete2();
2016-08-25 15:09:51 +02:00
//Here we need to make sure the db can be connected to
EnsureDatabaseConnection();
//This is a special case for the user service, we need to tell it if it's an upgrade, if so we need to ensure that
// exceptions are bubbled up if a user is attempted to be persisted during an upgrade (i.e. when they auth to login)
((UserService) Current.ApplicationContext.Services.UserService).IsUpgrading = true;
2016-08-25 15:09:51 +02:00
//call OnApplicationStarting of each application events handler
Parallel.ForEach(_appStartupEvtContainer.GetAllInstances<IApplicationEventHandler>(), x =>
{
try
{
using (ProfilingLogger.DebugDuration<CoreRuntime>(
$"Executing {x.GetType()} in ApplicationStarted",
$"Executed {x.GetType()} in ApplicationStarted",
//only log if more than 150ms
150))
{
x.OnApplicationStarted(UmbracoApplication, Current.ApplicationContext);
2016-08-25 15:09:51 +02:00
}
}
catch (Exception ex)
{
ProfilingLogger.Logger.Error<CoreRuntime>("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex);
throw;
}
});
//end the current scope which was created to intantiate all of the startup handlers,
//this will dispose them if they're IDisposable
_appStartupEvtContainer.EndCurrentScope();
//NOTE: DO NOT Dispose this cloned container since it will also dispose of any instances
// resolved from the parent container
_appStartupEvtContainer = null;
if (afterComplete != null)
{
afterComplete(ApplicationContext.Current);
}
_isComplete = true;
// we're ready to serve content!
Current.ApplicationContext.IsReady = true;
2016-08-25 15:09:51 +02:00
//stop the timer and log the output
_timer.Dispose();
return this;
}
protected virtual void Complete2()
{ }
2016-08-25 15:09:51 +02:00
}
}