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;
|
|
|
|
|
|
|
2016-08-31 16:48:57 +02:00
|
|
|
|
// 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?!
|
|
|
|
|
|
|
2016-08-31 16:48:57 +02:00
|
|
|
|
/// <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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-08-31 16:48:57 +02:00
|
|
|
|
/// <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
|
|
|
|
|
|
|
2016-08-31 16:48:57 +02:00
|
|
|
|
// 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()
|
|
|
|
|
|
{
|
2016-08-31 16:48:57 +02:00
|
|
|
|
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++)
|
|
|
|
|
|
{
|
2016-08-31 16:48:57 +02:00
|
|
|
|
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))
|
|
|
|
|
|
{
|
2016-08-31 16:48:57 +02:00
|
|
|
|
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))
|
|
|
|
|
|
{
|
2016-08-31 16:48:57 +02:00
|
|
|
|
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");
|
|
|
|
|
|
|
2016-09-01 11:25:00 +02:00
|
|
|
|
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)
|
2016-08-31 16:48:57 +02:00
|
|
|
|
((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))
|
|
|
|
|
|
{
|
2016-08-31 16:48:57 +02:00
|
|
|
|
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!
|
2016-08-31 16:48:57 +02:00
|
|
|
|
Current.ApplicationContext.IsReady = true;
|
2016-08-25 15:09:51 +02:00
|
|
|
|
|
|
|
|
|
|
//stop the timer and log the output
|
|
|
|
|
|
_timer.Dispose();
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-01 11:25:00 +02:00
|
|
|
|
protected virtual void Complete2()
|
|
|
|
|
|
{ }
|
2016-08-25 15:09:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|