using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Runtime { /// /// Represents the Core Umbraco runtime. /// /// Does not handle any of the web-related aspects of Umbraco (startup, etc). It /// should be possible to use this runtime in console apps. public class CoreRuntime : IRuntime { private ComponentCollection _components; private IFactory _factory; private readonly RuntimeState _state; private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker; private readonly IGlobalSettings _globalSettings; private readonly IConnectionStrings _connectionStrings; /// /// Constructor /// /// /// /// /// /// /// /// /// /// /// /// public CoreRuntime( Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, IUmbracoBootPermissionChecker umbracoBootPermissionChecker, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, IDbProviderFactoryCreator dbProviderFactoryCreator, IMainDom mainDom, ITypeFinder typeFinder) { IOHelper = ioHelper; Configs = configs; UmbracoVersion = umbracoVersion ; Profiler = profiler; HostingEnvironment = hostingEnvironment; BackOfficeInfo = backOfficeInfo; DbProviderFactoryCreator = dbProviderFactoryCreator; _umbracoBootPermissionChecker = umbracoBootPermissionChecker; Logger = logger; MainDom = mainDom; TypeFinder = typeFinder; _globalSettings = Configs.Global(); _connectionStrings = configs.ConnectionStrings(); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' // as the second one captures the current value (null) and therefore fails _state = new RuntimeState(Logger, Configs.Global(), UmbracoVersion, BackOfficeInfo) { Level = RuntimeLevel.Boot }; } /// /// Gets the logger. /// protected ILogger Logger { get; } protected IBackOfficeInfo BackOfficeInfo { get; } public IDbProviderFactoryCreator DbProviderFactoryCreator { get; } /// /// Gets the profiler. /// protected IProfiler Profiler { get; } /// /// Gets the profiling logger. /// protected IProfilingLogger ProfilingLogger { get; private set; } /// /// Gets the /// protected ITypeFinder TypeFinder { get; } /// /// Gets the /// protected IIOHelper IOHelper { get; } protected IHostingEnvironment HostingEnvironment { get; } protected Configs Configs { get; } protected IUmbracoVersion UmbracoVersion { get; } /// public IRuntimeState State => _state; public IMainDom MainDom { get; } /// public virtual IFactory Configure(IRegister register) { if (register is null) throw new ArgumentNullException(nameof(register)); // create and register the essential services // ie the bare minimum required to boot // 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. var umbracoVersion = new UmbracoVersion(); var profilingLogger = ProfilingLogger = new ProfilingLogger(Logger, Profiler); using (var timer = profilingLogger.TraceDuration( $"Booting Umbraco {umbracoVersion.SemanticVersion.ToSemanticString()}.", "Booted.", "Boot failed.")) { Logger.Info("Booting Core"); Logger.Debug("Runtime: {Runtime}", GetType().FullName); // application environment ConfigureUnhandledException(); return _factory = Configure(register, timer); } } /// /// Configure the runtime within a timer. /// private IFactory Configure(IRegister register, DisposableTimer timer) { if (register is null) throw new ArgumentNullException(nameof(register)); if (timer is null) throw new ArgumentNullException(nameof(timer)); Composition composition = null; IFactory factory = null; try { // run handlers RuntimeOptions.DoRuntimeBoot(ProfilingLogger); // application caches var appCaches = GetAppCaches(); // database factory var databaseFactory = GetDatabaseFactory(); // type finder/loader var typeLoader = new TypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); // create the composition composition = new Composition(register, typeLoader, ProfilingLogger, _state, Configs, IOHelper, appCaches); composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo); // register ourselves (TODO: Should we put this in RegisterEssentials?) composition.Register(_ => this, Lifetime.Singleton); // determine our runtime level DetermineRuntimeLevel(databaseFactory, ProfilingLogger); // get composers, and compose var composerTypes = ResolveComposerTypes(typeLoader); IEnumerable enableDisableAttributes; using (ProfilingLogger.DebugDuration("Scanning enable/disable composer attributes")) { enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); } var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); composers.Compose(); // create the factory factory = composition.CreateFactory(); } catch (Exception e) { var bfe = e as BootFailedException ?? new BootFailedException("Boot failed.", e); if (_state != null) { _state.Level = RuntimeLevel.BootFailed; _state.BootFailedException = bfe; } timer?.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves // 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 if (factory == null) { try { factory = composition?.CreateFactory(); } catch { /* yea */ } } Debugger.Break(); // 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. } return factory; } public void Start() { // throws if not full-trust _umbracoBootPermissionChecker.ThrowIfNotPermissions(); ConfigureApplicationRootPath(); // run handlers RuntimeOptions.DoRuntimeEssentials(_factory); var hostingEnvironmentLifetime = _factory.TryGetInstance(); 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()); // create & initialize the components _components = _factory.GetInstance(); _components.Initialize(); } protected virtual void ConfigureUnhandledException() { //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 += "."; Logger.Error(exception, msg); }; } protected virtual void ConfigureApplicationRootPath() { var path = GetApplicationRootPath(); if (string.IsNullOrWhiteSpace(path) == false) IOHelper.SetRootDirectory(path); } private bool AcquireMainDom(IMainDom mainDom, IHostingEnvironmentLifetime hostingEnvironmentLifetime) { using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { return mainDom.Acquire(hostingEnvironmentLifetime); } catch { timer?.Fail(); throw; } } } // internal for tests internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) { using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined.")) { try { _state.DetermineRuntimeLevel(databaseFactory, profilingLogger); profilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); if (_state.Level == RuntimeLevel.Upgrade) { profilingLogger.Debug("Configure database factory for upgrades."); databaseFactory.ConfigureForUpgrade(); } } catch { _state.Level = RuntimeLevel.BootFailed; _state.Reason = RuntimeLevelReason.BootFailedOnException; timer?.Fail(); throw; } } } private IEnumerable ResolveComposerTypes(TypeLoader typeLoader) { using (var timer = ProfilingLogger.TraceDuration("Resolving composer types.", "Resolved.")) { try { return GetComposerTypes(typeLoader); } catch { timer?.Fail(); throw; } } } /// public virtual void Terminate() { _components?.Terminate(); } #region Getters // getters can be implemented by runtimes inheriting from CoreRuntime /// /// Gets all composer types. /// protected virtual IEnumerable GetComposerTypes(TypeLoader typeLoader) => typeLoader.GetTypes(); /// /// Gets the application caches. /// protected virtual AppCaches GetAppCaches() { // 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, // is overridden by the web runtime return new AppCaches( new DeepCloneAppCache(new ObjectCacheAppCache()), NoAppCache.Instance, new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); } // 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 protected virtual string GetApplicationRootPath() => null; /// /// Gets the database factory. /// /// This is strictly internal, for tests only. protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory() => new UmbracoDatabaseFactory(Logger, _globalSettings, _connectionStrings, new Lazy(() => _factory.GetInstance()), DbProviderFactoryCreator); #endregion } }