using System; using System.IO; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Threading; using AutoMapper; using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; 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.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Publishing; using Umbraco.Core.Macros; using Umbraco.Core.Manifest; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Core.Strings; namespace Umbraco.Core { /// /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application /// /// /// This does not provide any startup functionality relating to web objects /// public class CoreBootManager : IBootManager { private ServiceContainer _appStartupEvtContainer; protected ProfilingLogger ProfilingLogger { get; private set; } private DisposableTimer _timer; protected PluginManager PluginManager { get; private set; } private CacheHelper _cacheHelper; private bool _isInitialized = false; private bool _isStarted = false; private bool _isComplete = false; private readonly UmbracoApplicationBase _umbracoApplication; protected ApplicationContext ApplicationContext { get; private set; } protected CacheHelper ApplicationCache { get; private set; } protected UmbracoApplicationBase UmbracoApplication { get { return _umbracoApplication; } } internal ServiceContainer Container { get { return _umbracoApplication.Container; } } protected IServiceProvider ServiceProvider { get; private set; } public CoreBootManager(UmbracoApplicationBase umbracoApplication) { if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); _umbracoApplication = umbracoApplication; } internal CoreBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) { if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); if (logger == null) throw new ArgumentNullException("logger"); _umbracoApplication = umbracoApplication; ProfilingLogger = logger; } public virtual IBootManager Initialize() { if (_isInitialized) throw new InvalidOperationException("The boot manager has already been initialized"); ProfilingLogger = ProfilingLogger?? new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); _timer = ProfilingLogger.TraceDuration( string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.GetSemanticVersion().ToSemanticString(), NetworkHelper.MachineName), "Umbraco application startup complete"); _cacheHelper = CreateApplicationCache(); ServiceProvider = new ActivatorServiceProvider(); PluginManager.Current = PluginManager = new PluginManager(ServiceProvider, _cacheHelper.RuntimeCache, ProfilingLogger, true); ApplicationCache = CreateApplicationCache(); ConfigureCoreServices(Container); //set the singleton resolved from the core container ApplicationContext.Current = ApplicationContext = Container.GetInstance(); //TODO: Remove these for v8! LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors(); //TODO: Make this as part of the db ctor! Database.Mapper = new PetaPocoMapper(); //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 _appStartupEvtContainer = Container.CreateChildContainer(); _appStartupEvtContainer.BeginScope(); _appStartupEvtContainer.RegisterCollection(PluginManager.ResolveApplicationStartupHandlers()); //build up standard IoC services ConfigureServices(Container); InitializeResolvers(); InitializeModelMappers(); //now we need to call the initialize methods //TODO: Make sure to try/catch the OnApplicationInitialized!! Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => x.OnApplicationInitialized(UmbracoApplication, ApplicationContext)); _isInitialized = true; return this; } /// /// Build the core container which contains all core things requird to build an app context /// private void ConfigureCoreServices(ServiceContainer container) { container.Register(factory => container); container.Register(factory => _umbracoApplication.Logger, new PerContainerLifetime()); container.Register(factory => ProfilingLogger.Profiler, new PerContainerLifetime()); container.Register(factory => ProfilingLogger, new PerContainerLifetime()); var settings = UmbracoConfig.For.UmbracoSettings(); container.Register(factory => settings); container.Register(factory => settings.Content); container.Register(factory => settings.RequestHandler); //TODO: Add the other config areas... container.Register(factory => _cacheHelper, new PerContainerLifetime()); container.Register(factory => _cacheHelper.RuntimeCache, new PerContainerLifetime()); container.Register(); container.Register(factory => PluginManager, new PerContainerLifetime()); container.Register(factory => new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, factory.GetInstance())); container.Register(factory => GetDbContext(factory), new PerContainerLifetime()); container.Register(factory => SqlSyntaxProviders.CreateDefault(factory.GetInstance())); container.Register(); container.Register(); container.Register(); container.Register(factory => new MappingResolver( factory.GetInstance(), factory.GetInstance(), () => PluginManager.ResolveAssignedMapperTypes())); container.Register(); container.Register(factory => new ServiceContext( factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetAllInstances())); container.Register(new PerContainerLifetime()); container.Register(factory => FileSystemProviderManager.Current.GetFileSystemProvider()); container.Register(factory => factory.GetInstance().SqlSyntax); } /// /// Called to customize the IoC container /// /// internal virtual void ConfigureServices(ServiceContainer container) { } /// /// Creates and initializes the db context when IoC requests it /// /// /// private DatabaseContext GetDbContext(IServiceFactory container) { var dbCtx = new DatabaseContext( container.GetInstance(), container.GetInstance(), container.GetInstance()); //when it's first created we need to initialize it dbCtx.Initialize(); return dbCtx; } /// /// Creates the ApplicationCache based on a new instance of System.Web.Caching.Cache /// protected virtual CacheHelper CreateApplicationCache() { var cacheHelper = new CacheHelper( new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), //we have no request based cache when not running in web-based context new NullCacheProvider()); return cacheHelper; } /// /// This method allows for configuration of model mappers /// /// /// Model mappers MUST be defined on ApplicationEventHandler instances with the interface IMapperConfiguration. /// This allows us to search for less types on startup. /// protected void InitializeModelMappers() { Mapper.Initialize(configuration => { //foreach (var m in ApplicationEventsResolver.Current.ApplicationEventHandlers.OfType()) foreach (var m in _appStartupEvtContainer.GetAllInstances().OfType()) { m.ConfigureMappings(configuration, ApplicationContext); } }); } /// /// Creates the application's IProfiler /// protected virtual IProfiler CreateProfiler() { return new LogProfiler(ProfilingLogger.Logger); } /// /// 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. /// /// Absolute protected virtual void InitializeApplicationRootPath(string rootPath) { IO.IOHelper.SetRootDirectory(rootPath); } /// /// Fires after initialization and calls the callback to allow for customizations to occur & /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called /// /// /// public virtual IBootManager Startup(Action afterStartup) { if (_isStarted) throw new InvalidOperationException("The boot manager has already been initialized"); //TODO: Make sure to try/catch the OnApplicationInitialized!! //call OnApplicationStarting of each application events handler Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => x.OnApplicationStarting(UmbracoApplication, ApplicationContext)); if (afterStartup != null) { afterStartup(ApplicationContext.Current); } _isStarted = true; return this; } /// /// Fires after startup and calls the callback once customizations are locked /// /// /// public virtual IBootManager Complete(Action afterComplete) { if (_isComplete) throw new InvalidOperationException("The boot manager has already been completed"); FreezeResolution(); //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) ApplicationContext.Services.UserService).IsUpgrading = true; //call OnApplicationStarting of each application events handler //TODO: Make sure to try/catch the OnApplicationInitialized!! Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => x.OnApplicationStarted(UmbracoApplication, ApplicationContext)); //end the current scope which was created to intantiate all of the startup handlers _appStartupEvtContainer.EndCurrentScope(); if (afterComplete != null) { afterComplete(ApplicationContext.Current); } _isComplete = true; // we're ready to serve content! ApplicationContext.IsReady = true; //stop the timer and log the output _timer.Dispose(); return this; } /// /// We cannot continue if the db cannot be connected to /// private void EnsureDatabaseConnection() { if (ApplicationContext.IsConfigured == false) return; if (ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return; //try now if (ApplicationContext.DatabaseContext.CanConnect) return; var currentTry = 0; while (currentTry < 5) { //first wait, then retry Thread.Sleep(1000); if (ApplicationContext.DatabaseContext.CanConnect) break; currentTry++; } if (currentTry == 5) { throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database."); } } /// /// Freeze resolution to not allow Resolvers to be modified /// protected virtual void FreezeResolution() { Resolution.Freeze(); } /// /// Create the resolvers /// protected virtual void InitializeResolvers() { var manifestParser = new ManifestParser(ProfilingLogger.Logger, new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), _cacheHelper.RuntimeCache); var manifestBuilder = new ManifestBuilder(_cacheHelper.RuntimeCache, manifestParser); PropertyEditorResolver.Current = new PropertyEditorResolver( Container, ProfilingLogger.Logger, () => PluginManager.ResolvePropertyEditors(), manifestBuilder); ParameterEditorResolver.Current = new ParameterEditorResolver( Container, ProfilingLogger.Logger, () => PluginManager.ResolveParameterEditors(), manifestBuilder); //setup the validators resolver with our predefined validators ValidatorsResolver.Current = new ValidatorsResolver( ServiceProvider, ProfilingLogger.Logger, new[] { new Lazy(() => typeof (RequiredManifestValueValidator)), new Lazy(() => typeof (RegexValidator)), new Lazy(() => typeof (DelimitedManifestValueValidator)), new Lazy(() => typeof (EmailValidator)), new Lazy(() => typeof (IntegerValidator)), new Lazy(() => typeof (DecimalValidator)), }); //by default we'll use the db server registrar unless the developer has the legacy // dist calls enabled, in which case we'll use the config server registrar if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) { ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar(UmbracoConfig.For.UmbracoSettings())); } else { ServerRegistrarResolver.Current = new ServerRegistrarResolver( new DatabaseServerRegistrar( new Lazy(() => ApplicationContext.Services.ServerRegistrationService), new DatabaseServerRegistrarOptions())); } //by default we'll use the database server messenger with default options (no callbacks), // this will be overridden in the web startup ServerMessengerResolver.Current = new ServerMessengerResolver( Container, factory => new DatabaseServerMessenger(ApplicationContext, true, new DatabaseServerMessengerOptions())); MappingResolver.Current = new MappingResolver( Container, ProfilingLogger.Logger, () => PluginManager.ResolveAssignedMapperTypes()); //RepositoryResolver.Current = new RepositoryResolver( // new RepositoryFactory(ApplicationCache)); CacheRefreshersResolver.Current = new CacheRefreshersResolver( ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveCacheRefreshers()); MacroFieldEditorsResolver.Current = new MacroFieldEditorsResolver( ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveMacroRenderings()); PackageActionsResolver.Current = new PackageActionsResolver( ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolvePackageActions()); ActionsResolver.Current = new ActionsResolver( ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveActions()); //the database migration objects MigrationResolver.Current = new MigrationResolver( Container, ProfilingLogger.Logger, () => PluginManager.ResolveTypes()); // need to filter out the ones we dont want!! PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( Container, ProfilingLogger.Logger, PluginManager.ResolveTypes()); // use the new DefaultShortStringHelper ShortStringHelperResolver.Current = new ShortStringHelperResolver(Container, factory => new DefaultShortStringHelper(factory.GetInstance()).WithDefaultConfig()); UrlSegmentProviderResolver.Current = new UrlSegmentProviderResolver( Container, ProfilingLogger.Logger, typeof(DefaultUrlSegmentProvider)); // by default, no factory is activated PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(Container); } ///// ///// An IoC lifetime that will dispose instances at the end of the bootup sequence ///// //private class BootManagerLifetime : ILifetime //{ // public BootManagerLifetime(UmbracoApplicationBase appBase) // { // appBase.ApplicationStarted += appBase_ApplicationStarted; // } // void appBase_ApplicationStarted(object sender, EventArgs e) // { // throw new NotImplementedException(); // } // private object _instance; // /// // /// Returns a service instance according to the specific lifetime characteristics. // /// // /// The function delegate used to create a new service instance. // /// The of the current service request. // /// The requested services instance. // public object GetInstance(Func createInstance, Scope scope) // { // if (_instance == null) // { // _instance = createInstance(); // var disposable = _instance as IDisposable; // if (disposable != null) // { // if (scope == null) // { // throw new InvalidOperationException("Attempt to create an disposable object without a current scope."); // } // scope.TrackInstance(disposable); // } // } // return createInstance; // } //} } }