2012-08-01 22:06:15 +06:00
using System ;
using System.Collections.Generic ;
2012-11-11 09:09:06 +05:00
using System.Globalization ;
using System.IO ;
2012-08-01 22:06:15 +06:00
using System.Linq ;
using System.Reflection ;
using System.Text ;
using System.Threading ;
2012-11-12 08:10:12 +05:00
using System.Web.Compilation ;
2012-11-11 09:09:06 +05:00
using System.Xml.Linq ;
2016-05-19 16:32:22 +02:00
using Umbraco.Core.Cache ;
2012-11-11 09:09:06 +05:00
using Umbraco.Core.IO ;
2012-08-01 22:06:15 +06:00
using Umbraco.Core.Logging ;
2013-03-13 01:09:29 +04:00
using Umbraco.Core.Persistence.Mappers ;
2012-11-05 06:14:44 +06:00
using Umbraco.Core.PropertyEditors ;
2016-03-17 16:05:56 +01:00
using Umbraco.Core._Legacy.PackageActions ;
2012-11-27 13:27:33 -01:00
using File = System . IO . File ;
2012-08-01 22:06:15 +06:00
2016-05-19 16:32:22 +02:00
namespace Umbraco.Core.Plugins
2012-08-01 22:06:15 +06:00
{
2016-07-22 14:07:03 +02:00
/// <summary>Resolves and caches all plugin types, and instanciates them.
2012-11-27 13:27:33 -01:00
/// </summary>
/// <remarks>
2016-07-22 14:07:03 +02:00
/// <para>This class should be used to resolve all plugin types, the TypeFinder should not be used directly!.</para>
/// <para>Before this class resolves any plugins it checks if the hash has changed for the DLLs in the /bin folder, if it hasn't
2012-11-27 13:27:33 -01:00
/// it will use the cached resolved plugins that it has already found which means that no assembly scanning is necessary. This leads
2016-07-22 14:07:03 +02:00
/// to much faster startup times.</para>
2012-11-27 13:27:33 -01:00
/// </remarks>
2014-02-17 15:08:27 +11:00
public class PluginManager
2012-11-27 13:27:33 -01:00
{
2016-07-22 14:07:03 +02:00
private const string CacheKey = "umbraco-plugins.list" ;
private static object _instanceLocker = new object ( ) ;
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim ( ) ;
private static PluginManager _instance ;
private static bool _hasInstance ;
private readonly IRuntimeCacheProvider _runtimeCache ;
private readonly ProfilingLogger _logger ;
private readonly string _tempFolder ;
private readonly HashSet < TypeList > _types = new HashSet < TypeList > ( ) ;
private long _cachedAssembliesHash = - 1 ;
private long _currentAssembliesHash = - 1 ;
private IEnumerable < Assembly > _assemblies ;
2012-11-27 13:27:33 -01:00
/// <summary>
2016-07-22 14:07:03 +02:00
/// Initializes a new instance of the <see cref="PluginManager"/> class.
2012-11-27 13:27:33 -01:00
/// </summary>
2016-07-22 14:07:03 +02:00
/// <param name="runtimeCache">A runtime cache.</param>
/// <param name="logger">A logger</param>
/// <param name="detectChanges">A value indicating whether to detect changes.</param>
public PluginManager ( IRuntimeCacheProvider runtimeCache , ProfilingLogger logger , bool detectChanges = true )
2012-11-27 13:27:33 -01:00
{
2016-07-22 14:07:03 +02:00
if ( runtimeCache = = null ) throw new ArgumentNullException ( nameof ( runtimeCache ) ) ;
if ( logger = = null ) throw new ArgumentNullException ( nameof ( logger ) ) ;
2015-01-16 15:47:44 +11:00
_runtimeCache = runtimeCache ;
_logger = logger ;
2012-11-27 13:27:33 -01:00
2016-07-22 14:07:03 +02:00
// create the temp folder if it doesn't exist
2012-11-27 13:27:33 -01:00
_tempFolder = IOHelper . MapPath ( "~/App_Data/TEMP/PluginCache" ) ;
2015-01-16 15:47:44 +11:00
if ( Directory . Exists ( _tempFolder ) = = false )
2012-11-27 13:27:33 -01:00
Directory . CreateDirectory ( _tempFolder ) ;
2014-05-12 17:07:29 +10:00
var pluginListFile = GetPluginListFilePath ( ) ;
2016-07-22 14:07:03 +02:00
// 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
2012-11-27 13:27:33 -01:00
if ( DetectLegacyPluginListFile ( ) )
2014-05-12 17:07:29 +10:00
File . Delete ( pluginListFile ) ;
2012-11-27 13:27:33 -01:00
2014-05-12 17:07:29 +10:00
if ( detectChanges )
2012-11-27 13:27:33 -01:00
{
//first check if the cached hash is 0, if it is then we ne
//do the check if they've changed
2014-05-12 17:07:29 +10:00
RequiresRescanning = ( CachedAssembliesHash ! = CurrentAssembliesHash ) | | CachedAssembliesHash = = 0 ;
2012-11-27 13:27:33 -01:00
//if they have changed, we need to write the new file
2014-05-12 17:07:29 +10:00
if ( RequiresRescanning )
2012-11-27 13:27:33 -01:00
{
2016-07-22 14:07:03 +02:00
// if the hash has changed, clear out the persisted list no matter what, this will force
2014-05-12 17:07:29 +10:00
// rescanning of all plugin types including lazy ones.
// http://issues.umbraco.org/issue/U4-4789
File . Delete ( pluginListFile ) ;
2012-11-27 13:27:33 -01:00
WriteCachePluginsHash ( ) ;
}
}
else
{
2016-07-22 14:07:03 +02:00
// if the hash has changed, clear out the persisted list no matter what, this will force
2014-05-12 17:07:29 +10:00
// rescanning of all plugin types including lazy ones.
// http://issues.umbraco.org/issue/U4-4789
File . Delete ( pluginListFile ) ;
2016-07-22 14:07:03 +02:00
// always set to true if we're not detecting (generally only for testing)
2014-05-12 17:07:29 +10:00
RequiresRescanning = true ;
2012-11-27 13:27:33 -01:00
}
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Gets the current plugin manager.
2012-11-27 13:27:33 -01:00
/// </summary>
/// <remarks>
2016-07-22 14:07:03 +02:00
/// <para>Ensures that no matter what, only one plugin manager is created, and thus proper caching always takes place.</para>
/// <para>The setter is generally only used for unit tests + when creating the master plugin manager in CoreBootManager.</para>
2012-11-27 13:27:33 -01:00
/// </remarks>
2014-02-17 15:08:27 +11:00
public static PluginManager Current
2012-11-27 13:27:33 -01:00
{
get
{
2016-07-22 14:07:03 +02:00
return LazyInitializer . EnsureInitialized ( ref _instance , ref _hasInstance , ref _instanceLocker , ( ) = >
2012-11-27 13:27:33 -01:00
{
2016-07-22 14:07:03 +02:00
var appctx = ApplicationContext . Current ;
var cacheProvider = appctx = = null
? new NullCacheProvider ( )
: appctx . ApplicationCache . RuntimeCache ;
ProfilingLogger profilingLogger ;
if ( appctx = = null )
2012-11-27 13:27:33 -01:00
{
2015-01-16 15:47:44 +11:00
var logger = LoggerResolver . HasCurrent ? LoggerResolver . Current . Logger : new DebugDiagnosticsLogger ( ) ;
var profiler = ProfilerResolver . HasCurrent ? ProfilerResolver . Current . Profiler : new LogProfiler ( logger ) ;
2016-07-22 14:07:03 +02:00
profilingLogger = new ProfilingLogger ( logger , profiler ) ;
2012-11-27 13:27:33 -01:00
}
2016-07-22 14:07:03 +02:00
else
{
profilingLogger = appctx . ProfilingLogger ;
}
return new PluginManager ( cacheProvider , profilingLogger ) ;
2015-01-16 15:47:44 +11:00
} ) ;
}
2016-07-22 14:07:03 +02:00
internal set
2015-01-16 15:47:44 +11:00
{
2016-07-22 14:07:03 +02:00
_hasInstance = true ;
_instance = value ;
2012-11-27 13:27:33 -01:00
}
}
#region Hash checking methods
/// <summary>
2016-07-22 14:07:03 +02:00
/// Gets a value indicating whether the assemblies in the /bin, app_code, global.asax, etc... have changed since they were last hashed.
2012-11-27 13:27:33 -01:00
/// </summary>
2016-07-22 14:07:03 +02:00
internal bool RequiresRescanning { get ; }
2012-11-27 13:27:33 -01:00
/// <summary>
2016-07-22 14:07:03 +02:00
/// Gets the currently cached hash value of the scanned assemblies in the /bin folder.
2012-11-27 13:27:33 -01:00
/// </summary>
2016-07-22 14:07:03 +02:00
/// <value>The cached hash value, or 0 if no cache is found.</value>
2012-11-27 13:27:33 -01:00
internal long CachedAssembliesHash
{
get
{
if ( _cachedAssembliesHash ! = - 1 )
return _cachedAssembliesHash ;
2013-02-22 21:29:48 +06:00
var filePath = GetPluginHashFilePath ( ) ;
2016-07-22 14:07:03 +02:00
if ( File . Exists ( filePath ) = = false ) return 0 ;
2012-11-27 13:27:33 -01:00
var hash = File . ReadAllText ( filePath , Encoding . UTF8 ) ;
2016-07-22 14:07:03 +02:00
long val ;
if ( long . TryParse ( hash , out val ) = = false ) return 0 ;
_cachedAssembliesHash = val ;
return _cachedAssembliesHash ;
2012-11-27 13:27:33 -01:00
}
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Gets the current assemblies hash based on creating a hash from the assemblies in the /bin folder.
2012-11-27 13:27:33 -01:00
/// </summary>
2016-07-22 14:07:03 +02:00
/// <value>The current hash.</value>
2012-11-27 13:27:33 -01:00
internal long CurrentAssembliesHash
{
get
{
if ( _currentAssembliesHash ! = - 1 )
return _currentAssembliesHash ;
2013-11-21 14:01:54 +11:00
_currentAssembliesHash = GetFileHash (
new List < Tuple < FileSystemInfo , bool > >
2012-11-21 08:28:11 +05:00
{
2016-07-22 14:07:03 +02:00
// add the bin folder and everything in it
2013-11-21 14:01:54 +11:00
new Tuple < FileSystemInfo , bool > ( new DirectoryInfo ( IOHelper . MapPath ( SystemDirectories . Bin ) ) , false ) ,
2016-07-22 14:07:03 +02:00
// add the app code folder and everything in it
2013-11-21 14:01:54 +11:00
new Tuple < FileSystemInfo , bool > ( new DirectoryInfo ( IOHelper . MapPath ( "~/App_Code" ) ) , false ) ,
2016-07-22 14:07:03 +02:00
// add the global.asax (the app domain also monitors this, if it changes will do a full restart)
2013-11-21 14:01:54 +11:00
new Tuple < FileSystemInfo , bool > ( new FileInfo ( IOHelper . MapPath ( "~/global.asax" ) ) , false ) ,
2016-07-22 14:07:03 +02:00
// add the trees.config - use the contents to create the has since this gets resaved on every app startup!
2013-11-21 14:01:54 +11:00
new Tuple < FileSystemInfo , bool > ( new FileInfo ( IOHelper . MapPath ( SystemDirectories . Config + "/trees.config" ) ) , true )
2015-01-16 15:47:44 +11:00
} , _logger
2012-11-27 13:27:33 -01:00
) ;
2016-07-22 14:07:03 +02:00
2012-11-27 13:27:33 -01:00
return _currentAssembliesHash ;
}
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Writes the assembly hash file.
2012-11-27 13:27:33 -01:00
/// </summary>
private void WriteCachePluginsHash ( )
{
2013-02-22 21:29:48 +06:00
var filePath = GetPluginHashFilePath ( ) ;
2012-11-27 13:27:33 -01:00
File . WriteAllText ( filePath , CurrentAssembliesHash . ToString ( ) , Encoding . UTF8 ) ;
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Returns a unique hash for a combination of FileInfo objects.
2012-11-27 13:27:33 -01:00
/// </summary>
2013-11-21 14:01:54 +11:00
/// <param name="filesAndFolders">
2016-07-22 14:07:03 +02:00
/// A collection of files and whether or not to use their file contents to determine the hash or the file's properties
2013-11-21 14:01:54 +11:00
/// (true will make a hash based on it's contents)
/// </param>
2016-07-22 14:07:03 +02:00
/// <param name="logger">A profiling logger.</param>
/// <returns>The hash.</returns>
2015-01-16 12:29:27 +11:00
internal static long GetFileHash ( IEnumerable < Tuple < FileSystemInfo , bool > > filesAndFolders , ProfilingLogger logger )
2012-11-27 13:27:33 -01:00
{
2016-07-22 14:07:03 +02:00
var ffA = filesAndFolders . ToArray ( ) ;
2015-01-16 12:29:27 +11:00
using ( logger . TraceDuration < PluginManager > ( "Determining hash of code files on disk" , "Hash determined" ) )
2012-11-27 13:27:33 -01:00
{
var hashCombiner = new HashCodeCombiner ( ) ;
2013-11-21 14:01:54 +11:00
2016-07-22 14:07:03 +02:00
// get the file info's to check
var fileInfos = ffA . Where ( x = > x . Item2 = = false ) . ToArray ( ) ;
var fileContents = ffA . Except ( fileInfos ) ;
// add each unique folder/file to the hash
2013-11-21 14:01:54 +11:00
foreach ( var i in fileInfos . Select ( x = > x . Item1 ) . DistinctBy ( x = > x . FullName ) )
2012-11-27 13:27:33 -01:00
hashCombiner . AddFileSystemItem ( i ) ;
2013-11-21 14:01:54 +11:00
2016-07-22 14:07:03 +02:00
// add each unique file's contents to the hash
foreach ( var i in fileContents . Select ( x = > x . Item1 )
. DistinctBy ( x = > x . FullName )
. Where ( x = > File . Exists ( x . FullName ) ) )
2013-11-21 14:01:54 +11:00
{
2016-07-22 14:07:03 +02:00
var content = File . ReadAllText ( i . FullName ) . Replace ( "\r\n" , string . Empty ) . Replace ( "\n" , string . Empty ) . Replace ( "\r" , string . Empty ) ;
hashCombiner . AddCaseInsensitiveString ( content ) ;
2013-11-21 14:01:54 +11:00
}
2012-11-27 13:27:33 -01:00
return ConvertPluginsHashFromHex ( hashCombiner . GetCombinedHashCode ( ) ) ;
}
}
2015-01-16 12:29:27 +11:00
internal static long GetFileHash ( IEnumerable < FileSystemInfo > filesAndFolders , ProfilingLogger logger )
2013-11-21 14:42:35 +11:00
{
2015-01-16 12:29:27 +11:00
using ( logger . TraceDuration < PluginManager > ( "Determining hash of code files on disk" , "Hash determined" ) )
2013-11-21 14:42:35 +11:00
{
var hashCombiner = new HashCodeCombiner ( ) ;
2016-07-22 14:07:03 +02:00
// add each unique folder/file to the hash
2013-11-21 14:42:35 +11:00
foreach ( var i in filesAndFolders . DistinctBy ( x = > x . FullName ) )
hashCombiner . AddFileSystemItem ( i ) ;
2016-07-22 14:07:03 +02:00
2013-11-21 14:42:35 +11:00
return ConvertPluginsHashFromHex ( hashCombiner . GetCombinedHashCode ( ) ) ;
}
}
2012-11-27 13:27:33 -01:00
/// <summary>
/// Converts the hash value of current plugins to long from string
/// </summary>
/// <param name="val"></param>
/// <returns></returns>
internal static long ConvertPluginsHashFromHex ( string val )
{
long outVal ;
2016-07-22 14:07:03 +02:00
return long . TryParse ( val , NumberStyles . AllowHexSpecifier , CultureInfo . InvariantCulture , out outVal ) ? outVal : 0 ;
2012-11-27 13:27:33 -01:00
}
/// <summary>
/// Attempts to resolve the list of plugin + assemblies found in the runtime for the base type 'T' passed in.
2016-07-22 14:07:03 +02:00
/// If the cache file doesn't exist, fails to load, is corrupt or the type 'T' element is not found then
2012-11-27 13:27:33 -01:00
/// a false attempt is returned.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal Attempt < IEnumerable < string > > TryGetCachedPluginsFromFile < T > ( TypeResolutionKind resolutionType )
{
var filePath = GetPluginListFilePath ( ) ;
2016-07-22 14:07:03 +02:00
if ( File . Exists ( filePath ) = = false )
2013-09-04 17:14:06 +02:00
return Attempt < IEnumerable < string > > . Fail ( ) ;
2012-11-27 13:27:33 -01:00
try
{
2016-07-22 14:07:03 +02:00
// 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)
2015-01-16 15:47:44 +11:00
var xml = _runtimeCache . GetCacheItem < XDocument > ( CacheKey ,
( ) = > XDocument . Load ( filePath ) ,
new TimeSpan ( 0 , 0 , 5 , 0 ) ) ;
2012-11-27 13:27:33 -01:00
if ( xml . Root = = null )
2013-09-04 17:14:06 +02:00
return Attempt < IEnumerable < string > > . Fail ( ) ;
2012-11-27 13:27:33 -01:00
var typeElement = xml . Root . Elements ( )
. SingleOrDefault ( x = >
x . Name . LocalName = = "baseType"
& & ( ( string ) x . Attribute ( "type" ) ) = = typeof ( T ) . FullName
& & ( ( string ) x . Attribute ( "resolutionType" ) ) = = resolutionType . ToString ( ) ) ;
2016-07-22 14:07:03 +02:00
// return false but specify this exception type so we can detect it
2012-11-27 13:27:33 -01:00
if ( typeElement = = null )
2013-12-06 14:06:53 +11:00
return Attempt < IEnumerable < string > > . Fail ( new CachedPluginNotFoundInFileException ( ) ) ;
2012-11-27 13:27:33 -01:00
2016-07-22 14:07:03 +02:00
// return success
2013-09-11 08:22:28 +02:00
return Attempt . Succeed ( typeElement . Elements ( "add" )
2012-11-27 13:27:33 -01:00
. Select ( x = > ( string ) x . Attribute ( "type" ) ) ) ;
}
catch ( Exception ex )
{
2016-07-22 14:07:03 +02:00
// if the file is corrupted, etc... return false
2013-09-04 17:14:06 +02:00
return Attempt < IEnumerable < string > > . Fail ( ex ) ;
2012-11-27 13:27:33 -01:00
}
}
2013-02-22 21:29:48 +06:00
/// <summary>
/// Removes cache files and internal cache as well
/// </summary>
/// <remarks>
/// Generally only used for resetting cache, for example during the install process
/// </remarks>
2014-05-12 17:07:29 +10:00
public void ClearPluginCache ( )
2013-02-22 21:29:48 +06:00
{
var path = GetPluginListFilePath ( ) ;
if ( File . Exists ( path ) )
File . Delete ( path ) ;
path = GetPluginHashFilePath ( ) ;
if ( File . Exists ( path ) )
File . Delete ( path ) ;
2015-01-16 15:47:44 +11:00
_runtimeCache . ClearCacheItem ( CacheKey ) ;
2013-02-22 21:29:48 +06:00
}
2016-07-22 14:07:03 +02:00
2012-11-27 13:27:33 -01:00
private string GetPluginListFilePath ( )
{
2016-07-22 14:07:03 +02:00
return Path . Combine ( _tempFolder , $"umbraco-plugins.{NetworkHelper.FileSafeMachineName}.list" ) ;
2012-11-27 13:27:33 -01:00
}
2013-02-22 21:29:48 +06:00
private string GetPluginHashFilePath ( )
{
2016-07-22 14:07:03 +02:00
return Path . Combine ( _tempFolder , $"umbraco-plugins.{NetworkHelper.FileSafeMachineName}.hash" ) ;
2013-02-22 21:29:48 +06:00
}
2012-11-27 13:27:33 -01:00
/// <summary>
/// This will return true if the plugin list file is a legacy one
/// </summary>
/// <returns></returns>
/// <remarks>
2016-07-22 14:07:03 +02:00
/// This method exists purely due to an error in 4.11. We were writing the plugin list file without the
2012-11-27 13:27:33 -01:00
/// 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.
/// </remarks>
internal bool DetectLegacyPluginListFile ( )
{
var filePath = GetPluginListFilePath ( ) ;
2016-07-22 14:07:03 +02:00
if ( File . Exists ( filePath ) = = false )
2012-11-27 13:27:33 -01:00
return false ;
try
{
var xml = XDocument . Load ( filePath ) ;
2016-07-22 14:07:03 +02:00
var typeElement = xml . Root ? . Elements ( )
2012-11-27 13:27:33 -01:00
. FirstOrDefault ( x = > x . Name . LocalName = = "baseType" ) ;
//now check if the typeElement is missing the resolutionType attribute
2016-07-22 14:07:03 +02:00
return typeElement ! = null & & typeElement . Attributes ( ) . All ( x = > x . Name . LocalName ! = "resolutionType" ) ;
2012-11-27 13:27:33 -01:00
}
catch ( Exception )
{
//if the file is corrupted, etc... return true so it is removed
return true ;
}
}
/// <summary>
/// Adds/Updates the type list for the base type 'T' in the cached file
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="typesFound"></param>
///<param name="resolutionType"> </param>
///<remarks>
/// THIS METHOD IS NOT THREAD SAFE
/// </remarks>
/// <example>
/// <![CDATA[
/// <plugins>
/// <baseType type="Test.Testing.Tester">
/// <add type="My.Assembly.MyTester" assembly="My.Assembly" />
/// <add type="Your.Assembly.YourTester" assembly="Your.Assembly" />
/// </baseType>
/// </plugins>
/// ]]>
/// </example>
internal void UpdateCachedPluginsFile < T > ( IEnumerable < Type > typesFound , TypeResolutionKind resolutionType )
{
var filePath = GetPluginListFilePath ( ) ;
XDocument xml ;
try
{
xml = XDocument . Load ( filePath ) ;
}
catch
{
2016-07-22 14:07:03 +02:00
// if there's an exception loading then this is somehow corrupt, we'll just replace it.
2012-11-27 13:27:33 -01:00
File . Delete ( filePath ) ;
2016-07-22 14:07:03 +02:00
// create the document and the root
2012-11-27 13:27:33 -01:00
xml = new XDocument ( new XElement ( "plugins" ) ) ;
}
if ( xml . Root = = null )
{
2016-07-22 14:07:03 +02:00
// if for some reason there is no root, create it
2012-11-27 13:27:33 -01:00
xml . Add ( new XElement ( "plugins" ) ) ;
}
2016-07-22 14:07:03 +02:00
// find the type 'T' element to add or update
2012-11-27 13:27:33 -01:00
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 )
{
2016-07-22 14:07:03 +02:00
// create the type element
2012-11-27 13:27:33 -01:00
typeElement = new XElement ( "baseType" ,
new XAttribute ( "type" , typeof ( T ) . FullName ) ,
new XAttribute ( "resolutionType" , resolutionType . ToString ( ) ) ) ;
2016-07-22 14:07:03 +02:00
// then add it to the root
2012-11-27 13:27:33 -01:00
xml . Root . Add ( typeElement ) ;
}
2016-07-22 14:07:03 +02:00
// now we have the type element, we need to clear any previous types as children and add/update it with new ones
2012-11-27 13:27:33 -01:00
typeElement . ReplaceNodes ( typesFound . Select ( x = > new XElement ( "add" , new XAttribute ( "type" , x . AssemblyQualifiedName ) ) ) ) ;
2016-07-22 14:07:03 +02:00
// save the xml file
2012-11-27 13:27:33 -01:00
xml . Save ( filePath ) ;
}
#endregion
/// <summary>
2016-07-22 14:07:03 +02:00
/// Gets or sets which assemblies to scan when type finding, generally used for unit testing, if not explicitly set
2012-11-27 13:27:33 -01:00
/// this will search all assemblies known to have plugins and exclude ones known to not have them.
/// </summary>
internal IEnumerable < Assembly > AssembliesToScan
{
get { return _assemblies ? ? ( _assemblies = TypeFinder . GetAssembliesWithKnownExclusions ( ) ) ; }
set { _assemblies = value ; }
2016-07-22 14:07:03 +02:00
}
#region Resolve Types
2012-11-27 13:27:33 -01:00
private IEnumerable < Type > ResolveTypes < T > (
Func < IEnumerable < Type > > finder ,
TypeResolutionKind resolutionType ,
bool cacheResult )
{
2013-11-21 14:42:35 +11:00
using ( var readLock = new UpgradeableReadLock ( Locker ) )
2012-11-27 13:27:33 -01:00
{
var typesFound = new List < Type > ( ) ;
2016-04-29 00:50:26 +02:00
using ( _logger . DebugDuration < PluginManager > (
$"Starting resolution types of {typeof(T).FullName}" ,
2016-07-22 14:07:03 +02:00
$"Completed resolution of types of {typeof(T).FullName}" , // cannot contain typesFound.Count as it's evaluated before the find!
2016-04-29 00:50:26 +02:00
50 ) )
2012-11-27 13:27:33 -01:00
{
//check if the TypeList already exists, if so return it, if not we'll create it
var typeList = _types . SingleOrDefault ( x = > x . IsTypeList < T > ( resolutionType ) ) ;
2013-12-06 14:06:53 +11:00
//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 )
{
2015-01-16 15:47:44 +11:00
_logger . Logger . Debug < PluginManager > ( "Existing typeList found for {0} with resolution type {1}" , ( ) = > typeof ( T ) , ( ) = > resolutionType ) ;
2013-12-06 14:06:53 +11:00
}
2016-07-22 14:07:03 +02:00
2012-11-27 13:27:33 -01:00
//if we're not caching the result then proceed, or if the type list doesn't exist then proceed
2013-12-06 14:06:53 +11:00
if ( cacheResult = = false | | typeList = = null )
2012-11-27 13:27:33 -01:00
{
//upgrade to a write lock since we're adding to the collection
readLock . UpgradeToWriteLock ( ) ;
typeList = new TypeList < T > ( 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.
2014-05-12 17:07:29 +10:00
if ( RequiresRescanning = = false & & File . Exists ( GetPluginListFilePath ( ) ) )
2012-11-27 13:27:33 -01:00
{
var fileCacheResult = TryGetCachedPluginsFromFile < T > ( 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.
2016-07-22 14:07:03 +02:00
if ( fileCacheResult . Exception is CachedPluginNotFoundInFileException )
2012-11-27 13:27:33 -01:00
{
2015-01-16 15:47:44 +11:00
_logger . Logger . Debug < PluginManager > ( "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 ) ;
2013-12-06 14:06:53 +11:00
2012-11-27 13:27:33 -01:00
//we don't have a cache for this so proceed to look them up by scanning
LoadViaScanningAndUpdateCacheFile < T > ( 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 )
{
2016-07-22 14:07:03 +02:00
//if there are any exceptions loading types, we have to exist, this should never happen so
2012-11-27 13:27:33 -01:00
//we will need to revert to scanning for types.
successfullyLoadedFromCache = false ;
2015-01-16 15:47:44 +11:00
_logger . Logger . Error < PluginManager > ( "Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof ( T ) . FullName , ex ) ;
2012-11-27 13:27:33 -01:00
break ;
}
}
2013-12-06 14:06:53 +11:00
if ( successfullyLoadedFromCache = = false )
2012-11-27 13:27:33 -01:00
{
//we need to manually load by scanning if loading from the file was not successful.
LoadViaScanningAndUpdateCacheFile < T > ( typeList , resolutionType , finder ) ;
}
else
{
2015-01-16 15:47:44 +11:00
_logger . Logger . Debug < PluginManager > ( "Loaded plugin types {0} with resolution {1} from persisted cache" , ( ) = > typeof ( T ) , ( ) = > resolutionType ) ;
2012-11-27 13:27:33 -01:00
}
}
}
}
else
{
2015-01-16 15:47:44 +11:00
_logger . Logger . Debug < PluginManager > ( "Assembly changes detected, loading types {0} for resolution {1} by assembly scan" , ( ) = > typeof ( T ) , ( ) = > resolutionType ) ;
2013-12-06 14:06:53 +11:00
2012-11-27 13:27:33 -01:00
//we don't have a cache for this so proceed to look them up by scanning
LoadViaScanningAndUpdateCacheFile < T > ( typeList , resolutionType , finder ) ;
}
//only add the cache if we are to cache the results
if ( cacheResult )
{
//add the type list to the collection
2013-12-06 14:06:53 +11:00
var added = _types . Add ( typeList ) ;
2015-01-16 15:47:44 +11:00
_logger . Logger . Debug < PluginManager > ( "Caching of typelist for type {0} and resolution {1} was successful = {2}" , ( ) = > typeof ( T ) , ( ) = > resolutionType , ( ) = > added ) ;
2013-12-06 14:06:53 +11:00
2012-11-27 13:27:33 -01:00
}
}
typesFound = typeList . GetTypes ( ) . ToList ( ) ;
}
return typesFound ;
}
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Invokes the finder which scans the assemblies for the types and then loads the result into the type finder,
/// then updates the cached type xml file.
2012-11-27 13:27:33 -01:00
/// </summary>
/// <param name="typeList"></param>
/// <param name="resolutionKind"> </param>
/// <param name="finder"></param>
2016-07-22 14:07:03 +02:00
/// <remarks>This method is not thread safe.</remarks>
2012-11-27 13:27:33 -01:00
private void LoadViaScanningAndUpdateCacheFile < T > ( TypeList typeList , TypeResolutionKind resolutionKind , Func < IEnumerable < Type > > finder )
{
2016-07-22 14:07:03 +02:00
// we don't have a cache for this so proceed to look them up by scanning
2012-11-27 13:27:33 -01:00
foreach ( var t in finder ( ) )
typeList . AddType ( t ) ;
2016-07-22 14:07:03 +02:00
2012-11-27 13:27:33 -01:00
UpdateCachedPluginsFile < T > ( typeList . GetTypes ( ) , resolutionKind ) ;
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Resolves specified types.
2012-11-27 13:27:33 -01:00
/// </summary>
2016-07-22 14:07:03 +02:00
/// <typeparam name="T">The type to find.</typeparam>
/// <returns>The types.</returns>
2014-02-17 15:58:57 +11:00
public IEnumerable < Type > ResolveTypes < T > ( bool cacheResult = true , IEnumerable < Assembly > specificAssemblies = null )
2012-11-27 13:27:33 -01:00
{
return ResolveTypes < T > (
2013-09-16 17:39:45 +10:00
( ) = > TypeFinder . FindClassesOfType < T > ( specificAssemblies ? ? AssembliesToScan ) ,
2012-11-27 13:27:33 -01:00
TypeResolutionKind . FindAllTypes ,
cacheResult ) ;
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Resolves specified, attributed types.
2012-11-27 13:27:33 -01:00
/// </summary>
2016-07-22 14:07:03 +02:00
/// <typeparam name="T">The type to find.</typeparam>
/// <typeparam name="TAttribute">The type of the attribute.</typeparam>
/// <returns>The corresponding types.</returns>
2014-02-17 15:58:57 +11:00
public IEnumerable < Type > ResolveTypesWithAttribute < T , TAttribute > ( bool cacheResult = true , IEnumerable < Assembly > specificAssemblies = null )
2012-11-27 13:27:33 -01:00
where TAttribute : Attribute
{
return ResolveTypes < T > (
2014-02-17 15:58:57 +11:00
( ) = > TypeFinder . FindClassesOfTypeWithAttribute < T , TAttribute > ( specificAssemblies ? ? AssembliesToScan ) ,
2012-11-27 13:27:33 -01:00
TypeResolutionKind . FindTypesWithAttribute ,
cacheResult ) ;
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Resolves attributed types.
2012-11-27 13:27:33 -01:00
/// </summary>
2016-07-22 14:07:03 +02:00
/// <typeparam name="TAttribute">The type of the attribute.</typeparam>
/// <returns>The corresopnding types.</returns>
2014-02-17 15:58:57 +11:00
public IEnumerable < Type > ResolveAttributedTypes < TAttribute > ( bool cacheResult = true , IEnumerable < Assembly > specificAssemblies = null )
2012-11-27 13:27:33 -01:00
where TAttribute : Attribute
{
return ResolveTypes < TAttribute > (
2014-02-17 15:58:57 +11:00
( ) = > TypeFinder . FindClassesWithAttribute < TAttribute > ( specificAssemblies ? ? AssembliesToScan ) ,
2012-11-27 13:27:33 -01:00
TypeResolutionKind . FindAttributedTypes ,
cacheResult ) ;
2016-07-22 14:07:03 +02:00
}
2014-02-17 15:08:27 +11:00
#endregion
2012-11-27 13:27:33 -01:00
2016-07-22 14:07:03 +02:00
#region Private
2012-11-27 13:27:33 -01:00
/// <summary>
2016-07-22 14:07:03 +02:00
/// Gets the list of types.
2012-11-27 13:27:33 -01:00
/// </summary>
2016-07-22 14:07:03 +02:00
/// <returns>The list of types.</returns>
/// <remarks>For unit tests only.</remarks>
2012-11-27 13:27:33 -01:00
internal HashSet < TypeList > GetTypeLists ( )
{
return _types ;
}
/// <summary>
/// The type of resolution being invoked
/// </summary>
internal enum TypeResolutionKind
{
FindAllTypes ,
FindAttributedTypes ,
FindTypesWithAttribute
}
internal abstract class TypeList
{
public abstract void AddType ( Type t ) ;
public abstract bool IsTypeList < TLookup > ( TypeResolutionKind resolutionType ) ;
public abstract IEnumerable < Type > GetTypes ( ) ;
}
internal class TypeList < T > : TypeList
{
private readonly TypeResolutionKind _resolutionType ;
public TypeList ( TypeResolutionKind resolutionType )
{
_resolutionType = resolutionType ;
}
private readonly List < Type > _types = new List < Type > ( ) ;
public override void AddType ( Type t )
{
2016-07-22 14:07:03 +02:00
//if the type is an attribute type we won't do the type check because typeof<T> is going to be the
2012-11-27 13:27:33 -01:00
//attribute type whereas the 't' type is the object type found with the attribute.
if ( _resolutionType = = TypeResolutionKind . FindAttributedTypes | | t . IsType < T > ( ) )
{
_types . Add ( t ) ;
}
}
/// <summary>
2013-12-06 14:06:53 +11:00
/// Returns true if the current TypeList is of the same lookup type
2012-11-27 13:27:33 -01:00
/// </summary>
/// <typeparam name="TLookup"></typeparam>
/// <param name="resolutionType"></param>
/// <returns></returns>
public override bool IsTypeList < TLookup > ( TypeResolutionKind resolutionType )
{
2013-12-06 14:06:53 +11:00
return _resolutionType = = resolutionType & & ( typeof ( T ) ) = = typeof ( TLookup ) ;
2012-11-27 13:27:33 -01:00
}
public override IEnumerable < Type > GetTypes ( )
{
return _types ;
}
}
/// <summary>
2016-07-22 14:07:03 +02:00
/// Represents the error that occurs when a plugin was not found in the cache plugin list with the specified
2012-11-27 13:27:33 -01:00
/// TypeResolutionKind.
/// </summary>
2013-12-06 14:06:53 +11:00
internal class CachedPluginNotFoundInFileException : Exception
2016-07-22 14:07:03 +02:00
{ }
#endregion
}
internal static class PluginManagerExtensions
{
/// <summary>
/// Resolves property editors (based on the resolved Iparameter editors - this saves a scan).
/// </summary>
public static IEnumerable < Type > ResolvePropertyEditors ( this PluginManager mgr )
2012-11-27 13:27:33 -01:00
{
2016-07-22 14:07:03 +02:00
//return all proeprty editor types found except for the base property editor type
return mgr . ResolveTypes < IParameterEditor > ( )
. Where ( x = > x . IsType < PropertyEditor > ( ) )
. Except ( new [ ] { typeof ( PropertyEditor ) } ) ;
}
2012-11-27 13:27:33 -01:00
2016-07-22 14:07:03 +02:00
/// <summary>
/// Resolves parameter editors (which includes property editors)
/// </summary>
internal static IEnumerable < Type > ResolveParameterEditors ( this PluginManager mgr )
{
//return all paramter editor types found except for the base property editor type
return mgr . ResolveTypes < IParameterEditor > ( )
. Except ( new [ ] { typeof ( ParameterEditor ) , typeof ( PropertyEditor ) } ) ;
2012-11-27 13:27:33 -01:00
}
2016-07-22 14:07:03 +02:00
/// <summary>
/// Resolves IApplicationStartupHandler objects.
/// </summary>
internal static IEnumerable < Type > ResolveApplicationStartupHandlers ( this PluginManager mgr )
{
return mgr . ResolveTypes < IApplicationEventHandler > ( ) ;
}
/// <summary>
/// Resolves ICacheRefresher objects.
/// </summary>
internal static IEnumerable < Type > ResolveCacheRefreshers ( this PluginManager mgr )
{
return mgr . ResolveTypes < ICacheRefresher > ( ) ;
}
/// <summary>
/// Resolves IPackageAction objects.
/// </summary>
internal static IEnumerable < Type > ResolvePackageActions ( this PluginManager mgr )
{
return mgr . ResolveTypes < IPackageAction > ( ) ;
}
/// <summary>
/// Resolves mapper types that have a MapperFor attribute defined.
/// </summary>
internal static IEnumerable < Type > ResolveAssignedMapperTypes ( this PluginManager mgr )
{
return mgr . ResolveTypesWithAttribute < BaseMapper , MapperForAttribute > ( ) ;
}
2012-11-27 13:27:33 -01:00
}
2012-08-01 22:06:15 +06:00
}