using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Web; using AutoMapper; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; 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.Migrations.Upgrades.TargetVersionSix; using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Publishing; using Umbraco.Core.Macros; using Umbraco.Core.Manifest; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Core.Strings; using MigrationsVersionFourNineZero = Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero; 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 { protected ProfilingLogger ProfilingLogger { get; private set; } private DisposableTimer _timer; private bool _isInitialized = false; private bool _isStarted = false; private bool _isComplete = false; private readonly IServiceProvider _serviceProvider = new ActivatorServiceProvider(); private readonly UmbracoApplicationBase _umbracoApplication; protected ApplicationContext ApplicationContext { get; private set; } protected CacheHelper ApplicationCache { get; private set; } protected PluginManager PluginManager { get; private set; } protected UmbracoApplicationBase UmbracoApplication { get { return _umbracoApplication; } } protected IServiceProvider ServiceProvider { get { return _serviceProvider; } } 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"); InitializeLoggerResolver(); InitializeProfilerResolver(); 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"); ApplicationCache = CreateApplicationCache(); //create and set the plugin manager (I'd much prefer to not use this singleton anymore but many things are using it unfortunately and // the way that it is setup, there must only ever be one per app so without IoC it would be hard to make this not a singleton) PluginManager = new PluginManager(ServiceProvider, ApplicationCache.RuntimeCache, ProfilingLogger); PluginManager.Current = PluginManager; //Create the legacy prop-eds mapping LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors(); //create database and service contexts for the app context var dbFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, ProfilingLogger.Logger); Database.Mapper = new PetaPocoMapper(); var dbContext = new DatabaseContext( dbFactory, ProfilingLogger.Logger, SqlSyntaxProviders.CreateDefault(ProfilingLogger.Logger)); //initialize the DatabaseContext dbContext.Initialize(); //get the service context var serviceContext = CreateServiceContext(dbContext, dbFactory); //set property and singleton from response ApplicationContext.Current = ApplicationContext = CreateApplicationContext(dbContext, serviceContext); InitializeApplicationEventsResolver(); InitializeResolvers(); InitializeModelMappers(); using (ProfilingLogger.DebugDuration( string.Format("Executing {0} IApplicationEventHandler.OnApplicationInitialized", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), "Finished executing IApplicationEventHandler.OnApplicationInitialized")) { //now we need to call the initialize methods ApplicationEventsResolver.Current.ApplicationEventHandlers .ForEach(x => { try { x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); } catch (Exception ex) { ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex); throw; } }); } _isInitialized = true; return this; } /// /// Creates and returns the service context for the app /// /// /// /// protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory) { //default transient factory var msgFactory = new TransientMessagesFactory(); return new ServiceContext( new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(msgFactory, ProfilingLogger.Logger), ApplicationCache, ProfilingLogger.Logger, msgFactory); } /// /// Creates and returns the application context for the app /// /// /// protected virtual ApplicationContext CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext) { //create the ApplicationContext return new ApplicationContext(dbContext, serviceContext, ApplicationCache, ProfilingLogger); } /// /// Creates and returns the CacheHelper for the app /// protected virtual CacheHelper CreateApplicationCache() { var cacheHelper = 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()))); 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()) { m.ConfigureMappings(configuration, ApplicationContext); } }); } /// /// Special method to initialize the LoggerResolver /// protected virtual void InitializeLoggerResolver() { LoggerResolver.Current = new LoggerResolver(ProfilingLogger == null ? Logger.CreateWithDefaultLog4NetConfiguration() : ProfilingLogger.Logger) { //This is another special resolver that needs to be resolvable before resolution is frozen //since it is used for profiling the application startup CanResolveBeforeFrozen = true }; } /// /// Special method to initialize the ProfilerResolver /// protected virtual void InitializeProfilerResolver() { //By default we'll initialize the Log profiler (in the web project, we'll override with the web profiler) ProfilerResolver.Current = new ProfilerResolver(ProfilingLogger == null ? new LogProfiler(LoggerResolver.Current.Logger) : ProfilingLogger.Profiler) { //This is another special resolver that needs to be resolvable before resolution is frozen //since it is used for profiling the application startup CanResolveBeforeFrozen = true }; } /// /// Special method to initialize the ApplicationEventsResolver and any modifications required for it such /// as adding custom types to the resolver. /// protected virtual void InitializeApplicationEventsResolver() { //find and initialize the application startup handlers, we need to initialize this resolver here because //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to //events and to call their events during bootup. //ApplicationStartupHandler.RegisterHandlers(); //... and set the special flag to let us resolve before frozen resolution ApplicationEventsResolver.Current = new ApplicationEventsResolver( ServiceProvider, ProfilingLogger.Logger, PluginManager.ResolveApplicationStartupHandlers()) { CanResolveBeforeFrozen = true }; } /// /// 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) { 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"); using (ProfilingLogger.DebugDuration( string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarting", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), "Finished executing IApplicationEventHandler.OnApplicationStarting")) { //call OnApplicationStarting of each application events handler ApplicationEventsResolver.Current.ApplicationEventHandlers .ForEach(x => { try { x.OnApplicationStarting(UmbracoApplication, ApplicationContext); } catch (Exception ex) { ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex); throw; } }); } 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; using (ProfilingLogger.DebugDuration( string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarted", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), "Finished executing IApplicationEventHandler.OnApplicationStarted")) { //call OnApplicationStarting of each application events handler ApplicationEventsResolver.Current.ApplicationEventHandlers .ForEach(x => { try { x.OnApplicationStarted(UmbracoApplication, ApplicationContext); } catch (Exception ex) { ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex); throw; } }); } //Now, startup all of our legacy startup handler ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers(); 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 builder = new ManifestBuilder( ApplicationCache.RuntimeCache, new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), ApplicationCache.RuntimeCache)); PropertyEditorResolver.Current = new PropertyEditorResolver(ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolvePropertyEditors(), builder); ParameterEditorResolver.Current = new ParameterEditorResolver(ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveParameterEditors(), builder); //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)), }); //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()); } 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( new DatabaseServerMessenger(ApplicationContext, true, new DatabaseServerMessengerOptions())); MappingResolver.Current = new MappingResolver( ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveAssignedMapperTypes()); //RepositoryResolver.Current = new RepositoryResolver( // new RepositoryFactory(ApplicationCache)); CacheRefreshersResolver.Current = new CacheRefreshersResolver( ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveCacheRefreshers()); DataTypesResolver.Current = new DataTypesResolver( ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveDataTypes()); 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( ProfilingLogger.Logger, () => PluginManager.ResolveTypes()); // todo: remove once we drop IPropertyEditorValueConverter support. PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( ServiceProvider, ProfilingLogger.Logger, PluginManager.ResolvePropertyEditorValueConverters()); // need to filter out the ones we dont want!! PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( ServiceProvider, ProfilingLogger.Logger, PluginManager.ResolveTypes()); // use the new DefaultShortStringHelper ShortStringHelperResolver.Current = new ShortStringHelperResolver( //new LegacyShortStringHelper()); new DefaultShortStringHelper(UmbracoConfig.For.UmbracoSettings()).WithDefaultConfig()); UrlSegmentProviderResolver.Current = new UrlSegmentProviderResolver( ServiceProvider, ProfilingLogger.Logger, typeof(DefaultUrlSegmentProvider)); // by default, no factory is activated PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); } } }