From a0a99e8f12ac9e730a358340ed99fa9e90c1969a Mon Sep 17 00:00:00 2001 From: "shannon@ShandemVaio" Date: Wed, 1 Aug 2012 09:49:10 +0600 Subject: [PATCH] Obsoletes umbraco.presentation.cache with new Umbraco.Core.CacheRefreshersResolver Have removed ResolverBase as we cannot rely on manually setting the current singleton object since applications outside of the standard umbraco web application might be using these singletons and would expect they already be setup. Have changed all current resolvers to manage their own singleton instances and sealed them. --- src/Umbraco.Core/CacheRefreshersResolver.cs | 99 ++++++++ src/Umbraco.Core/PluginTypeResolver.cs | 52 ++-- .../Resolving/ManyObjectResolverBase.cs | 223 ++++++++++++++---- .../Resolving/ObjectLifetimeScope.cs | 9 + src/Umbraco.Core/Resolving/ResolverBase.cs | 42 ---- .../Resolving/SingleObjectResolverBase.cs | 4 +- src/Umbraco.Core/Umbraco.Core.csproj | 9 +- .../CacheRefresherFactoryTests.cs | 10 +- src/Umbraco.Tests/DataTypeFactoryTests.cs | 12 +- src/Umbraco.Tests/PluginTypeResolverTests.cs | 3 +- .../PluginTypeResolverExtensions.cs | 10 - .../Routing/DocumentLookupsResolver.cs | 112 +++------ src/Umbraco.Web/Routing/DocumentRequest.cs | 7 +- .../Routing/LastChanceLookupResolver.cs | 45 ++++ .../Routing/RoutesCacheResolver.cs | 29 ++- src/Umbraco.Web/Routing/RoutingContext.cs | 20 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 + src/Umbraco.Web/UmbracoApplication.cs | 21 +- src/Umbraco.Web/UmbracoModule.cs | 3 +- .../umbraco/cache/dispatcher.cs | 5 +- .../umbraco/cache/factory.cs | 54 +---- .../webservices/CacheRefresher.asmx.cs | 11 +- src/umbraco.businesslogic/Utils/Singleton.cs | 10 +- .../businesslogic/datatype/factory.cs | 2 +- 24 files changed, 467 insertions(+), 327 deletions(-) create mode 100644 src/Umbraco.Core/CacheRefreshersResolver.cs create mode 100644 src/Umbraco.Core/Resolving/ObjectLifetimeScope.cs delete mode 100644 src/Umbraco.Core/Resolving/ResolverBase.cs create mode 100644 src/Umbraco.Web/Routing/LastChanceLookupResolver.cs diff --git a/src/Umbraco.Core/CacheRefreshersResolver.cs b/src/Umbraco.Core/CacheRefreshersResolver.cs new file mode 100644 index 0000000000..9b363b4a80 --- /dev/null +++ b/src/Umbraco.Core/CacheRefreshersResolver.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using Umbraco.Core.Resolving; +using umbraco.interfaces; + +namespace Umbraco.Core +{ + internal sealed class CacheRefreshersResolver : ManyObjectResolverBase + { + + #region Singleton + + private static readonly CacheRefreshersResolver Instance = new CacheRefreshersResolver(PluginTypeResolver.Current.ResolveCacheRefreshers()); + + public static CacheRefreshersResolver Current + { + get { return Instance; } + } + #endregion + + #region Constructors + static CacheRefreshersResolver() { } + + /// + /// Constructor + /// + /// + /// + /// We are creating Transient instances (new instances each time) because this is how the legacy code worked and + /// I don't want to muck anything up by changing them to application based instances. + /// TODO: However, it would make much more sense to do this and would speed up the application plus this would make the GetById method much easier. + /// + internal CacheRefreshersResolver(IEnumerable refreshers) + : base(true) + { + foreach (var l in refreshers) + { + this.Add(l); + } + } + #endregion + + /// + /// Maintains a list of Ids and their types when first call to CacheResolvers or GetById occurs, this is used + /// in order to return a single object by id without instantiating the entire type stack. + /// + private static ConcurrentDictionary _refreshers; + private readonly ReaderWriterLockSlim _lock= new ReaderWriterLockSlim(); + + /// + /// Gets the implementations. + /// + public IEnumerable CacheResolvers + { + get + { + EnsureRefreshersList(); + return Values; + } + } + + /// + /// Returns a new ICacheRefresher instance by id + /// + /// + /// + public ICacheRefresher GetById(Guid id) + { + EnsureRefreshersList(); + return !_refreshers.ContainsKey(id) + ? null + : PluginTypeResolver.Current.CreateInstance(_refreshers[id]); + } + + /// + /// Populates the refreshers dictionary to allow us to instantiate a type by Id since the ICacheRefresher type doesn't contain any metadata + /// + private void EnsureRefreshersList() + { + using (var l = new UpgradeableReadLock(_lock)) + { + if (_refreshers == null) + { + l.UpgradeToWriteLock(); + _refreshers = new ConcurrentDictionary(); + foreach(var v in Values) + { + _refreshers.TryAdd(v.UniqueIdentifier, v.GetType()); + } + } + } + } + + } +} diff --git a/src/Umbraco.Core/PluginTypeResolver.cs b/src/Umbraco.Core/PluginTypeResolver.cs index 957f501529..c1218233aa 100644 --- a/src/Umbraco.Core/PluginTypeResolver.cs +++ b/src/Umbraco.Core/PluginTypeResolver.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Text; using System.Threading; using Umbraco.Core.Logging; +using umbraco.interfaces; namespace Umbraco.Core { @@ -40,22 +41,32 @@ namespace Umbraco.Core { get { - if (_resolver == null) + using (var l = new UpgradeableReadLock(Lock)) { - using (new WriteLock(Lock)) + if (_resolver == null) { + l.UpgradeToWriteLock(); _resolver = new PluginTypeResolver(); - } + } + return _resolver; } - return _resolver; } set { _resolver = value; } } - + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private readonly HashSet _types = new HashSet(); private IEnumerable _assemblies; + /// + /// Returns all classes attributed with XsltExtensionAttribute attribute + /// + /// + internal IEnumerable ResolveCacheRefreshers() + { + return ResolveTypes(); + } + /// /// Gets/sets which assemblies to scan when type finding, generally used for unit testing, if not explicitly set /// this will search all assemblies known to have plugins and exclude ones known to not have them. @@ -89,8 +100,8 @@ namespace Umbraco.Core { var typesAsArray = types.ToArray(); using (DisposableTimer.DebugDuration( - string.Format("Starting instantiation of {0} objects of type {1}", typesAsArray.Length, typeof(T).FullName), - string.Format("Completed instantiation of {0} objects of type {1}", typesAsArray.Length, typeof(T).FullName))) + String.Format("Starting instantiation of {0} objects of type {1}", typesAsArray.Length, typeof(T).FullName), + String.Format("Completed instantiation of {0} objects of type {1}", typesAsArray.Length, typeof(T).FullName))) { var instances = new List(); foreach (var t in typesAsArray) @@ -102,17 +113,17 @@ namespace Umbraco.Core } catch (Exception ex) { - - LogHelper.Error(string.Format("Error creating type {0}", t.FullName), ex); - + + LogHelper.Error(String.Format("Error creating type {0}", t.FullName), ex); + if (throwException) { throw ex; } } } - return instances; - } + return instances; + } } /// @@ -124,7 +135,7 @@ namespace Umbraco.Core /// internal T CreateInstance(Type type, bool throwException = false) { - var instances = CreateInstances(new[] {type}, throwException); + var instances = CreateInstances(new[] { type }, throwException); return instances.FirstOrDefault(); } @@ -133,8 +144,8 @@ namespace Umbraco.Core using (var readLock = new UpgradeableReadLock(_lock)) { using (DisposableTimer.TraceDuration( - string.Format("Starting resolution types of {0}", typeof(T).FullName), - string.Format("Completed resolution of types of {0}", typeof(T).FullName))) + String.Format("Starting resolution types of {0}", typeof(T).FullName), + String.Format("Completed resolution of types of {0}", typeof(T).FullName))) { //check if the TypeList already exists, if so return it, if not we'll create it var typeList = _types.SingleOrDefault(x => x.GetListType().IsType()); @@ -153,8 +164,8 @@ namespace Umbraco.Core //add the type list to the collection _types.Add(typeList); } - return typeList.GetTypes(); - } + return typeList.GetTypes(); + } } } @@ -185,13 +196,13 @@ namespace Umbraco.Core /// /// /// - internal IEnumerable ResolveAttributedTypes() + internal IEnumerable ResolveAttributedTypes() where TAttribute : Attribute { return ResolveTypes( - () => TypeFinder2.FindClassesWithAttribute(AssembliesToScan), + () => TypeFinder2.FindClassesWithAttribute(AssembliesToScan), true); - } + } /// /// Used for unit tests @@ -244,6 +255,5 @@ namespace Umbraco.Core } } #endregion - } } diff --git a/src/Umbraco.Core/Resolving/ManyObjectResolverBase.cs b/src/Umbraco.Core/Resolving/ManyObjectResolverBase.cs index 47dfa296a3..83e747cb03 100644 --- a/src/Umbraco.Core/Resolving/ManyObjectResolverBase.cs +++ b/src/Umbraco.Core/Resolving/ManyObjectResolverBase.cs @@ -1,114 +1,237 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Web; namespace Umbraco.Core.Resolving { - - /// - /// A Resolver which manages an ordered list of objects. - /// - /// The type of the resolver. - /// The type of the resolved objects. - /// - /// Used to resolve multiple types from a collection. The collection can also be modified at runtime/application startup. - /// An example of this is MVCs ViewEngines collection. - /// - internal abstract class ManyObjectResolverBase : ResolverBase - where TResolver : class + internal abstract class ManyObjectResolverBase where TResolved : class { - readonly List _resolved; - protected readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); - + private readonly bool _instancePerApplication = false; + private List _applicationInstances = null; + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + + #region Constructors /// - /// Initializes a new instance of the class with an empty list of objects. + /// Initializes a new instance of the class with an empty list of objects. /// - protected ManyObjectResolverBase() + /// If set to true will resolve singleton objects which will be created once for the lifetime of the application + protected ManyObjectResolverBase(bool instancePerApplication = true) { - _resolved = new List(); + _instancePerApplication = instancePerApplication; + InstanceTypes = new List(); } /// - /// Initializes a new instance of the class with an initial list of objects. + /// Initializes a new instance of the class with an initial list of objects + /// with creation of objects based on an HttpRequest lifetime scope. + /// + /// + protected ManyObjectResolverBase(HttpContextBase httpContext) + : this(false) + { + CurrentHttpContext = httpContext; + } + + /// + /// Initializes a new instance of the class with an initial list of objects. /// /// The list of objects. - protected ManyObjectResolverBase(IEnumerable value) + /// If set to true will resolve singleton objects which will be created once for the lifetime of the application + protected ManyObjectResolverBase(IEnumerable value, bool instancePerApplication = true) { - _resolved = new List(value); + _instancePerApplication = instancePerApplication; + InstanceTypes = new List(value); } /// - /// Gets the list of objects. + /// Initializes a new instance of the class with an empty list of objects + /// with creation of objects based on an HttpRequest lifetime scope. + /// + /// + /// + protected ManyObjectResolverBase(HttpContextBase httpContext, IEnumerable value) + : this(value, false) + { + CurrentHttpContext = httpContext; + } + #endregion + + /// + /// Returns the list of Types registered that instances will be created from + /// + protected List InstanceTypes { get; private set; } + + /// + /// Returns the Current HttpContextBase used to construct this object if one exists. + /// If one exists then the LifetimeScope will be ObjectLifetimeScope.HttpRequest + /// + protected HttpContextBase CurrentHttpContext { get; private set; } + + /// + /// Returns the ObjectLifetimeScope for created objects + /// + protected ObjectLifetimeScope LifetimeScope + { + get + { + if (_instancePerApplication) + return ObjectLifetimeScope.Application; + if (CurrentHttpContext != null) + return ObjectLifetimeScope.HttpRequest; + return ObjectLifetimeScope.Transient; + } + } + + /// + /// Returns the list of new object instances. /// protected IEnumerable Values { - get { return _resolved; } - } + get + { + //we should not allow the returning/creating of objects if resolution is not yet frozen! + if (Resolution.IsFrozen) + throw new InvalidOperationException("Resolution is not frozen. It is not possible to instantiate and returng objects until resolution is frozen."); - /// - /// Removes an object. - /// - /// The object to remove. - public void Remove(TResolved value) - { - Resolution.EnsureNotFrozen(); - using (new WriteLock(Lock)) - { - _resolved.Remove(value); + switch (LifetimeScope) + { + case ObjectLifetimeScope.HttpRequest: + //create new instances per HttpContext, this means we'll lazily create them and once created, cache them in the HttpContext + //create new instances per application, this means we'll lazily create them and once created, cache them + using (var l = new UpgradeableReadLock(_lock)) + { + //check if the items contain the key (based on the full type name) + if (CurrentHttpContext.Items[this.GetType().FullName] == null) + { + l.UpgradeToWriteLock(); + //add the items to the context items (based on full type name) + CurrentHttpContext.Items[this.GetType().FullName] = new List( + PluginTypeResolver.Current.CreateInstances(InstanceTypes)); + } + return _applicationInstances; + } + case ObjectLifetimeScope.Application: + //create new instances per application, this means we'll lazily create them and once created, cache them + using(var l = new UpgradeableReadLock(_lock)) + { + if (_applicationInstances == null) + { + l.UpgradeToWriteLock(); + _applicationInstances = new List( + PluginTypeResolver.Current.CreateInstances(InstanceTypes)); + } + return _applicationInstances; + } + case ObjectLifetimeScope.Transient: + default: + //create new instances each time + return PluginTypeResolver.Current.CreateInstances(InstanceTypes); + } } } /// - /// Adds an object to the end of the list. + /// Removes a type. + /// + /// The type to remove. + public void Remove(Type value) + { + Resolution.EnsureNotFrozen(); + EnsureCorrectType(value); + using (new WriteLock(_lock)) + { + InstanceTypes.Remove(value); + } + } + + /// + /// Removes a type. + /// + /// + public void Remove() + { + Remove(typeof (T)); + } + + /// + /// Adds a Type to the end of the list. /// /// The object to be added. - public void Add(TResolved value) + public void Add(Type value) { Resolution.EnsureNotFrozen(); - using (var l = new UpgradeableReadLock(Lock)) + EnsureCorrectType(value); + using (var l = new UpgradeableReadLock(_lock)) { - if (_resolved.Contains(value)) + if (InstanceTypes.Contains(value)) { - throw new InvalidOperationException("The object " + value + " already exists in the collection"); + throw new InvalidOperationException("The Type " + value + " already exists in the collection"); }; - + l.UpgradeToWriteLock(); - _resolved.Add(value); + InstanceTypes.Add(value); } } + /// + /// Adds a Type to the end of the list. + /// + /// + public void Add() + { + Add(typeof (T)); + } + /// /// Clears the list. /// public void Clear() { Resolution.EnsureNotFrozen(); - using (new WriteLock(Lock)) + using (new WriteLock(_lock)) { - _resolved.Clear(); + InstanceTypes.Clear(); } } /// - /// Inserts an object at the specified index. + /// Inserts a Type at the specified index. /// /// The zero-based index at which the object should be inserted. /// The object to insert. - public void Insert(int index, TResolved value) + public void Insert(int index, Type value) { Resolution.EnsureNotFrozen(); - using (var l = new UpgradeableReadLock(Lock)) + EnsureCorrectType(value); + using (var l = new UpgradeableReadLock(_lock)) { - if (_resolved.Contains(value)) + if (InstanceTypes.Contains(value)) { - throw new InvalidOperationException("The object " + value + " already exists in the collection"); + throw new InvalidOperationException("The Type " + value + " already exists in the collection"); }; l.UpgradeToWriteLock(); - _resolved.Insert(index, value); + InstanceTypes.Insert(index, value); } } - + + /// + /// Inserts a Type at the specified index. + /// + /// + /// + public void Insert(int index) + { + Insert(index, typeof (T)); + } + + private void EnsureCorrectType(Type t) + { + if (!TypeHelper.IsTypeAssignableFrom(t)) + throw new InvalidOperationException("The resolver " + this.GetType() + " can only accept types of " + typeof(TResolved) + ". The Type passed in to this method is " + t); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Resolving/ObjectLifetimeScope.cs b/src/Umbraco.Core/Resolving/ObjectLifetimeScope.cs new file mode 100644 index 0000000000..8c290ff54b --- /dev/null +++ b/src/Umbraco.Core/Resolving/ObjectLifetimeScope.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Resolving +{ + internal enum ObjectLifetimeScope + { + HttpRequest, + Application, + Transient + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Resolving/ResolverBase.cs b/src/Umbraco.Core/Resolving/ResolverBase.cs deleted file mode 100644 index 56c0bcd735..0000000000 --- a/src/Umbraco.Core/Resolving/ResolverBase.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Threading; - -namespace Umbraco.Core.Resolving -{ - /// - /// base class for resolvers which declare a singleton accessor - /// - /// - internal abstract class ResolverBase where TResolver : class - { - static TResolver _resolver; - - //TODO: This is not correct, this will be the same lock for all ResolverBase classes!! - static readonly ReaderWriterLockSlim ResolversLock = new ReaderWriterLockSlim(); - - public static TResolver Current - { - get - { - using (new ReadLock(ResolversLock)) - { - if (_resolver == null) - throw new InvalidOperationException("Current has not been initialized. You must initialize Current before trying to read it."); - return _resolver; - } - } - - set - { - using (new WriteLock(ResolversLock)) - { - if (value == null) - throw new ArgumentNullException("value"); - if (_resolver != null) - throw new InvalidOperationException("Current has already been initialized. It is not possible to re-initialize Current once it has been initialized."); - _resolver = value; - } - } - } - } -} diff --git a/src/Umbraco.Core/Resolving/SingleObjectResolverBase.cs b/src/Umbraco.Core/Resolving/SingleObjectResolverBase.cs index a77d98f680..c793627c0d 100644 --- a/src/Umbraco.Core/Resolving/SingleObjectResolverBase.cs +++ b/src/Umbraco.Core/Resolving/SingleObjectResolverBase.cs @@ -5,14 +5,12 @@ namespace Umbraco.Core.Resolving /// /// A Resolver to return and set a Single registered object. /// - /// /// /// /// Used for 'singly' registered objects. An example is like the MVC Controller Factory, only one exists application wide and it can /// be get/set. /// - internal abstract class SingleObjectResolverBase : ResolverBase - where TResolver : class + internal abstract class SingleObjectResolverBase where TResolved : class { TResolved _resolved; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ce596fc188..9d8780f644 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -50,6 +50,7 @@ + @@ -68,8 +69,8 @@ + - @@ -93,6 +94,12 @@ + + + {511F6D8D-7717-440A-9A57-A507E9A8B27F} + umbraco.interfaces + +