using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web; namespace Umbraco.Core.ObjectResolution { /// /// The base class for all many-objects resolvers. /// /// The type of the concrete resolver class. /// The type of the resolved objects. public abstract class ManyObjectsResolverBase : ResolverBase where TResolved : class where TResolver : ResolverBase { private IEnumerable _applicationInstances = null; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private readonly string _httpContextKey; private readonly List _instanceTypes = new List(); private IEnumerable _sortedValues = null; private int _defaultPluginWeight = 10; #region Constructors /// /// Initializes a new instance of the class with an empty list of objects, /// and an optional lifetime scope. /// /// The lifetime scope of instantiated objects, default is per Application. /// If is per HttpRequest then there must be a current HttpContext. /// is per HttpRequest but the current HttpContext is null. protected ManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application) { CanResolveBeforeFrozen = false; if (scope == ObjectLifetimeScope.HttpRequest) { if (HttpContext.Current == null) throw new InvalidOperationException("Use alternative constructor accepting a HttpContextBase object in order to set the lifetime scope to HttpRequest when HttpContext.Current is null"); CurrentHttpContext = new HttpContextWrapper(HttpContext.Current); } LifetimeScope = scope; if (scope == ObjectLifetimeScope.HttpRequest) _httpContextKey = this.GetType().FullName; _instanceTypes = new List(); } /// /// Initializes a new instance of the class with an empty list of objects, /// with creation of objects based on an HttpRequest lifetime scope. /// /// The HttpContextBase corresponding to the HttpRequest. /// is null. protected ManyObjectsResolverBase(HttpContextBase httpContext) { CanResolveBeforeFrozen = false; if (httpContext == null) throw new ArgumentNullException("httpContext"); LifetimeScope = ObjectLifetimeScope.HttpRequest; _httpContextKey = this.GetType().FullName; CurrentHttpContext = httpContext; _instanceTypes = new List(); } /// /// Initializes a new instance of the class with an initial list of object types, /// and an optional lifetime scope. /// /// The list of object types. /// The lifetime scope of instantiated objects, default is per Application. /// If is per HttpRequest then there must be a current HttpContext. /// is per HttpRequest but the current HttpContext is null. protected ManyObjectsResolverBase(IEnumerable value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) : this(scope) { _instanceTypes = value.ToList(); } /// /// Initializes a new instance of the class with an initial list of objects, /// with creation of objects based on an HttpRequest lifetime scope. /// /// The HttpContextBase corresponding to the HttpRequest. /// The list of object types. /// is null. protected ManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable value) : this(httpContext) { _instanceTypes = value.ToList(); } #endregion /// /// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen. /// /// This is false by default and is used for some special internal resolvers. internal bool CanResolveBeforeFrozen { get; set; } /// /// Gets the list of types to create instances from. /// protected virtual IEnumerable InstanceTypes { get { return _instanceTypes; } } /// /// Gets or sets the used to initialize this object, if any. /// /// If not null, then LifetimeScope will be ObjectLifetimeScope.HttpRequest. protected HttpContextBase CurrentHttpContext { get; private set; } /// /// Gets or sets the lifetime scope of resolved objects. /// protected ObjectLifetimeScope LifetimeScope { get; private set; } /// /// Gets the resolved object instances, sorted by weight. /// /// The sorted resolved object instances. /// /// The order is based upon the WeightedPluginAttribute and DefaultPluginWeight. /// Weights are sorted ascendingly (lowest weights come first). /// protected IEnumerable GetSortedValues() { if (_sortedValues == null) { var values = Values.ToList(); values.Sort((f1, f2) => GetObjectWeight(f1).CompareTo(GetObjectWeight(f2))); _sortedValues = values; } return _sortedValues; } /// /// Gets or sets the default type weight. /// /// Determines the weight of types that do not have a WeightedPluginAttribute set on /// them, when calling GetSortedValues. protected virtual int DefaultPluginWeight { get { return _defaultPluginWeight; } set { _defaultPluginWeight = value; } } /// /// Returns the weight of an object for user with GetSortedValues /// /// /// protected virtual int GetObjectWeight(object o) { var type = o.GetType(); var attr = type.GetCustomAttribute(true); return attr == null ? DefaultPluginWeight : attr.Weight; } /// /// Gets the resolved object instances. /// /// CanResolveBeforeFrozen is false, and resolution is not frozen. protected IEnumerable Values { get { // ensure we can if (!CanResolveBeforeFrozen) Resolution.EnsureIsFrozen(); // note: we apply .ToArray() to the output of CreateInstance() because that is an IEnumerable that // comes from the PluginManager we want to be _sure_ that it's not a Linq of some sort, but the // instances have actually been instanciated when we return. switch (LifetimeScope) { case ObjectLifetimeScope.HttpRequest: // create new instances per HttpContext using (var l = new UpgradeableReadLock(_lock)) { // create if not already there if (CurrentHttpContext.Items[_httpContextKey] == null) { l.UpgradeToWriteLock(); CurrentHttpContext.Items[_httpContextKey] = CreateInstances().ToArray(); } return (TResolved[])CurrentHttpContext.Items[_httpContextKey]; } case ObjectLifetimeScope.Application: // create new instances per application using(var l = new UpgradeableReadLock(_lock)) { // create if not already there if (_applicationInstances == null) { l.UpgradeToWriteLock(); _applicationInstances = CreateInstances().ToArray(); } return _applicationInstances; } case ObjectLifetimeScope.Transient: default: // create new instances each time return CreateInstances().ToArray(); } } } /// /// Creates the object instances for the types contained in the types collection. /// /// A list of objects of type . protected virtual IEnumerable CreateInstances() { return PluginManager.Current.CreateInstances(InstanceTypes); } #region Types collection manipulation /// /// Removes a type. /// /// The type to remove. /// the resolver does not support removing types, or /// the type is not a valid type for the resolver. public virtual void RemoveType(Type value) { EnsureSupportsRemove(); using (Resolution.Configuration) using (var l = new UpgradeableReadLock(_lock)) { EnsureCorrectType(value); l.UpgradeToWriteLock(); _instanceTypes.Remove(value); } } /// /// Removes a type. /// /// The type to remove. /// the resolver does not support removing types, or /// the type is not a valid type for the resolver. public void RemoveType() where T : TResolved { RemoveType(typeof(T)); } /// /// Adds types. /// /// The types to add. /// The types are appended at the end of the list. /// the resolver does not support adding types, or /// a type is not a valid type for the resolver, or a type is already in the collection of types. protected void AddTypes(IEnumerable types) { EnsureSupportsAdd(); using (Resolution.Configuration) using (new WriteLock(_lock)) { foreach(var t in types) { EnsureCorrectType(t); if (_instanceTypes.Contains(t)) { throw new InvalidOperationException(string.Format( "Type {0} is already in the collection of types.", t.FullName)); } _instanceTypes.Add(t); } } } /// /// Adds a type. /// /// The type to add. /// The type is appended at the end of the list. /// the resolver does not support adding types, or /// the type is not a valid type for the resolver, or the type is already in the collection of types. public virtual void AddType(Type value) { EnsureSupportsAdd(); using (Resolution.Configuration) using (var l = new UpgradeableReadLock(_lock)) { EnsureCorrectType(value); if (_instanceTypes.Contains(value)) { throw new InvalidOperationException(string.Format( "Type {0} is already in the collection of types.", value.FullName)); } l.UpgradeToWriteLock(); _instanceTypes.Add(value); } } /// /// Adds a type. /// /// The type to add. /// The type is appended at the end of the list. /// the resolver does not support adding types, or /// the type is not a valid type for the resolver, or the type is already in the collection of types. public void AddType() where T : TResolved { AddType(typeof(T)); } /// /// Clears the list of types. /// /// the resolver does not support clearing types. public virtual void Clear() { EnsureSupportsClear(); using (Resolution.Configuration) using (new WriteLock(_lock)) { _instanceTypes.Clear(); } } /// /// Inserts a type at the specified index. /// /// The zero-based index at which the type should be inserted. /// The type to insert. /// the resolver does not support inserting types, or /// the type is not a valid type for the resolver, or the type is already in the collection of types. /// is out of range. public virtual void InsertType(int index, Type value) { EnsureSupportsInsert(); using (Resolution.Configuration) using (var l = new UpgradeableReadLock(_lock)) { EnsureCorrectType(value); if (_instanceTypes.Contains(value)) { throw new InvalidOperationException(string.Format( "Type {0} is already in the collection of types.", value.FullName)); } l.UpgradeToWriteLock(); _instanceTypes.Insert(index, value); } } /// /// Inserts a type at the beginning of the list. /// /// The type to insert. /// the resolver does not support inserting types, or /// the type is not a valid type for the resolver, or the type is already in the collection of types. public virtual void InsertType(Type value) { InsertType(0, value); } /// /// Inserts a type at the specified index. /// /// The type to insert. /// The zero-based index at which the type should be inserted. /// is out of range. public void InsertType(int index) where T : TResolved { InsertType(index, typeof(T)); } /// /// Inserts a type at the beginning of the list. /// /// The type to insert. public void InsertType() where T : TResolved { InsertType(0, typeof(T)); } /// /// Inserts a type before a specified, already existing type. /// /// The existing type before which to insert. /// The type to insert. /// the resolver does not support inserting types, or /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, /// or the new type is already in the collection of types. public virtual void InsertTypeBefore(Type existingType, Type value) { EnsureSupportsInsert(); using (Resolution.Configuration) using (var l = new UpgradeableReadLock(_lock)) { EnsureCorrectType(existingType); EnsureCorrectType(value); if (!_instanceTypes.Contains(existingType)) { throw new InvalidOperationException(string.Format( "Type {0} is not in the collection of types.", existingType.FullName)); } if (_instanceTypes.Contains(value)) { throw new InvalidOperationException(string.Format( "Type {0} is already in the collection of types.", value.FullName)); } int index = _instanceTypes.IndexOf(existingType); l.UpgradeToWriteLock(); _instanceTypes.Insert(index, value); } } /// /// Inserts a type before a specified, already existing type. /// /// The existing type before which to insert. /// The type to insert. /// the resolver does not support inserting types, or /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, /// or the new type is already in the collection of types. public void InsertTypeBefore() where TExisting : TResolved where T : TResolved { InsertTypeBefore(typeof(TExisting), typeof(T)); } /// /// Returns a value indicating whether the specified type is already in the collection of types. /// /// The type to look for. /// A value indicating whether the type is already in the collection of types. public virtual bool ContainsType(Type value) { using (new ReadLock(_lock)) { return _instanceTypes.Contains(value); } } /// /// Returns a value indicating whether the specified type is already in the collection of types. /// /// The type to look for. /// A value indicating whether the type is already in the collection of types. public bool ContainsType() where T : TResolved { return ContainsType(typeof(T)); } #endregion /// /// Returns a WriteLock to use when modifying collections /// /// protected WriteLock GetWriteLock() { return new WriteLock(_lock); } #region Type utilities /// /// Ensures that a type is a valid type for the resolver. /// /// The type to test. /// the type is not a valid type for the resolver. protected void EnsureCorrectType(Type value) { if (!TypeHelper.IsTypeAssignableFrom(value)) throw new InvalidOperationException(string.Format( "Type {0} is not an acceptable type for resolver {1}.", value.FullName, this.GetType().FullName)); } #endregion #region Types collection manipulation support /// /// Ensures that the resolver supports removing types. /// /// The resolver does not support removing types. protected void EnsureSupportsRemove() { if (!SupportsRemove) throw new InvalidOperationException("This resolver does not support removing types"); } /// /// Ensures that the resolver supports clearing types. /// /// The resolver does not support clearing types. protected void EnsureSupportsClear() { if (!SupportsClear) throw new InvalidOperationException("This resolver does not support clearing types"); } /// /// Ensures that the resolver supports adding types. /// /// The resolver does not support adding types. protected void EnsureSupportsAdd() { if (!SupportsAdd) throw new InvalidOperationException("This resolver does not support adding new types"); } /// /// Ensures that the resolver supports inserting types. /// /// The resolver does not support inserting types. protected void EnsureSupportsInsert() { if (!SupportsInsert) throw new InvalidOperationException("This resolver does not support inserting new types"); } /// /// Gets a value indicating whether the resolver supports adding types. /// protected virtual bool SupportsAdd { get { return true; } } /// /// Gets a value indicating whether the resolver supports inserting types. /// protected virtual bool SupportsInsert { get { return true; } } /// /// Gets a value indicating whether the resolver supports clearing types. /// protected virtual bool SupportsClear { get { return true; } } /// /// Gets a value indicating whether the resolver supports removing types. /// protected virtual bool SupportsRemove { get { return true; } } #endregion } }