diff --git a/src/Umbraco.Core/ApplicationEventHandler.cs b/src/Umbraco.Core/ApplicationEventHandler.cs deleted file mode 100644 index 47035f80dd..0000000000 --- a/src/Umbraco.Core/ApplicationEventHandler.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Linq; -using LightInject; -using Umbraco.Core.Components; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Plugins; -using Umbraco.Core.Services; - -namespace Umbraco.Core -{ - /// - /// A plugin type that allows developers to execute code during the Umbraco bootup process - /// - /// - /// Allows you to override the methods that you would like to execute code for: ApplicationInitialized, ApplicationStarting, ApplicationStarted. - /// - /// By default none of these methods will execute if the Umbraco application is not configured or if the Umbraco database is not configured, however - /// if you need these methods to execute even if either of these are not configured you can override the properties: - /// ExecuteWhenApplicationNotConfigured and ExecuteWhenDatabaseNotConfigured - /// - // fixme - kill.kill.kill - public abstract class ApplicationEventHandler : IApplicationEventHandler - { - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication) - { - if (ShouldExecute()) ApplicationInitialized(umbracoApplication); - } - - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication) - { - if (ShouldExecute()) ApplicationStarting(umbracoApplication); - } - - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - if (ShouldExecute()) ApplicationStarted(umbracoApplication); - } - - /// - /// Overridable method to execute when the ApplicationContext is created and other static objects that require initialization have been setup - /// - /// - protected virtual void ApplicationInitialized(UmbracoApplicationBase umbracoApplication) - { } - - /// - /// Overridable method to execute when All resolvers have been initialized but resolution is not frozen so they can be modified in this method - /// - /// - protected virtual void ApplicationStarting(UmbracoApplicationBase umbracoApplication) - { } - - /// - /// Overridable method to execute when Bootup is completed, this allows you to perform any other bootup logic required for the application. - /// Resolution is frozen so now they can be used to resolve instances. - /// - /// - protected virtual void ApplicationStarted(UmbracoApplicationBase umbracoApplication) - { } - - private bool ShouldExecute() - { - var level = Current.RuntimeState.Level; - - switch (Current.RuntimeState.Level) - { - case RuntimeLevel.Unknown: - case RuntimeLevel.Failed: - case RuntimeLevel.Boot: - return false; - case RuntimeLevel.Install: - case RuntimeLevel.Upgrade: - // sort-of equivalent I assume? - //if (!applicationContext.IsConfigured && ExecuteWhenApplicationNotConfigured) - if (ExecuteWhenApplicationNotConfigured) - return true; - //if (!applicationContext.DatabaseContext.IsDatabaseConfigured && ExecuteWhenDatabaseNotConfigured) - if (level == RuntimeLevel.Install && ExecuteWhenDatabaseNotConfigured) - return true; - return false; - case RuntimeLevel.Run: - //if (applicationContext.IsConfigured && applicationContext.DatabaseContext.IsDatabaseConfigured) - return true; - default: - throw new NotSupportedException($"Invalid runtime level {level}"); - } - } - - /// - /// A flag to determine if the overridable methods for this class will execute even if the - /// Umbraco application is not configured - /// - /// - /// An Umbraco Application is not configured when it requires a new install or upgrade. When the latest version in the - /// assembly does not match the version in the config. - /// - protected virtual bool ExecuteWhenApplicationNotConfigured => false; - - /// - /// A flag to determine if the overridable methods for this class will execute even if the - /// Umbraco database is not configured - /// - /// - /// The Umbraco database is not configured when we cannot connect to the database or when the database tables are not installed. - /// - protected virtual bool ExecuteWhenDatabaseNotConfigured => false; - } - - // that *could* replace CoreRuntime mess almost entirely - // but what about what's in WebRuntime? - - [DisableComponent] // disabled for now, breaks - public class ApplicationEventHandlerComponent : UmbracoComponentBase, IUmbracoCoreComponent - { - public override void Compose(ServiceContainer container) - { - base.Compose(container); - - // assuming we don't do anything in Compose? or is this where we should create the handlers? - } - - public void Initialize(PluginManager pluginManager, UmbracoApplicationBase umbracoApplication) - { - var startupHandlerTypes = pluginManager.ResolveApplicationStartupHandlers(); - var handlers = startupHandlerTypes.Select(x => - { - try - { - return Activator.CreateInstance(x); - } - catch - { - // fixme - breaks here! - // cannot unstanciate GridPropertyEditor and other oddities BUT they have been refactored in 7.6? - // well, no, so NOW is the time... bah... first we need to figure out STATES - throw new Exception("cannot instanciate " + x.FullName); - } - }).Cast().ToArray(); - - // just a test at that point - return; - - foreach (var handler in handlers) - handler.OnApplicationInitialized(umbracoApplication); - foreach (var handler in handlers) - handler.OnApplicationStarting(umbracoApplication); - - //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) - // fixme - wtf? never going back to FALSE ! - ((UserService) Current.Services.UserService).IsUpgrading = true; - - foreach (var handler in handlers) - handler.OnApplicationStarted(umbracoApplication); - - //applicationContext.IsReady = true; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/BootLoader.cs b/src/Umbraco.Core/Components/BootLoader.cs index f40ea424dd..0840449789 100644 --- a/src/Umbraco.Core/Components/BootLoader.cs +++ b/src/Umbraco.Core/Components/BootLoader.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using LightInject; -using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; @@ -225,7 +224,7 @@ namespace Umbraco.Core.Components { if (_booted == false) throw new InvalidOperationException("Cannot terminate, has not booted."); - using (_proflog.DebugDuration($"Terminating Umbraco. (log components when >{LogThresholdMilliseconds}ms)", "Terminated Umbraco.")) + using (_proflog.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) { foreach (var component in _components) { diff --git a/src/Umbraco.Core/CoreRuntime.cs b/src/Umbraco.Core/CoreRuntime.cs index f5db9f8cbe..45f72e50df 100644 --- a/src/Umbraco.Core/CoreRuntime.cs +++ b/src/Umbraco.Core/CoreRuntime.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Configuration; -using System.Threading.Tasks; using System.Threading; using LightInject; +using Semver; using Umbraco.Core.Cache; using Umbraco.Core.Components; using Umbraco.Core.Configuration; @@ -38,16 +38,23 @@ namespace Umbraco.Core public CoreRuntime(UmbracoApplicationBase umbracoApplication) { if (umbracoApplication == null) throw new ArgumentNullException(nameof(umbracoApplication)); - UmbracoApplication = umbracoApplication; } /// public virtual void Boot(ServiceContainer container) { + // some components may want to initialize with the UmbracoApplicationBase + // well, they should not - let's not do this + //container.RegisterInstance(_app); + Compose(container); // prepare essential stuff + var path = GetApplicationRootPath(); + if (string.IsNullOrWhiteSpace(path) == false) + IOHelper.SetRootDirectory(path); + _state = (RuntimeState) container.GetInstance(); _state.Level = RuntimeLevel.Boot; @@ -55,9 +62,6 @@ namespace Umbraco.Core Profiler = container.GetInstance(); ProfilingLogger = container.GetInstance(); - // fixme - totally temp - container.RegisterInstance(UmbracoApplication); - // 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 @@ -95,13 +99,26 @@ namespace Umbraco.Core // boot _bootLoader = new BootLoader(container); _bootLoader.Boot(componentTypes, _state.Level); + + // this was done in Complete() right before running the Started event handlers + // "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)" + // + // was *always* setting the value to true which is?! so using the runtime level + // and then, it is *never* resetted to false, meaning Umbraco has been running with IsUpgrading being true? + // fixme - this is... bad + ((UserService) Current.Services.UserService).IsUpgrading = _state.Level == RuntimeLevel.Upgrade; + } } /// public virtual void Terminate() { - _bootLoader?.Terminate(); + using (ProfilingLogger.DebugDuration("Terminating Umbraco.", "Terminated.")) + { + _bootLoader?.Terminate(); + } } /// @@ -150,12 +167,15 @@ namespace Umbraco.Core container.RegisterSingleton(); } - private static void SetRuntimeStateLevel(RuntimeState runtimeState, IDatabaseFactory databaseFactory, ILogger logger) + private void SetRuntimeStateLevel(RuntimeState runtimeState, IDatabaseFactory databaseFactory, ILogger logger) { var localVersion = LocalVersion; // the local, files, version var codeVersion = runtimeState.SemanticVersion; // the executing code version var connect = false; + // we don't know yet + runtimeState.Level = RuntimeLevel.Unknown; + if (string.IsNullOrWhiteSpace(localVersion)) { // there is no local version, we are not installed @@ -177,47 +197,44 @@ namespace Umbraco.Core runtimeState.Level = RuntimeLevel.Install; } + // install? not going to test anything else + if (runtimeState.Level == RuntimeLevel.Install) + return; + + // else, keep going, // anything other than install wants a database - see if we can connect - if (runtimeState.Level != RuntimeLevel.Install) + for (var i = 0; i < 5; i++) { - for (var i = 0; i < 5; i++) - { - connect = databaseFactory.CanConnect; - if (connect) break; - logger.Debug(i == 0 - ? "Could not immediately connect to database, trying again." - : "Could not connect to database."); - Thread.Sleep(1000); - } - - if (connect == false) - { - // cannot connect to configured database, this is bad, fail - logger.Debug("Could not connect to database."); - runtimeState.Level = RuntimeLevel.Failed; - - // in fact, this is bad enough that we want to throw - throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); - } + connect = databaseFactory.CanConnect; + if (connect) break; + logger.Debug(i == 0 + ? "Could not immediately connect to database, trying again." + : "Could not connect to database."); + Thread.Sleep(1000); } - // if we cannot connect, cannot do more - if (connect == false) return; + if (connect == false) + { + // cannot connect to configured database, this is bad, fail + logger.Debug("Could not connect to database."); + runtimeState.Level = RuntimeLevel.Failed; + + // in fact, this is bad enough that we want to throw + throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); + } + + // if we already know we want to upgrade, no need to look for migrations... + if (runtimeState.Level == RuntimeLevel.Upgrade) + return; // else // look for a matching migration entry - bypassing services entirely - they are not 'up' yet // fixme - in a LB scenario, ensure that the DB gets upgraded only once! // fixme - eventually move to yol-style guid-based transitions - var database = databaseFactory.GetDatabase(); - var codeVersionString = codeVersion.ToString(); - var sql = database.Sql() - .Select() - .From() - .Where(x => x.Name.InvariantEquals(GlobalSettings.UmbracoMigrationName) && x.Version == codeVersionString); bool exists; try { - exists = database.FirstOrDefault(sql) != null; + exists = EnsureMigration(databaseFactory, codeVersion); } catch { @@ -243,6 +260,17 @@ namespace Umbraco.Core runtimeState.Level = RuntimeLevel.Upgrade; } + protected virtual bool EnsureMigration(IDatabaseFactory databaseFactory, SemVersion codeVersion) + { + var database = databaseFactory.GetDatabase(); + var codeVersionString = codeVersion.ToString(); + var sql = database.Sql() + .Select() + .From() + .Where(x => x.Name.InvariantEquals(GlobalSettings.UmbracoMigrationName) && x.Version == codeVersionString); + return database.FirstOrDefault(sql) != null; + } + private static string LocalVersion { get @@ -261,8 +289,6 @@ namespace Umbraco.Core #region Locals - // fixme - we almost certainly DONT need these! - protected ILogger Logger { get; private set; } protected IProfiler Profiler { get; private set; } @@ -277,178 +303,10 @@ namespace Umbraco.Core protected virtual IEnumerable GetComponentTypes() => Current.PluginManager.ResolveTypes(); - //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()))); + // 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; #endregion - - // fixme - kill everything below! - - private IServiceContainer _appStartupEvtContainer; - private bool _isInitialized; - private bool _isStarted; - private bool _isComplete; - - protected UmbracoApplicationBase UmbracoApplication { get; } - - protected ServiceContainer Container => Current.Container; - - /// - /// 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 Umbraco root path. - protected virtual void InitializeApplicationRootPath(string rootPath) - { - IOHelper.SetRootDirectory(rootPath); - } - - public virtual IRuntime Initialize() - { - if (_isInitialized) - throw new InvalidOperationException("The boot manager has already been initialized"); - - //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 ;-( - _appStartupEvtContainer.BeginScope(); - _appStartupEvtContainer.RegisterCollection(factory => factory.GetInstance().ResolveApplicationStartupHandlers()); - - // fixme - parallel? what about our dependencies? - Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => - { - try - { - using (ProfilingLogger.DebugDuration( - $"Executing {x.GetType()} in ApplicationInitialized", - $"Executed {x.GetType()} in ApplicationInitialized", - //only log if more than 150ms - 150)) - { - x.OnApplicationInitialized(UmbracoApplication); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex); - throw; - } - }); - - _isInitialized = true; - - return this; - } - - - /// - /// 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 IRuntime Startup(Action afterStartup) - { - if (_isStarted) - throw new InvalidOperationException("The boot manager has already been initialized"); - - //call OnApplicationStarting of each application events handler - Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => - { - try - { - using (ProfilingLogger.DebugDuration( - $"Executing {x.GetType()} in ApplicationStarting", - $"Executed {x.GetType()} in ApplicationStarting", - //only log if more than 150ms - 150)) - { - x.OnApplicationStarting(UmbracoApplication); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex); - throw; - } - }); - - if (afterStartup != null) - { - afterStartup(); - } - - _isStarted = true; - - return this; - } - - /// - /// Fires after startup and calls the callback once customizations are locked - /// - /// - /// - public virtual IRuntime Complete(Action afterComplete) - { - if (_isComplete) - throw new InvalidOperationException("The boot manager has already been completed"); - - //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) - // fixme - wtf? never going back to FALSE ! - ((UserService) Current.Services.UserService).IsUpgrading = true; - - //call OnApplicationStarting of each application events handler - Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => - { - try - { - using (ProfilingLogger.DebugDuration( - $"Executing {x.GetType()} in ApplicationStarted", - $"Executed {x.GetType()} in ApplicationStarted", - //only log if more than 150ms - 150)) - { - x.OnApplicationStarted(UmbracoApplication); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("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(); - } - - _isComplete = true; - - return this; - } } } diff --git a/src/Umbraco.Core/CoreRuntimeComponent.cs b/src/Umbraco.Core/CoreRuntimeComponent.cs index 2927a6e23e..9101620356 100644 --- a/src/Umbraco.Core/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/CoreRuntimeComponent.cs @@ -1,11 +1,6 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using AutoMapper; using LightInject; using Umbraco.Core.Cache; @@ -13,7 +8,6 @@ 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; @@ -136,6 +130,14 @@ namespace Umbraco.Core foreach (var m in modelMapperConfigurations) m.ConfigureMappings(configuration); }); + + // ensure we have some essential directories + // every other component can then initialize safely + IOHelper.EnsurePathExists("~/App_Data"); + IOHelper.EnsurePathExists(SystemDirectories.Media); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); + IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); } } } diff --git a/src/Umbraco.Core/IApplicationEventHandler.cs b/src/Umbraco.Core/IApplicationEventHandler.cs deleted file mode 100644 index 1421af63ea..0000000000 --- a/src/Umbraco.Core/IApplicationEventHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ - - -namespace Umbraco.Core -{ - /// - /// Custom IApplicationStartupHandler that auto subscribes to the applications events - /// - public interface IApplicationEventHandler - { - /// - /// ApplicationContext is created and other static objects that require initialization have been setup - /// - /// - void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication); - - /// - /// All resolvers have been initialized but resolution is not frozen so they can be modified in this method - /// - /// - void OnApplicationStarting(UmbracoApplicationBase umbracoApplication); - - /// - /// Bootup is completed, this allows you to perform any other bootup logic required for the application. - /// Resolution is frozen so now they can be used to resolve instances. - /// - /// - void OnApplicationStarted(UmbracoApplicationBase umbracoApplication); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/WebProfiler.cs b/src/Umbraco.Core/Logging/WebProfiler.cs index 6b5d98e46c..8f0c52a8ba 100644 --- a/src/Umbraco.Core/Logging/WebProfiler.cs +++ b/src/Umbraco.Core/Logging/WebProfiler.cs @@ -2,94 +2,37 @@ using System.Web; using StackExchange.Profiling; using StackExchange.Profiling.SqlFormatters; -using Umbraco.Core.Configuration; namespace Umbraco.Core.Logging { /// - /// A profiler used for web based activity based on the MiniProfiler framework + /// A profiler used for web based activity based on the MiniProfiler framework. /// - internal class WebProfiler : ApplicationEventHandler, IProfiler + internal class WebProfiler : IProfiler { - /// - ///Binds to application events to enable the MiniProfiler - /// - /// - protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication) + private readonly IRuntimeState _runtime; + + public WebProfiler(IRuntimeState runtime) { - UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; + _runtime = runtime; } - - /// - /// Handle the Init event o fthe UmbracoApplication which allows us to subscribe to the HttpApplication events - /// - /// - /// - void UmbracoApplicationApplicationInit(object sender, EventArgs e) - { - var app = sender as HttpApplication; - if (app == null) return; - - if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) - { - //If we don't have a high enough trust level we cannot bind to the events - LogHelper.Info("Cannot start the WebProfiler since the application is running in Medium trust"); - } - else - { - app.BeginRequest += UmbracoApplicationBeginRequest; - app.EndRequest += UmbracoApplicationEndRequest; - } - } - - /// - /// Handle the begin request event - /// - /// - /// - void UmbracoApplicationEndRequest(object sender, EventArgs e) + public void UmbracoApplicationEndRequest(object sender, EventArgs e) { if (CanPerformProfilingAction(sender)) - { Stop(); - } } - /// - /// Handle the end request event - /// - /// - /// - void UmbracoApplicationBeginRequest(object sender, EventArgs e) + public void UmbracoApplicationBeginRequest(object sender, EventArgs e) { if (CanPerformProfilingAction(sender)) - { Start(); - } } - private bool CanPerformProfilingAction(object sender) + private static bool CanPerformProfilingAction(object sender) { - if (GlobalSettings.DebugMode == false) - return false; - - //will not run in medium trust - if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) - return false; - var request = TryGetRequest(sender); - - if (request.Success == false || request.Result.Url.IsClientSideRequest()) - return false; - - if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"])) - return true; - - if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) - return true; - - return true; + return request.Success && request.Result.Url.IsClientSideRequest() == false; } /// @@ -114,7 +57,7 @@ namespace Umbraco.Core.Logging /// public IDisposable Step(string name) { - return GlobalSettings.DebugMode == false ? null : MiniProfiler.Current.Step(name); + return _runtime.Debug ? MiniProfiler.Current.Step(name) : null; } /// @@ -144,7 +87,7 @@ namespace Umbraco.Core.Logging /// /// The application object /// - private Attempt TryGetRequest(object sender) + private static Attempt TryGetRequest(object sender) { var app = sender as HttpApplication; if (app == null) return Attempt.Fail(); diff --git a/src/Umbraco.Core/Logging/WebProfilerComponent.cs b/src/Umbraco.Core/Logging/WebProfilerComponent.cs new file mode 100644 index 0000000000..2f92e181c3 --- /dev/null +++ b/src/Umbraco.Core/Logging/WebProfilerComponent.cs @@ -0,0 +1,55 @@ +using System; +using System.Web; +using Umbraco.Core.Components; + +namespace Umbraco.Core.Logging +{ + internal class WebProfilerComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + // the profiler is too important to be composed in a component, + // it is composed first thing in WebRuntime.Compose - this component + // only initializes it if needed. + // + //public override void Compose(ServiceContainer container) + //{ + // container.RegisterSingleton(); + //} + + private WebProfiler _profiler; + + public void Initialize(IProfiler profiler, IRuntimeState runtime) + { + // although registered in WebRuntime.Compose, ensure that we have + // not been replaced by another component, and we are still "the" profiler + _profiler = profiler as WebProfiler; + if (_profiler == null) return; + + if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) + { + // if we don't have a high enough trust level we cannot bind to the events + LogHelper.Info("Cannot install when the application is running in Medium trust."); + } + else if (runtime.Debug == false) + { + // only when debugging + LogHelper.Info("Cannot install when the application is not running in debug mode."); + } + else + { + // bind to ApplicationInit - ie execute the application initialization for *each* application + // it would be a mistake to try and bind to the current application events + UmbracoApplicationBase.ApplicationInit += InitializeApplication; + } + } + + private void InitializeApplication(object sender, EventArgs args) + { + var app = sender as HttpApplication; + if (app == null) return; + + // for *each* application (this will run more than once) + app.BeginRequest += (s, a) => _profiler.UmbracoApplicationBeginRequest(s, a); + app.EndRequest += (s, a) => _profiler.UmbracoApplicationEndRequest(s, a); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Plugins/HideFromTypeFinderAttribute.cs b/src/Umbraco.Core/Plugins/HideFromTypeFinderAttribute.cs index 1568bc2995..6654123d6c 100644 --- a/src/Umbraco.Core/Plugins/HideFromTypeFinderAttribute.cs +++ b/src/Umbraco.Core/Plugins/HideFromTypeFinderAttribute.cs @@ -3,11 +3,9 @@ using System; namespace Umbraco.Core.Plugins { /// - /// Used to notify the TypeFinder to ignore any class attributed with this during it's discovery + /// Notifies the TypeFinder that it should ignore the class marked with this attribute. /// [AttributeUsage(AttributeTargets.Class)] public sealed class HideFromTypeFinderAttribute : Attribute - { - - } + { } } \ No newline at end of file diff --git a/src/Umbraco.Core/Plugins/PluginManager.cs b/src/Umbraco.Core/Plugins/PluginManager.cs index 9d37874598..331508eeae 100644 --- a/src/Umbraco.Core/Plugins/PluginManager.cs +++ b/src/Umbraco.Core/Plugins/PluginManager.cs @@ -680,14 +680,6 @@ namespace Umbraco.Core.Plugins .Except(new[] { typeof(ParameterEditor), typeof(PropertyEditor) }); } - /// - /// Resolves IApplicationStartupHandler objects. - /// - internal static IEnumerable ResolveApplicationStartupHandlers(this PluginManager mgr) - { - return mgr.ResolveTypes(); - } - /// /// Resolves ICacheRefresher objects. /// diff --git a/src/Umbraco.Web/ManifestWatcherComponent.cs b/src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs similarity index 81% rename from src/Umbraco.Web/ManifestWatcherComponent.cs rename to src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs index 7bf9c27aab..40b77eb700 100644 --- a/src/Umbraco.Web/ManifestWatcherComponent.cs +++ b/src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs @@ -1,10 +1,10 @@ using System.IO; -using Umbraco.Core; using Umbraco.Core.Components; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.IO; using Umbraco.Core.Manifest; -namespace Umbraco.Web +namespace Umbraco.Core.Strategies { public class ManifestWatcherComponent : UmbracoComponentBase, IUmbracoCoreComponent { @@ -14,8 +14,6 @@ namespace Umbraco.Web public void Initialize(IRuntimeState runtime) { - // BUT how can we tell that runtime is "ready enough"? need to depend on some sort of UmbracoRuntimeComponent? - // and... will this kind of dependency issue be repro everywhere?! if (runtime.Level < RuntimeLevel.Run || runtime.Debug == false) return; //if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false) diff --git a/src/Umbraco.Core/Strategies/ManifestWatcherHandler.cs b/src/Umbraco.Core/Strategies/ManifestWatcherHandler.cs deleted file mode 100644 index efa9138306..0000000000 --- a/src/Umbraco.Core/Strategies/ManifestWatcherHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.IO; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.IO; -using Umbraco.Core.Manifest; - -namespace Umbraco.Core.Strategies -{ - public sealed class ManifestWatcherHandler : ApplicationEventHandler - { - private ManifestWatcher _mw; - - protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication) - { - UmbracoApplicationBase.ApplicationEnd += app_ApplicationEnd; - } - - void app_ApplicationEnd(object sender, EventArgs e) - { - if (_mw != null) - { - _mw.Dispose(); - } - } - - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - _mw = new ManifestWatcher(Current.ProfilingLogger.Logger); - _mw.Start(Directory.GetDirectories(IOHelper.MapPath("~/App_Plugins/"))); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Strategies/RelateOnCopyHandler.cs b/src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs similarity index 90% rename from src/Umbraco.Core/Strategies/RelateOnCopyHandler.cs rename to src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs index b7db0fdf47..aa42131dfc 100644 --- a/src/Umbraco.Core/Strategies/RelateOnCopyHandler.cs +++ b/src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Components; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -6,9 +7,9 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Strategies { //TODO: This should just exist in the content service/repo! - public sealed class RelateOnCopyHandler : ApplicationEventHandler + public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) + public void Initialize() { ContentService.Copied += ContentServiceCopied; } diff --git a/src/Umbraco.Core/Strategies/RelateOnTrashHandler.cs b/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs similarity index 94% rename from src/Umbraco.Core/Strategies/RelateOnTrashHandler.cs rename to src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs index f2cfc3ac3b..b55ff98dbb 100644 --- a/src/Umbraco.Core/Strategies/RelateOnTrashHandler.cs +++ b/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Umbraco.Core.Components; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Events; using Umbraco.Core.Models; @@ -7,9 +8,9 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Strategies { - public sealed class RelateOnTrashHandler : ApplicationEventHandler + public sealed class RelateOnTrashComponent : UmbracoComponentBase, IUmbracoCoreComponent { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) + public void Initialize() { ContentService.Moved += ContentService_Moved; ContentService.Trashed += ContentService_Trashed; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6528dc95b0..8350275e89 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -68,7 +68,6 @@ - @@ -280,6 +279,7 @@ Files.resx + @@ -299,7 +299,6 @@ - @@ -536,7 +535,7 @@ - + @@ -1227,8 +1226,8 @@ - - + + diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index ffa623c507..3713574896 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -13,10 +13,6 @@ namespace Umbraco.Core /// /// Provides an abstract base class for the Umbraco HttpApplication. /// - /// - /// This is exposed in Core so that we can have the IApplicationEventHandler in the core project so that - /// IApplicationEventHandler's can fire/execute outside of the web contenxt (i.e. in console applications). fixme wtf? - /// public abstract class UmbracoApplicationBase : HttpApplication { private IRuntime _runtime; @@ -34,16 +30,33 @@ namespace Umbraco.Core return Logger.CreateWithDefaultLog4NetConfiguration(); } - #region Start + // events - in the order they trigger - // fixme? dont make much sense! - public event EventHandler ApplicationStarting; - public event EventHandler ApplicationStarted; + // were part of the BootManager architecture, would trigger only for the initial + // application, so they need not be static, and they would let ppl hook into the + // boot process... but I believe this can be achieved with components as well and + // we don't need these events. + //public event EventHandler ApplicationStarting; + //public event EventHandler ApplicationStarted; + + // this event can only be static since there will be several instances of this class + // triggers for each application instance, ie many times per lifetime of the application + public static event EventHandler ApplicationInit; + + // this event can only be static since there will be several instances of this class + // triggers once per error + public static event EventHandler ApplicationError; + + // this event can only be static since there will be several instances of this class + // triggers once per lifetime of the application, before it is unloaded + public static event EventHandler ApplicationEnd; + + #region Start // internal for tests internal void HandleApplicationStart(object sender, EventArgs evargs) { - // NOTE: THIS IS WHERE EVERYTHING BEGINS! + // ******** THIS IS WHERE EVERYTHING BEGINS ******** // create the container for the application, and configure. // the boot manager is responsible for registrations @@ -74,11 +87,6 @@ namespace Umbraco.Core // get runtime & boot _runtime = GetRuntime(); _runtime.Boot(container); - - // this is extra that should get removed - ((CoreRuntime)_runtime).Initialize(); - ((CoreRuntime)_runtime).Startup(() => OnApplicationStarting(sender, evargs)); - ((CoreRuntime)_runtime).Complete(() => OnApplicationStarted(sender, evargs)); } // called by ASP.NET (auto event wireup) once per app domain @@ -94,20 +102,9 @@ namespace Umbraco.Core #region Init - // this event can only be static since there will be several instances of this class - public static event EventHandler ApplicationInit; - private void OnApplicationInit(object sender, EventArgs evargs) { - try - { - ApplicationInit?.Invoke(sender, evargs); - } - catch (Exception ex) - { - Current.Logger.Error("Exception in an ApplicationInit event handler.", ex); - throw; - } + TryInvoke(ApplicationInit, "ApplicationInit", sender, evargs); } // called by ASP.NET for every HttpApplication instance after all modules have been created @@ -126,9 +123,6 @@ namespace Umbraco.Core #region End - // this event can only be static since there will be several instances of this class - public static event EventHandler ApplicationEnd; - protected virtual void OnApplicationEnd(object sender, EventArgs evargs) { ApplicationEnd?.Invoke(this, EventArgs.Empty); @@ -187,9 +181,6 @@ namespace Umbraco.Core #region Error - // this event can only be static since there will be several instances of this class - public static event EventHandler ApplicationError; - protected virtual void OnApplicationError(object sender, EventArgs evargs) { ApplicationError?.Invoke(this, EventArgs.Empty); @@ -214,42 +205,22 @@ namespace Umbraco.Core } #endregion + + #region Utilities - - /// - /// Developers can override this method to modify objects on startup - /// - /// - /// - protected virtual void OnApplicationStarting(object sender, EventArgs evargs) + private static void TryInvoke(EventHandler handler, string name, object sender, EventArgs evargs) { try { - ApplicationStarting?.Invoke(sender, evargs); + handler?.Invoke(sender, evargs); } catch (Exception ex) { - Current.Logger.Error("An error occurred in an ApplicationStarting event handler", ex); + Current.Logger.Error($"Error in {name} handler.", ex); throw; } } - /// - /// Developers can override this method to do anything they need to do once the application startup routine is completed. - /// - /// - /// - protected virtual void OnApplicationStarted(object sender, EventArgs evargs) - { - try - { - ApplicationStarted?.Invoke(sender, evargs); - } - catch (Exception ex) - { - Current.Logger.Error("An error occurred in an ApplicationStarted event handler", ex); - throw; - } - } + #endregion } } diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 12b397e783..ca5e789744 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -29,7 +29,7 @@ namespace Umbraco.Tests.Integration { base.Initialize(); - _h1 = new CacheRefresherEventHandler(); + _h1 = new CacheRefresherComponent(); _h1.AddHandlers(); _events = new List(); @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Integration ContentCacheRefresher.CacheUpdated -= ContentCacheUpdated; } - private CacheRefresherEventHandler _h1; + private CacheRefresherComponent _h1; private IList _events; private int _msgCount; private IContentType _contentType; diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs index 406f263d62..d2271630cf 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs @@ -1,5 +1,4 @@ -using System; -using System.Data.Common; +using System.Data.Common; using Moq; using NPoco; using NUnit.Framework; @@ -28,6 +27,7 @@ namespace Umbraco.Tests.Migrations.Upgrades var db = GetConfiguredDatabase(); var fix = new PublishAfterUpgradeToVersionSixth(); + MigrationRunner.Migrated += fix.Migrated; //Setup the MigrationRunner var migrationContext = new MigrationContext(db, Mock.Of()); @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Migrations.Upgrades bool hasPropertyTypeGroupTable = schemaHelper.TableExist("cmsPropertyTypeGroup"); bool hasAppTreeTable = schemaHelper.TableExist("umbracoAppTree"); - fix.Unsubscribe(); + MigrationRunner.Migrated -= fix.Migrated; Assert.That(hasTabTable, Is.False); Assert.That(hasPropertyTypeGroupTable, Is.True); diff --git a/src/Umbraco.Tests/NUnitTests.cs b/src/Umbraco.Tests/NUnitTests.cs new file mode 100644 index 0000000000..2d7b345409 --- /dev/null +++ b/src/Umbraco.Tests/NUnitTests.cs @@ -0,0 +1,128 @@ +using NUnit.Framework; + +namespace Umbraco.Tests +{ + // these 4 test classes validate that our test class pattern *should* be: + // - test base classes *not* marked as [TestFixture] but having a virtual SetUp + // method marked as [SetUp] + // - test classes inheriting from base class, marked as [TestFixture], and + // overriding the SetUp method *without* repeating the [SetUp] attribute + // - same for TearDown + // + // ie what InheritsTestClassWithoutAttribute does - and it works + + [TestFixture] + public class InheritsTestClassWithAttribute : NUnitTestClassBase + { + public const int ConstValue = 77; + + protected int Value { get; set; } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + Value = ConstValue; + } + + [Test] + public void AssertValues() + { + Assert.AreEqual(ConstBaseValue, BaseValue); + Assert.AreEqual(ConstValue, Value); + } + } + + [TestFixture] + public class InheritsTestClassWithoutAttribute : NUnitTestClassBase + { + public const int ConstValue = 88; + + protected int Value { get; set; } + + // not needed! + public override void SetUp() + { + base.SetUp(); + Value = ConstValue; + } + + [Test] + public void AssertValues() + { + Assert.AreEqual(ConstBaseValue, BaseValue); + Assert.AreEqual(ConstValue, Value); + } + } + + [TestFixture] + public class InheritTestFixtureWithAttribute : NUnitTestFixtureBase + { + public const int ConstValue = 99; + + protected int Value { get; set; } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + Value = ConstValue; + } + + [Test] + public void AssertValues() + { + Assert.AreEqual(ConstBaseValue, BaseValue); + Assert.AreEqual(ConstValue, Value); + } + } + + [TestFixture] + public class InheritTestFixtureWithoutAttribute : NUnitTestFixtureBase + { + public const int ConstValue = 66; + + protected int Value { get; set; } + + // not needed! + public override void SetUp() + { + base.SetUp(); + Value = ConstValue; + } + + [Test] + public void AssertValues() + { + Assert.AreEqual(ConstBaseValue, BaseValue); + Assert.AreEqual(ConstValue, Value); + } + } + + public class NUnitTestClassBase + { + public const int ConstBaseValue = 33; + + protected int BaseValue { get; set; } + + [SetUp] + public virtual void SetUp() + { + BaseValue = ConstBaseValue; + } + } + + [TestFixture] + public class NUnitTestFixtureBase + { + public const int ConstBaseValue = 42; + + protected int BaseValue { get; set; } + + [SetUp] + public virtual void SetUp() + { + BaseValue = ConstBaseValue; + } + } +} diff --git a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs b/src/Umbraco.Tests/Persistence/Migrations/PostMigrationTests.cs similarity index 77% rename from src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs rename to src/Umbraco.Tests/Persistence/Migrations/PostMigrationTests.cs index 240c93e696..cfe08d304f 100644 --- a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs +++ b/src/Umbraco.Tests/Persistence/Migrations/PostMigrationTests.cs @@ -1,22 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Moq; -using NPoco; +using Moq; using NUnit.Framework; using Semver; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Profiling; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Strategies.Migrations; @@ -24,7 +12,7 @@ using Umbraco.Web.Strategies.Migrations; namespace Umbraco.Tests.Persistence.Migrations { [TestFixture] - public class MigrationStartupHandlerTests + public class PostMigrationTests { [Test] public void Executes_For_Any_Product_Name_When_Not_Specified() @@ -33,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Migrations var changed1 = new Args { CountExecuted = 0 }; var testHandler1 = new TestMigrationHandler(changed1); - testHandler1.OnApplicationStarting(Mock.Of()); + MigrationRunner.Migrated += testHandler1.Migrated; var db = TestObjects.GetUmbracoSqlCeDatabase(logger); var migrationContext = new MigrationContext(db, logger); @@ -50,11 +38,11 @@ namespace Umbraco.Tests.Persistence.Migrations var changed1 = new Args { CountExecuted = 0}; var testHandler1 = new TestMigrationHandler("Test1", changed1); - testHandler1.OnApplicationStarting(Mock.Of()); + MigrationRunner.Migrated += testHandler1.Migrated; var changed2 = new Args { CountExecuted = 0 }; var testHandler2 = new TestMigrationHandler("Test2", changed2); - testHandler2.OnApplicationStarting(Mock.Of()); + MigrationRunner.Migrated += testHandler2.Migrated; var db = TestObjects.GetUmbracoSqlCeDatabase(logger); var migrationContext = new MigrationContext(db, logger); @@ -76,7 +64,7 @@ namespace Umbraco.Tests.Persistence.Migrations public int CountExecuted { get; set; } } - public class TestMigrationHandler : MigrationStartupHandler + public class TestMigrationHandler : IPostMigration { private readonly string _prodName; private readonly Args _changed; @@ -98,15 +86,11 @@ namespace Umbraco.Tests.Persistence.Migrations _changed = changed; } - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) + public void Migrated(MigrationRunner sender, MigrationEventArgs args) { + if (_prodName.IsNullOrWhiteSpace() == false && args.ProductName != _prodName) return; _changed.CountExecuted++; } - - public override string[] TargetProductNames - { - get { return _prodName.IsNullOrWhiteSpace() ? new string[] {} : new[] {_prodName}; } - } } } } diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index d8ce512b46..b44ff14f4c 100644 --- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs @@ -69,26 +69,26 @@ namespace Umbraco.Tests.Plugins Assert.AreEqual(2, typesFound.Count()); } - [Test] - public void Find_Classes_Of_Type() - { - var typesFound = TypeFinder.FindClassesOfType(_assemblies); - var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); + //[Test] + //public void Find_Classes_Of_Type() + //{ + // var typesFound = TypeFinder.FindClassesOfType(_assemblies); + // var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); - foreach (var type in typesFound) - Console.WriteLine(type); - Console.WriteLine(); - foreach (var type in originalTypesFound) - Console.WriteLine(type); + // foreach (var type in typesFound) + // Console.WriteLine(type); + // Console.WriteLine(); + // foreach (var type in originalTypesFound) + // Console.WriteLine(type); - // 6 classes in _assemblies implement IApplicationEventHandler - Assert.AreEqual(6, typesFound.Count()); + // // 6 classes in _assemblies implement IApplicationEventHandler + // Assert.AreEqual(6, typesFound.Count()); - // however, - // Umbraco.Core.Profiling.WebProfiler is internal and is not returned by TypeFinderOriginal, - // that's a known issue of the legacy type finder, so we have to tweak the count here. - Assert.AreEqual(5, originalTypesFound.Count()); - } + // // however, + // // Umbraco.Core.Profiling.WebProfiler is internal and is not returned by TypeFinderOriginal, + // // that's a known issue of the legacy type finder, so we have to tweak the count here. + // Assert.AreEqual(5, originalTypesFound.Count()); + //} [Test] public void Find_Classes_With_Attribute() diff --git a/src/Umbraco.Tests/Plugins/TypeHelperTests.cs b/src/Umbraco.Tests/Plugins/TypeHelperTests.cs index 93889555f4..9b2b1f03db 100644 --- a/src/Umbraco.Tests/Plugins/TypeHelperTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeHelperTests.cs @@ -76,11 +76,11 @@ namespace Umbraco.Tests.Plugins Assert.IsTrue(t5.Success); Assert.AreEqual(typeof(PropertyAliasDto), t5.Result); - var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler), - typeof (SchedulerComponent), - typeof(CacheRefresherEventHandler)); - Assert.IsTrue(t6.Success); - Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result); + //var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler), + // typeof (SchedulerComponent), + // typeof(CacheRefresherComponent)); + //Assert.IsTrue(t6.Success); + //Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result); } diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index 4128b4bd71..641e577a7e 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using LightInject; using Moq; using NUnit.Framework; +using Semver; using Umbraco.Core; using Umbraco.Core.Components; using Umbraco.Core.Configuration.UmbracoSettings; @@ -18,70 +19,140 @@ namespace Umbraco.Tests.Runtimes [TestFixture] public class CoreRuntimeTests : BaseUmbracoConfigurationTest { + public override void Initialize() // fixme SetUp! + { + base.Initialize(); + TestComponent.Reset(); + } + [TearDown] // fixme TearDown is INHERITED public override void TearDown() { base.TearDown(); - - TestApplicationEventHandler.Reset(); - + TestComponent.Reset(); Current.Reset(); } + [Test] + public void ComponentLifeCycle() + { + using (var app = new TestUmbracoApplication()) + { + app.HandleApplicationStart(app, new EventArgs()); + + Assert.IsTrue(TestComponent.Ctored); + Assert.IsTrue(TestComponent.Composed); + Assert.IsTrue(TestComponent.Initialized1); + Assert.IsTrue(TestComponent.Initialized2); + Assert.IsNotNull(TestComponent.ProfilingLogger); + Assert.IsInstanceOf(TestComponent.ProfilingLogger.Logger); + + // note: components are NOT disposed after boot + + Assert.IsFalse(TestComponent.Terminated); + + app.HandleApplicationEnd(); + Assert.IsTrue(TestComponent.Terminated); + } + } + // test application public class TestUmbracoApplication : UmbracoApplicationBase { - private readonly ILogger _logger = Mock.Of(); - protected override IRuntime GetRuntime() { - return new TestRuntime(this, new ProfilingLogger(_logger, Mock.Of())); + return new TestRuntime(this); } + // the application's logger is created by the application + // through GetLogger, that custom application can override protected override ILogger GetLogger() { - return _logger; + //return Mock.Of(); + return new DebugDiagnosticsLogger(); } } - // test runtime - inheriting from core runtime + // test runtime public class TestRuntime : CoreRuntime { - private readonly ProfilingLogger _proflog; - - public TestRuntime(UmbracoApplicationBase umbracoApplication, ProfilingLogger proflog) + public TestRuntime(UmbracoApplicationBase umbracoApplication) : base(umbracoApplication) + { } + + public override void Compose(ServiceContainer container) { - _proflog = proflog; + base.Compose(container); + + // the application's profiler and profiling logger are + // registered by CoreRuntime.Compose() but can be + // overriden afterwards - they haven't been resolved yet + container.RegisterSingleton(_ => new TestProfiler()); + container.RegisterSingleton(factory => new ProfilingLogger(factory.GetInstance(), factory.GetInstance())); + + // must override the database factory + container.RegisterSingleton(_ => GetDatabaseFactory()); // fixme painful, is it... singleton? or what? + //container.RegisterKnownService(_ => GetDatabaseFactory()); // fixme - would pick the correct LifeTime! } + // must override the database factory + // else BootFailedException because U cannot connect to the configured db + private static IDatabaseFactory GetDatabaseFactory() + { + var mock = new Mock(); + mock.Setup(x => x.Configured).Returns(true); + mock.Setup(x => x.CanConnect).Returns(true); + return mock.Object; + } + + // pretend we have the proper migration + // else BootFailedException because our mock IDatabaseFactory does not provide databases + protected override bool EnsureMigration(IDatabaseFactory databaseFactory, SemVersion codeVersion) + { + return true; + } + + private MainDom _mainDom; + public override void Boot(ServiceContainer container) { - // do it before anything else - this is the only place where it's possible - container.RegisterInstance(_proflog.Logger); - container.RegisterInstance(_proflog.Profiler); - container.RegisterInstance(_proflog); - base.Boot(container); + _mainDom = container.GetInstance(); } + public override void Terminate() + { + _mainDom.Stop(false); + base.Terminate(); + } + + // runs with only one single component + // UmbracoCoreComponent will be force-added too + // and that's it protected override IEnumerable GetComponentTypes() { return new[] { typeof(TestComponent) }; } } + public class TestComponent : UmbracoComponentBase { - // test + // test flags public static bool Ctored; public static bool Composed; public static bool Initialized1; public static bool Initialized2; public static bool Terminated; + public static ProfilingLogger ProfilingLogger; + + public static void Reset() + { + Ctored = Composed = Initialized1 = Initialized2 = Terminated = false; + ProfilingLogger = null; + } public TestComponent() - : base() { Ctored = true; } @@ -90,10 +161,10 @@ namespace Umbraco.Tests.Runtimes { base.Compose(container); - container.Register(factory => SettingsForTests.GetDefault()); - container.Register(factory => new DatabaseContext( + container.Register(factory => SettingsForTests.GetDefault()); + container.Register(factory => new DatabaseContext( factory.GetInstance(), - factory.GetInstance(), Mock.Of(), Mock.Of()), new PerContainerLifetime()); + factory.GetInstance(), factory.GetInstance(), Mock.Of()), new PerContainerLifetime()); container.RegisterSingleton(); Composed = true; @@ -109,121 +180,16 @@ namespace Umbraco.Tests.Runtimes Initialized2 = true; } + public void Initialize(ProfilingLogger proflog) + { + ProfilingLogger = proflog; + } + public override void Terminate() { base.Terminate(); Terminated = true; } } - - // test all event handler - // fixme should become test component - public class TestApplicationEventHandler : DisposableObject, IApplicationEventHandler - { - public static void Reset() - { - Initialized = false; - Starting = false; - Started = false; - HasBeenDisposed = false; - } - - public static bool Initialized; - public static bool Starting; - public static bool Started; - public static bool HasBeenDisposed; - - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication) - { - Initialized = true; - } - - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication) - { - Starting = true; - } - - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - Started = true; - } - - protected override void DisposeResources() - { - HasBeenDisposed = true; - } - } - - [Test] - public void ComponentLifeCycle() - { - using (var app = new TestUmbracoApplication()) - { - app.HandleApplicationStart(app, new EventArgs()); - - Assert.IsTrue(TestComponent.Ctored); - Assert.IsTrue(TestComponent.Composed); - Assert.IsTrue(TestComponent.Initialized1); - Assert.IsTrue(TestComponent.Initialized2); - - Assert.IsFalse(TestComponent.Terminated); - - app.HandleApplicationEnd(); - Assert.IsTrue(TestComponent.Terminated); - } - } - - // note: components are NOT disposed after boot - [Test] - public void Disposes_App_Startup_Handlers_After_Startup() - { - using (var app = new TestUmbracoApplication()) - { - app.HandleApplicationStart(app, new EventArgs()); - - Assert.IsTrue(TestApplicationEventHandler.HasBeenDisposed); - } - } - - [Test] - public void Handle_IApplicationEventHandler_Objects_Outside_Web_Context() - { - using (var app = new TestUmbracoApplication()) - { - app.HandleApplicationStart(app, new EventArgs()); - - Assert.IsTrue(TestApplicationEventHandler.Initialized); - Assert.IsTrue(TestApplicationEventHandler.Starting); - Assert.IsTrue(TestApplicationEventHandler.Started); - } - } - - [Test] - public void Raises_Starting_Events() - { - using (var app = new TestUmbracoApplication()) - { - EventHandler starting = (sender, args) => - { - Assert.IsTrue(TestApplicationEventHandler.Initialized); - Assert.IsTrue(TestApplicationEventHandler.Starting); - Assert.IsFalse(TestApplicationEventHandler.Started); - }; - EventHandler started = (sender, args) => - { - Assert.IsTrue(TestApplicationEventHandler.Initialized); - Assert.IsTrue(TestApplicationEventHandler.Starting); - Assert.IsTrue(TestApplicationEventHandler.Started); - }; - - app.ApplicationStarting += starting; - app.ApplicationStarted += started; - - app.HandleApplicationStart(app, new EventArgs()); - - app.ApplicationStarting -= starting; - app.ApplicationStarting -= started; - } - } } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 3b1b4a25cb..2e1e071375 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -232,7 +232,8 @@ - + + diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherComponent.cs similarity index 95% rename from src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs rename to src/Umbraco.Web/Cache/CacheRefresherComponent.cs index e2743a5233..3421e1946a 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherComponent.cs @@ -1,485 +1,486 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Services; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Services.Changes; -using Umbraco.Web.Services; -using Content = Umbraco.Core.Models.Content; -using ApplicationTree = Umbraco.Core.Models.ApplicationTree; - -namespace Umbraco.Web.Cache -{ - /// - /// Installs listeners on service events in order to refresh our caches. - /// - public class CacheRefresherEventHandler : ApplicationEventHandler - { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing."); - AddHandlers(); - } - - internal void AddHandlers() - { - // bind to application tree events - ApplicationTreeService.Deleted += ApplicationTreeDeleted; - ApplicationTreeService.Updated += ApplicationTreeUpdated; - ApplicationTreeService.New += ApplicationTreeNew; - - // bind to application events - SectionService.Deleted += ApplicationDeleted; - SectionService.New += ApplicationNew; - - // bind to user / user type events - UserService.SavedUserType += UserServiceSavedUserType; - UserService.DeletedUserType += UserServiceDeletedUserType; - UserService.SavedUser += UserServiceSavedUser; - UserService.DeletedUser += UserServiceDeletedUser; - - // bind to dictionary events - LocalizationService.DeletedDictionaryItem += LocalizationServiceDeletedDictionaryItem; - LocalizationService.SavedDictionaryItem += LocalizationServiceSavedDictionaryItem; - - // bind to data type events - // NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 - DataTypeService.Deleted += DataTypeServiceDeleted; - DataTypeService.Saved += DataTypeServiceSaved; - - // bind to stylesheet events - //FileService.SavedStylesheet += FileServiceSavedStylesheet; - //FileService.DeletedStylesheet += FileServiceDeletedStylesheet; - - // bind to domain events - DomainService.Saved += DomainServiceSaved; - DomainService.Deleted += DomainServiceDeleted; - - // bind to language events - LocalizationService.SavedLanguage += LocalizationServiceSavedLanguage; - LocalizationService.DeletedLanguage += LocalizationServiceDeletedLanguage; - - // bind to content type events - ContentTypeService.Changed += ContentTypeServiceChanged; - MediaTypeService.Changed += ContentTypeServiceChanged; - MemberTypeService.Changed += ContentTypeServiceChanged; - - // bind to permission events - PermissionRepository.AssignedPermissions += PermissionRepositoryAssignedPermissions; - - // bind to template events - FileService.SavedTemplate += FileServiceSavedTemplate; - FileService.DeletedTemplate += FileServiceDeletedTemplate; - - // bind to macro events - MacroService.Saved += MacroServiceSaved; - MacroService.Deleted += MacroServiceDeleted; - - // bind to member events - MemberService.Saved += MemberServiceSaved; - MemberService.Deleted += MemberServiceDeleted; - MemberGroupService.Saved += MemberGroupServiceSaved; - MemberGroupService.Deleted += MemberGroupServiceDeleted; - - // bind to media events - MediaService.TreeChanged += MediaServiceChanged; // handles all media changes - - // bind to content events - ContentService.Saved += ContentServiceSaved; // needed for permissions - ContentService.Copied += ContentServiceCopied; // needed for permissions - ContentService.TreeChanged += ContentServiceChanged; // handles all content changes - - // bind to public access events - PublicAccessService.Saved += PublicAccessServiceSaved; - PublicAccessService.Deleted += PublicAccessServiceDeleted; - } - - internal void RemoveHandlers() - { - // bind to application tree events - ApplicationTreeService.Deleted -= ApplicationTreeDeleted; - ApplicationTreeService.Updated -= ApplicationTreeUpdated; - ApplicationTreeService.New -= ApplicationTreeNew; - - // bind to application events - SectionService.Deleted -= ApplicationDeleted; - SectionService.New -= ApplicationNew; - - // bind to user / user type events - UserService.SavedUserType -= UserServiceSavedUserType; - UserService.DeletedUserType -= UserServiceDeletedUserType; - UserService.SavedUser -= UserServiceSavedUser; - UserService.DeletedUser -= UserServiceDeletedUser; - - // bind to dictionary events - LocalizationService.DeletedDictionaryItem -= LocalizationServiceDeletedDictionaryItem; - LocalizationService.SavedDictionaryItem -= LocalizationServiceSavedDictionaryItem; - - // bind to data type events - // NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 - DataTypeService.Deleted -= DataTypeServiceDeleted; - DataTypeService.Saved -= DataTypeServiceSaved; - - // bind to stylesheet events - //FileService.SavedStylesheet -= FileServiceSavedStylesheet; - //FileService.DeletedStylesheet -= FileServiceDeletedStylesheet; - - // bind to domain events - DomainService.Saved -= DomainServiceSaved; - DomainService.Deleted -= DomainServiceDeleted; - - // bind to language events - LocalizationService.SavedLanguage -= LocalizationServiceSavedLanguage; - LocalizationService.DeletedLanguage -= LocalizationServiceDeletedLanguage; - - // bind to content type events - ContentTypeService.Changed -= ContentTypeServiceChanged; - MediaTypeService.Changed -= ContentTypeServiceChanged; - MemberTypeService.Changed -= ContentTypeServiceChanged; - - // bind to permission events - PermissionRepository.AssignedPermissions -= PermissionRepositoryAssignedPermissions; - - // bind to template events - FileService.SavedTemplate -= FileServiceSavedTemplate; - FileService.DeletedTemplate -= FileServiceDeletedTemplate; - - // bind to macro events - MacroService.Saved -= MacroServiceSaved; - MacroService.Deleted -= MacroServiceDeleted; - - // bind to member events - MemberService.Saved -= MemberServiceSaved; - MemberService.Deleted -= MemberServiceDeleted; - MemberGroupService.Saved -= MemberGroupServiceSaved; - MemberGroupService.Deleted -= MemberGroupServiceDeleted; - - // bind to media events - MediaService.TreeChanged -= MediaServiceChanged; // handles all media changes - - // bind to content events - ContentService.Saved -= ContentServiceSaved; // needed for permissions - ContentService.Copied -= ContentServiceCopied; // needed for permissions - ContentService.TreeChanged -= ContentServiceChanged; // handles all content changes - - // bind to public access events - PublicAccessService.Saved -= PublicAccessServiceSaved; - PublicAccessService.Deleted -= PublicAccessServiceDeleted; - } - - #region PublicAccessService - - static void PublicAccessServiceSaved(IPublicAccessService sender, SaveEventArgs e) - { - DistributedCache.Instance.RefreshPublicAccess(); - } - - private void PublicAccessServiceDeleted(IPublicAccessService sender, DeleteEventArgs e) - { - DistributedCache.Instance.RefreshPublicAccess(); - } - - #endregion - - #region ContentService - - /// - /// Handles cache refreshing for when content is copied - /// - /// - /// - /// - /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the - /// case then we need to clear all user permissions cache. - /// - static void ContentServiceCopied(IContentService sender, CopyEventArgs e) - { - //check if permissions have changed - var permissionsChanged = ((Content)e.Copy).WasPropertyDirty("PermissionsChanged"); - if (permissionsChanged) - { - DistributedCache.Instance.RefreshAllUserPermissionsCache(); - } - } - - /// - /// Handles cache refreshing for when content is saved (not published) - /// - /// - /// - /// - /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to - /// stay up-to-date for unpublished content. - /// - /// When an entity is created new permissions may be assigned to it based on it's parent, if that is the - /// case then we need to clear all user permissions cache. - /// - static void ContentServiceSaved(IContentService sender, SaveEventArgs e) - { - var clearUserPermissions = false; - e.SavedEntities.ForEach(x => - { - //check if it is new - if (x.IsNewEntity()) - { - //check if permissions have changed - var permissionsChanged = ((Content)x).WasPropertyDirty("PermissionsChanged"); - if (permissionsChanged) - { - clearUserPermissions = true; - } - } - }); - - if (clearUserPermissions) - { - DistributedCache.Instance.RefreshAllUserPermissionsCache(); - } - } - - private static void ContentServiceChanged(IContentService sender, TreeChange.EventArgs args) - { - DistributedCache.Instance.RefreshContentCache(args.Changes.ToArray()); - } - - #endregion - - #region ApplicationTreeService - - static void ApplicationTreeNew(ApplicationTree sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationTreeCache(); - } - - static void ApplicationTreeUpdated(ApplicationTree sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationTreeCache(); - } - - static void ApplicationTreeDeleted(ApplicationTree sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationTreeCache(); - } - #endregion - - #region Application event handlers - static void ApplicationNew(Section sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationCache(); - } - - static void ApplicationDeleted(Section sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationCache(); - } - - #endregion - - #region UserService / UserType - - static void UserServiceDeletedUserType(IUserService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserTypeCache(x.Id)); - } - - static void UserServiceSavedUserType(IUserService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserTypeCache(x.Id)); - } - - #endregion - - #region LocalizationService / Dictionary - - static void LocalizationServiceSavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDictionaryCache(x.Id)); - } - - static void LocalizationServiceDeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDictionaryCache(x.Id)); - } - - #endregion - - #region DataTypeService - static void DataTypeServiceSaved(IDataTypeService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDataTypeCache(x)); - } - - static void DataTypeServiceDeleted(IDataTypeService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDataTypeCache(x)); - } - - #endregion - - #region DomainService - - static void DomainServiceSaved(IDomainService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDomainCache(x)); - } - - static void DomainServiceDeleted(IDomainService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDomainCache(x)); - } - - #endregion - - #region LocalizationService / Language - /// - /// Fires when a langauge is deleted - /// - /// - /// - static void LocalizationServiceDeletedLanguage(ILocalizationService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveLanguageCache(x)); - } - - /// - /// Fires when a langauge is saved - /// - /// - /// - static void LocalizationServiceSavedLanguage(ILocalizationService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshLanguageCache(x)); - } - - #endregion - - #region Content|Media|MemberTypeService - - private void ContentTypeServiceChanged(IContentTypeService sender, ContentTypeChange.EventArgs args) - { - DistributedCache.Instance.RefreshContentTypeCache(args.Changes.ToArray()); - } - - private void ContentTypeServiceChanged(IMediaTypeService sender, ContentTypeChange.EventArgs args) - { - DistributedCache.Instance.RefreshContentTypeCache(args.Changes.ToArray()); - } - - private void ContentTypeServiceChanged(IMemberTypeService sender, ContentTypeChange.EventArgs args) - { - DistributedCache.Instance.RefreshContentTypeCache(args.Changes.ToArray()); - } - - #endregion - - #region UserService & PermissionRepository - - static void PermissionRepositoryAssignedPermissions(PermissionRepository sender, SaveEventArgs e) - { - var userIds = e.SavedEntities.Select(x => x.UserId).Distinct(); - userIds.ForEach(x => DistributedCache.Instance.RefreshUserPermissionsCache(x)); - } - - static void UserServiceSavedUser(IUserService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserCache(x.Id)); - } - - static void UserServiceDeletedUser(IUserService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); - } - - #endregion - - #region FileService / Template - - /// - /// Removes cache for template - /// - /// - /// - static void FileServiceDeletedTemplate(IFileService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveTemplateCache(x.Id)); - } - - /// - /// Refresh cache for template - /// - /// - /// - static void FileServiceSavedTemplate(IFileService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshTemplateCache(x.Id)); - } - - #endregion - - #region MacroService - - void MacroServiceDeleted(IMacroService sender, DeleteEventArgs e) - { - foreach (var entity in e.DeletedEntities) - { - DistributedCache.Instance.RemoveMacroCache(entity); - } - } - - void MacroServiceSaved(IMacroService sender, SaveEventArgs e) - { - foreach (var entity in e.SavedEntities) - { - DistributedCache.Instance.RefreshMacroCache(entity); - } - } - - #endregion - - #region MediaService - - private static void MediaServiceChanged(IMediaService sender, TreeChange.EventArgs args) - { - DistributedCache.Instance.RefreshMediaCache(args.Changes.ToArray()); - } - - #endregion - - #region MemberService - - static void MemberServiceDeleted(IMemberService sender, DeleteEventArgs e) - { - DistributedCache.Instance.RemoveMemberCache(e.DeletedEntities.ToArray()); - } - - static void MemberServiceSaved(IMemberService sender, SaveEventArgs e) - { - DistributedCache.Instance.RefreshMemberCache(e.SavedEntities.ToArray()); - } - - #endregion - - #region MemberGroupService - - static void MemberGroupServiceDeleted(IMemberGroupService sender, DeleteEventArgs e) - { - foreach (var m in e.DeletedEntities.ToArray()) - { - DistributedCache.Instance.RemoveMemberGroupCache(m.Id); - } - } - - static void MemberGroupServiceSaved(IMemberGroupService sender, SaveEventArgs e) - { - foreach (var m in e.SavedEntities.ToArray()) - { - DistributedCache.Instance.RemoveMemberGroupCache(m.Id); - } - } - #endregion - } +using System; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Services; +using System.Linq; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Core.Services.Changes; +using Umbraco.Web.Services; +using Content = Umbraco.Core.Models.Content; +using ApplicationTree = Umbraco.Core.Models.ApplicationTree; + +namespace Umbraco.Web.Cache +{ + /// + /// Installs listeners on service events in order to refresh our caches. + /// + public class CacheRefresherComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public void Initialize() + { + LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing."); + AddHandlers(); + } + + internal void AddHandlers() + { + // bind to application tree events + ApplicationTreeService.Deleted += ApplicationTreeDeleted; + ApplicationTreeService.Updated += ApplicationTreeUpdated; + ApplicationTreeService.New += ApplicationTreeNew; + + // bind to application events + SectionService.Deleted += ApplicationDeleted; + SectionService.New += ApplicationNew; + + // bind to user / user type events + UserService.SavedUserType += UserServiceSavedUserType; + UserService.DeletedUserType += UserServiceDeletedUserType; + UserService.SavedUser += UserServiceSavedUser; + UserService.DeletedUser += UserServiceDeletedUser; + + // bind to dictionary events + LocalizationService.DeletedDictionaryItem += LocalizationServiceDeletedDictionaryItem; + LocalizationService.SavedDictionaryItem += LocalizationServiceSavedDictionaryItem; + + // bind to data type events + // NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 + DataTypeService.Deleted += DataTypeServiceDeleted; + DataTypeService.Saved += DataTypeServiceSaved; + + // bind to stylesheet events + //FileService.SavedStylesheet += FileServiceSavedStylesheet; + //FileService.DeletedStylesheet += FileServiceDeletedStylesheet; + + // bind to domain events + DomainService.Saved += DomainServiceSaved; + DomainService.Deleted += DomainServiceDeleted; + + // bind to language events + LocalizationService.SavedLanguage += LocalizationServiceSavedLanguage; + LocalizationService.DeletedLanguage += LocalizationServiceDeletedLanguage; + + // bind to content type events + ContentTypeService.Changed += ContentTypeServiceChanged; + MediaTypeService.Changed += ContentTypeServiceChanged; + MemberTypeService.Changed += ContentTypeServiceChanged; + + // bind to permission events + PermissionRepository.AssignedPermissions += PermissionRepositoryAssignedPermissions; + + // bind to template events + FileService.SavedTemplate += FileServiceSavedTemplate; + FileService.DeletedTemplate += FileServiceDeletedTemplate; + + // bind to macro events + MacroService.Saved += MacroServiceSaved; + MacroService.Deleted += MacroServiceDeleted; + + // bind to member events + MemberService.Saved += MemberServiceSaved; + MemberService.Deleted += MemberServiceDeleted; + MemberGroupService.Saved += MemberGroupServiceSaved; + MemberGroupService.Deleted += MemberGroupServiceDeleted; + + // bind to media events + MediaService.TreeChanged += MediaServiceChanged; // handles all media changes + + // bind to content events + ContentService.Saved += ContentServiceSaved; // needed for permissions + ContentService.Copied += ContentServiceCopied; // needed for permissions + ContentService.TreeChanged += ContentServiceChanged; // handles all content changes + + // bind to public access events + PublicAccessService.Saved += PublicAccessServiceSaved; + PublicAccessService.Deleted += PublicAccessServiceDeleted; + } + + internal void RemoveHandlers() + { + // bind to application tree events + ApplicationTreeService.Deleted -= ApplicationTreeDeleted; + ApplicationTreeService.Updated -= ApplicationTreeUpdated; + ApplicationTreeService.New -= ApplicationTreeNew; + + // bind to application events + SectionService.Deleted -= ApplicationDeleted; + SectionService.New -= ApplicationNew; + + // bind to user / user type events + UserService.SavedUserType -= UserServiceSavedUserType; + UserService.DeletedUserType -= UserServiceDeletedUserType; + UserService.SavedUser -= UserServiceSavedUser; + UserService.DeletedUser -= UserServiceDeletedUser; + + // bind to dictionary events + LocalizationService.DeletedDictionaryItem -= LocalizationServiceDeletedDictionaryItem; + LocalizationService.SavedDictionaryItem -= LocalizationServiceSavedDictionaryItem; + + // bind to data type events + // NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 + DataTypeService.Deleted -= DataTypeServiceDeleted; + DataTypeService.Saved -= DataTypeServiceSaved; + + // bind to stylesheet events + //FileService.SavedStylesheet -= FileServiceSavedStylesheet; + //FileService.DeletedStylesheet -= FileServiceDeletedStylesheet; + + // bind to domain events + DomainService.Saved -= DomainServiceSaved; + DomainService.Deleted -= DomainServiceDeleted; + + // bind to language events + LocalizationService.SavedLanguage -= LocalizationServiceSavedLanguage; + LocalizationService.DeletedLanguage -= LocalizationServiceDeletedLanguage; + + // bind to content type events + ContentTypeService.Changed -= ContentTypeServiceChanged; + MediaTypeService.Changed -= ContentTypeServiceChanged; + MemberTypeService.Changed -= ContentTypeServiceChanged; + + // bind to permission events + PermissionRepository.AssignedPermissions -= PermissionRepositoryAssignedPermissions; + + // bind to template events + FileService.SavedTemplate -= FileServiceSavedTemplate; + FileService.DeletedTemplate -= FileServiceDeletedTemplate; + + // bind to macro events + MacroService.Saved -= MacroServiceSaved; + MacroService.Deleted -= MacroServiceDeleted; + + // bind to member events + MemberService.Saved -= MemberServiceSaved; + MemberService.Deleted -= MemberServiceDeleted; + MemberGroupService.Saved -= MemberGroupServiceSaved; + MemberGroupService.Deleted -= MemberGroupServiceDeleted; + + // bind to media events + MediaService.TreeChanged -= MediaServiceChanged; // handles all media changes + + // bind to content events + ContentService.Saved -= ContentServiceSaved; // needed for permissions + ContentService.Copied -= ContentServiceCopied; // needed for permissions + ContentService.TreeChanged -= ContentServiceChanged; // handles all content changes + + // bind to public access events + PublicAccessService.Saved -= PublicAccessServiceSaved; + PublicAccessService.Deleted -= PublicAccessServiceDeleted; + } + + #region PublicAccessService + + static void PublicAccessServiceSaved(IPublicAccessService sender, SaveEventArgs e) + { + DistributedCache.Instance.RefreshPublicAccess(); + } + + private void PublicAccessServiceDeleted(IPublicAccessService sender, DeleteEventArgs e) + { + DistributedCache.Instance.RefreshPublicAccess(); + } + + #endregion + + #region ContentService + + /// + /// Handles cache refreshing for when content is copied + /// + /// + /// + /// + /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the + /// case then we need to clear all user permissions cache. + /// + static void ContentServiceCopied(IContentService sender, CopyEventArgs e) + { + //check if permissions have changed + var permissionsChanged = ((Content)e.Copy).WasPropertyDirty("PermissionsChanged"); + if (permissionsChanged) + { + DistributedCache.Instance.RefreshAllUserPermissionsCache(); + } + } + + /// + /// Handles cache refreshing for when content is saved (not published) + /// + /// + /// + /// + /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to + /// stay up-to-date for unpublished content. + /// + /// When an entity is created new permissions may be assigned to it based on it's parent, if that is the + /// case then we need to clear all user permissions cache. + /// + static void ContentServiceSaved(IContentService sender, SaveEventArgs e) + { + var clearUserPermissions = false; + e.SavedEntities.ForEach(x => + { + //check if it is new + if (x.IsNewEntity()) + { + //check if permissions have changed + var permissionsChanged = ((Content)x).WasPropertyDirty("PermissionsChanged"); + if (permissionsChanged) + { + clearUserPermissions = true; + } + } + }); + + if (clearUserPermissions) + { + DistributedCache.Instance.RefreshAllUserPermissionsCache(); + } + } + + private static void ContentServiceChanged(IContentService sender, TreeChange.EventArgs args) + { + DistributedCache.Instance.RefreshContentCache(args.Changes.ToArray()); + } + + #endregion + + #region ApplicationTreeService + + static void ApplicationTreeNew(ApplicationTree sender, EventArgs e) + { + DistributedCache.Instance.RefreshAllApplicationTreeCache(); + } + + static void ApplicationTreeUpdated(ApplicationTree sender, EventArgs e) + { + DistributedCache.Instance.RefreshAllApplicationTreeCache(); + } + + static void ApplicationTreeDeleted(ApplicationTree sender, EventArgs e) + { + DistributedCache.Instance.RefreshAllApplicationTreeCache(); + } + #endregion + + #region Application event handlers + static void ApplicationNew(Section sender, EventArgs e) + { + DistributedCache.Instance.RefreshAllApplicationCache(); + } + + static void ApplicationDeleted(Section sender, EventArgs e) + { + DistributedCache.Instance.RefreshAllApplicationCache(); + } + + #endregion + + #region UserService / UserType + + static void UserServiceDeletedUserType(IUserService sender, DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserTypeCache(x.Id)); + } + + static void UserServiceSavedUserType(IUserService sender, SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserTypeCache(x.Id)); + } + + #endregion + + #region LocalizationService / Dictionary + + static void LocalizationServiceSavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDictionaryCache(x.Id)); + } + + static void LocalizationServiceDeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDictionaryCache(x.Id)); + } + + #endregion + + #region DataTypeService + static void DataTypeServiceSaved(IDataTypeService sender, SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDataTypeCache(x)); + } + + static void DataTypeServiceDeleted(IDataTypeService sender, DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDataTypeCache(x)); + } + + #endregion + + #region DomainService + + static void DomainServiceSaved(IDomainService sender, SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDomainCache(x)); + } + + static void DomainServiceDeleted(IDomainService sender, DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDomainCache(x)); + } + + #endregion + + #region LocalizationService / Language + /// + /// Fires when a langauge is deleted + /// + /// + /// + static void LocalizationServiceDeletedLanguage(ILocalizationService sender, DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveLanguageCache(x)); + } + + /// + /// Fires when a langauge is saved + /// + /// + /// + static void LocalizationServiceSavedLanguage(ILocalizationService sender, SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshLanguageCache(x)); + } + + #endregion + + #region Content|Media|MemberTypeService + + private void ContentTypeServiceChanged(IContentTypeService sender, ContentTypeChange.EventArgs args) + { + DistributedCache.Instance.RefreshContentTypeCache(args.Changes.ToArray()); + } + + private void ContentTypeServiceChanged(IMediaTypeService sender, ContentTypeChange.EventArgs args) + { + DistributedCache.Instance.RefreshContentTypeCache(args.Changes.ToArray()); + } + + private void ContentTypeServiceChanged(IMemberTypeService sender, ContentTypeChange.EventArgs args) + { + DistributedCache.Instance.RefreshContentTypeCache(args.Changes.ToArray()); + } + + #endregion + + #region UserService & PermissionRepository + + static void PermissionRepositoryAssignedPermissions(PermissionRepository sender, SaveEventArgs e) + { + var userIds = e.SavedEntities.Select(x => x.UserId).Distinct(); + userIds.ForEach(x => DistributedCache.Instance.RefreshUserPermissionsCache(x)); + } + + static void UserServiceSavedUser(IUserService sender, SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserCache(x.Id)); + } + + static void UserServiceDeletedUser(IUserService sender, DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); + } + + #endregion + + #region FileService / Template + + /// + /// Removes cache for template + /// + /// + /// + static void FileServiceDeletedTemplate(IFileService sender, DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveTemplateCache(x.Id)); + } + + /// + /// Refresh cache for template + /// + /// + /// + static void FileServiceSavedTemplate(IFileService sender, SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshTemplateCache(x.Id)); + } + + #endregion + + #region MacroService + + void MacroServiceDeleted(IMacroService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + { + DistributedCache.Instance.RemoveMacroCache(entity); + } + } + + void MacroServiceSaved(IMacroService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + { + DistributedCache.Instance.RefreshMacroCache(entity); + } + } + + #endregion + + #region MediaService + + private static void MediaServiceChanged(IMediaService sender, TreeChange.EventArgs args) + { + DistributedCache.Instance.RefreshMediaCache(args.Changes.ToArray()); + } + + #endregion + + #region MemberService + + static void MemberServiceDeleted(IMemberService sender, DeleteEventArgs e) + { + DistributedCache.Instance.RemoveMemberCache(e.DeletedEntities.ToArray()); + } + + static void MemberServiceSaved(IMemberService sender, SaveEventArgs e) + { + DistributedCache.Instance.RefreshMemberCache(e.SavedEntities.ToArray()); + } + + #endregion + + #region MemberGroupService + + static void MemberGroupServiceDeleted(IMemberGroupService sender, DeleteEventArgs e) + { + foreach (var m in e.DeletedEntities.ToArray()) + { + DistributedCache.Instance.RemoveMemberGroupCache(m.Id); + } + } + + static void MemberGroupServiceSaved(IMemberGroupService sender, SaveEventArgs e) + { + foreach (var m in e.SavedEntities.ToArray()) + { + DistributedCache.Instance.RemoveMemberGroupCache(m.Id); + } + } + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs index 0264f2b3fc..a129e0a394 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Plugins; using Umbraco.Core.Services; using Umbraco.Web.PublishedCache; @@ -13,6 +14,7 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity "XML Data Integrity", Description = "Checks the integrity of the XML data in Umbraco", Group = "Data Integrity")] + [HideFromTypeFinder] // only if running the Xml cache! added by XmlCacheComponent! public class XmlDataIntegrityHealthCheck : HealthCheck { private readonly ILocalizedTextService _textService; diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index bcc91ad042..576ffb2887 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Drawing; -using System.Globalization; using System.Linq; -using System.Text.RegularExpressions; -using System.Xml; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -20,7 +14,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Constants.PropertyEditors.UploadFieldAlias, "File upload", "fileupload", Icon = "icon-download-alt", Group = "media")] - public class FileUploadPropertyEditor : PropertyEditor, IApplicationEventHandler + public class FileUploadPropertyEditor : PropertyEditor { private readonly MediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSettings; @@ -29,14 +23,13 @@ namespace Umbraco.Web.PropertyEditors public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings, ILocalizedTextService textService) : base(logger) { - if (mediaFileSystem == null) throw new ArgumentNullException("mediaFileSystem"); - if (contentSettings == null) throw new ArgumentNullException("contentSettings"); - if (textService == null) throw new ArgumentNullException("textService"); - _applicationStartup = new FileUploadPropertyEditorApplicationStartup(this); + if (mediaFileSystem == null) throw new ArgumentNullException(nameof(mediaFileSystem)); + if (contentSettings == null) throw new ArgumentNullException(nameof(contentSettings)); + if (textService == null) throw new ArgumentNullException(nameof(textService)); + _mediaFileSystem = mediaFileSystem; _contentSettings = contentSettings; - _textService = textService; - + _textService = textService; } /// @@ -59,7 +52,7 @@ namespace Umbraco.Web.PropertyEditors /// Ensures any files associated are removed /// /// - IEnumerable ServiceEmptiedRecycleBin(Dictionary> allPropertyData) + internal IEnumerable ServiceEmptiedRecycleBin(Dictionary> allPropertyData) { var list = new List(); //Get all values for any image croppers found @@ -81,7 +74,7 @@ namespace Umbraco.Web.PropertyEditors /// Ensures any files associated are removed /// /// - IEnumerable ServiceDeleted(IEnumerable deletedEntities) + internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) { var list = new List(); foreach (var property in deletedEntities.SelectMany(deletedEntity => deletedEntity @@ -103,7 +96,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs e) + internal void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs e) { if (e.Original.Properties.Any(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)) { @@ -142,12 +135,12 @@ namespace Umbraco.Web.PropertyEditors } } - void MediaServiceCreating(IMediaService sender, Core.Events.NewEventArgs e) + internal void MediaServiceCreating(IMediaService sender, Core.Events.NewEventArgs e) { AutoFillProperties(e.Entity); } - void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs e) + internal void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs e) { foreach (var m in e.SavedEntities) { @@ -265,62 +258,5 @@ namespace Umbraco.Web.PropertyEditors } } } - - #region Application event handler, used to bind to events on startup - - private readonly FileUploadPropertyEditorApplicationStartup _applicationStartup; - - /// - /// we're using a sub -class because this has the logic to prevent it from executing if the application is not configured - /// - private class FileUploadPropertyEditorApplicationStartup : ApplicationEventHandler - { - private FileUploadPropertyEditor _fileUploadPropertyEditor; - - public FileUploadPropertyEditorApplicationStartup(FileUploadPropertyEditor fileUploadPropertyEditor) - { - this._fileUploadPropertyEditor = fileUploadPropertyEditor; - } - - /// - /// We're going to bind to the MediaService Saving event so that we can populate the umbracoFile size, type, etc... label fields - /// if we find any attached to the current media item. - /// - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - MediaService.Saving += _fileUploadPropertyEditor.MediaServiceSaving; - MediaService.Created += _fileUploadPropertyEditor.MediaServiceCreating; - ContentService.Copied += _fileUploadPropertyEditor.ContentServiceCopied; - - MediaService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(_fileUploadPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast())); - MediaService.EmptiedRecycleBin += (sender, args) => - args.Files.AddRange(_fileUploadPropertyEditor.ServiceEmptiedRecycleBin(args.AllPropertyData)); - ContentService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(_fileUploadPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.EmptiedRecycleBin += (sender, args) => - args.Files.AddRange(_fileUploadPropertyEditor.ServiceEmptiedRecycleBin(args.AllPropertyData)); - MemberService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(_fileUploadPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast())); - } - } - - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationInitialized(umbracoApplication); - } - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationStarting(umbracoApplication); - } - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationStarted(umbracoApplication); - } - #endregion - } } diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index fdef4c9be4..30deedc22b 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -12,18 +12,14 @@ using UmbracoExamine; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Core.Constants.PropertyEditors.GridAlias, "Grid layout", "grid", HideLabel = true, IsParameterEditor = false, ValueType = PropertyEditorValueTypes.Json, Group="rich content", Icon="icon-layout")] - public class GridPropertyEditor : PropertyEditor, IApplicationEventHandler - { - /// - /// Constructor - /// - public GridPropertyEditor(ILogger logger, IExamineIndexCollectionAccessor indexCollection) : base(logger) - { - _applicationStartup = new GridPropertyEditorApplicationStartup(indexCollection); - } + [PropertyEditor(Constants.PropertyEditors.GridAlias, "Grid layout", "grid", HideLabel = true, IsParameterEditor = false, ValueType = PropertyEditorValueTypes.Json, Group="rich content", Icon="icon-layout")] + public class GridPropertyEditor : PropertyEditor + { + public GridPropertyEditor(ILogger logger) + : base(logger) + { } - private static void DocumentWriting(object sender, Examine.LuceneEngine.DocumentWritingEventArgs e) + internal void DocumentWriting(object sender, Examine.LuceneEngine.DocumentWritingEventArgs e) { var indexer = (BaseUmbracoIndexer)sender; foreach (var field in indexer.IndexerData.UserFields) @@ -128,52 +124,5 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public string Rte { get; set; } } - - #region Application event handler, used to bind to events on startup - - private readonly GridPropertyEditorApplicationStartup _applicationStartup; - - /// - /// we're using a sub -class because this has the logic to prevent it from executing if the application is not configured - /// - private class GridPropertyEditorApplicationStartup : ApplicationEventHandler - { - private readonly IExamineIndexCollectionAccessor _indexCollection; - - public GridPropertyEditorApplicationStartup(IExamineIndexCollectionAccessor indexCollection) - { - this._indexCollection = indexCollection; - } - - /// - /// We're going to bind to the Examine events so we can ensure grid data is index nicely. - /// - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - foreach (var i in _indexCollection.Indexes.Values.OfType()) - { - i.DocumentWriting += DocumentWriting; - } - } - } - - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationInitialized(umbracoApplication); - } - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationStarting(umbracoApplication); - } - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationStarted(umbracoApplication); - } - #endregion } - - } diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index e4aa3bdecc..e15bcef307 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -3,10 +3,7 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -17,7 +14,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Constants.PropertyEditors.ImageCropperAlias, "Image Cropper", "imagecropper", ValueType = PropertyEditorValueTypes.Json, HideLabel = false, Group="media", Icon="icon-crop")] - public class ImageCropperPropertyEditor : PropertyEditor, IApplicationEventHandler + public class ImageCropperPropertyEditor : PropertyEditor { private readonly MediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSettings; @@ -25,10 +22,8 @@ namespace Umbraco.Web.PropertyEditors public ImageCropperPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings) : base(logger) { - if (mediaFileSystem == null) throw new ArgumentNullException("mediaFileSystem"); - if (contentSettings == null) throw new ArgumentNullException("contentSettings"); - - _applicationStartup = new FileUploadPropertyEditorApplicationStartup(this); + if (mediaFileSystem == null) throw new ArgumentNullException(nameof(mediaFileSystem)); + if (contentSettings == null) throw new ArgumentNullException(nameof(contentSettings)); _mediaFileSystem = mediaFileSystem; _contentSettings = contentSettings; @@ -60,7 +55,7 @@ namespace Umbraco.Web.PropertyEditors /// Ensures any files associated are removed /// /// - IEnumerable ServiceEmptiedRecycleBin(Dictionary> allPropertyData) + internal IEnumerable ServiceEmptiedRecycleBin(Dictionary> allPropertyData) { var list = new List(); //Get all values for any image croppers found @@ -93,7 +88,7 @@ namespace Umbraco.Web.PropertyEditors /// Ensures any files associated are removed /// /// - IEnumerable ServiceDeleted(IEnumerable deletedEntities) + internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) { var list = new List(); foreach (var property in deletedEntities.SelectMany(deletedEntity => deletedEntity @@ -126,7 +121,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs e) + internal void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs e) { if (e.Original.Properties.Any(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)) { @@ -145,7 +140,7 @@ namespace Umbraco.Web.PropertyEditors } catch (Exception ex) { - Logger.Error("An error occurred parsing the value stored in the image cropper value: " + property.Value.ToString(), ex); + Logger.Error("An error occurred parsing the value stored in the image cropper value: " + property.Value, ex); continue; } @@ -170,8 +165,6 @@ namespace Umbraco.Web.PropertyEditors isUpdated = true; } } - - } if (isUpdated) @@ -182,12 +175,12 @@ namespace Umbraco.Web.PropertyEditors } } - void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs e) + internal void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs e) { AutoFillProperties(e.Entity); } - void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs e) + internal void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs e) { foreach (var m in e.SavedEntities) { @@ -252,61 +245,5 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("crops", "Crop sizes", "views/propertyeditors/imagecropper/imagecropper.prevalues.html")] public string Crops { get; set; } } - - #region Application event handler, used to bind to events on startup - - private readonly FileUploadPropertyEditorApplicationStartup _applicationStartup; - - /// - /// we're using a sub -class because this has the logic to prevent it from executing if the application is not configured - /// - private class FileUploadPropertyEditorApplicationStartup : ApplicationEventHandler - { - private readonly ImageCropperPropertyEditor _imageCropperPropertyEditor; - - public FileUploadPropertyEditorApplicationStartup(ImageCropperPropertyEditor imageCropperPropertyEditor) - { - _imageCropperPropertyEditor = imageCropperPropertyEditor; - } - - /// - /// We're going to bind to the MediaService Saving event so that we can populate the umbracoFile size, type, etc... label fields - /// if we find any attached to the current media item. - /// - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - MediaService.Saving += _imageCropperPropertyEditor.MediaServiceSaving; - MediaService.Created += _imageCropperPropertyEditor.MediaServiceCreated; - ContentService.Copied += _imageCropperPropertyEditor.ContentServiceCopied; - - MediaService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(_imageCropperPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast())); - MediaService.EmptiedRecycleBin += (sender, args) => - args.Files.AddRange(_imageCropperPropertyEditor.ServiceEmptiedRecycleBin(args.AllPropertyData)); - ContentService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(_imageCropperPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.EmptiedRecycleBin += (sender, args) => - args.Files.AddRange(_imageCropperPropertyEditor.ServiceEmptiedRecycleBin(args.AllPropertyData)); - MemberService.Deleted += (sender, args) => - args.MediaFilesToDelete.AddRange(_imageCropperPropertyEditor.ServiceDeleted(args.DeletedEntities.Cast())); - } - } - - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationInitialized(umbracoApplication); - } - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationStarting(umbracoApplication); - } - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication) - { - //wrap - _applicationStartup.OnApplicationStarted(umbracoApplication); - } - #endregion } } diff --git a/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs new file mode 100644 index 0000000000..86bc06f7fb --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs @@ -0,0 +1,73 @@ +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using UmbracoExamine; + +namespace Umbraco.Web.PropertyEditors +{ + internal class PropertyEditorsComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public void Initialize(IRuntimeState runtime, PropertyEditorCollection propertyEditors, IExamineIndexCollectionAccessor indexCollection) + { + if (runtime.Level != RuntimeLevel.Run) return; + + var fileUpload = propertyEditors.OfType().FirstOrDefault(); + if (fileUpload != null) Initialize(fileUpload); + + var imageCropper = propertyEditors.OfType().FirstOrDefault(); + if (imageCropper != null) Initialize(imageCropper); + + var grid = propertyEditors.OfType().FirstOrDefault(); + if (grid != null) Initialize(grid, indexCollection); + } + + // as long as these methods are private+static they won't be executed by the boot loader + + private static void Initialize(FileUploadPropertyEditor fileUpload) + { + MediaService.Saving += fileUpload.MediaServiceSaving; + MediaService.Created += fileUpload.MediaServiceCreating; + ContentService.Copied += fileUpload.ContentServiceCopied; + + MediaService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + MediaService.EmptiedRecycleBin += (sender, args) + => args.Files.AddRange(fileUpload.ServiceEmptiedRecycleBin(args.AllPropertyData)); + ContentService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.EmptiedRecycleBin += (sender, args) + => args.Files.AddRange(fileUpload.ServiceEmptiedRecycleBin(args.AllPropertyData)); + MemberService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + } + + private static void Initialize(ImageCropperPropertyEditor imageCropper) + { + MediaService.Saving += imageCropper.MediaServiceSaving; + MediaService.Created += imageCropper.MediaServiceCreated; + ContentService.Copied += imageCropper.ContentServiceCopied; + + MediaService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + MediaService.EmptiedRecycleBin += (sender, args) + => args.Files.AddRange(imageCropper.ServiceEmptiedRecycleBin(args.AllPropertyData)); + ContentService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.EmptiedRecycleBin += (sender, args) + => args.Files.AddRange(imageCropper.ServiceEmptiedRecycleBin(args.AllPropertyData)); + MemberService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + } + + private static void Initialize(GridPropertyEditor grid, IExamineIndexCollectionAccessor indexCollection) + { + var indexes = indexCollection.Indexes; + if (indexes == null) return; + foreach (var i in indexes.Values.OfType()) + i.DocumentWriting += grid.DocumentWriting; + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs index 3e42cbed94..2d98eadd11 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs @@ -23,6 +23,11 @@ namespace Umbraco.Web.PublishedCache.NuCache factory.GetInstance(), factory.GetInstance(), factory.GetInstance())); + + // add the NuCache health check (hidden from type finder) + // todo - no NuCache health check yet + //var builder = container.GetInstance(); + //builder.Add(); } public void Initialize(IFacadeService service) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs index 810603ea60..f823017ea2 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs @@ -6,6 +6,8 @@ using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.DependencyInjection; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.HealthCheck.Checks.DataIntegrity; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { @@ -24,6 +26,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache factory.GetAllInstances(), factory.GetInstance(), factory.GetInstance())); + + // add the Xml cache health check (hidden from type finder) + var builder = container.GetInstance(); + builder.Exclude(); } public void Initialize(IFacadeService service) diff --git a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Redirects/RedirectTrackingComponent.cs similarity index 97% rename from src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs rename to src/Umbraco.Web/Redirects/RedirectTrackingComponent.cs index becc9ed7d4..c23ca26f05 100644 --- a/src/Umbraco.Web/Redirects/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Redirects/RedirectTrackingComponent.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Services; using Umbraco.Core.Events; using System.Collections.Generic; using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Sync; using Umbraco.Web.Cache; @@ -19,14 +20,13 @@ namespace Umbraco.Web.Redirects /// not managing domains because we don't know how to do it - changing domains => must create a higher level strategy using rewriting rules probably /// recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same /// - public class RedirectTrackingEventHandler : ApplicationEventHandler + public class RedirectTrackingComponent : UmbracoComponentBase, IUmbracoCoreComponent { private const string ContextKey1 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.1"; private const string ContextKey2 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.2"; private const string ContextKey3 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.3"; - /// - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) + protected void Initialize() { // events are weird // on 'published' we 'could' get the old or the new route depending on event handlers order diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineComponent.cs similarity index 92% rename from src/Umbraco.Web/Search/ExamineEvents.cs rename to src/Umbraco.Web/Search/ExamineComponent.cs index 835a31f46b..47eab1c9b7 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -1,345 +1,337 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Xml; -using System.Xml.Linq; -using Examine; -using Examine.LuceneEngine; -using Examine.Session; -using Lucene.Net.Documents; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Changes; -using Umbraco.Core.Sync; -using Umbraco.Web.Cache; -using UmbracoExamine; - -namespace Umbraco.Web.Search -{ - /// - /// Used to wire up events for Examine - /// - public sealed class ExamineEvents : ApplicationEventHandler - { - - /// - /// Once the application has started we should bind to all events and initialize the providers. - /// - /// - /// - /// - /// We need to do this on the Started event as to guarantee that all resolvers are setup properly. - /// - protected override void ApplicationStarted(UmbracoApplicationBase httpApplication) - { - LogHelper.Info("Initializing Examine and binding to business logic events"); - - //TODO: For now we'll make this true, it means that indexes will be near real time - // we'll see about what implications this may have - should be great in most scenarios - DefaultExamineSession.RequireImmediateConsistency = true; - - var registeredProviders = ExamineManager.Instance.IndexProviderCollection - .OfType().Count(x => x.EnableDefaultEventHandler); - - LogHelper.Info("Adding examine event handlers for index providers: {0}", () => registeredProviders); - - // don't bind event handlers if we're not suppose to listen - if (registeredProviders == 0) - return; - - // bind to distributed cache events - this ensures that this logic occurs on ALL servers - // that are taking part in a load balanced environment. - ContentCacheRefresher.CacheUpdated += ContentCacheRefresherUpdated; - MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; - MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; - - var contentIndexer = ExamineManager.Instance.IndexProviderCollection["InternalIndexer"] as UmbracoContentIndexer; - if (contentIndexer != null) - { - contentIndexer.DocumentWriting += IndexerDocumentWriting; - } - var memberIndexer = ExamineManager.Instance.IndexProviderCollection["InternalMemberIndexer"] as UmbracoMemberIndexer; - if (memberIndexer != null) - { - memberIndexer.DocumentWriting += IndexerDocumentWriting; - } - } - - static void MemberCacheRefresherUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs args) - { - switch (args.MessageType) - { - case MessageType.RefreshById: - var c1 = Current.Services.MemberService.GetById((int)args.MessageObject); - if (c1 != null) - { - ReIndexForMember(c1); - } - break; - case MessageType.RemoveById: - - // This is triggered when the item is permanently deleted - - DeleteIndexForEntity((int)args.MessageObject, false); - break; - case MessageType.RefreshByInstance: - var c3 = args.MessageObject as IMember; - if (c3 != null) - { - ReIndexForMember(c3); - } - break; - case MessageType.RemoveByInstance: - - // This is triggered when the item is permanently deleted - - var c4 = args.MessageObject as IMember; - if (c4 != null) - { - DeleteIndexForEntity(c4.Id, false); - } - break; - case MessageType.RefreshAll: - case MessageType.RefreshByJson: - default: - //We don't support these, these message types will not fire for unpublished content - break; - } - } - - static void MediaCacheRefresherUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs args) - { - if (args.MessageType != MessageType.RefreshByPayload) - throw new NotSupportedException(); - - var mediaService = Current.Services.MediaService; - - foreach (var payload in (MediaCacheRefresher.JsonPayload[]) args.MessageObject) - { - if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) - { - // remove from *all* indexes - DeleteIndexForEntity(payload.Id, false); - } - else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) - { - // ExamineEvents does not support RefreshAll - // just ignore that payload - // so what?! - } - else // RefreshNode or RefreshBranch (maybe trashed) - { - var media = mediaService.GetById(payload.Id); - if (media == null || media.Trashed) - { - // gone fishing, remove entirely - DeleteIndexForEntity(payload.Id, false); - continue; - } - - // just that media - ReIndexForMedia(media, media.Trashed == false); - - // branch - if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) - { - var descendants = mediaService.GetDescendants(media); - foreach (var descendant in descendants) - { - ReIndexForMedia(descendant, descendant.Trashed == false); - } - } - } - } - } - - static void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) - { - if (args.MessageType != MessageType.RefreshByPayload) - throw new NotSupportedException(); - - var contentService = Current.Services.ContentService; - - foreach (var payload in (ContentCacheRefresher.JsonPayload[]) args.MessageObject) - { - if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) - { - // delete content entirely (with descendants) - // false: remove entirely from all indexes - DeleteIndexForEntity(payload.Id, false); - } - else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) - { - // ExamineEvents does not support RefreshAll - // just ignore that payload - // so what?! - } - else // RefreshNode or RefreshBranch (maybe trashed) - { - // don't try to be too clever - refresh entirely - // there has to be race conds in there ;-( - - var content = contentService.GetById(payload.Id); - if (content == null || content.Trashed) - { - // gone fishing, remove entirely from all indexes (with descendants) - DeleteIndexForEntity(payload.Id, false); - continue; - } - - IContent published = null; - if (content.HasPublishedVersion && ((ContentService)contentService).IsPathPublished(content)) - { - published = content.Published - ? content - : contentService.GetByVersion(content.PublishedVersionGuid); - } - - // just that content - ReIndexForContent(content, published); - - // branch - if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) - { - var masked = published == null ? null : new List(); - var descendants = contentService.GetDescendants(content); - foreach (var descendant in descendants) - { - published = null; - if (masked != null) // else everything is masked - { - if (masked.Contains(descendant.ParentId) || descendant.HasPublishedVersion == false) - { - masked.Add(descendant.Id); - } - else - { - published = descendant.Published - ? descendant - : contentService.GetByVersion(descendant.PublishedVersionGuid); - } - } - - ReIndexForContent(descendant, published); - } - } - } - - // NOTE - // - // DeleteIndexForEntity is handled by UmbracoContentIndexer.DeleteFromIndex() which takes - // care of also deleting the descendants - // - // ReIndexForContent is NOT taking care of descendants so we have to reload everything - // again in order to process the branch - we COULD improve that by just reloading the - // XML from database instead of reloading content & re-serializing! - } - } - - private static void ReIndexForContent(IContent content, IContent published) - { - if (published != null && content.Version == published.Version) - { - ReIndexForContent(content); // same = both - } - else - { - if (published == null) - { - // remove 'published' - keep 'draft' - DeleteIndexForEntity(content.Id, true); - } - else - { - // index 'published' - don't overwrite 'draft' - ReIndexForContent(published, false); - } - ReIndexForContent(content, true); // index 'draft' - } - } - - private static void ReIndexForContent(IContent sender, bool? supportUnpublished = null) - { - var xml = sender.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", sender.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Content, - ExamineManager.Instance.IndexProviderCollection.OfType() - // only for the specified indexers - .Where(x => supportUnpublished.HasValue == false || supportUnpublished.Value == x.SupportUnpublishedContent) - .Where(x => x.EnableDefaultEventHandler)); - } - - private static void ReIndexForMember(IMember member) - { - ExamineManager.Instance.ReIndexNode( - member.ToXml(), IndexTypes.Member, - ExamineManager.Instance.IndexProviderCollection.OfType() - //ensure that only the providers are flagged to listen execute - .Where(x => x.EnableDefaultEventHandler)); - } - - private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) - { - var xml = sender.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", sender.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Media, - ExamineManager.Instance.IndexProviderCollection.OfType() - // index this item for all indexers if the media is not trashed, otherwise if the item is trashed - // then only index this for indexers supporting unpublished media - .Where(x => isMediaPublished || (x.SupportUnpublishedContent)) - .Where(x => x.EnableDefaultEventHandler)); - } - - /// - /// Remove items from any index that doesn't support unpublished content - /// - /// - /// - /// If true, indicates that we will only delete this item from indexes that don't support unpublished content. - /// If false it will delete this from all indexes regardless. - /// - private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) - { - ExamineManager.Instance.DeleteFromIndex( - entityId.ToString(CultureInfo.InvariantCulture), - ExamineManager.Instance.IndexProviderCollection.OfType() - // if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, - // otherwise if keepIfUnpublished == false then remove from all indexes - .Where(x => keepIfUnpublished == false || (x is UmbracoContentIndexer && ((UmbracoContentIndexer)x).SupportUnpublishedContent == false)) - .Where(x => x.EnableDefaultEventHandler)); - } - - /// - /// Event handler to create a lower cased version of the node name, this is so we can support case-insensitive searching and still - /// use the Whitespace Analyzer - /// - /// - /// - - private static void IndexerDocumentWriting(object sender, DocumentWritingEventArgs e) - { - if (e.Fields.Keys.Contains("nodeName")) - { - //TODO: This logic should really be put into the content indexer instead of hidden here!! - - //add the lower cased version - e.Document.Add(new Field("__nodeName", - e.Fields["nodeName"].ToLower(), - Field.Store.YES, - Field.Index.ANALYZED, - Field.TermVector.NO - )); - } - } - } +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Examine; +using Examine.LuceneEngine; +using Examine.Session; +using Lucene.Net.Documents; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; +using UmbracoExamine; + +namespace Umbraco.Web.Search +{ + /// + /// Used to wire up events for Examine + /// + public sealed class ExamineComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public void Initialize() + { + LogHelper.Info("Initializing Examine and binding to business logic events"); + + //TODO: For now we'll make this true, it means that indexes will be near real time + // we'll see about what implications this may have - should be great in most scenarios + DefaultExamineSession.RequireImmediateConsistency = true; + + var registeredProviders = ExamineManager.Instance.IndexProviderCollection + .OfType().Count(x => x.EnableDefaultEventHandler); + + LogHelper.Info("Adding examine event handlers for index providers: {0}", () => registeredProviders); + + // don't bind event handlers if we're not suppose to listen + if (registeredProviders == 0) + return; + + // bind to distributed cache events - this ensures that this logic occurs on ALL servers + // that are taking part in a load balanced environment. + ContentCacheRefresher.CacheUpdated += ContentCacheRefresherUpdated; + MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; + MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; + + var contentIndexer = ExamineManager.Instance.IndexProviderCollection["InternalIndexer"] as UmbracoContentIndexer; + if (contentIndexer != null) + { + contentIndexer.DocumentWriting += IndexerDocumentWriting; + } + var memberIndexer = ExamineManager.Instance.IndexProviderCollection["InternalMemberIndexer"] as UmbracoMemberIndexer; + if (memberIndexer != null) + { + memberIndexer.DocumentWriting += IndexerDocumentWriting; + } + } + + static void MemberCacheRefresherUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs args) + { + switch (args.MessageType) + { + case MessageType.RefreshById: + var c1 = Current.Services.MemberService.GetById((int)args.MessageObject); + if (c1 != null) + { + ReIndexForMember(c1); + } + break; + case MessageType.RemoveById: + + // This is triggered when the item is permanently deleted + + DeleteIndexForEntity((int)args.MessageObject, false); + break; + case MessageType.RefreshByInstance: + var c3 = args.MessageObject as IMember; + if (c3 != null) + { + ReIndexForMember(c3); + } + break; + case MessageType.RemoveByInstance: + + // This is triggered when the item is permanently deleted + + var c4 = args.MessageObject as IMember; + if (c4 != null) + { + DeleteIndexForEntity(c4.Id, false); + } + break; + case MessageType.RefreshAll: + case MessageType.RefreshByJson: + default: + //We don't support these, these message types will not fire for unpublished content + break; + } + } + + static void MediaCacheRefresherUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs args) + { + if (args.MessageType != MessageType.RefreshByPayload) + throw new NotSupportedException(); + + var mediaService = Current.Services.MediaService; + + foreach (var payload in (MediaCacheRefresher.JsonPayload[]) args.MessageObject) + { + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + // remove from *all* indexes + DeleteIndexForEntity(payload.Id, false); + } + else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + // ExamineEvents does not support RefreshAll + // just ignore that payload + // so what?! + } + else // RefreshNode or RefreshBranch (maybe trashed) + { + var media = mediaService.GetById(payload.Id); + if (media == null || media.Trashed) + { + // gone fishing, remove entirely + DeleteIndexForEntity(payload.Id, false); + continue; + } + + // just that media + ReIndexForMedia(media, media.Trashed == false); + + // branch + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) + { + var descendants = mediaService.GetDescendants(media); + foreach (var descendant in descendants) + { + ReIndexForMedia(descendant, descendant.Trashed == false); + } + } + } + } + } + + static void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) + { + if (args.MessageType != MessageType.RefreshByPayload) + throw new NotSupportedException(); + + var contentService = Current.Services.ContentService; + + foreach (var payload in (ContentCacheRefresher.JsonPayload[]) args.MessageObject) + { + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + // delete content entirely (with descendants) + // false: remove entirely from all indexes + DeleteIndexForEntity(payload.Id, false); + } + else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + // ExamineEvents does not support RefreshAll + // just ignore that payload + // so what?! + } + else // RefreshNode or RefreshBranch (maybe trashed) + { + // don't try to be too clever - refresh entirely + // there has to be race conds in there ;-( + + var content = contentService.GetById(payload.Id); + if (content == null || content.Trashed) + { + // gone fishing, remove entirely from all indexes (with descendants) + DeleteIndexForEntity(payload.Id, false); + continue; + } + + IContent published = null; + if (content.HasPublishedVersion && ((ContentService)contentService).IsPathPublished(content)) + { + published = content.Published + ? content + : contentService.GetByVersion(content.PublishedVersionGuid); + } + + // just that content + ReIndexForContent(content, published); + + // branch + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) + { + var masked = published == null ? null : new List(); + var descendants = contentService.GetDescendants(content); + foreach (var descendant in descendants) + { + published = null; + if (masked != null) // else everything is masked + { + if (masked.Contains(descendant.ParentId) || descendant.HasPublishedVersion == false) + { + masked.Add(descendant.Id); + } + else + { + published = descendant.Published + ? descendant + : contentService.GetByVersion(descendant.PublishedVersionGuid); + } + } + + ReIndexForContent(descendant, published); + } + } + } + + // NOTE + // + // DeleteIndexForEntity is handled by UmbracoContentIndexer.DeleteFromIndex() which takes + // care of also deleting the descendants + // + // ReIndexForContent is NOT taking care of descendants so we have to reload everything + // again in order to process the branch - we COULD improve that by just reloading the + // XML from database instead of reloading content & re-serializing! + } + } + + private static void ReIndexForContent(IContent content, IContent published) + { + if (published != null && content.Version == published.Version) + { + ReIndexForContent(content); // same = both + } + else + { + if (published == null) + { + // remove 'published' - keep 'draft' + DeleteIndexForEntity(content.Id, true); + } + else + { + // index 'published' - don't overwrite 'draft' + ReIndexForContent(published, false); + } + ReIndexForContent(content, true); // index 'draft' + } + } + + private static void ReIndexForContent(IContent sender, bool? supportUnpublished = null) + { + var xml = sender.ToXml(); + //add an icon attribute to get indexed + xml.Add(new XAttribute("icon", sender.ContentType.Icon)); + + ExamineManager.Instance.ReIndexNode( + xml, IndexTypes.Content, + ExamineManager.Instance.IndexProviderCollection.OfType() + // only for the specified indexers + .Where(x => supportUnpublished.HasValue == false || supportUnpublished.Value == x.SupportUnpublishedContent) + .Where(x => x.EnableDefaultEventHandler)); + } + + private static void ReIndexForMember(IMember member) + { + ExamineManager.Instance.ReIndexNode( + member.ToXml(), IndexTypes.Member, + ExamineManager.Instance.IndexProviderCollection.OfType() + //ensure that only the providers are flagged to listen execute + .Where(x => x.EnableDefaultEventHandler)); + } + + private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) + { + var xml = sender.ToXml(); + //add an icon attribute to get indexed + xml.Add(new XAttribute("icon", sender.ContentType.Icon)); + + ExamineManager.Instance.ReIndexNode( + xml, IndexTypes.Media, + ExamineManager.Instance.IndexProviderCollection.OfType() + // index this item for all indexers if the media is not trashed, otherwise if the item is trashed + // then only index this for indexers supporting unpublished media + .Where(x => isMediaPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); + } + + /// + /// Remove items from any index that doesn't support unpublished content + /// + /// + /// + /// If true, indicates that we will only delete this item from indexes that don't support unpublished content. + /// If false it will delete this from all indexes regardless. + /// + private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) + { + ExamineManager.Instance.DeleteFromIndex( + entityId.ToString(CultureInfo.InvariantCulture), + ExamineManager.Instance.IndexProviderCollection.OfType() + // if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, + // otherwise if keepIfUnpublished == false then remove from all indexes + .Where(x => keepIfUnpublished == false || (x is UmbracoContentIndexer && ((UmbracoContentIndexer)x).SupportUnpublishedContent == false)) + .Where(x => x.EnableDefaultEventHandler)); + } + + /// + /// Event handler to create a lower cased version of the node name, this is so we can support case-insensitive searching and still + /// use the Whitespace Analyzer + /// + /// + /// + + private static void IndexerDocumentWriting(object sender, DocumentWritingEventArgs e) + { + if (e.Fields.Keys.Contains("nodeName")) + { + //TODO: This logic should really be put into the content indexer instead of hidden here!! + + //add the lower cased version + e.Document.Add(new Field("__nodeName", + e.Fields["nodeName"].ToLower(), + Field.Store.YES, + Field.Index.ANALYZED, + Field.TermVector.NO + )); + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs index f40490c2d1..ecf3409a7c 100644 --- a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs @@ -1,5 +1,4 @@ -using System.Linq; -using System.Web; +using System.Web; using Umbraco.Core.Events; using Umbraco.Core.Persistence.Migrations; using Umbraco.Web.WebApi.Filters; @@ -10,16 +9,14 @@ namespace Umbraco.Web.Strategies.Migrations /// /// After upgrade we clear out the csrf tokens /// - public class ClearCsrfCookiesAfterUpgrade : MigrationStartupHandler + public class ClearCsrfCookiesAfterUpgrade : IPostMigration { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) + public void Migrated(MigrationRunner sender, MigrationEventArgs args) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; - + if (args.ProductName != GlobalSettings.UmbracoMigrationName) return; if (HttpContext.Current == null) return; var http = new HttpContextWrapper(HttpContext.Current); - http.ExpireCookie(AngularAntiForgeryHelper.AngularCookieName); http.ExpireCookie(AngularAntiForgeryHelper.CsrfValidationCookieName); } diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs index e0e55e0405..a13396fc91 100644 --- a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs @@ -3,7 +3,6 @@ using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Configuration; namespace Umbraco.Web.Strategies.Migrations @@ -16,27 +15,27 @@ namespace Umbraco.Web.Strategies.Migrations /// /// * If current is less than or equal to 7.0.0 /// - public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : MigrationStartupHandler + public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : IPostMigration { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) + public void Migrated(MigrationRunner sender, MigrationEventArgs args) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (args.ProductName != GlobalSettings.UmbracoMigrationName) return; var target70 = new Version(7, 0, 0); - if (e.ConfiguredVersion <= target70) + if (args.ConfiguredVersion <= target70) { //This query is structured to work with MySql, SQLCE and SqlServer: // http://issues.umbraco.org/issue/U4-3876 - var syntax = e.MigrationContext.Database.SqlSyntax; + var syntax = args.MigrationContext.Database.SqlSyntax; var sql = @"DELETE FROM cmsContentXml WHERE nodeId IN (SELECT nodeId FROM (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id WHERE nodeObjectType = '" + Constants.ObjectTypes.Media + "' AND " + syntax.GetQuotedColumnName("path") + " LIKE '%-21%') x)"; - var count = e.MigrationContext.Database.Execute(sql); + var count = args.MigrationContext.Database.Execute(sql); LogHelper.Info("Cleared " + count + " items from the media xml cache that were trashed and not meant to be there"); } diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs index 65f8abf7a0..63353ae0cd 100644 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs +++ b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; using NPoco; using Umbraco.Core; using Umbraco.Core.Events; -using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Configuration; namespace Umbraco.Web.Strategies.Migrations @@ -17,17 +11,17 @@ namespace Umbraco.Web.Strategies.Migrations /// /// Creates the built in list view data types /// - public class EnsureDefaultListViewDataTypesCreated : MigrationStartupHandler + public class EnsureDefaultListViewDataTypesCreated : IPostMigration { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) + public void Migrated(MigrationRunner sender, MigrationEventArgs args) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (args.ProductName != GlobalSettings.UmbracoMigrationName) return; var target720 = new Version(7, 2, 0); - if (e.ConfiguredVersion <= target720) + if (args.ConfiguredVersion <= target720) { - EnsureListViewDataTypeCreated(e); + EnsureListViewDataTypeCreated(args); } } diff --git a/src/Umbraco.Web/Strategies/Migrations/IPostMigration.cs b/src/Umbraco.Web/Strategies/Migrations/IPostMigration.cs new file mode 100644 index 0000000000..16e3a6e22b --- /dev/null +++ b/src/Umbraco.Web/Strategies/Migrations/IPostMigration.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.Events; +using Umbraco.Core.Persistence.Migrations; + +namespace Umbraco.Web.Strategies.Migrations +{ + public interface IPostMigration + { + void Migrated(MigrationRunner runner, MigrationEventArgs args); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs b/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs deleted file mode 100644 index 8bfda241ff..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Persistence.Migrations; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// Base class that can be used to run code after the migration runner has executed - /// - public abstract class MigrationStartupHandler : ApplicationEventHandler, IDisposable - { - private bool _disposed; - - /// - /// Ensure this is run when not configured - /// - protected override bool ExecuteWhenApplicationNotConfigured - { - get { return true; } - } - - /// - /// Ensure this is run when not configured - /// - protected override bool ExecuteWhenDatabaseNotConfigured - { - get { return true; } - } - - public void Unsubscribe() - { - MigrationRunner.Migrated -= MigrationRunner_Migrated; - } - - /// - /// Attach to event on starting - /// - /// - /// - protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication) - { - MigrationRunner.Migrated += MigrationRunner_Migrated; - } - - private void MigrationRunner_Migrated(MigrationRunner sender, Core.Events.MigrationEventArgs e) - { - if (TargetProductNames.Length == 0 || TargetProductNames.Contains(e.ProductName)) - { - AfterMigration(sender, e); - } - } - - protected abstract void AfterMigration(MigrationRunner sender, Core.Events.MigrationEventArgs e); - - /// - /// Override to specify explicit target product names - /// - /// - /// Leaving empty will run for all migration products - /// - public virtual string[] TargetProductNames { get { return new string[] {}; } } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~MigrationStartupHandler() - { - Dispose(false); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) return; - _disposed = true; - Unsubscribe(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs index d8e5ded98f..0b842f683f 100644 --- a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs +++ b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core.Events; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -19,15 +15,15 @@ namespace Umbraco.Web.Strategies.Migrations /// files during the migration since other parts of the migration might fail. So once the migration is complete, we'll then copy over the temp /// files that this migration created over top of the developer's files. We'll also create a backup of their files. /// - public sealed class OverwriteStylesheetFilesFromTempFiles : MigrationStartupHandler + public sealed class OverwriteStylesheetFilesFromTempFiles : IPostMigration { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) + public void Migrated(MigrationRunner sender, MigrationEventArgs args) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (args.ProductName != GlobalSettings.UmbracoMigrationName) return; var target73 = new Version(7, 3, 0); - if (e.ConfiguredVersion <= target73) + if (args.ConfiguredVersion <= target73) { var tempCssFolder = IOHelper.MapPath("~/App_Data/TEMP/CssMigration/"); var cssFolder = IOHelper.MapPath("~/css"); @@ -42,7 +38,7 @@ namespace Umbraco.Web.Strategies.Migrations { //backup var targetPath = Path.Combine(tempCssFolder, relativePath.EnsureEndsWith(".bak")); - e.MigrationContext.Logger.Info("CSS file is being backed up from {0}, to {1} before being migrated to new format", () => cssFilePath, () => targetPath); + args.MigrationContext.Logger.Info("CSS file is being backed up from {0}, to {1} before being migrated to new format", () => cssFilePath, () => targetPath); File.Copy(cssFilePath, targetPath, true); } diff --git a/src/Umbraco.Web/Strategies/Migrations/PostMigrationCollection.cs b/src/Umbraco.Web/Strategies/Migrations/PostMigrationCollection.cs new file mode 100644 index 0000000000..58c04e44e3 --- /dev/null +++ b/src/Umbraco.Web/Strategies/Migrations/PostMigrationCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.DependencyInjection; + +namespace Umbraco.Web.Strategies.Migrations +{ + public class PostMigrationCollection : BuilderCollectionBase + { + public PostMigrationCollection(IEnumerable items) + : base(items) + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/PostMigrationCollectionBuilder.cs b/src/Umbraco.Web/Strategies/Migrations/PostMigrationCollectionBuilder.cs new file mode 100644 index 0000000000..63683c1edb --- /dev/null +++ b/src/Umbraco.Web/Strategies/Migrations/PostMigrationCollectionBuilder.cs @@ -0,0 +1,16 @@ +using LightInject; +using Umbraco.Core.DependencyInjection; + +namespace Umbraco.Web.Strategies.Migrations +{ + public class PostMigrationCollectionBuilder : LazyCollectionBuilderBase + { + public PostMigrationCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override PostMigrationCollectionBuilder This => this; + + protected override ILifetime CollectionLifetime => null; // transient + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/PostMigrationComponent.cs b/src/Umbraco.Web/Strategies/Migrations/PostMigrationComponent.cs new file mode 100644 index 0000000000..3d4d76a95d --- /dev/null +++ b/src/Umbraco.Web/Strategies/Migrations/PostMigrationComponent.cs @@ -0,0 +1,24 @@ +using LightInject; +using Umbraco.Core.Components; +using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Plugins; + +namespace Umbraco.Web.Strategies.Migrations +{ + public class PostMigrationComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public override void Compose(ServiceContainer container) + { + PostMigrationCollectionBuilder.Register(container) + .AddProducer(factory => factory.GetInstance().ResolveTypes()); + } + + public void Initialize(PostMigrationCollection posts) + { + // whatever the runtime level, ie also when installing or upgrading + + foreach (var post in posts) + MigrationRunner.Migrated += post.Migrated; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs index 66a412c1ff..5a80c4458a 100644 --- a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs +++ b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs @@ -1,14 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using NPoco; using Umbraco.Core.Events; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Configuration; namespace Umbraco.Web.Strategies.Migrations @@ -17,16 +14,16 @@ namespace Umbraco.Web.Strategies.Migrations /// This event ensures that upgrades from (configured) versions lower then 6.0.0 /// have their publish state updated after the database schema has been migrated. /// - public class PublishAfterUpgradeToVersionSixth : MigrationStartupHandler + public class PublishAfterUpgradeToVersionSixth : IPostMigration { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) + public void Migrated(MigrationRunner sender, MigrationEventArgs args) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (args.ProductName != GlobalSettings.UmbracoMigrationName) return; var target = new Version(6, 0, 0); - if (e.ConfiguredVersion < target) + if (args.ConfiguredVersion < target) { - var sql = e.MigrationContext.Database.Sql() + var sql = args.MigrationContext.Database.Sql() .SelectAll() .From() .InnerJoin() @@ -38,7 +35,7 @@ namespace Umbraco.Web.Strategies.Migrations .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) .Where(x => x.Path.StartsWith("-1")); - var dtos = e.MigrationContext.Database.Fetch(sql); + var dtos = args.MigrationContext.Database.Fetch(sql); var toUpdate = new List(); var versionGroup = dtos.GroupBy(x => x.NodeId); foreach (var grp in versionGroup) @@ -62,12 +59,12 @@ namespace Umbraco.Web.Strategies.Migrations } //Commit the updated entries for the cmsDocument table - using (var transaction = e.MigrationContext.Database.GetTransaction()) + using (var transaction = args.MigrationContext.Database.GetTransaction()) { //Loop through the toUpdate foreach (var dto in toUpdate) { - e.MigrationContext.Database.Update(dto); + args.MigrationContext.Database.Update(dto); } transaction.Complete(); diff --git a/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs index ca478cd1b3..ded50bf671 100644 --- a/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs @@ -1,10 +1,7 @@ using System; -using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Services; using Umbraco.Core.Configuration; -using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Web.Strategies.Migrations @@ -19,15 +16,15 @@ namespace Umbraco.Web.Strategies.Migrations /// /// * If current is less than or equal to 7.0.0 /// - public class RebuildMediaXmlCacheAfterUpgrade : MigrationStartupHandler + public class RebuildMediaXmlCacheAfterUpgrade : IPostMigration { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) + public void Migrated(MigrationRunner sender, MigrationEventArgs args) { - if (e.ProductName != GlobalSettings.UmbracoMigrationName) return; + if (args.ProductName != GlobalSettings.UmbracoMigrationName) return; var target70 = new Version(7, 0, 0); - if (e.ConfiguredVersion <= target70) + if (args.ConfiguredVersion <= target70) { // maintain - for backward compatibility? //var mediasvc = (MediaService)ApplicationContext.Current.Services.MediaService; diff --git a/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs b/src/Umbraco.Web/Strategies/PublicAccessComponent.cs similarity index 71% rename from src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs rename to src/Umbraco.Web/Strategies/PublicAccessComponent.cs index 275b00c72f..3dcb723cd9 100644 --- a/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs +++ b/src/Umbraco.Web/Strategies/PublicAccessComponent.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using umbraco.cms.businesslogic.web; -using Umbraco.Core; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core; +using Umbraco.Core.Components; using Umbraco.Core.Services; namespace Umbraco.Web.Strategies @@ -12,12 +7,10 @@ namespace Umbraco.Web.Strategies /// /// Used to ensure that the public access data file is kept up to date properly /// - public sealed class PublicAccessEventHandler : ApplicationEventHandler + public sealed class PublicAccessComponent : UmbracoComponentBase, IUmbracoCoreComponent { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication) + public void Initialize() { - base.ApplicationStarted(umbracoApplication); - MemberGroupService.Saved += MemberGroupService_Saved; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e1f1f85129..6091433e57 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -208,7 +208,6 @@ - @@ -220,6 +219,7 @@ + @@ -277,6 +277,10 @@ + + + + @@ -377,7 +381,7 @@ - + @@ -431,7 +435,7 @@ - + @@ -652,7 +656,6 @@ - @@ -855,7 +858,7 @@ - + @@ -984,7 +987,7 @@ - + @@ -1106,7 +1109,6 @@ - Code diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index fc07c6c55f..a7b06ce656 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -4,7 +4,6 @@ using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Logging; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; diff --git a/src/Umbraco.Web/WebRuntimeComponent.cs b/src/Umbraco.Web/WebRuntimeComponent.cs index 0696d68f5b..6538641509 100644 --- a/src/Umbraco.Web/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/WebRuntimeComponent.cs @@ -20,7 +20,6 @@ using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Macros; -using Umbraco.Core.Persistence; using Umbraco.Core.Plugins; using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; @@ -33,7 +32,6 @@ using Umbraco.Web.DependencyInjection; using Umbraco.Web.Dictionary; using Umbraco.Web.Editors; using Umbraco.Web.HealthCheck; -using Umbraco.Web.HealthCheck.Checks.DataIntegrity; using Umbraco.Web.Install; using Umbraco.Web.Media; using Umbraco.Web.Media.ThumbnailProviders; @@ -67,8 +65,6 @@ namespace Umbraco.Web // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when // we have no http context, eg when booting Umbraco or in background threads, so instead // let's use an hybrid accessor that can fall back to a ThreadStatic context. - // fixme - move that one to runtime - //container.RegisterSingleton(); // replaces HttpContext.Current container.RegisterSingleton(); // register the 'current' umbraco context - transient - for eg controllers @@ -81,11 +77,6 @@ namespace Umbraco.Web // register the facade accessor - the "current" facade is in the umbraco context container.RegisterSingleton(); - // register the umbraco database accessor - // have to use the hybrid thing... - // fixme moving to runtime - //container.RegisterSingleton(); - // register a per-request UmbracoContext object // no real need to be per request but assuming it is faster container.Register(factory => factory.GetInstance().UmbracoContext, new PerRequestLifeTime()); @@ -102,9 +93,6 @@ namespace Umbraco.Web container.RegisterSingleton(); - // fixme - still, doing it before we get the INITIALIZE scope is a BAD idea - // fixme - also note that whatever you REGISTER in a scope, stays registered => create the scope beforehand? - // fixme - BUT not enough, once per-request is enabled it WANTS per-request scope even though a scope already exists // IoC setup for LightInject for MVC/WebApi // see comments on MixedScopeManagerProvider for explainations of what we are doing here var smp = container.ScopeManagerProvider as MixedScopeManagerProvider; @@ -186,16 +174,15 @@ namespace Umbraco.Web container.RegisterSingleton(); + // register *all* checks, except those marked [HideFromTypeFinder] of course HealthCheckCollectionBuilder.Register(container) - .AddProducer(() => pluginManager.ResolveTypes()) - .Exclude(); // fixme must remove else NuCache dies! - // but we should also have one for NuCache AND NuCache should be a component that does all this + .AddProducer(() => pluginManager.ResolveTypes()); - // fixme - will this work? others? + // auto-register views container.RegisterAuto(typeof(UmbracoViewPage<>)); } - private void ComposeLegacyMessenger(ServiceContainer container, RuntimeLevel runtimeLevel, ILogger logger) + private static void ComposeLegacyMessenger(IServiceRegistry container, RuntimeLevel runtimeLevel, ILogger logger) { //set the legacy one by default - this maintains backwards compat container.Register(factory => @@ -224,7 +211,7 @@ namespace Umbraco.Web }, new PerContainerLifetime()); } - private void ComposeMessenger(ServiceContainer container, ILogger logger, ProfilingLogger proflog) + private static void ComposeMessenger(IServiceRegistry container, ILogger logger, ProfilingLogger proflog) { container.Register(factory => { @@ -263,13 +250,10 @@ namespace Umbraco.Web } internal void Initialize( - UmbracoApplicationBase umbracoApplication, IRuntimeState runtime, SurfaceControllerTypeCollection surfaceControllerTypes, UmbracoApiControllerTypeCollection apiControllerTypes) { - // fixme - need to review & cleanup - // setup mvc and webapi services SetupMvcAndWebApi(); @@ -308,7 +292,7 @@ namespace Umbraco.Web //before we do anything, we'll ensure the umbraco context //see: http://issues.umbraco.org/issue/U4-1717 - var httpContext = new HttpContextWrapper(umbracoApplication.Context); + var httpContext = new HttpContextWrapper(HttpContext.Current); UmbracoContext.EnsureContext( // fixme - refactor! UmbracoContext & UmbracoRequestContext! + inject, accessor, etc httpContext, Current.FacadeService, diff --git a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs b/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs deleted file mode 100644 index 4c06619147..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.IO; - -namespace umbraco.presentation -{ - public class EnsureSystemPathsApplicationStartupHandler : ApplicationEventHandler - { - protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication) - { - base.ApplicationInitialized(umbracoApplication); - - IOHelper.EnsurePathExists("~/App_Data"); - IOHelper.EnsurePathExists(SystemDirectories.Media); - IOHelper.EnsurePathExists(SystemDirectories.MvcViews); - IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); - IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); - } - } -} \ No newline at end of file