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 + +