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.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 { private ProfilingLogger _profilingLogger; 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; set; } protected CacheHelper ApplicationCache { get; 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; } public virtual IBootManager Initialize() { if (_isInitialized) throw new InvalidOperationException("The boot manager has already been initialized"); InitializeLoggerResolver(); InitializeProfilerResolver(); _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"); 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, LoggerResolver.Current.Logger); Database.Mapper = new PetaPocoMapper(); var dbContext = new DatabaseContext( dbFactory, LoggerResolver.Current.Logger, SqlSyntaxProviders.CreateDefault(LoggerResolver.Current.Logger)); //initialize the DatabaseContext dbContext.Initialize(); var serviceContext = new ServiceContext( new RepositoryFactory(ApplicationCache, LoggerResolver.Current.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(), ApplicationCache, LoggerResolver.Current.Logger); 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 assigns the application context singleton /// /// /// protected virtual void CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext) { //create the ApplicationContext ApplicationContext = ApplicationContext.Current = new ApplicationContext(dbContext, serviceContext, ApplicationCache, _profilingLogger); } /// /// Creates and assigns the ApplicationCache based on a new instance of System.Web.Caching.Cache /// protected virtual void CreateApplicationCache() { var cacheHelper = new CacheHelper( new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), //we have no request based cache when not running in web-based context new NullCacheProvider()); ApplicationCache = 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(Logger.CreateWithDefaultLog4NetConfiguration()) { //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(new LogProfiler(LoggerResolver.Current.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 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, LoggerResolver.Current.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(); 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; var currentTry = 0; while (currentTry < 5) { if (ApplicationContext.DatabaseContext.CanConnect) break; //wait and retry Thread.Sleep(1000); 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, LoggerResolver.Current.Logger, () => PluginManager.ResolvePropertyEditors(), builder); ParameterEditorResolver.Current = new ParameterEditorResolver(ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveParameterEditors(), builder); //setup the validators resolver with our predefined validators ValidatorsResolver.Current = new ValidatorsResolver( ServiceProvider, LoggerResolver.Current.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, LoggerResolver.Current.Logger, () => PluginManager.ResolveAssignedMapperTypes()); //RepositoryResolver.Current = new RepositoryResolver( // new RepositoryFactory(ApplicationCache)); CacheRefreshersResolver.Current = new CacheRefreshersResolver( ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveCacheRefreshers()); DataTypesResolver.Current = new DataTypesResolver( ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveDataTypes()); MacroFieldEditorsResolver.Current = new MacroFieldEditorsResolver( ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveMacroRenderings()); PackageActionsResolver.Current = new PackageActionsResolver( ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolvePackageActions()); ActionsResolver.Current = new ActionsResolver( ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveActions()); //the database migration objects MigrationResolver.Current = new MigrationResolver( LoggerResolver.Current.Logger, () => PluginManager.ResolveTypes()); // todo: remove once we drop IPropertyEditorValueConverter support. PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( ServiceProvider, LoggerResolver.Current.Logger, PluginManager.ResolvePropertyEditorValueConverters()); // need to filter out the ones we dont want!! PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( ServiceProvider, LoggerResolver.Current.Logger, PluginManager.ResolveTypes()); // use the new DefaultShortStringHelper ShortStringHelperResolver.Current = new ShortStringHelperResolver( //new LegacyShortStringHelper()); new DefaultShortStringHelper(UmbracoConfig.For.UmbracoSettings()).WithDefaultConfig()); UrlSegmentProviderResolver.Current = new UrlSegmentProviderResolver( ServiceProvider, LoggerResolver.Current.Logger, typeof(DefaultUrlSegmentProvider)); // by default, no factory is activated PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); } } }