2016-08-25 15:09:51 +02:00
using System ;
using System.Collections.Generic ;
2019-01-07 10:43:28 +01:00
using System.Diagnostics ;
2019-11-11 18:56:14 +11:00
using System.IO ;
2016-08-25 15:09:51 +02:00
using Umbraco.Core.Cache ;
2017-05-30 15:46:25 +02:00
using Umbraco.Core.Composing ;
2017-12-28 09:27:57 +01:00
using Umbraco.Core.Configuration ;
2016-08-25 15:09:51 +02:00
using Umbraco.Core.Exceptions ;
2019-11-20 13:38:41 +01:00
using Umbraco.Core.Hosting ;
2016-08-25 15:09:51 +02:00
using Umbraco.Core.IO ;
using Umbraco.Core.Logging ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Persistence ;
2016-10-17 15:16:07 +02:00
using Umbraco.Core.Persistence.Mappers ;
2016-08-25 15:09:51 +02:00
2017-12-28 09:27:57 +01:00
namespace Umbraco.Core.Runtime
2016-08-25 15:09:51 +02:00
{
/// <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
{
2019-01-03 21:00:28 +01:00
private ComponentCollection _components ;
2018-11-28 12:59:40 +01:00
private IFactory _factory ;
2020-03-25 15:06:22 +11:00
private readonly RuntimeState _state ;
2019-11-20 10:20:20 +01:00
private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker ;
2020-03-17 16:26:56 +01:00
private readonly IGlobalSettings _globalSettings ;
private readonly IConnectionStrings _connectionStrings ;
2016-08-25 15:09:51 +02:00
2019-11-15 11:07:37 +01:00
2020-03-25 15:06:22 +11:00
/// <summary>
/// Constructor
/// </summary>
/// <param name="configs"></param>
/// <param name="umbracoVersion"></param>
/// <param name="ioHelper"></param>
/// <param name="logger"></param>
/// <param name="profiler"></param>
/// <param name="umbracoBootPermissionChecker"></param>
/// <param name="hostingEnvironment"></param>
/// <param name="backOfficeInfo"></param>
/// <param name="dbProviderFactoryCreator"></param>
/// <param name="mainDom"></param>
/// <param name="typeFinder"></param>
2019-12-12 12:55:17 +01:00
public CoreRuntime (
Configs configs ,
IUmbracoVersion umbracoVersion ,
IIOHelper ioHelper ,
ILogger logger ,
IProfiler profiler ,
IUmbracoBootPermissionChecker umbracoBootPermissionChecker ,
IHostingEnvironment hostingEnvironment ,
IBackOfficeInfo backOfficeInfo ,
IDbProviderFactoryCreator dbProviderFactoryCreator ,
2020-03-13 14:43:41 +11:00
IMainDom mainDom ,
ITypeFinder typeFinder )
2019-11-15 11:07:37 +01:00
{
IOHelper = ioHelper ;
Configs = configs ;
UmbracoVersion = umbracoVersion ;
2019-11-20 10:20:20 +01:00
Profiler = profiler ;
2019-11-20 13:38:41 +01:00
HostingEnvironment = hostingEnvironment ;
2019-11-21 14:19:56 +01:00
BackOfficeInfo = backOfficeInfo ;
2019-12-12 08:11:23 +01:00
DbProviderFactoryCreator = dbProviderFactoryCreator ;
2019-11-20 10:20:20 +01:00
_umbracoBootPermissionChecker = umbracoBootPermissionChecker ;
2019-11-15 11:07:37 +01:00
Logger = logger ;
2020-01-06 14:59:17 +01:00
MainDom = mainDom ;
2020-03-13 14:43:41 +11:00
TypeFinder = typeFinder ;
2020-01-06 14:59:17 +01:00
2020-03-17 16:26:56 +01:00
_globalSettings = Configs . Global ( ) ;
_connectionStrings = configs . ConnectionStrings ( ) ;
2019-11-15 11:07:37 +01:00
// runtime state
// beware! must use '() => _factory.GetInstance<T>()' and NOT '_factory.GetInstance<T>'
// as the second one captures the current value (null) and therefore fails
2020-03-25 15:06:22 +11:00
_state = new RuntimeState ( Logger , Configs . Global ( ) , UmbracoVersion , BackOfficeInfo )
2019-11-15 11:07:37 +01:00
{
Level = RuntimeLevel . Boot
} ;
}
2018-11-26 16:54:32 +01:00
/// <summary>
/// Gets the logger.
/// </summary>
2019-11-18 14:11:50 +01:00
protected ILogger Logger { get ; }
2018-11-26 16:54:32 +01:00
2019-11-21 14:19:56 +01:00
protected IBackOfficeInfo BackOfficeInfo { get ; }
2020-03-25 15:06:22 +11:00
2019-12-12 08:11:23 +01:00
public IDbProviderFactoryCreator DbProviderFactoryCreator { get ; }
2019-11-21 14:19:56 +01:00
2018-12-21 16:25:16 +01:00
/// <summary>
/// Gets the profiler.
/// </summary>
2020-03-25 15:06:22 +11:00
protected IProfiler Profiler { get ; }
2018-12-21 16:25:16 +01:00
2018-11-26 16:54:32 +01:00
/// <summary>
/// Gets the profiling logger.
/// </summary>
protected IProfilingLogger ProfilingLogger { get ; private set ; }
2019-11-11 16:07:47 +11:00
/// <summary>
/// Gets the <see cref="ITypeFinder"/>
/// </summary>
2020-03-13 14:43:41 +11:00
protected ITypeFinder TypeFinder { get ; }
2019-11-11 16:07:47 +11:00
2019-11-11 18:56:14 +11:00
/// <summary>
/// Gets the <see cref="IIOHelper"/>
/// </summary>
2019-11-15 11:07:37 +01:00
protected IIOHelper IOHelper { get ; }
2019-11-20 13:38:41 +01:00
protected IHostingEnvironment HostingEnvironment { get ; }
2019-11-18 14:11:50 +01:00
protected Configs Configs { get ; }
2019-11-15 11:07:37 +01:00
protected IUmbracoVersion UmbracoVersion { get ; }
2019-11-11 18:56:14 +11:00
2018-11-27 10:37:33 +01:00
/// <inheritdoc />
public IRuntimeState State = > _state ;
2020-03-25 15:06:22 +11:00
public IMainDom MainDom { get ; }
2019-12-04 17:18:15 +11:00
2016-08-31 16:48:57 +02:00
/// <inheritdoc/>
2020-03-25 15:06:22 +11:00
public virtual IFactory Configure ( IRegister register )
2016-08-25 15:09:51 +02:00
{
2020-03-24 11:53:56 +11:00
if ( register is null ) throw new ArgumentNullException ( nameof ( register ) ) ;
2018-11-26 16:54:32 +01:00
// create and register the essential services
// ie the bare minimum required to boot
2016-08-25 15:09:51 +02:00
// 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.
2019-11-14 11:47:34 +01:00
var umbracoVersion = new UmbracoVersion ( ) ;
2019-11-20 10:20:20 +01:00
var profilingLogger = ProfilingLogger = new ProfilingLogger ( Logger , Profiler ) ;
2018-12-21 16:25:16 +01:00
using ( var timer = profilingLogger . TraceDuration < CoreRuntime > (
2019-11-14 11:47:34 +01:00
$"Booting Umbraco {umbracoVersion.SemanticVersion.ToSemanticString()}." ,
2016-09-11 19:57:33 +02:00
"Booted." ,
"Boot failed." ) )
2016-09-01 19:06:08 +02:00
{
2019-11-20 10:20:20 +01:00
Logger . Info < CoreRuntime > ( "Booting Core" ) ;
2019-11-15 11:07:37 +01:00
Logger . Debug < CoreRuntime > ( "Runtime: {Runtime}" , GetType ( ) . FullName ) ;
2018-12-21 16:25:16 +01:00
// application environment
ConfigureUnhandledException ( ) ;
2020-03-25 15:06:22 +11:00
return _factory = Configure ( register , timer ) ;
2018-12-21 16:25:16 +01:00
}
}
/// <summary>
2020-03-25 15:06:22 +11:00
/// Configure the runtime within a timer.
2018-12-21 16:25:16 +01:00
/// </summary>
2020-03-25 15:06:22 +11:00
private IFactory Configure ( IRegister register , DisposableTimer timer )
2018-12-21 16:25:16 +01:00
{
2020-03-24 11:53:56 +11:00
if ( register is null ) throw new ArgumentNullException ( nameof ( register ) ) ;
if ( timer is null ) throw new ArgumentNullException ( nameof ( timer ) ) ;
2018-12-21 16:25:16 +01:00
Composition composition = null ;
2020-03-25 15:06:22 +11:00
IFactory factory = null ;
2018-12-21 16:25:16 +01:00
try
{
2020-03-25 15:06:22 +11:00
2018-12-21 16:25:16 +01:00
2019-01-23 21:22:49 +01:00
// run handlers
RuntimeOptions . DoRuntimeBoot ( ProfilingLogger ) ;
2018-12-21 16:25:16 +01:00
// application caches
var appCaches = GetAppCaches ( ) ;
// database factory
var databaseFactory = GetDatabaseFactory ( ) ;
2019-11-08 14:26:06 +11:00
// type finder/loader
2019-11-20 13:38:41 +01:00
var typeLoader = new TypeLoader ( IOHelper , TypeFinder , appCaches . RuntimeCache , new DirectoryInfo ( HostingEnvironment . LocalTempPath ) , ProfilingLogger ) ;
2018-11-26 16:54:32 +01:00
2018-12-21 16:25:16 +01:00
// create the composition
2019-12-04 14:03:39 +01:00
composition = new Composition ( register , typeLoader , ProfilingLogger , _state , Configs , IOHelper , appCaches ) ;
2020-03-24 14:48:32 +11:00
composition . RegisterEssentials ( Logger , Profiler , ProfilingLogger , MainDom , appCaches , databaseFactory , typeLoader , _state , TypeFinder , IOHelper , UmbracoVersion , DbProviderFactoryCreator , HostingEnvironment , BackOfficeInfo ) ;
2018-11-26 16:54:32 +01:00
2020-03-25 15:06:22 +11:00
// register ourselves (TODO: Should we put this in RegisterEssentials?)
composition . Register < IRuntime > ( _ = > this , Lifetime . Singleton ) ;
2019-01-23 21:22:49 +01:00
// determine our runtime level
2018-12-21 16:25:16 +01:00
DetermineRuntimeLevel ( databaseFactory , ProfilingLogger ) ;
2018-11-26 16:54:32 +01:00
2019-01-03 21:00:28 +01:00
// get composers, and compose
var composerTypes = ResolveComposerTypes ( typeLoader ) ;
2019-12-18 11:08:15 +11:00
IEnumerable < Attribute > enableDisableAttributes ;
using ( ProfilingLogger . DebugDuration < CoreRuntime > ( "Scanning enable/disable composer attributes" ) )
{
enableDisableAttributes = typeLoader . GetAssemblyAttributes ( typeof ( EnableComposerAttribute ) , typeof ( DisableComposerAttribute ) ) ;
2020-01-06 14:59:17 +01:00
}
2019-12-18 11:08:15 +11:00
2019-10-09 10:22:39 +02:00
var composers = new Composers ( composition , composerTypes , enableDisableAttributes , ProfilingLogger ) ;
2019-01-03 21:00:28 +01:00
composers . Compose ( ) ;
2018-11-26 16:54:32 +01:00
2018-12-21 16:25:16 +01:00
// create the factory
2020-03-25 15:06:22 +11:00
factory = composition . CreateFactory ( ) ;
2018-12-21 16:25:16 +01:00
}
catch ( Exception e )
{
var bfe = e as BootFailedException ? ? new BootFailedException ( "Boot failed." , e ) ;
2019-01-07 10:43:28 +01:00
if ( _state ! = null )
{
_state . Level = RuntimeLevel . BootFailed ;
_state . BootFailedException = bfe ;
}
2019-03-06 18:10:43 +01:00
timer ? . Fail ( exception : bfe ) ; // be sure to log the exception - even if we repeat ourselves
2018-12-21 16:25:16 +01:00
// if something goes wrong above, we may end up with no factory
// meaning nothing can get the runtime state, etc - so let's try
// to make sure we have a factory
2020-03-25 15:06:22 +11:00
if ( factory = = null )
2016-09-11 19:57:33 +02:00
{
2018-12-21 16:25:16 +01:00
try
2018-11-29 12:27:16 +01:00
{
2020-03-25 15:06:22 +11:00
factory = composition ? . CreateFactory ( ) ;
2018-11-29 12:27:16 +01:00
}
2018-12-21 16:25:16 +01:00
catch { /* yea */ }
2016-09-11 19:57:33 +02:00
}
2018-12-21 16:25:16 +01:00
2019-01-07 10:43:28 +01:00
Debugger . Break ( ) ;
2018-12-21 16:25:16 +01:00
// throwing here can cause w3wp to hard-crash and we want to avoid it.
// instead, we're logging the exception and setting level to BootFailed.
// various parts of Umbraco such as UmbracoModule and UmbracoDefaultOwinStartup
// understand this and will nullify themselves, while UmbracoModule will
// throw a BootFailedException for every requests.
2016-09-11 19:57:33 +02:00
}
2018-11-29 10:35:16 +01:00
2020-03-25 15:06:22 +11:00
return factory ;
}
public void Start ( )
{
// throws if not full-trust
_umbracoBootPermissionChecker . ThrowIfNotPermissions ( ) ;
ConfigureApplicationRootPath ( ) ;
// run handlers
RuntimeOptions . DoRuntimeEssentials ( _factory ) ;
var hostingEnvironmentLifetime = _factory . TryGetInstance < IHostingEnvironmentLifetime > ( ) ;
if ( hostingEnvironmentLifetime = = null )
throw new InvalidOperationException ( $"An instance of {typeof(IHostingEnvironmentLifetime)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(Start)}" ) ;
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
AcquireMainDom ( MainDom , _factory . GetInstance < IHostingEnvironmentLifetime > ( ) ) ;
// create & initialize the components
_components = _factory . GetInstance < ComponentCollection > ( ) ;
_components . Initialize ( ) ;
2016-09-11 19:57:33 +02:00
}
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
protected virtual void ConfigureUnhandledException ( )
2018-11-19 22:02:51 +11:00
{
//take care of unhandled exceptions - there is nothing we can do to
// prevent the launch process to go down but at least we can try
// and log the exception
AppDomain . CurrentDomain . UnhandledException + = ( _ , args ) = >
{
var exception = ( Exception ) args . ExceptionObject ;
var isTerminating = args . IsTerminating ; // always true?
var msg = "Unhandled exception in AppDomain" ;
if ( isTerminating ) msg + = " (terminating)" ;
msg + = "." ;
2018-11-26 16:54:32 +01:00
Logger . Error < CoreRuntime > ( exception , msg ) ;
2018-11-19 22:02:51 +11:00
} ;
}
2018-11-26 16:54:32 +01:00
protected virtual void ConfigureApplicationRootPath ( )
{
var path = GetApplicationRootPath ( ) ;
if ( string . IsNullOrWhiteSpace ( path ) = = false )
2019-11-13 21:00:54 +01:00
IOHelper . SetRootDirectory ( path ) ;
2018-11-26 16:54:32 +01:00
}
2018-11-19 22:02:51 +11:00
2020-03-25 15:06:22 +11:00
private bool AcquireMainDom ( IMainDom mainDom , IHostingEnvironmentLifetime hostingEnvironmentLifetime )
2016-09-11 19:57:33 +02:00
{
2018-11-26 16:54:32 +01:00
using ( var timer = ProfilingLogger . DebugDuration < CoreRuntime > ( "Acquiring MainDom." , "Acquired." ) )
2016-09-11 19:57:33 +02:00
{
try
2016-09-01 19:06:08 +02:00
{
2020-03-25 15:06:22 +11:00
return mainDom . Acquire ( hostingEnvironmentLifetime ) ;
2016-09-01 19:06:08 +02:00
}
2016-09-11 19:57:33 +02:00
catch
{
2019-03-06 11:56:03 +01:00
timer ? . Fail ( ) ;
2016-09-11 19:57:33 +02:00
throw ;
}
}
}
2016-09-01 19:06:08 +02:00
2016-10-13 21:08:07 +02:00
// internal for tests
2018-12-17 18:52:43 +01:00
internal void DetermineRuntimeLevel ( IUmbracoDatabaseFactory databaseFactory , IProfilingLogger profilingLogger )
2016-09-11 19:57:33 +02:00
{
2018-12-17 18:52:43 +01:00
using ( var timer = profilingLogger . DebugDuration < CoreRuntime > ( "Determining runtime level." , "Determined." ) )
2016-09-11 19:57:33 +02:00
{
try
2016-09-01 19:06:08 +02:00
{
2018-12-17 18:52:43 +01:00
_state . DetermineRuntimeLevel ( databaseFactory , profilingLogger ) ;
2018-03-30 10:16:21 +02:00
2019-01-10 14:03:25 +01:00
profilingLogger . Debug < CoreRuntime > ( "Runtime level: {RuntimeLevel} - {RuntimeLevelReason}" , _state . Level , _state . Reason ) ;
2018-03-30 10:16:21 +02:00
if ( _state . Level = = RuntimeLevel . Upgrade )
{
2018-12-17 18:52:43 +01:00
profilingLogger . Debug < CoreRuntime > ( "Configure database factory for upgrades." ) ;
2018-11-26 16:54:32 +01:00
databaseFactory . ConfigureForUpgrade ( ) ;
2018-03-30 10:16:21 +02:00
}
2016-09-01 19:06:08 +02:00
}
2016-09-11 19:57:33 +02:00
catch
2016-09-01 19:06:08 +02:00
{
2018-11-26 16:54:32 +01:00
_state . Level = RuntimeLevel . BootFailed ;
2019-01-10 14:03:25 +01:00
_state . Reason = RuntimeLevelReason . BootFailedOnException ;
2019-02-05 20:01:20 +01:00
timer ? . Fail ( ) ;
2016-09-11 19:57:33 +02:00
throw ;
2016-09-01 19:06:08 +02:00
}
2016-09-11 19:57:33 +02:00
}
}
2016-09-01 19:06:08 +02:00
2019-01-03 21:00:28 +01:00
private IEnumerable < Type > ResolveComposerTypes ( TypeLoader typeLoader )
2016-09-11 19:57:33 +02:00
{
2019-01-03 21:00:28 +01:00
using ( var timer = ProfilingLogger . TraceDuration < CoreRuntime > ( "Resolving composer types." , "Resolved." ) )
2016-09-11 19:57:33 +02:00
{
try
{
2019-01-03 21:00:28 +01:00
return GetComposerTypes ( typeLoader ) ;
2016-09-11 19:57:33 +02:00
}
catch
{
2019-02-05 20:01:20 +01:00
timer ? . Fail ( ) ;
2016-09-11 19:57:33 +02:00
throw ;
}
2016-09-01 19:06:08 +02:00
}
2016-08-25 15:09:51 +02:00
}
2016-09-01 19:06:08 +02:00
/// <inheritdoc/>
2016-08-25 15:09:51 +02:00
public virtual void Terminate ( )
{
2019-01-24 13:54:54 +01:00
_components ? . Terminate ( ) ;
2016-08-25 15:09:51 +02:00
}
2018-11-26 16:54:32 +01:00
#region Getters
2016-08-25 15:09:51 +02:00
2018-11-26 16:54:32 +01:00
// getters can be implemented by runtimes inheriting from CoreRuntime
2016-08-25 15:09:51 +02:00
2018-11-26 16:54:32 +01:00
/// <summary>
2019-01-03 21:00:28 +01:00
/// Gets all composer types.
2018-11-26 16:54:32 +01:00
/// </summary>
2019-01-03 21:00:28 +01:00
protected virtual IEnumerable < Type > GetComposerTypes ( TypeLoader typeLoader )
= > typeLoader . GetTypes < IComposer > ( ) ;
2016-08-25 15:09:51 +02:00
2018-11-26 16:54:32 +01:00
/// <summary>
/// Gets the application caches.
/// </summary>
2019-01-17 08:34:29 +01:00
protected virtual AppCaches GetAppCaches ( )
2018-11-26 16:54:32 +01:00
{
// need the deep clone runtime cache provider to ensure entities are cached properly, ie
// are cloned in and cloned out - no request-based cache here since no web-based context,
2019-01-22 18:03:39 -05:00
// is overridden by the web runtime
2016-08-31 16:48:57 +02:00
2019-01-17 08:34:29 +01:00
return new AppCaches (
2020-03-19 08:53:18 +01:00
new DeepCloneAppCache ( new ObjectCacheAppCache ( ) ) ,
2019-01-17 11:01:23 +01:00
NoAppCache . Instance ,
2020-03-19 08:53:18 +01:00
new IsolatedCaches ( type = > new DeepCloneAppCache ( new ObjectCacheAppCache ( ) ) ) ) ;
2018-11-26 16:54:32 +01:00
}
2016-08-25 15:09:51 +02:00
2016-09-08 18:43:58 +02:00
// by default, returns null, meaning that Umbraco should auto-detect the application root path.
// override and return the absolute path to the Umbraco site/solution, if needed
2018-11-26 16:54:32 +01:00
protected virtual string GetApplicationRootPath ( )
= > null ;
2016-08-25 15:09:51 +02:00
2018-11-27 10:37:33 +01:00
/// <summary>
/// Gets the database factory.
/// </summary>
/// <remarks>This is strictly internal, for tests only.</remarks>
protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory ( )
2020-03-17 16:26:56 +01:00
= > new UmbracoDatabaseFactory ( Logger , _globalSettings , _connectionStrings , new Lazy < IMapperCollection > ( ( ) = > _factory . GetInstance < IMapperCollection > ( ) ) , DbProviderFactoryCreator ) ;
2018-11-27 10:37:33 +01:00
2019-01-07 10:43:28 +01:00
2016-08-25 15:09:51 +02:00
#endregion
2020-03-25 15:06:22 +11:00
2016-08-25 15:09:51 +02:00
}
}