diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index d1c0bf61de..65553b991d 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using umbraco.interfaces; using Umbraco.Core; namespace Umbraco.Core.Deploy @@ -8,7 +9,7 @@ namespace Umbraco.Core.Deploy /// /// Connects to an Umbraco service. /// - public interface IServiceConnector + public interface IServiceConnector : IDiscoverable { /// /// Gets an artifact. diff --git a/src/Umbraco.Core/Media/IImageUrlProvider.cs b/src/Umbraco.Core/Media/IImageUrlProvider.cs index 3854e1f1ec..29c0ae34ed 100644 --- a/src/Umbraco.Core/Media/IImageUrlProvider.cs +++ b/src/Umbraco.Core/Media/IImageUrlProvider.cs @@ -1,8 +1,14 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Media { - public interface IImageUrlProvider + // note: because this interface is obsolete is is *not* IDiscoverable, and in case the + // PluginManager is asked to find types implementing this interface it will fall back + // to a complete scan. + + [Obsolete("IImageUrlProvider is no longer used and will be removed in future versions")] + public interface IImageUrlProvider // IDiscoverable { string Name { get; } string GetImageUrlFromMedia(int mediaId, IDictionary parameters); diff --git a/src/Umbraco.Core/Media/IThumbnailProvider.cs b/src/Umbraco.Core/Media/IThumbnailProvider.cs index a5be69b72e..18b8453324 100644 --- a/src/Umbraco.Core/Media/IThumbnailProvider.cs +++ b/src/Umbraco.Core/Media/IThumbnailProvider.cs @@ -1,6 +1,13 @@ -namespace Umbraco.Core.Media +using System; + +namespace Umbraco.Core.Media { - public interface IThumbnailProvider + // note: because this interface is obsolete is is *not* IDiscoverable, and in case the + // PluginManager is asked to find types implementing this interface it will fall back + // to a complete scan. + + [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] + public interface IThumbnailProvider // : IDiscoverable { bool CanProvideThumbnail(string fileUrl); string GetThumbnailUrl(string fileUrl); diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs index 0b478d54cf..3cdaab365b 100644 --- a/src/Umbraco.Core/ObjectResolution/Resolution.cs +++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs @@ -63,49 +63,6 @@ namespace Umbraco.Core.ObjectResolution } } - // NOTE - the ugly code below exists only because of umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers - // which wants to re-register actions and handlers instead of properly restarting the application. Don't even think - // about using it for anything else. Also, while the backdoor is open, the resolution system is locked so nothing - // can work properly => deadlocks. Therefore, open the backdoor, do resolution changes EXCLUSIVELY, and close the door! - - /// - /// Returns a disposable object that reprents dirty access to temporarily unfrozen resolution configuration. - /// - /// - /// Should not be used. - /// Should be used in a using(Resolution.DirtyBackdoorToConfiguration) { ... } mode. - /// Because we just lift the frozen state, and we don't actually re-freeze, the Frozen event does not trigger. - /// - internal static IDisposable DirtyBackdoorToConfiguration - { - get { return new DirtyBackdoor(); } - } - - // keep the class here because it needs write-access to Resolution.IsFrozen - private class DirtyBackdoor : IDisposable - { - - private readonly IDisposable _lock; - private readonly bool _frozen; - - public DirtyBackdoor() - { - LogHelper.Debug(typeof(DirtyBackdoor), "Creating back door for resolution"); - - _lock = new WriteLock(ConfigurationLock); - _frozen = _isFrozen; - _isFrozen = false; - } - - public void Dispose() - { - LogHelper.Debug(typeof(DirtyBackdoor), "Disposing back door for resolution"); - - _isFrozen = _frozen; - _lock.Dispose(); - } - } - /// /// Freezes resolution. /// diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs index 40ec415b30..99f9cebf6b 100644 --- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs @@ -2,11 +2,12 @@ using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; +using umbraco.interfaces; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Mappers { - public abstract class BaseMapper + public abstract class BaseMapper : IDiscoverable { private readonly ISqlSyntaxProvider _sqlSyntax; diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigration.cs b/src/Umbraco.Core/Persistence/Migrations/IMigration.cs index 2769400e44..8d0adcf4bc 100644 --- a/src/Umbraco.Core/Persistence/Migrations/IMigration.cs +++ b/src/Umbraco.Core/Persistence/Migrations/IMigration.cs @@ -1,9 +1,11 @@ -namespace Umbraco.Core.Persistence.Migrations +using umbraco.interfaces; + +namespace Umbraco.Core.Persistence.Migrations { /// /// Marker interface for database migrations /// - public interface IMigration + public interface IMigration : IDiscoverable { void Up(); void Down(); diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs index af4a597ea3..3eb0f2a018 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/PluginManager.cs @@ -7,45 +7,55 @@ using System.Reflection; using System.Text; using System.Threading; using System.Web.Compilation; -using System.Xml.Linq; -using Umbraco.Core.Configuration; +using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Models; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Cache; using umbraco.interfaces; using File = System.IO.File; namespace Umbraco.Core { /// - /// Used to resolve all plugin types and cache them and is also used to instantiate plugin types + /// Provides methods to find and instanciate types. /// /// - /// - /// This class should be used to resolve all plugin types, the TypeFinder should not be used directly! - /// - /// This class can expose extension methods to resolve custom plugins - /// - /// Before this class resolves any plugins it checks if the hash has changed for the DLLs in the /bin folder, if it hasn't - /// it will use the cached resolved plugins that it has already found which means that no assembly scanning is necessary. This leads - /// to much faster startup times. + /// This class should be used to resolve all types, the class should never be used directly. + /// In most cases this class is not used directly but through extension methods that retrieve specific types. + /// This class caches the types it knows to avoid excessive assembly scanning and shorten startup times, relying + /// on a hash of the DLLs in the ~/bin folder to check for cache expiration. /// public class PluginManager { + private const string CacheKey = "umbraco-plugins.list"; + + private static PluginManager _current; + private static bool _hasCurrent; + private static object _currentLock = new object(); + + private readonly IServiceProvider _serviceProvider; + private readonly IRuntimeCacheProvider _runtimeCache; + private readonly ProfilingLogger _logger; + private readonly string _tempFolder; + + private readonly object _typesLock = new object(); + private readonly Dictionary _types = new Dictionary(); + + private long _cachedAssembliesHash = -1; + private long _currentAssembliesHash = -1; + private IEnumerable _assemblies; + private bool _reportedChange; + /// - /// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml - /// file is cached temporarily until app startup completes. + /// Initializes a new instance of the class. /// - /// - /// - /// - /// + /// A mechanism for retrieving service objects. + /// The application runtime cache. + /// A profiling logger. + /// Whether to detect changes using hashes. internal PluginManager(IServiceProvider serviceProvider, IRuntimeCacheProvider runtimeCache, ProfilingLogger logger, bool detectChanges = true) { if (serviceProvider == null) throw new ArgumentNullException("serviceProvider"); @@ -56,22 +66,13 @@ namespace Umbraco.Core _runtimeCache = runtimeCache; _logger = logger; + // the temp folder where the cache file lives _tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache"); - //create the folder if it doesn't exist if (Directory.Exists(_tempFolder) == false) - { Directory.CreateDirectory(_tempFolder); - } var pluginListFile = GetPluginListFilePath(); - //this is a check for legacy changes, before we didn't store the TypeResolutionKind in the file which was a mistake, - //so we need to detect if the old file is there without this attribute, if it is then we delete it - if (DetectLegacyPluginListFile()) - { - File.Delete(pluginListFile); - } - if (detectChanges) { //first check if the cached hash is 0, if it is then we ne @@ -80,7 +81,7 @@ namespace Umbraco.Core //if they have changed, we need to write the new file if (RequiresRescanning) { - //if the hash has changed, clear out the persisted list no matter what, this will force + // if the hash has changed, clear out the persisted list no matter what, this will force // rescanning of all plugin types including lazy ones. // http://issues.umbraco.org/issue/U4-4789 File.Delete(pluginListFile); @@ -90,75 +91,96 @@ namespace Umbraco.Core } else { - - //if the hash has changed, clear out the persisted list no matter what, this will force + // if the hash has changed, clear out the persisted list no matter what, this will force // rescanning of all plugin types including lazy ones. // http://issues.umbraco.org/issue/U4-4789 File.Delete(pluginListFile); - //always set to true if we're not detecting (generally only for testing) + // always set to true if we're not detecting (generally only for testing) RequiresRescanning = true; } } - private readonly IServiceProvider _serviceProvider; - private readonly IRuntimeCacheProvider _runtimeCache; - private readonly ProfilingLogger _logger; - private const string CacheKey = "umbraco-plugins.list"; - static PluginManager _resolver; - private readonly string _tempFolder; - private long _cachedAssembliesHash = -1; - private long _currentAssembliesHash = -1; - private static bool _initialized = false; - private static object _singletonLock = new object(); - /// - /// We will ensure that no matter what, only one of these is created, this is to ensure that caching always takes place + /// Gets or sets the set of assemblies to scan. /// /// - /// The setter is generally only used for unit tests + /// If not explicitely set, defaults to all assemblies except those that are know to not have any of the + /// types we might scan. Because we only scan for application types, this means we can safely exclude GAC assemblies + /// for example. + /// This is for unit tests. /// + internal IEnumerable AssembliesToScan + { + get { return _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); } + set { _assemblies = value; } + } + + /// + /// Gets the type lists. + /// + /// For unit tests. + internal IEnumerable TypeLists + { + get { return _types.Values; } + } + + /// + /// Sets a type list. + /// + /// For unit tests. + internal void AddTypeList(TypeList typeList) + { + _types[new TypeListKey(typeList.BaseType, typeList.AttributeType)] = typeList; + } + + /// + /// Gets or sets the singleton instance. + /// + /// The setter exists for unit tests. public static PluginManager Current { get { - return LazyInitializer.EnsureInitialized(ref _resolver, ref _initialized, ref _singletonLock, () => + return LazyInitializer.EnsureInitialized(ref _current, ref _hasCurrent, ref _currentLock, () => { + IRuntimeCacheProvider runtimeCache; + ProfilingLogger profilingLogger; + if (ApplicationContext.Current == null) { + runtimeCache = new NullCacheProvider(); var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(); var profiler = ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger); - return new PluginManager( - new ActivatorServiceProvider(), - new NullCacheProvider(), - new ProfilingLogger(logger, profiler)); + profilingLogger = new ProfilingLogger(logger, profiler); } - return new PluginManager( - new ActivatorServiceProvider(), - ApplicationContext.Current.ApplicationCache.RuntimeCache, - ApplicationContext.Current.ProfilingLogger); + else + { + runtimeCache = ApplicationContext.Current.ApplicationCache.RuntimeCache; + profilingLogger = ApplicationContext.Current.ProfilingLogger; + } + + return new PluginManager(new ActivatorServiceProvider(), runtimeCache, profilingLogger); }); } set { - _initialized = true; - _resolver = value; + _hasCurrent = true; + _current = value; } } - #region Hash checking methods - + #region Hashing /// - /// Returns a bool if the assemblies in the /bin, app_code, global.asax, etc... have changed since they were last hashed. + /// Gets a value indicating whether the assemblies in bin, app_code, global.asax, etc... have changed since they were last hashed. /// internal bool RequiresRescanning { get; private set; } /// - /// Returns the currently cached hash value of the scanned assemblies in the /bin folder. Returns 0 - /// if no cache is found. + /// Gets the currently cached hash value of the scanned assemblies. /// - /// + /// The cached hash value, or 0 if no cache is found. internal long CachedAssembliesHash { get @@ -167,24 +189,22 @@ namespace Umbraco.Core return _cachedAssembliesHash; var filePath = GetPluginHashFilePath(); - if (!File.Exists(filePath)) - return 0; + if (File.Exists(filePath) == false) return 0; + var hash = File.ReadAllText(filePath, Encoding.UTF8); - Int64 val; - if (Int64.TryParse(hash, out val)) - { - _cachedAssembliesHash = val; - return _cachedAssembliesHash; - } - //it could not parse for some reason so we'll return 0. - return 0; + + long val; + if (long.TryParse(hash, out val) == false) return 0; + + _cachedAssembliesHash = val; + return _cachedAssembliesHash; } } /// - /// Returns the current assemblies hash based on creating a hash from the assemblies in the /bin + /// Gets the current assemblies hash based on creating a hash from the assemblies in various places. /// - /// + /// The current hash. internal long CurrentAssembliesHash { get @@ -192,26 +212,24 @@ namespace Umbraco.Core if (_currentAssembliesHash != -1) return _currentAssembliesHash; - _currentAssembliesHash = GetFileHash( - new List> - { - //add the bin folder and everything in it - new Tuple(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)), false), - //add the app code folder and everything in it - new Tuple(new DirectoryInfo(IOHelper.MapPath("~/App_Code")), false), - //add the global.asax (the app domain also monitors this, if it changes will do a full restart) - new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false), + _currentAssembliesHash = GetFileHash(new List> + { + // the bin folder and everything in it + new Tuple(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)), false), + // the app code folder and everything in it + new Tuple(new DirectoryInfo(IOHelper.MapPath("~/App_Code")), false), + // global.asax (the app domain also monitors this, if it changes will do a full restart) + new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false), + // trees.config - use the contents to create the hash since this gets resaved on every app startup! + new Tuple(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true) + }, _logger); - //add the trees.config - use the contents to create the has since this gets resaved on every app startup! - new Tuple(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true) - }, _logger - ); return _currentAssembliesHash; } } /// - /// Writes the assembly hash file + /// Writes the assembly hash file. /// private void WriteCachePluginsHash() { @@ -220,668 +238,725 @@ namespace Umbraco.Core } /// - /// Returns a unique hash for the combination of FileInfo objects passed in + /// Returns a unique hash for a combination of FileInfo objects. /// - /// - /// A collection of files and whether or not to use their file contents to determine the hash or the file's properties - /// (true will make a hash based on it's contents) - /// - /// + /// A collection of files. + /// A profiling logger. + /// The hash. + /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the + /// file properties (false) or the file contents (true). internal static long GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger) { using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) { var hashCombiner = new HashCodeCombiner(); - //get the file info's to check - var fileInfos = filesAndFolders.Where(x => x.Item2 == false).ToArray(); - var fileContents = filesAndFolders.Except(fileInfos); - - //add each unique folder/file to the hash - foreach (var i in fileInfos.Select(x => x.Item1).DistinctBy(x => x.FullName)) - { - hashCombiner.AddFileSystemItem(i); - } + // get the distinct file infos to hash + var uniqInfos = new HashSet(); + var uniqContent = new HashSet(); - //add each unique file's contents to the hash - foreach (var i in fileContents.Select(x => x.Item1).DistinctBy(x => x.FullName)) + foreach (var fileOrFolder in filesAndFolders) { - if (File.Exists(i.FullName)) + var info = fileOrFolder.Item1; + if (fileOrFolder.Item2) { - var content = File.ReadAllText(i.FullName).Replace("\r\n", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty); - hashCombiner.AddCaseInsensitiveString(content); - } - - } + // add each unique file's contents to the hash + // normalize the content for cr/lf and case-sensitivity - return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode()); + if (uniqContent.Contains(info.FullName)) continue; + uniqContent.Add(info.FullName); + if (File.Exists(info.FullName) == false) continue; + var content = RemoveCrLf(File.ReadAllText(info.FullName)); + hashCombiner.AddCaseInsensitiveString(content); + } + else + { + // add each unique folder/file to the hash + + if (uniqInfos.Contains(info.FullName)) continue; + uniqInfos.Add(info.FullName); + hashCombiner.AddFileSystemItem(info); + } + } + + return ConvertHashToInt64(hashCombiner.GetCombinedHashCode()); } } + // fast! (yes, according to benchmarks) + private static string RemoveCrLf(string s) + { + var buffer = new char[s.Length]; + var count = 0; + // ReSharper disable once ForCanBeConvertedToForeach - no! + for (var i = 0; i < s.Length; i++) + { + if (s[i] != '\r' && s[i] != '\n') + buffer[count++] = s[i]; + } + return new string(buffer, 0, count); + } + + /// + /// Returns a unique hash for a combination of FileInfo objects. + /// + /// A collection of files. + /// A profiling logger. + /// The hash. internal static long GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger) { using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) { var hashCombiner = new HashCodeCombiner(); - //add each unique folder/file to the hash - foreach (var i in filesAndFolders.DistinctBy(x => x.FullName)) + // get the distinct file infos to hash + var uniqInfos = new HashSet(); + + foreach (var fileOrFolder in filesAndFolders) { - hashCombiner.AddFileSystemItem(i); + if (uniqInfos.Contains(fileOrFolder.FullName)) continue; + uniqInfos.Add(fileOrFolder.FullName); + hashCombiner.AddFileSystemItem(fileOrFolder); } - return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode()); + + return ConvertHashToInt64(hashCombiner.GetCombinedHashCode()); } } /// - /// Converts the hash value of current plugins to long from string + /// Converts a string hash value into an Int64. /// - /// - /// - internal static long ConvertPluginsHashFromHex(string val) + internal static long ConvertHashToInt64(string val) { long outVal; - if (Int64.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal)) - { - return outVal; - } - return 0; + return long.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal) ? outVal : 0; } - /// - /// Attempts to resolve the list of plugin + assemblies found in the runtime for the base type 'T' passed in. - /// If the cache file doesn't exist, fails to load, is corrupt or the type 'T' element is not found then - /// a false attempt is returned. - /// - /// - /// - internal Attempt> TryGetCachedPluginsFromFile(TypeResolutionKind resolutionType) - { - var filePath = GetPluginListFilePath(); - if (!File.Exists(filePath)) - return Attempt>.Fail(); + #endregion + #region Cache + + /// + /// Attemps to retrieve the list of types from the cache. + /// + /// Fails if the cache is missing or corrupt in any way. + internal Attempt> TryGetCached(Type baseType, Type attributeType) + { + var cache = _runtimeCache.GetCacheItem, IEnumerable>>(CacheKey, ReadCacheSafe, TimeSpan.FromMinutes(4)); + + IEnumerable types; + cache.TryGetValue(Tuple.Create(baseType == null ? string.Empty : baseType.FullName, attributeType == null ? string.Empty : attributeType.FullName), out types); + return types == null + ? Attempt>.Fail() + : Attempt.Succeed(types); + } + + internal Dictionary, IEnumerable> ReadCacheSafe() + { try { - //we will load the xml document, if the app context exist, we will load it from the cache (which is only around for 5 minutes) - //while the app boots up, this should save some IO time on app startup when the app context is there (which is always unless in unit tests) - var xml = _runtimeCache.GetCacheItem(CacheKey, - () => XDocument.Load(filePath), - new TimeSpan(0, 0, 5, 0)); - - if (xml.Root == null) - return Attempt>.Fail(); - - var typeElement = xml.Root.Elements() - .SingleOrDefault(x => - x.Name.LocalName == "baseType" - && ((string)x.Attribute("type")) == typeof(T).FullName - && ((string)x.Attribute("resolutionType")) == resolutionType.ToString()); - - //return false but specify this exception type so we can detect it - if (typeElement == null) - return Attempt>.Fail(new CachedPluginNotFoundInFileException()); - - //return success - return Attempt.Succeed(typeElement.Elements("add") - .Select(x => (string)x.Attribute("type"))); + return ReadCache(); } - catch (Exception ex) + catch { - //if the file is corrupted, etc... return false - return Attempt>.Fail(ex); + try + { + var filePath = GetPluginListFilePath(); + File.Delete(filePath); + } + catch + { + // on-purpose, does not matter + } } + + return new Dictionary, IEnumerable>(); + } + + internal Dictionary, IEnumerable> ReadCache() + { + var cache = new Dictionary, IEnumerable>(); + + var filePath = GetPluginListFilePath(); + if (File.Exists(filePath) == false) + return cache; + + using (var stream = File.OpenRead(filePath)) + using (var reader = new StreamReader(stream)) + { + while (true) + { + var baseType = reader.ReadLine(); + if (baseType == null) return cache; // exit + if (baseType.StartsWith("<")) break; // old xml + + var attributeType = reader.ReadLine(); + if (attributeType == null) break; + + var types = new List(); + while (true) + { + var type = reader.ReadLine(); + if (type == null) + { + types = null; // break 2 levels + break; + } + if (type == string.Empty) + { + cache[Tuple.Create(baseType, attributeType)] = types; + break; + } + types.Add(type); + } + + if (types == null) break; + } + } + + cache.Clear(); + return cache; } /// - /// Removes cache files and internal cache as well + /// Removes cache files and internal cache. /// - /// - /// Generally only used for resetting cache, for example during the install process - /// + /// Generally only used for resetting cache, for example during the install process. public void ClearPluginCache() { var path = GetPluginListFilePath(); if (File.Exists(path)) File.Delete(path); + path = GetPluginHashFilePath(); if (File.Exists(path)) File.Delete(path); _runtimeCache.ClearCacheItem(CacheKey); } - + private string GetPluginListFilePath() { - return Path.Combine(_tempFolder, string.Format("umbraco-plugins.{0}.list", NetworkHelper.FileSafeMachineName)); + var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".list"; + return Path.Combine(_tempFolder, filename); } private string GetPluginHashFilePath() { - return Path.Combine(_tempFolder, string.Format("umbraco-plugins.{0}.hash", NetworkHelper.FileSafeMachineName)); + var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".hash"; + return Path.Combine(_tempFolder, filename); } - /// - /// This will return true if the plugin list file is a legacy one - /// - /// - /// - /// This method exists purely due to an error in 4.11. We were writing the plugin list file without the - /// type resolution kind which will have caused some problems. Now we detect this legacy file and if it is detected - /// we remove it so it can be recreated properly. - /// - internal bool DetectLegacyPluginListFile() + internal void WriteCache() { var filePath = GetPluginListFilePath(); - if (!File.Exists(filePath)) - return false; - try + using (var stream = File.Open(filePath, FileMode.Create, FileAccess.ReadWrite)) + using (var writer = new StreamWriter(stream)) { - var xml = XDocument.Load(filePath); - if (xml.Root == null) - return false; - - var typeElement = xml.Root.Elements() - .FirstOrDefault(x => x.Name.LocalName == "baseType"); - - if (typeElement == null) - return false; - - //now check if the typeElement is missing the resolutionType attribute - return typeElement.Attributes().All(x => x.Name.LocalName != "resolutionType"); - } - catch (Exception) - { - //if the file is corrupted, etc... return true so it is removed - return true; + foreach (var typeList in _types.Values) + { + writer.WriteLine(typeList.BaseType == null ? string.Empty : typeList.BaseType.FullName); + writer.WriteLine(typeList.AttributeType == null ? string.Empty : typeList.AttributeType.FullName); + foreach (var type in typeList.Types) + writer.WriteLine(type.AssemblyQualifiedName); + writer.WriteLine(); + } } } - /// - /// Adds/Updates the type list for the base type 'T' in the cached file - /// - /// - /// - /// - /// - /// THIS METHOD IS NOT THREAD SAFE - /// - /// - /// - /// - /// - /// - /// - /// - /// ]]> - /// - internal void UpdateCachedPluginsFile(IEnumerable typesFound, TypeResolutionKind resolutionType) + internal void UpdateCache() { - var filePath = GetPluginListFilePath(); - XDocument xml = null; - - if (File.Exists(filePath)) - { - try - { - xml = XDocument.Load(filePath); - } - catch - { - try - { - File.Delete(filePath); // file is corrupt somehow - } - catch - { - // on-purpose, does not matter - } - xml = null; - } - } - - // else replace the xml, create the document and the root - if (xml == null) xml = new XDocument(new XElement("plugins")); - - if (xml.Root == null) - { - //if for some reason there is no root, create it - xml.Add(new XElement("plugins")); - } - //find the type 'T' element to add or update - var typeElement = xml.Root.Elements() - .SingleOrDefault(x => - x.Name.LocalName == "baseType" - && ((string)x.Attribute("type")) == typeof(T).FullName - && ((string)x.Attribute("resolutionType")) == resolutionType.ToString()); - - if (typeElement == null) - { - //create the type element - typeElement = new XElement("baseType", - new XAttribute("type", typeof(T).FullName), - new XAttribute("resolutionType", resolutionType.ToString())); - //then add it to the root - xml.Root.Add(typeElement); - } - - - //now we have the type element, we need to clear any previous types as children and add/update it with new ones - typeElement.ReplaceNodes(typesFound.Select(x => new XElement("add", new XAttribute("type", x.AssemblyQualifiedName)))); - //save the xml file - var dir = Path.GetDirectoryName(filePath); - if (Directory.Exists(dir) == false) - Directory.CreateDirectory(dir); - xml.Save(filePath); + // note + // at the moment we write the cache to disk every time we update it. ideally we defer the writing + // since all the updates are going to happen in a row when Umbraco starts. that being said, the + // file is small enough, so it is not a priority. + WriteCache(); } #endregion - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - private readonly HashSet _types = new HashSet(); - private IEnumerable _assemblies; + #region Create Instances /// - /// Returns all found property editors (based on the resolved Iparameter editors - this saves a scan) + /// Resolves and creates instances. /// - internal IEnumerable ResolvePropertyEditors() + /// The type to use for resolution. + /// Indicates whether to throw if an instance cannot be created. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// The created instances. + /// + /// By default is false and instances that cannot be created are just skipped. + /// By default is true and cache is used for type resolution. + /// By default is null and is used. + /// Caching is disabled when using specific assemblies. + /// + internal IEnumerable FindAndCreateInstances(bool throwException = false, bool cache = true, IEnumerable specificAssemblies = null) { - //return all proeprty editor types found except for the base property editor type - return ResolveTypes() - .Where(x => x.IsType()) - .Except(new[] { typeof(PropertyEditor) }); - } - - /// - /// Returns all found parameter editors (which includes property editors) - /// - internal IEnumerable ResolveParameterEditors() - { - //return all paramter editor types found except for the base property editor type - return ResolveTypes() - .Except(new[] { typeof(ParameterEditor), typeof(PropertyEditor) }); - } - - /// - /// Returns all available IApplicationStartupHandler objects - /// - /// - internal IEnumerable ResolveApplicationStartupHandlers() - { - return ResolveTypes(); - } - - /// - /// Returns all classes of type ICacheRefresher - /// - /// - internal IEnumerable ResolveCacheRefreshers() - { - return ResolveTypes(); - } - - /// - /// Returns all available IPropertyEditorValueConverter - /// - /// - internal IEnumerable ResolvePropertyEditorValueConverters() - { - return ResolveTypes(); - } - - /// - /// Returns all available IDataType in application - /// - /// - internal IEnumerable ResolveDataTypes() - { - return ResolveTypes(); - } - - /// - /// Returns all available IMacroGuiRendering in application - /// - /// - internal IEnumerable ResolveMacroRenderings() - { - return ResolveTypes(); - } - - /// - /// Returns all available IPackageAction in application - /// - /// - internal IEnumerable ResolvePackageActions() - { - return ResolveTypes(); - } - - /// - /// Returns all available IAction in application - /// - /// - internal IEnumerable ResolveActions() - { - return ResolveTypes(); - } - - /// - /// Returns all mapper types that have a MapperFor attribute defined - /// - /// - internal IEnumerable ResolveAssignedMapperTypes() - { - return ResolveTypesWithAttribute(); - } - - /// - /// Returns all SqlSyntaxProviders with the SqlSyntaxProviderAttribute - /// - /// - internal IEnumerable ResolveSqlSyntaxProviders() - { - return ResolveTypesWithAttribute(); - } - - /// - /// 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. - /// - internal IEnumerable AssembliesToScan - { - get { return _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); } - set { _assemblies = value; } - } - - /// - /// Used to resolve and create instances of the specified type based on the resolved/cached plugin types - /// - /// - /// set to true if an exception is to be thrown if there is an error during instantiation - /// - /// - /// - internal IEnumerable FindAndCreateInstances(bool throwException = false, bool cacheResult = true, IEnumerable specificAssemblies = null) - { - var types = ResolveTypes(cacheResult, specificAssemblies); + var types = ResolveTypes(cache, specificAssemblies); return CreateInstances(types, throwException); } /// - /// Used to create instances of the specified type based on the resolved/cached plugin types + /// Creates instances of the specified types. /// - /// - /// - /// set to true if an exception is to be thrown if there is an error during instantiation - /// + /// The base type for all instances. + /// The instance types. + /// Indicates whether to throw if an instance cannot be created. + /// The created instances. + /// By default is false and instances that cannot be created are just skipped. internal IEnumerable CreateInstances(IEnumerable types, bool throwException = false) { return _serviceProvider.CreateInstances(types, _logger.Logger, throwException); } /// - /// Used to create an instance of the specified type based on the resolved/cached plugin types + /// Creates an instance of the specified type. /// - /// - /// + /// The base type of the instance. + /// The type of the instance. /// - /// + /// The created instance. internal T CreateInstance(Type type, bool throwException = false) { var instances = CreateInstances(new[] { type }, throwException); return instances.FirstOrDefault(); } - private IEnumerable ResolveTypes( - Func> finder, - TypeResolutionKind resolutionType, - bool cacheResult) - { - using (var readLock = new UpgradeableReadLock(Locker)) - { - var typesFound = new List(); - - using (_logger.TraceDuration( - String.Format("Starting resolution types of {0}", typeof(T).FullName), - String.Format("Completed resolution of types of {0}, found {1}", typeof(T).FullName, typesFound.Count))) - { - //check if the TypeList already exists, if so return it, if not we'll create it - var typeList = _types.SingleOrDefault(x => x.IsTypeList(resolutionType)); - - //need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 - if (cacheResult && typeList != null) - { - _logger.Logger.Debug("Existing typeList found for {0} with resolution type {1}", () => typeof(T), () => resolutionType); - } - - //if we're not caching the result then proceed, or if the type list doesn't exist then proceed - if (cacheResult == false || typeList == null) - { - //upgrade to a write lock since we're adding to the collection - readLock.UpgradeToWriteLock(); - - typeList = new TypeList(resolutionType); - - //we first need to look into our cache file (this has nothing to do with the 'cacheResult' parameter which caches in memory). - //if assemblies have not changed and the cache file actually exists, then proceed to try to lookup by the cache file. - if (RequiresRescanning == false && File.Exists(GetPluginListFilePath())) - { - var fileCacheResult = TryGetCachedPluginsFromFile(resolutionType); - - //here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan - //in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed - //so in this instance there will never be a result. - if (fileCacheResult.Exception != null && fileCacheResult.Exception is CachedPluginNotFoundInFileException) - { - _logger.Logger.Debug("Tried to find typelist for type {0} and resolution {1} in file cache but the type was not found so loading types by assembly scan ", () => typeof(T), () => resolutionType); - - //we don't have a cache for this so proceed to look them up by scanning - LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); - } - else - { - if (fileCacheResult.Success) - { - var successfullyLoadedFromCache = true; - //we have a previous cache for this so we don't need to scan we just load what has been found in the file - foreach (var t in fileCacheResult.Result) - { - try - { - //we use the build manager to ensure we get all types loaded, this is slightly slower than - //Type.GetType but if the types in the assembly aren't loaded yet then we have problems with that. - var type = BuildManager.GetType(t, true); - typeList.AddType(type); - } - catch (Exception ex) - { - //if there are any exceptions loading types, we have to exist, this should never happen so - //we will need to revert to scanning for types. - successfullyLoadedFromCache = false; - _logger.Logger.Error("Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof(T).FullName, ex); - break; - } - } - if (successfullyLoadedFromCache == false) - { - //we need to manually load by scanning if loading from the file was not successful. - LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); - } - else - { - _logger.Logger.Debug("Loaded plugin types {0} with resolution {1} from persisted cache", () => typeof(T), () => resolutionType); - } - } - } - } - else - { - _logger.Logger.Debug("Assembly changes detected, loading types {0} for resolution {1} by assembly scan", () => typeof(T), () => resolutionType); - - //we don't have a cache for this so proceed to look them up by scanning - LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); - } - - //only add the cache if we are to cache the results - if (cacheResult) - { - //add the type list to the collection - var added = _types.Add(typeList); - - _logger.Logger.Debug("Caching of typelist for type {0} and resolution {1} was successful = {2}", () => typeof(T), () => resolutionType, () => added); - - } - } - typesFound = typeList.GetTypes().ToList(); - } - - return typesFound; - } - } - - /// - /// This method invokes the finder which scans the assemblies for the types and then loads the result into the type finder. - /// Once the results are loaded, we update the cached type xml file - /// - /// - /// - /// - /// - /// THIS METHODS IS NOT THREAD SAFE - /// - private void LoadViaScanningAndUpdateCacheFile(TypeList typeList, TypeResolutionKind resolutionKind, Func> finder) - { - //we don't have a cache for this so proceed to look them up by scanning - foreach (var t in finder()) - { - typeList.AddType(t); - } - UpdateCachedPluginsFile(typeList.GetTypes(), resolutionKind); - } - - #region Public Methods - /// - /// Generic method to find the specified type and cache the result - /// - /// - /// - public IEnumerable ResolveTypes(bool cacheResult = true, IEnumerable specificAssemblies = null) - { - return ResolveTypes( - () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), - TypeResolutionKind.FindAllTypes, - cacheResult); - } - - /// - /// Generic method to find the specified type that has an attribute and cache the result - /// - /// - /// - /// - public IEnumerable ResolveTypesWithAttribute(bool cacheResult = true, IEnumerable specificAssemblies = null) - where TAttribute : Attribute - { - return ResolveTypes( - () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), - TypeResolutionKind.FindTypesWithAttribute, - cacheResult); - } - - /// - /// Generic method to find any type that has the specified attribute - /// - /// - /// - public IEnumerable ResolveAttributedTypes(bool cacheResult = true, IEnumerable specificAssemblies = null) - where TAttribute : Attribute - { - return ResolveTypes( - () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), - TypeResolutionKind.FindAttributedTypes, - cacheResult); - } #endregion - /// - /// Used for unit tests - /// - /// - internal HashSet GetTypeLists() - { - return _types; - } - - - - #region Private classes/Enums + #region Resolve Types /// - /// The type of resolution being invoked + /// Resolves class types inheriting from or implementing the specified type /// - internal enum TypeResolutionKind + /// The type to inherit from or implement. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types inheriting from or implementing the specified type. + /// Caching is disabled when using specific assemblies. + public IEnumerable ResolveTypes(bool cache = true, IEnumerable specificAssemblies = null) { - FindAllTypes, - FindAttributedTypes, - FindTypesWithAttribute - } + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; - internal abstract class TypeList - { - public abstract void AddType(Type t); - public abstract bool IsTypeList(TypeResolutionKind resolutionType); - public abstract IEnumerable GetTypes(); - } - - internal class TypeList : TypeList - { - private readonly TypeResolutionKind _resolutionType; - - public TypeList(TypeResolutionKind resolutionType) + // if not caching, or not IDiscoverable, directly resolve types + if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false) { - _resolutionType = resolutionType; + return ResolveTypesInternal( + typeof (T), null, + () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), + cache); } - private readonly List _types = new List(); + // if caching and IDiscoverable + // filter the cached discovered types (and cache the result) - public override void AddType(Type t) + var discovered = ResolveTypesInternal( + typeof (IDiscoverable), null, + () => TypeFinder.FindClassesOfType(AssembliesToScan), + true); + + return ResolveTypesInternal( + typeof (T), null, + () => discovered + .Where(x => typeof (T).IsAssignableFrom(x)), + true); + } + + /// + /// Resolves class types inheriting from or implementing the specified type and marked with the specified attribute. + /// + /// The type to inherit from or implement. + /// The type of the attribute. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types inheriting from or implementing the specified type and marked with the specified attribute. + /// Caching is disabled when using specific assemblies. + public IEnumerable ResolveTypesWithAttribute(bool cache = true, IEnumerable specificAssemblies = null) + where TAttribute : Attribute + { + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; + + // if not caching, or not IDiscoverable, directly resolve types + if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false) { - //if the type is an attribute type we won't do the type check because typeof is going to be the - //attribute type whereas the 't' type is the object type found with the attribute. - if (_resolutionType == TypeResolutionKind.FindAttributedTypes || t.IsType()) + return ResolveTypesInternal( + typeof (T), typeof (TAttribute), + () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), + cache); + } + + // if caching and IDiscoverable + // filter the cached discovered types (and cache the result) + + var discovered = ResolveTypesInternal( + typeof (IDiscoverable), null, + () => TypeFinder.FindClassesOfType(AssembliesToScan), + true); + + return ResolveTypesInternal( + typeof (T), typeof (TAttribute), + () => discovered + .Where(x => typeof(T).IsAssignableFrom(x)) + .Where(x => x.GetCustomAttributes(false).Any()), + true); + } + + /// + /// Resolves class types marked with the specified attribute. + /// + /// The type of the attribute. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types marked with the specified attribute. + /// Caching is disabled when using specific assemblies. + public IEnumerable ResolveAttributedTypes(bool cache = true, IEnumerable specificAssemblies = null) + where TAttribute : Attribute + { + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; + + return ResolveTypesInternal( + typeof (object), typeof (TAttribute), + () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), + cache); + } + + private IEnumerable ResolveTypesInternal( + Type baseType, Type attributeType, + Func> finder, + bool cache) + { + // using an upgradeable lock makes little sense here as only one thread can enter the upgradeable + // lock at a time, and we don't have non-upgradeable readers, and quite probably the plugin + // manager is mostly not going to be used in any kind of massively multi-threaded scenario - so, + // a plain lock is enough + + var name = ResolvedName(baseType, attributeType); + + lock (_typesLock) + using (_logger.TraceDuration( + "Resolving " + name, + "Resolved " + name)) // cannot contain typesFound.Count as it's evaluated before the find + { + // resolve within a lock & timer + return ResolveTypesInternalLocked(baseType, attributeType, finder, cache); + } + } + + private static string ResolvedName(Type baseType, Type attributeType) + { + var s = attributeType == null ? string.Empty : ("[" + attributeType + "]"); + s += baseType; + return s; + } + + private IEnumerable ResolveTypesInternalLocked( + Type baseType, Type attributeType, + Func> finder, + bool cache) + { + // check if the TypeList already exists, if so return it, if not we'll create it + var listKey = new TypeListKey(baseType, attributeType); + TypeList typeList = null; + if (cache) + _types.TryGetValue(listKey, out typeList); // else null + + // if caching and found, return + if (typeList != null) + { + // need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 + _logger.Logger.Debug("Resolving {0}: found a cached type list.", () => ResolvedName(baseType, attributeType)); + return typeList.Types; + } + + // else proceed, + typeList = new TypeList(baseType, attributeType); + + var scan = RequiresRescanning || File.Exists(GetPluginListFilePath()) == false; + + if (scan) + { + // either we have to rescan, or we could not find the cache file: + // report (only once) and scan and update the cache file - this variable is purely used to check if we need to log + if (_reportedChange == false) { - _types.Add(t); + _logger.Logger.Debug("Assembly changes detected, need to rescan everything."); + _reportedChange = true; } } + if (scan == false) + { + // if we don't have to scan, try the cache + var cacheResult = TryGetCached(baseType, attributeType); + + // here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan + // in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed + // so in this instance there will never be a result. + if (cacheResult.Exception is CachedPluginNotFoundInFileException || cacheResult.Success == false) + { + _logger.Logger.Debug("Resolving {0}: failed to load from cache file, must scan assemblies.", () => ResolvedName(baseType, attributeType)); + scan = true; + } + else + { + // successfully retrieved types from the file cache: load + foreach (var type in cacheResult.Result) + { + try + { + // we use the build manager to ensure we get all types loaded, this is slightly slower than + // Type.GetType but if the types in the assembly aren't loaded yet it would fail whereas + // BuildManager will load them - this is how eg MVC loads types, etc - no need to make it + // more complicated + typeList.Add(BuildManager.GetType(type, true)); + } + catch (Exception ex) + { + // in case of any exception, we have to exit, and revert to scanning + _logger.Logger.Error("Resolving " + ResolvedName(baseType, attributeType) + ": failed to load cache file type " + type + ", reverting to scanning assemblies.", ex); + scan = true; + break; + } + } + + if (scan == false) + { + _logger.Logger.Debug("Resolving {0}: loaded types from cache file.", () => ResolvedName(baseType, attributeType)); + } + } + } + + if (scan) + { + // either we had to scan, or we could not resolve the types from the cache file - scan now + _logger.Logger.Debug("Resolving {0}: scanning assemblies.", () => ResolvedName(baseType, attributeType)); + + foreach (var t in finder()) + typeList.Add(t); + } + + // if we are to cache the results, do so + if (cache) + { + var added = _types.ContainsKey(listKey) == false; + if (added) + { + _types[listKey] = typeList; + //if we are scanning then update the cache file + if (scan) + { + UpdateCache(); + } + } + + _logger.Logger.Debug("Resolved {0}, caching ({1}).", () => ResolvedName(baseType, attributeType), () => added.ToString().ToLowerInvariant()); + } + else + { + _logger.Logger.Debug("Resolved {0}.", () => ResolvedName(baseType, attributeType)); + } + + return typeList.Types; + } + + #endregion + + #region Nested classes and stuff + + /// + /// Groups a type and a resolution kind into a key. + /// + private struct TypeListKey + { + // ReSharper disable MemberCanBePrivate.Local + public readonly Type BaseType; + public readonly Type AttributeType; + // ReSharper restore MemberCanBePrivate.Local + + public TypeListKey(Type baseType, Type attributeType) + { + BaseType = baseType ?? typeof (object); + AttributeType = attributeType; + } + + public override bool Equals(object obj) + { + if (obj == null || obj is TypeListKey == false) return false; + var o = (TypeListKey)obj; + return BaseType == o.BaseType && AttributeType == o.AttributeType; + } + + public override int GetHashCode() + { + // in case AttributeType is null we need something else, using typeof (TypeListKey) + // which does not really "mean" anything, it's just a value... + + var hash = 5381; + hash = ((hash << 5) + hash) ^ BaseType.GetHashCode(); + hash = ((hash << 5) + hash) ^ (AttributeType ?? typeof (TypeListKey)).GetHashCode(); + return hash; + } + } + + /// + /// Represents a list of types obtained by looking for types inheriting/implementing a + /// specified type, and/or marked with a specified attribute type. + /// + internal class TypeList + { + private readonly HashSet _types = new HashSet(); + + public TypeList(Type baseType, Type attributeType) + { + BaseType = baseType; + AttributeType = attributeType; + } + + public Type BaseType { get; private set; } + public Type AttributeType { get; private set; } + + /// + /// Adds a type. + /// + public void Add(Type type) + { + if (BaseType.IsAssignableFrom(type) == false) + throw new ArgumentException("Base type " + BaseType + " is not assignable from type " + type + ".", "type"); + _types.Add(type); + } + /// - /// Returns true if the current TypeList is of the same lookup type + /// Gets the types. /// - /// - /// - /// - public override bool IsTypeList(TypeResolutionKind resolutionType) + public IEnumerable Types { - return _resolutionType == resolutionType && (typeof(T)) == typeof(TLookup); - } - - public override IEnumerable GetTypes() - { - return _types; + get { return _types; } } } /// - /// This class is used simply to determine that a plugin was not found in the cache plugin list with the specified - /// TypeResolutionKind. + /// Represents the error that occurs when a plugin was not found in the cache plugin + /// list with the specified TypeResolutionKind. /// internal class CachedPluginNotFoundInFileException : Exception - { - - } + { } #endregion } + + internal static class PluginManagerExtensions + { + /// + /// Gets all classes inheriting from PropertyEditor. + /// + /// + /// Excludes the actual PropertyEditor base type. + /// + public static IEnumerable ResolvePropertyEditors(this PluginManager mgr) + { + // look for IParameterEditor (fast, IDiscoverable) then filter + + var propertyEditor = typeof (PropertyEditor); + + return mgr.ResolveTypes() + .Where(x => propertyEditor.IsAssignableFrom(x) && x != propertyEditor); + } + + /// + /// Gets all classes implementing IParameterEditor. + /// + /// + /// Includes property editors. + /// Excludes the actual ParameterEditor and PropertyEditor base types. + /// + public static IEnumerable ResolveParameterEditors(this PluginManager mgr) + { + var propertyEditor = typeof (PropertyEditor); + var parameterEditor = typeof (ParameterEditor); + + return mgr.ResolveTypes() + .Where(x => x != propertyEditor && x != parameterEditor); + } + + /// + /// Gets all classes implementing IApplicationStartupHandler. + /// + [Obsolete("IApplicationStartupHandler is obsolete.")] + public static IEnumerable ResolveApplicationStartupHandlers(this PluginManager mgr) + { + return mgr.ResolveTypes(); + } + + /// + /// Gets all classes implementing ICacheRefresher. + /// + public static IEnumerable ResolveCacheRefreshers(this PluginManager mgr) + { + return mgr.ResolveTypes(); + } + + /// + /// Gets all classes implementing IPropertyEditorValueConverter. + /// + [Obsolete("IPropertyEditorValueConverter is obsolete.")] + public static IEnumerable ResolvePropertyEditorValueConverters(this PluginManager mgr) + { + return mgr.ResolveTypes(); + } + + /// + /// Gets all classes implementing IDataType. + /// + [Obsolete("IDataType is obsolete.")] + public static IEnumerable ResolveDataTypes(this PluginManager mgr) + { + return mgr.ResolveTypes(); + } + + /// + /// Gets all classes implementing IMacroGuiRendering. + /// + [Obsolete("IMacroGuiRendering is obsolete.")] + public static IEnumerable ResolveMacroRenderings(this PluginManager mgr) + { + return mgr.ResolveTypes(); + } + + /// + /// Gets all classes implementing IPackageAction. + /// + public static IEnumerable ResolvePackageActions(this PluginManager mgr) + { + return mgr.ResolveTypes(); + } + + /// + /// Gets all classes implementing IAction. + /// + public static IEnumerable ResolveActions(this PluginManager mgr) + { + return mgr.ResolveTypes(); + } + + /// + /// Gets all classes inheriting from BaseMapper and marked with the MapperForAttribute. + /// + public static IEnumerable ResolveAssignedMapperTypes(this PluginManager mgr) + { + return mgr.ResolveTypesWithAttribute(); + } + + /// + /// Gets all classes implementing ISqlSyntaxProvider and marked with the SqlSyntaxProviderAttribute. + /// + public static IEnumerable ResolveSqlSyntaxProviders(this PluginManager mgr) + { + return mgr.ResolveTypesWithAttribute(); + } + } } diff --git a/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs index 44320c9464..cbff5e45ab 100644 --- a/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; +using umbraco.interfaces; namespace Umbraco.Core.PropertyEditors { - public interface IParameterEditor + public interface IParameterEditor : IDiscoverable { /// /// The id of the property editor diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs index c7cbc0f473..b2e41ad628 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs @@ -1,4 +1,5 @@ using System; +using umbraco.interfaces; namespace Umbraco.Core.PropertyEditors { @@ -7,8 +8,8 @@ namespace Umbraco.Core.PropertyEditors /// // todo: drop IPropertyEditorValueConverter support (when?). [Obsolete("Use IPropertyValueConverter.")] - public interface IPropertyEditorValueConverter - { + public interface IPropertyEditorValueConverter : IDiscoverable + { /// /// Returns a value indicating whether this provider applies to the specified property. /// diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index 79887843a0..0666aca087 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -1,11 +1,12 @@ -using Umbraco.Core.Models.PublishedContent; +using umbraco.interfaces; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors { /// /// Provides published content properties conversion service. /// - public interface IPropertyValueConverter + public interface IPropertyValueConverter : IDiscoverable { /// /// Gets a value indicating whether the converter supports a property type. diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index f970cf225b..2af6e99cc7 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -1,32 +1,24 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Linq.Expressions; using System.Reflection; using System.Security; using System.Text; -using System.Threading; using System.Web; using System.Web.Compilation; -using System.Web.Hosting; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core { - /// /// A utility class to find all classes of a certain type by reflection in the current bin folder /// of the web application. /// public static class TypeFinder { - private static volatile HashSet _localFilteredAssemblyCache = null; + private static volatile HashSet _localFilteredAssemblyCache; private static readonly object LocalFilteredAssemblyCacheLocker = new object(); /// @@ -63,7 +55,7 @@ namespace Umbraco.Core } catch (InvalidOperationException e) { - if (!(e.InnerException is SecurityException)) + if ((e.InnerException is SecurityException) == false) throw; } @@ -99,7 +91,7 @@ namespace Umbraco.Core } //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (!assemblies.Any()) + if (assemblies.Any() == false) { foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) { @@ -111,12 +103,12 @@ namespace Umbraco.Core var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) + if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) { try { var appCodeAssembly = Assembly.Load("App_Code"); - if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already + if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already assemblies.Add(appCodeAssembly); } catch (FileNotFoundException ex) @@ -128,7 +120,7 @@ namespace Umbraco.Core } catch (InvalidOperationException e) { - if (!(e.InnerException is SecurityException)) + if (e.InnerException is SecurityException == false) throw; } @@ -145,23 +137,15 @@ namespace Umbraco.Core internal static HashSet GetAssembliesWithKnownExclusions( IEnumerable excludeFromResults = null) { - if (_localFilteredAssemblyCache == null) + lock (LocalFilteredAssemblyCacheLocker) { - lock (LocalFilteredAssemblyCacheLocker) - { - //double check - if (_localFilteredAssemblyCache == null) - { - _localFilteredAssemblyCache = new HashSet(); - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - foreach (var a in assemblies) - { - _localFilteredAssemblyCache.Add(a); - } - } - } + if (_localFilteredAssemblyCache != null) + return _localFilteredAssemblyCache; + + var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); + _localFilteredAssemblyCache = new HashSet(assemblies); + return _localFilteredAssemblyCache; } - return _localFilteredAssemblyCache; } /// @@ -180,9 +164,9 @@ namespace Umbraco.Core exclusionFilter = new string[] { }; return GetAllAssemblies() - .Where(x => !excludeFromResults.Contains(x) - && !x.GlobalAssemblyCache - && !exclusionFilter.Any(f => x.FullName.StartsWith(f))); + .Where(x => excludeFromResults.Contains(x) == false + && x.GlobalAssemblyCache == false + && exclusionFilter.Any(f => x.FullName.StartsWith(f)) == false); } /// @@ -298,7 +282,7 @@ namespace Umbraco.Core { if (assemblies == null) throw new ArgumentNullException("assemblies"); - return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses, + return GetClassesWithBaseType(assignTypeFrom, assemblies, onlyConcreteClasses, //the additional filter will ensure that any found types also have the attribute applied. t => t.GetCustomAttributes(false).Any()); } @@ -324,7 +308,7 @@ namespace Umbraco.Core { if (assemblies == null) throw new ArgumentNullException("assemblies"); - return GetClasses(typeof(T), assemblies, onlyConcreteClasses); + return GetClassesWithBaseType(typeof(T), assemblies, onlyConcreteClasses); } /// @@ -363,105 +347,9 @@ namespace Umbraco.Core IEnumerable assemblies, bool onlyConcreteClasses) { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - if (TypeHelper.IsTypeAssignableFrom(attributeType) == false) - throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type"); - - var foundAttributedTypes = new HashSet(); - - var assemblyList = assemblies.ToArray(); - - //find all assembly references that are referencing the attribute type's assembly since we - //should only be scanning those assemblies because any other assembly will definitely not - //contain a class that has this attribute. - var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList); - - //get a list of non-referenced assemblies (we'll use this when we recurse below) - var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray(); - - //loop through the referenced assemblies - foreach (var a in referencedAssemblies) - { - //get all types in this assembly - var allTypes = GetTypesWithFormattedException(a) - .ToArray(); - - var attributedTypes = new Type[] { }; - try - { - //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes but have - //the specified attribute - attributedTypes = allTypes - .Where(t => (TypeHelper.IsNonStaticClass(t) - && (onlyConcreteClasses == false || t.IsAbstract == false)) - //the type must have this attribute - && t.GetCustomAttributes(attributeType, false).Any()) - .ToArray(); - } - catch (TypeLoadException ex) - { - LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); - continue; - } - - //add the types to our list to return - foreach (var t in attributedTypes) - { - foundAttributedTypes.Add(t); - } - - //get all attributes of the type being searched for - var allAttributeTypes = allTypes.Where(attributeType.IsAssignableFrom); - - //now we need to include types that may be inheriting from sub classes of the attribute type being searched for - //so we will search in assemblies that reference those types too. - foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly)) - { - - //So that we are not scanning too much, we need to group the sub types: - // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. - // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class - // * We should not search for sub types if the type is static since you cannot inherit from them. - var subTypeList = subTypesInAssembly - .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false) - .ToArray(); - - var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList); - - //if there's a base class amongst the types then we'll only search for that type. - //otherwise we'll have to search for all of them. - var subTypesToSearch = new HashSet(); - if (baseClassAttempt.Success) - { - subTypesToSearch.Add(baseClassAttempt.Result); - } - else - { - foreach (var t in subTypeList) - { - subTypesToSearch.Add(t); - } - } - - foreach (var typeToSearch in subTypesToSearch) - { - //recursively find the types inheriting from this sub type in the other non-scanned assemblies. - var foundTypes = FindClassesWithAttribute(typeToSearch, otherAssemblies, onlyConcreteClasses); - - foreach (var f in foundTypes) - { - foundAttributedTypes.Add(f); - } - } - - } - } - - return foundAttributedTypes; + return GetClassesWithAttribute(attributeType, assemblies, onlyConcreteClasses); } - /// /// Finds the classes with attribute. /// @@ -485,122 +373,129 @@ namespace Umbraco.Core return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); } - #region Private methods + private static IEnumerable GetClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses) + { + if (typeof(Attribute).IsAssignableFrom(attributeType) == false) + throw new ArgumentException("Type " + attributeType + " is not an Attribute type."); + + var candidateAssemblies = new HashSet(assemblies); + var attributeAssemblyIsCandidate = candidateAssemblies.Contains(attributeType.Assembly); + candidateAssemblies.Remove(attributeType.Assembly); + var types = new List(); + + var stack = new Stack(); + stack.Push(attributeType.Assembly); + + while (stack.Count > 0) + { + var assembly = stack.Pop(); + + Type[] assemblyTypes = null; + if (assembly != attributeType.Assembly || attributeAssemblyIsCandidate) + { + // get all assembly types that can be assigned to baseType + try + { + assemblyTypes = GetTypesWithFormattedException(assembly) + .ToArray(); // in try block + } + catch (TypeLoadException ex) + { + LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly), ex); + continue; + } + + types.AddRange(assemblyTypes.Where(x => + x.IsClass // only classes + && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed + && x.IsNestedPrivate == false // exclude nested private + && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract + && x.GetCustomAttribute() == null // exclude hidden + && x.GetCustomAttributes(attributeType, false).Any())); // marked with the attribute + } + + if (assembly != attributeType.Assembly && assemblyTypes.Where(attributeType.IsAssignableFrom).Any() == false) + continue; + + foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + { + candidateAssemblies.Remove(referencing); + stack.Push(referencing); + } + } + + return types; + } + /// /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type /// deriving from the base type. /// - /// + /// /// /// /// An additional filter to apply for what types will actually be included in the return value /// - private static IEnumerable GetClasses( - Type assignTypeFrom, + private static IEnumerable GetClassesWithBaseType( + Type baseType, IEnumerable assemblies, bool onlyConcreteClasses, Func additionalFilter = null) { - //the default filter will always return true. - if (additionalFilter == null) + var candidateAssemblies = new HashSet(assemblies); + var baseTypeAssemblyIsCandidate = candidateAssemblies.Contains(baseType.Assembly); + candidateAssemblies.Remove(baseType.Assembly); + var types = new List(); + + var stack = new Stack(); + stack.Push(baseType.Assembly); + + while (stack.Count > 0) { - additionalFilter = type => true; - } + var assembly = stack.Pop(); - var foundAssignableTypes = new HashSet(); - - var assemblyList = assemblies.ToArray(); - - //find all assembly references that are referencing the current type's assembly since we - //should only be scanning those assemblies because any other assembly will definitely not - //contain sub type's of the one we're currently looking for - var referencedAssemblies = TypeHelper.GetReferencedAssemblies(assignTypeFrom, assemblyList); - - //get a list of non-referenced assemblies (we'll use this when we recurse below) - var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray(); - - //loop through the referenced assemblies - foreach (var a in referencedAssemblies) - { - //get all types in the assembly that are sub types of the current type - var allSubTypes = GetTypesWithFormattedException(a) - .Where(assignTypeFrom.IsAssignableFrom) - .ToArray(); - - var filteredTypes = new Type[] { }; - try + // get all assembly types that can be assigned to baseType + Type[] assemblyTypes = null; + if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate) { - //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes - filteredTypes = allSubTypes - .Where(t => (TypeHelper.IsNonStaticClass(t) - //Do not include nested private classes - since we are in full trust now this will find those too! - && t.IsNestedPrivate == false - && (onlyConcreteClasses == false || t.IsAbstract == false) - //Do not include classes that are flagged to hide from the type finder - && t.GetCustomAttribute() == null - && additionalFilter(t))) - .ToArray(); + try + { + assemblyTypes = GetTypesWithFormattedException(assembly) + .Where(baseType.IsAssignableFrom) + .ToArray(); // in try block + } + catch (TypeLoadException ex) + { + LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly), ex); + continue; + } + + types.AddRange(assemblyTypes.Where(x => + x.IsClass // only classes + && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed + && x.IsNestedPrivate == false // exclude nested private + && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract + && x.GetCustomAttribute() == null // exclude hidden + && (additionalFilter == null || additionalFilter(x)))); // filter } - catch (TypeLoadException ex) - { - LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); + + if (assembly != baseType.Assembly && assemblyTypes.All(x => x.IsSealed)) continue; - } - //add the types to our list to return - foreach (var t in filteredTypes) + foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) { - foundAssignableTypes.Add(t); + candidateAssemblies.Remove(referencing); + stack.Push(referencing); } - - //now we need to include types that may be inheriting from sub classes of the type being searched for - //so we will search in assemblies that reference those types too. - foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly)) - { - - //So that we are not scanning too much, we need to group the sub types: - // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. - // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class - // * We should not search for sub types if the type is static since you cannot inherit from them. - var subTypeList = subTypesInAssembly - .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false) - .ToArray(); - - var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList); - - //if there's a base class amongst the types then we'll only search for that type. - //otherwise we'll have to search for all of them. - var subTypesToSearch = new HashSet(); - if (baseClassAttempt.Success) - { - subTypesToSearch.Add(baseClassAttempt.Result); - } - else - { - foreach (var t in subTypeList) - { - subTypesToSearch.Add(t); - } - } - - foreach (var typeToSearch in subTypesToSearch) - { - //recursively find the types inheriting from this sub type in the other non-scanned assemblies. - var foundTypes = GetClasses(typeToSearch, otherAssemblies, onlyConcreteClasses, additionalFilter); - - foreach (var f in foundTypes) - { - foundAssignableTypes.Add(f); - } - } - - } - } - return foundAssignableTypes; + + return types; } internal static IEnumerable GetTypesWithFormattedException(Assembly a) @@ -666,7 +561,6 @@ namespace Umbraco.Core #endregion - public static Type GetTypeByName(string typeName) { var type = BuildManager.GetType(typeName, false); @@ -684,6 +578,5 @@ namespace Umbraco.Core .Select(x => x.GetType(typeName)) .FirstOrDefault(x => x != null); } - } } diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index f0b3a82961..14c441dcd5 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -13,9 +13,12 @@ namespace Umbraco.Core /// internal static class TypeHelper { - - private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary, PropertyInfo[]>(); + private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache + = new ConcurrentDictionary, PropertyInfo[]>(); + private static readonly ConcurrentDictionary GetFieldsCache + = new ConcurrentDictionary(); + + private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; /// /// Checks if the method is actually overriding a base method @@ -30,44 +33,40 @@ namespace Umbraco.Core /// /// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList /// - /// - /// + /// The referenced assembly. + /// A list of assemblies. /// /// /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot /// reference that assembly, same with the global.asax assembly. /// - public static Assembly[] GetReferencedAssemblies(Type assignTypeFrom, IEnumerable assemblies) + public static Assembly[] GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies) { - //check if it is the app_code assembly. - //check if it is App_global.asax assembly - if (assignTypeFrom.Assembly.IsAppCodeAssembly() || assignTypeFrom.Assembly.IsGlobalAsaxAssembly()) - { - return Enumerable.Empty().ToArray(); - } + if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) + return EmptyAssemblies; + - //find all assembly references that are referencing the current type's assembly since we - //should only be scanning those assemblies because any other assembly will definitely not - //contain sub type's of the one we're currently looking for - return assemblies - .Where(assembly => - assembly == assignTypeFrom.Assembly - || HasReferenceToAssemblyWithName(assembly, assignTypeFrom.Assembly.GetName().Name)) - .ToArray(); + // find all assembly references that are referencing the current type's assembly since we + // should only be scanning those assemblies because any other assembly will definitely not + // contain sub type's of the one we're currently looking for + var name = assembly.GetName().Name; + return assemblies.Where(x => x == assembly || HasReference(x, name)).ToArray(); } /// - /// checks if the assembly has a reference with the same name as the expected assembly name. + /// Determines if an assembly references another assembly. /// /// - /// + /// /// - private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName) - { - return assembly - .GetReferencedAssemblies() - .Select(a => a.Name) - .Contains(expectedAssemblyName, StringComparer.Ordinal); + public static bool HasReference(Assembly assembly, string name) + { + // ReSharper disable once LoopCanBeConvertedToQuery - no! + foreach (var a in assembly.GetReferencedAssemblies()) + { + if (string.Equals(a.Name, name, StringComparison.Ordinal)) return true; + } + return false; } /// @@ -106,13 +105,10 @@ namespace Umbraco.Core public static Attempt GetLowestBaseType(params Type[] types) { if (types.Length == 0) - { return Attempt.Fail(); - } - if (types.Length == 1) - { + + if (types.Length == 1) return Attempt.Succeed(types[0]); - } foreach (var curr in types) { @@ -196,20 +192,15 @@ namespace Umbraco.Core /// /// /// - public static PropertyInfo GetProperty(Type type, string name, - bool mustRead = true, - bool mustWrite = true, + public static PropertyInfo GetProperty(Type type, string name, + bool mustRead = true, + bool mustWrite = true, bool includeIndexed = false, bool caseSensitive = true) { - return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed) - .FirstOrDefault(x => - { - if (caseSensitive) - return x.Name == name; - return x.Name.InvariantEquals(name); - }); - } + return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed) + .FirstOrDefault(x => caseSensitive ? (x.Name == name) : x.Name.InvariantEquals(name)); + } /// /// Gets (and caches) discoverable in the current for a given . @@ -222,7 +213,7 @@ namespace Umbraco.Core type, x => type .GetFields(BindingFlags.Public | BindingFlags.Instance) - .Where(y => !y.IsInitOnly) + .Where(y => y.IsInitOnly == false) .ToArray()); } @@ -240,13 +231,12 @@ namespace Umbraco.Core new Tuple(type, mustRead, mustWrite, includeIndexed), x => type .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(y => (!mustRead || y.CanRead) - && (!mustWrite || y.CanWrite) - && (includeIndexed || !y.GetIndexParameters().Any())) + .Where(y => (mustRead == false || y.CanRead) + && (mustWrite == false || y.CanWrite) + && (includeIndexed || y.GetIndexParameters().Any() == false)) .ToArray()); } - #region Match Type //TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric @@ -337,9 +327,9 @@ namespace Umbraco.Core // not a generic type, not a generic parameter // so normal class or interface - // fixme structs? enums? array types? // about primitive types, value types, etc: // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + // if it's a primitive type... it needs to be == if (implementation == contract) return true; if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true; diff --git a/src/Umbraco.Tests/CodeFirst/TypeInheritanceTest.cs b/src/Umbraco.Tests/CodeFirst/TypeInheritanceTest.cs index e48e13a385..7d2942f1c9 100644 --- a/src/Umbraco.Tests/CodeFirst/TypeInheritanceTest.cs +++ b/src/Umbraco.Tests/CodeFirst/TypeInheritanceTest.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.CodeFirst } }; - + } [Test] @@ -84,10 +84,7 @@ namespace Umbraco.Tests.CodeFirst var foundTypes = _pluginManager.ResolveContentTypeBaseTypes(); Assert.That(foundTypes.Count(), Is.EqualTo(15)); - Assert.AreEqual(1, - _pluginManager.GetTypeLists() - .Count(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes))); + Assert.AreEqual(1, _pluginManager.TypeLists.Count(x => x.BaseType == typeof (ContentTypeBase) && x.AttributeType == null)); } - } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index 95e8fb16e2..333970bf67 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; using umbraco.DataLayer; using umbraco.editorControls; +using umbraco.interfaces; using umbraco.MacroEngines; using umbraco.uicontrols; using Umbraco.Web; @@ -34,14 +35,14 @@ namespace Umbraco.Tests.Plugins public void Initialize() { //this ensures its reset - _manager = new PluginManager(new ActivatorServiceProvider(), new NullCacheProvider(), + _manager = new PluginManager(new ActivatorServiceProvider(), new NullCacheProvider(), new ProfilingLogger(Mock.Of(), Mock.Of())); //for testing, we'll specify which assemblies are scanned for the PluginTypeResolver //TODO: Should probably update this so it only searches this assembly and add custom types to be found _manager.AssembliesToScan = new[] { - this.GetType().Assembly, + this.GetType().Assembly, typeof(ApplicationStartupHandler).Assembly, typeof(SqlCEHelper).Assembly, typeof(CMSNode).Assembly, @@ -160,7 +161,7 @@ namespace Umbraco.Tests.Plugins public void Detect_Legacy_Plugin_File_List() { var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache"); - + var filePath= Path.Combine(tempFolder, string.Format("umbraco-plugins.{0}.list", NetworkHelper.FileSafeMachineName)); File.WriteAllText(filePath, @" @@ -169,12 +170,11 @@ namespace Umbraco.Tests.Plugins "); - - Assert.IsTrue(_manager.DetectLegacyPluginListFile()); + + Assert.IsEmpty(_manager.ReadCache()); // uber-legacy cannot be read File.Delete(filePath); - //now create a valid one File.WriteAllText(filePath, @" @@ -182,19 +182,32 @@ namespace Umbraco.Tests.Plugins "); - Assert.IsFalse(_manager.DetectLegacyPluginListFile()); + Assert.IsEmpty(_manager.ReadCache()); // legacy cannot be read + + File.Delete(filePath); + + File.WriteAllText(filePath, @"IContentFinder + +MyContentFinder +AnotherContentFinder + +"); + + Assert.IsNotNull(_manager.ReadCache()); // works } [Test] public void Create_Cached_Plugin_File() { - var types = new[] { typeof(PluginManager), typeof(PluginManagerTests), typeof(UmbracoContext) }; + var types = new[] { typeof (PluginManager), typeof (PluginManagerTests), typeof (UmbracoContext) }; - //yes this is silly, none of these types inherit from string, but this is just to test the xml file format - _manager.UpdateCachedPluginsFile(types, PluginManager.TypeResolutionKind.FindAllTypes); + var typeList1 = new PluginManager.TypeList(typeof (object), null); + foreach (var type in types) typeList1.Add(type); + _manager.AddTypeList(typeList1); + _manager.WriteCache(); - var plugins = _manager.TryGetCachedPluginsFromFile(PluginManager.TypeResolutionKind.FindAllTypes); - var diffType = _manager.TryGetCachedPluginsFromFile(PluginManager.TypeResolutionKind.FindAttributedTypes); + var plugins = _manager.TryGetCached(typeof (object), null); + var diffType = _manager.TryGetCached(typeof (object), typeof (ObsoleteAttribute)); Assert.IsTrue(plugins.Success); //this will be false since there is no cache of that type resolution kind @@ -210,7 +223,7 @@ namespace Umbraco.Tests.Plugins public void PluginHash_From_String() { var s = "hello my name is someone".GetHashCode().ToString("x", CultureInfo.InvariantCulture); - var output = PluginManager.ConvertPluginsHashFromHex(s); + var output = PluginManager.ConvertHashToInt64(s); Assert.AreNotEqual(0, output); } @@ -240,7 +253,7 @@ namespace Umbraco.Tests.Plugins var list1 = new[] { f1, f2, f3, f4, f5, f6 }; var list2 = new[] { f1, f3, f5 }; var list3 = new[] { f1, f3, f5, f7 }; - + //Act var hash1 = PluginManager.GetFileHash(list1, new ProfilingLogger(Mock.Of(), Mock.Of())); var hash2 = PluginManager.GetFileHash(list2, new ProfilingLogger(Mock.Of(), Mock.Of())); @@ -259,9 +272,7 @@ namespace Umbraco.Tests.Plugins { var foundTypes1 = _manager.ResolveFindMeTypes(); var foundTypes2 = _manager.ResolveFindMeTypes(); - Assert.AreEqual(1, - _manager.GetTypeLists() - .Count(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes))); + Assert.AreEqual(1, _manager.TypeLists.Count(x => x.BaseType == typeof(IFindMe) && x.AttributeType == null)); } [Test] @@ -344,20 +355,20 @@ namespace Umbraco.Tests.Plugins { var types = new HashSet(); - var propEditors = new PluginManager.TypeList(PluginManager.TypeResolutionKind.FindAllTypes); - propEditors.AddType(typeof(LabelPropertyEditor)); + var propEditors = new PluginManager.TypeList(typeof (PropertyEditor), null); + propEditors.Add(typeof(LabelPropertyEditor)); types.Add(propEditors); - var found = types.SingleOrDefault(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes)); + var found = types.SingleOrDefault(x => x.BaseType == typeof (PropertyEditor) && x.AttributeType == null); Assert.IsNotNull(found); //This should not find a type list of this type - var shouldNotFind = types.SingleOrDefault(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes)); + var shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof (IParameterEditor) && x.AttributeType == null); Assert.IsNull(shouldNotFind); } - + [XsltExtension("Blah.Blah")] public class MyXsltExtension { @@ -371,7 +382,7 @@ namespace Umbraco.Tests.Plugins } - public interface IFindMe + public interface IFindMe : IDiscoverable { } diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index 50ab053668..35c14860d7 100644 --- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs @@ -26,11 +26,11 @@ using Umbraco.Web.BaseRest; namespace Umbraco.Tests.Plugins { - + /// /// Tests for typefinder /// - [TestFixture] + [TestFixture] public class TypeFinderTests { /// @@ -43,7 +43,7 @@ namespace Umbraco.Tests.Plugins { _assemblies = new[] { - this.GetType().Assembly, + this.GetType().Assembly, typeof(ApplicationStartupHandler).Assembly, typeof(SqlCEHelper).Assembly, typeof(CMSNode).Assembly, @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Plugins [Test] public void Find_Classes_Of_Type() { - var typesFound = TypeFinder.FindClassesOfType(_assemblies); + var typesFound = TypeFinder.FindClassesOfType(_assemblies); var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); Assert.AreEqual(originalTypesFound.Count(), typesFound.Count()); @@ -118,7 +118,7 @@ namespace Umbraco.Tests.Plugins } } } - + } [Ignore] @@ -149,7 +149,7 @@ namespace Umbraco.Tests.Plugins } } } - + } public class MyTag : ITag @@ -161,7 +161,7 @@ namespace Umbraco.Tests.Plugins public class MySuperTag : MyTag { - + } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] @@ -203,7 +203,7 @@ namespace Umbraco.Tests.Plugins /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder /// /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been /// loaded in the CLR, not all assemblies. /// See these threads: /// http://issues.umbraco.org/issue/U5-198 @@ -329,8 +329,8 @@ namespace Umbraco.Tests.Plugins } catch (SecurityException) { - //we will just ignore this because this will fail - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to + //we will just ignore this because this will fail + //in medium trust for system assemblies, we get an exception but we just want to continue until we get to //an assembly that is ok. } } @@ -347,9 +347,9 @@ namespace Umbraco.Tests.Plugins } catch (SecurityException) { - //we will just ignore this because if we are trying to do a call to: + //we will just ignore this because if we are trying to do a call to: // AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName))) - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to + //in medium trust for system assemblies, we get an exception but we just want to continue until we get to //an assembly that is ok. } } @@ -361,7 +361,7 @@ namespace Umbraco.Tests.Plugins } /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan + /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are /// cached for perforance reasons. /// @@ -429,7 +429,7 @@ namespace Umbraco.Tests.Plugins "RouteDebugger,", "SqlCE4Umbraco,", "umbraco.datalayer,", - "umbraco.interfaces,", + "umbraco.interfaces,", "umbraco.providers,", "Umbraco.Web.UI,", "umbraco.webservices", @@ -631,5 +631,5 @@ namespace Umbraco.Tests.Plugins } } - + } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index f96f176036..87af8f3234 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -62,7 +62,7 @@ namespace Umbraco.Tests.TestHelpers public override void TearDown() { base.TearDown(); - + // reset settings SettingsForTests.Reset(); UmbracoContext.Current = null; @@ -119,7 +119,7 @@ namespace Umbraco.Tests.TestHelpers } /// - /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan + /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs /// to be set to true if the SetupPluginManager has been overridden. /// diff --git a/src/Umbraco.Web/Editors/IEditorValidator.cs b/src/Umbraco.Web/Editors/IEditorValidator.cs index e1d4e68ed2..8cbd4e2949 100644 --- a/src/Umbraco.Web/Editors/IEditorValidator.cs +++ b/src/Umbraco.Web/Editors/IEditorValidator.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using umbraco.interfaces; namespace Umbraco.Web.Editors { - internal interface IEditorValidator + internal interface IEditorValidator : IDiscoverable { Type ModelType { get; } IEnumerable Validate(object model); diff --git a/src/Umbraco.Web/HealthCheck/HealthCheck.cs b/src/Umbraco.Web/HealthCheck/HealthCheck.cs index ec1358947a..34158a66eb 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheck.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheck.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.Serialization; +using umbraco.interfaces; using Umbraco.Core; namespace Umbraco.Web.HealthCheck @@ -9,7 +10,7 @@ namespace Umbraco.Web.HealthCheck /// The abstract health check class /// [DataContract(Name = "healtCheck", Namespace = "")] - public abstract class HealthCheck + public abstract class HealthCheck : IDiscoverable { protected HealthCheck(HealthCheckContext healthCheckContext) { diff --git a/src/Umbraco.Web/Media/ImageUrl.cs b/src/Umbraco.Web/Media/ImageUrl.cs index dff9358a38..2af7fa8b9b 100644 --- a/src/Umbraco.Web/Media/ImageUrl.cs +++ b/src/Umbraco.Web/Media/ImageUrl.cs @@ -11,6 +11,7 @@ using umbraco; namespace Umbraco.Web.Media { + [Obsolete("This is no longer used and will be removed in future versions")] public class ImageUrl { [Obsolete("Use TryGetImageUrl() instead")] diff --git a/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs b/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs index c52d937d8f..dfed61e69d 100644 --- a/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs +++ b/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs @@ -8,9 +8,10 @@ using Umbraco.Web.Media.ImageUrlProviders; namespace Umbraco.Web.Media { - internal sealed class ImageUrlProviderResolver : ManyObjectsResolverBase + [Obsolete("IImageUrlProvider is no longer used and will be removed in future versions")] + internal sealed class ImageUrlProviderResolver : LazyManyObjectsResolverBase { - internal ImageUrlProviderResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable value) + internal ImageUrlProviderResolver(IServiceProvider serviceProvider, ILogger logger, Func> value) : base(serviceProvider, logger, value) { } diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs index 2513e8c1f4..f172dc3b7e 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs @@ -12,7 +12,8 @@ using umbraco.BusinessLogic.Utils; namespace Umbraco.Web.Media.ThumbnailProviders { - public sealed class ThumbnailProvidersResolver : ManyObjectsResolverBase + [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] + public sealed class ThumbnailProvidersResolver : LazyManyObjectsResolverBase { /// /// Constructor @@ -20,7 +21,7 @@ namespace Umbraco.Web.Media.ThumbnailProviders /// /// /// - internal ThumbnailProvidersResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable providers) + internal ThumbnailProvidersResolver(IServiceProvider serviceProvider, ILogger logger, Func> providers) : base(serviceProvider, logger, providers) { diff --git a/src/Umbraco.Web/Mvc/PluginController.cs b/src/Umbraco.Web/Mvc/PluginController.cs index 48608fc9a1..4ff4b961b8 100644 --- a/src/Umbraco.Web/Mvc/PluginController.cs +++ b/src/Umbraco.Web/Mvc/PluginController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Web.Mvc; +using umbraco.interfaces; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Services; @@ -12,7 +13,7 @@ namespace Umbraco.Web.Mvc /// /// A base class for all plugin controllers to inherit from /// - public abstract class PluginController : Controller + public abstract class PluginController : Controller, IDiscoverable { /// /// stores the metadata about plugin controllers diff --git a/src/Umbraco.Web/WebApi/UmbracoApiController.cs b/src/Umbraco.Web/WebApi/UmbracoApiController.cs index b850532011..0356432c66 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Web.Http; using System.Web.Http.ModelBinding; +using umbraco.interfaces; using Umbraco.Core.Models; using Umbraco.Core.Models.Validation; using Umbraco.Web.Models.ContentEditing; @@ -11,7 +12,7 @@ namespace Umbraco.Web.WebApi /// /// The base class for auto-routed API controllers for Umbraco /// - public abstract class UmbracoApiController : UmbracoApiControllerBase + public abstract class UmbracoApiController : UmbracoApiControllerBase, IDiscoverable { protected UmbracoApiController() { diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 3f970b06ab..29565065e2 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -533,11 +533,11 @@ namespace Umbraco.Web ThumbnailProvidersResolver.Current = new ThumbnailProvidersResolver( ServiceProvider, LoggerResolver.Current.Logger, - PluginManager.ResolveThumbnailProviders()); + () => PluginManager.ResolveThumbnailProviders()); ImageUrlProviderResolver.Current = new ImageUrlProviderResolver( ServiceProvider, LoggerResolver.Current.Logger, - PluginManager.ResolveImageUrlProviders()); + () => PluginManager.ResolveImageUrlProviders()); CultureDictionaryFactoryResolver.Current = new CultureDictionaryFactoryResolver( new DefaultCultureDictionaryFactory()); diff --git a/src/Umbraco.Web/WebServices/FolderBrowserService.cs b/src/Umbraco.Web/WebServices/FolderBrowserService.cs index bf5a3f1bd5..15a6c10880 100644 --- a/src/Umbraco.Web/WebServices/FolderBrowserService.cs +++ b/src/Umbraco.Web/WebServices/FolderBrowserService.cs @@ -15,7 +15,7 @@ using Tag = umbraco.cms.businesslogic.Tags.Tag; namespace Umbraco.Web.WebServices { //TODO: Can we convert this to MVC please instead of /base? - + [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] [RestExtension("FolderBrowserService")] public class FolderBrowserService { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Image.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Image.cs index c920d43c7a..dd0c0e0554 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Image.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Image.cs @@ -1,10 +1,12 @@ -using System.Web.UI; +using System; +using System.Web.UI; using System.Web.UI.HtmlControls; using Umbraco.Core.Media; using Umbraco.Web.Media; namespace umbraco.presentation.templateControls { + [Obsolete("This is no longer used and will be removed in future versions")] public class Image : HtmlImage { public string NodeId { get; set; } diff --git a/src/umbraco.businesslogic/PluginManagerExtensions.cs b/src/umbraco.businesslogic/PluginManagerExtensions.cs index 7a61414315..0df66b3b0c 100644 --- a/src/umbraco.businesslogic/PluginManagerExtensions.cs +++ b/src/umbraco.businesslogic/PluginManagerExtensions.cs @@ -16,9 +16,8 @@ namespace umbraco.businesslogic /// /// internal static IEnumerable ResolveApplications(this PluginManager resolver) - { - //don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason - return resolver.ResolveTypesWithAttribute(cacheResult:false); + { + return resolver.ResolveTypesWithAttribute(); } /// @@ -28,8 +27,7 @@ namespace umbraco.businesslogic /// internal static IEnumerable ResolveAttributedTrees(this PluginManager resolver) { - //don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason - return resolver.ResolveTypesWithAttribute(cacheResult:false); + return resolver.ResolveTypesWithAttribute(); } } diff --git a/src/umbraco.cms/Actions/Action.cs b/src/umbraco.cms/Actions/Action.cs index df6c257396..3a5503d714 100644 --- a/src/umbraco.cms/Actions/Action.cs +++ b/src/umbraco.cms/Actions/Action.cs @@ -35,40 +35,10 @@ namespace umbraco.BusinessLogic.Actions public class Action { private static readonly Dictionary ActionJs = new Dictionary(); - - private static readonly object Lock = new object(); - - static Action() - { - ReRegisterActionsAndHandlers(); - } - - /// - /// This is used when an IAction or IActionHandler is installed into the system - /// and needs to be loaded into memory. - /// - /// - /// TODO: this shouldn't be needed... we should restart the app pool when a package is installed! - /// + + [Obsolete("This no longer performs any action there is never a reason to rescan because the app domain will be restarted if new IActions are added because they are included in assemblies")] public static void ReRegisterActionsAndHandlers() - { - lock (Lock) - { - // NOTE use the DirtyBackdoor to change the resolution configuration EXCLUSIVELY - // ie do NOT do ANYTHING else while holding the backdoor, because while it is open - // the whole resolution system is locked => nothing can work properly => deadlocks - - var newResolver = new ActionsResolver( - new ActivatorServiceProvider(), LoggerResolver.Current.Logger, - () => TypeFinder.FindClassesOfType(PluginManager.Current.AssembliesToScan)); - - using (Umbraco.Core.ObjectResolution.Resolution.DirtyBackdoorToConfiguration) - { - ActionsResolver.Reset(false); // and do NOT reset the whole resolution! - ActionsResolver.Current = newResolver; - } - - } + { } /// diff --git a/src/umbraco.cms/businesslogic/macro/IMacroEngine.cs b/src/umbraco.cms/businesslogic/macro/IMacroEngine.cs index 952ee11158..9655a9b0c7 100644 --- a/src/umbraco.cms/businesslogic/macro/IMacroEngine.cs +++ b/src/umbraco.cms/businesslogic/macro/IMacroEngine.cs @@ -6,7 +6,8 @@ using umbraco.interfaces; namespace umbraco.cms.businesslogic.macro { - public interface IMacroEngine { + public interface IMacroEngine : IDiscoverable + { string Name { get; } IEnumerable SupportedExtensions { get; } IEnumerable SupportedUIExtensions { get; } diff --git a/src/umbraco.cms/businesslogic/media/IMediaFactory.cs b/src/umbraco.cms/businesslogic/media/IMediaFactory.cs index 8f8f87556e..0106f131b4 100644 --- a/src/umbraco.cms/businesslogic/media/IMediaFactory.cs +++ b/src/umbraco.cms/businesslogic/media/IMediaFactory.cs @@ -3,11 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; using umbraco.BusinessLogic; +using umbraco.interfaces; namespace umbraco.cms.businesslogic.media { [Obsolete("This interface is no longer used and will be removed from the codebase in future versions")] - public interface IMediaFactory + public interface IMediaFactory : IDiscoverable { List Extensions { get; } int Priority { get; } diff --git a/src/umbraco.interfaces/IAction.cs b/src/umbraco.interfaces/IAction.cs index 6d62a0780f..e82c5935c4 100644 --- a/src/umbraco.interfaces/IAction.cs +++ b/src/umbraco.interfaces/IAction.cs @@ -5,8 +5,8 @@ namespace umbraco.interfaces /// /// Summary description for ActionI. /// - public interface IAction - { + public interface IAction : IDiscoverable + { char Letter {get;} bool ShowInNotifier {get;} bool CanBePermissionAssigned {get;} diff --git a/src/umbraco.interfaces/IApplication.cs b/src/umbraco.interfaces/IApplication.cs index 98dc043dcd..3c0528f961 100644 --- a/src/umbraco.interfaces/IApplication.cs +++ b/src/umbraco.interfaces/IApplication.cs @@ -7,6 +7,6 @@ namespace umbraco.interfaces /// /// Interface for created applications in the umbraco backoffice /// - public interface IApplication - {} + public interface IApplication : IDiscoverable + { } } diff --git a/src/umbraco.interfaces/IApplicationStartupHandler.cs b/src/umbraco.interfaces/IApplicationStartupHandler.cs index cff7d25cb9..4e66135db0 100644 --- a/src/umbraco.interfaces/IApplicationStartupHandler.cs +++ b/src/umbraco.interfaces/IApplicationStartupHandler.cs @@ -14,6 +14,6 @@ namespace umbraco.interfaces /// and bind to any custom events in the OnApplicationInitialized method. /// [Obsolete("This interface is obsolete, use IApplicationEventHandler or ApplicationEventHandler instead")] - public interface IApplicationStartupHandler + public interface IApplicationStartupHandler : IDiscoverable { } } diff --git a/src/umbraco.interfaces/ICacheRefresher.cs b/src/umbraco.interfaces/ICacheRefresher.cs index d239e81fe6..642be660d3 100644 --- a/src/umbraco.interfaces/ICacheRefresher.cs +++ b/src/umbraco.interfaces/ICacheRefresher.cs @@ -7,7 +7,7 @@ namespace umbraco.interfaces /// The IcacheRefresher Interface is used for loadbalancing. /// /// - public interface ICacheRefresher + public interface ICacheRefresher : IDiscoverable { Guid UniqueIdentifier { get; } string Name { get; } diff --git a/src/umbraco.interfaces/IDataType.cs b/src/umbraco.interfaces/IDataType.cs index 72d611a843..591057d76b 100644 --- a/src/umbraco.interfaces/IDataType.cs +++ b/src/umbraco.interfaces/IDataType.cs @@ -8,8 +8,8 @@ namespace umbraco.interfaces /// And finally it contains IData which manages the actual data in the Data Type /// [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - public interface IDataType - { + public interface IDataType : IDiscoverable + { /// /// Gets the id. /// diff --git a/src/umbraco.interfaces/IDiscoverable.cs b/src/umbraco.interfaces/IDiscoverable.cs new file mode 100644 index 0000000000..2c65122104 --- /dev/null +++ b/src/umbraco.interfaces/IDiscoverable.cs @@ -0,0 +1,8 @@ +namespace umbraco.interfaces +{ + /// + /// Marks a class or an interface as discoverable by PluginManager. + /// + public interface IDiscoverable + { } +} \ No newline at end of file diff --git a/src/umbraco.interfaces/IMacroGuiRendering.cs b/src/umbraco.interfaces/IMacroGuiRendering.cs index a9b1bdebe2..717b441889 100644 --- a/src/umbraco.interfaces/IMacroGuiRendering.cs +++ b/src/umbraco.interfaces/IMacroGuiRendering.cs @@ -6,8 +6,8 @@ namespace umbraco.interfaces /// Summary description for IMacroGuiRendering. /// [Obsolete("This interface is no longer used and will be removed from the codebase in future versions")] - public interface IMacroGuiRendering - { + public interface IMacroGuiRendering : IDiscoverable + { /// /// Gets or sets the value. /// diff --git a/src/umbraco.interfaces/IPackageAction.cs b/src/umbraco.interfaces/IPackageAction.cs index aea727221f..b52be3a245 100644 --- a/src/umbraco.interfaces/IPackageAction.cs +++ b/src/umbraco.interfaces/IPackageAction.cs @@ -4,7 +4,8 @@ using System.Text; using System.Xml; namespace umbraco.interfaces { - public interface IPackageAction { + public interface IPackageAction : IDiscoverable + { bool Execute(string packageName, XmlNode xmlData); string Alias(); bool Undo(string packageName, XmlNode xmlData); diff --git a/src/umbraco.interfaces/ITree.cs b/src/umbraco.interfaces/ITree.cs index 43cfe5d322..857c0a49ed 100644 --- a/src/umbraco.interfaces/ITree.cs +++ b/src/umbraco.interfaces/ITree.cs @@ -6,8 +6,8 @@ namespace umbraco.interfaces /// /// Interface for created application trees in the umbraco backoffice /// - public interface ITree - { + public interface ITree : IDiscoverable + { /// /// Sets the tree id. /// diff --git a/src/umbraco.interfaces/umbraco.interfaces.csproj b/src/umbraco.interfaces/umbraco.interfaces.csproj index 43a96244b1..dc246fc9bf 100644 --- a/src/umbraco.interfaces/umbraco.interfaces.csproj +++ b/src/umbraco.interfaces/umbraco.interfaces.csproj @@ -127,6 +127,7 @@ +