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 ;
2013-09-16 17:39:45 +10:00
using Umbraco.Core.Configuration ;
2012-11-11 09:09:06 +05:00
using Umbraco.Core.IO ;
2012-08-01 22:06:15 +06:00
using Umbraco.Core.Logging ;
2012-11-28 14:39:11 -01:00
using Umbraco.Core.Models ;
2013-03-13 01:09:29 +04:00
using Umbraco.Core.Persistence.Mappers ;
2013-01-18 12:05:00 -01:00
using Umbraco.Core.Persistence.Migrations ;
2013-03-09 10:43:34 -01:00
using Umbraco.Core.Persistence.SqlSyntax ;
2015-01-16 15:47:44 +11:00
using Umbraco.Core.Profiling ;
2012-11-05 06:14:44 +06:00
using Umbraco.Core.PropertyEditors ;
2015-01-16 15:47:44 +11:00
using Umbraco.Core.Cache ;
2012-08-01 22:06:15 +06:00
using umbraco.interfaces ;
2012-11-27 13:27:33 -01:00
using File = System . IO . File ;
2012-08-01 22:06:15 +06:00
namespace Umbraco.Core
{
2012-11-27 13:27:33 -01:00
/// <summary>
/// Used to resolve all plugin types and cache them and is also used to instantiate plugin types
/// </summary>
/// <remarks>
///
/// 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.
/// </remarks>
2014-02-17 15:08:27 +11:00
public class PluginManager
2012-11-27 13:27:33 -01:00
{
/// <summary>
/// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml
/// file is cached temporarily until app startup completes.
/// </summary>
2015-01-16 15:47:44 +11:00
/// <param name="logger"></param>
2014-05-12 17:07:29 +10:00
/// <param name="detectChanges"></param>
2015-01-16 15:47:44 +11:00
/// <param name="serviceProvider"></param>
/// <param name="runtimeCache"></param>
internal PluginManager ( IServiceProvider serviceProvider , IRuntimeCacheProvider runtimeCache , ProfilingLogger logger , bool detectChanges = true )
2012-11-27 13:27:33 -01:00
{
2015-01-16 15:47:44 +11:00
if ( serviceProvider = = null ) throw new ArgumentNullException ( "serviceProvider" ) ;
if ( runtimeCache = = null ) throw new ArgumentNullException ( "runtimeCache" ) ;
if ( logger = = null ) throw new ArgumentNullException ( "logger" ) ;
_serviceProvider = serviceProvider ;
_runtimeCache = runtimeCache ;
_logger = logger ;
2012-11-27 13:27:33 -01:00
_tempFolder = IOHelper . MapPath ( "~/App_Data/TEMP/PluginCache" ) ;
//create the folder if it doesn't exist
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 ( ) ;
2012-11-27 13:27:33 -01:00
//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 ( ) )
{
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
{
2014-05-12 17:07:29 +10:00
//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 ) ;
2012-11-27 13:27:33 -01:00
WriteCachePluginsHash ( ) ;
}
}
else
{
2014-05-12 17:07:29 +10:00
//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 ) ;
2012-11-27 13:27:33 -01: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
}
}
2015-01-16 15:47:44 +11:00
private readonly IServiceProvider _serviceProvider ;
private readonly IRuntimeCacheProvider _runtimeCache ;
private readonly ProfilingLogger _logger ;
private const string CacheKey = "umbraco-plugins.list" ;
2012-11-27 13:27:33 -01:00
static PluginManager _resolver ;
private readonly string _tempFolder ;
private long _cachedAssembliesHash = - 1 ;
private long _currentAssembliesHash = - 1 ;
2015-01-16 15:47:44 +11:00
private static bool _initialized = false ;
private static object _singletonLock = new object ( ) ;
2012-11-27 13:27:33 -01:00
/// <summary>
/// We will ensure that no matter what, only one of these is created, this is to ensure that caching always takes place
/// </summary>
/// <remarks>
/// The setter is generally only used for unit tests
/// </remarks>
2014-02-17 15:08:27 +11:00
public static PluginManager Current
2012-11-27 13:27:33 -01:00
{
get
{
2015-01-16 15:47:44 +11:00
return LazyInitializer . EnsureInitialized ( ref _resolver , ref _initialized , ref _singletonLock , ( ) = >
2012-11-27 13:27:33 -01:00
{
2015-01-16 15:47:44 +11:00
if ( ApplicationContext . Current = = 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 ) ;
return new PluginManager (
new ActivatorServiceProvider ( ) ,
new NullCacheProvider ( ) ,
new ProfilingLogger ( logger , profiler ) ) ;
2012-11-27 13:27:33 -01:00
}
2015-01-16 15:47:44 +11:00
return new PluginManager (
new ActivatorServiceProvider ( ) ,
ApplicationContext . Current . ApplicationCache . RuntimeCache ,
ApplicationContext . Current . ProfilingLogger ) ;
} ) ;
}
set
{
_initialized = true ;
_resolver = value ;
2012-11-27 13:27:33 -01:00
}
}
#region Hash checking methods
/// <summary>
2014-05-12 17:07:29 +10:00
/// Returns a bool if 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>
2014-05-12 17:07:29 +10:00
internal bool RequiresRescanning { get ; private set ; }
2012-11-27 13:27:33 -01:00
/// <summary>
/// Returns the currently cached hash value of the scanned assemblies in the /bin folder. Returns 0
/// if no cache is found.
/// </summary>
/// <value> </value>
internal long CachedAssembliesHash
{
get
{
if ( _cachedAssembliesHash ! = - 1 )
return _cachedAssembliesHash ;
2013-02-22 21:29:48 +06:00
var filePath = GetPluginHashFilePath ( ) ;
2012-11-27 13:27:33 -01:00
if ( ! File . Exists ( filePath ) )
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 ;
}
}
/// <summary>
/// Returns the current assemblies hash based on creating a hash from the assemblies in the /bin
/// </summary>
/// <value> </value>
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
{
//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 ) ,
2012-11-21 08:28:11 +05: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 ) ,
2012-11-21 08:28:11 +05: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 ) ,
//add the trees.config - use the contents to create the has since this gets resaved on every app startup!
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
) ;
return _currentAssembliesHash ;
}
}
/// <summary>
/// Writes the assembly hash file
/// </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>
/// Returns a unique hash for the combination of FileInfo objects passed in
/// </summary>
2013-11-21 14:01:54 +11:00
/// <param name="filesAndFolders">
/// 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)
/// </param>
2012-11-27 13:27:33 -01:00
/// <returns></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
{
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
//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 ) )
2012-11-27 13:27:33 -01:00
{
hashCombiner . AddFileSystemItem ( i ) ;
}
2013-11-21 14:01:54 +11:00
//add each unique file's contents to the hash
foreach ( var i in fileContents . Select ( x = > x . Item1 ) . DistinctBy ( x = > x . FullName ) )
{
2013-11-21 17:47:23 +11:00
if ( File . Exists ( i . FullName ) )
{
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 ( ) ;
//add each unique folder/file to the hash
foreach ( var i in filesAndFolders . DistinctBy ( x = > x . FullName ) )
{
hashCombiner . AddFileSystemItem ( i ) ;
}
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 ;
if ( Int64 . TryParse ( val , NumberStyles . AllowHexSpecifier , CultureInfo . InvariantCulture , out outVal ) )
{
return outVal ;
}
return 0 ;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal Attempt < IEnumerable < string > > TryGetCachedPluginsFromFile < T > ( TypeResolutionKind resolutionType )
{
var filePath = GetPluginListFilePath ( ) ;
if ( ! File . Exists ( filePath ) )
2013-09-04 17:14:06 +02:00
return Attempt < IEnumerable < string > > . Fail ( ) ;
2012-11-27 13:27:33 -01:00
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)
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 ( ) ) ;
//return false but specify this exception type so we can detect it
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
//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 )
{
//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
}
2013-08-16 12:25:38 +10:00
2012-11-27 13:27:33 -01:00
private string GetPluginListFilePath ( )
{
2013-08-16 12:25:38 +10:00
return Path . Combine ( _tempFolder , string . Format ( "umbraco-plugins.{0}.list" , NetworkHelper . FileSafeMachineName ) ) ;
2012-11-27 13:27:33 -01:00
}
2013-02-22 21:29:48 +06:00
private string GetPluginHashFilePath ( )
{
2013-08-16 12:25:38 +10:00
return Path . Combine ( _tempFolder , string . Format ( "umbraco-plugins.{0}.hash" , NetworkHelper . FileSafeMachineName ) ) ;
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>
/// 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.
/// </remarks>
internal bool DetectLegacyPluginListFile ( )
{
var filePath = GetPluginListFilePath ( ) ;
if ( ! File . Exists ( filePath ) )
return false ;
try
{
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 ;
}
}
/// <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
{
//if there's an exception loading then this is somehow corrupt, we'll just replace it.
File . Delete ( filePath ) ;
//create the document and the root
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
xml . Save ( filePath ) ;
}
#endregion
2013-11-21 14:42:35 +11:00
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim ( ) ;
2012-11-27 13:27:33 -01:00
private readonly HashSet < TypeList > _types = new HashSet < TypeList > ( ) ;
private IEnumerable < Assembly > _assemblies ;
2013-05-23 22:15:52 -10:00
/// <summary>
2013-12-06 15:01:58 +11:00
/// Returns all found property editors (based on the resolved Iparameter editors - this saves a scan)
2013-05-23 22:15:52 -10:00
/// </summary>
internal IEnumerable < Type > ResolvePropertyEditors ( )
{
2013-08-14 13:38:28 +10:00
//return all proeprty editor types found except for the base property editor type
2013-12-06 15:01:58 +11:00
return ResolveTypes < IParameterEditor > ( )
. Where ( x = > x . IsType < PropertyEditor > ( ) )
. Except ( new [ ] { typeof ( PropertyEditor ) } ) ;
2013-09-19 17:34:57 +10:00
}
/// <summary>
2013-12-06 15:01:58 +11:00
/// Returns all found parameter editors (which includes property editors)
2013-09-19 17:34:57 +10:00
/// </summary>
internal IEnumerable < Type > ResolveParameterEditors ( )
{
//return all paramter editor types found except for the base property editor type
2013-12-06 15:01:58 +11:00
return ResolveTypes < IParameterEditor > ( )
2013-10-30 14:12:06 +11:00
. Except ( new [ ] { typeof ( ParameterEditor ) , typeof ( PropertyEditor ) } ) ;
2013-05-23 22:15:52 -10:00
}
2013-01-29 09:45:12 +06:00
/// <summary>
/// Returns all available IApplicationStartupHandler objects
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolveApplicationStartupHandlers ( )
{
return ResolveTypes < IApplicationStartupHandler > ( ) ;
}
2013-09-16 17:39:45 +10:00
/// <summary>
/// Returns all classes of type ICacheRefresher
2012-11-27 13:27:33 -01:00
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolveCacheRefreshers ( )
{
return ResolveTypes < ICacheRefresher > ( ) ;
}
/// <summary>
/// Returns all available IPropertyEditorValueConverter
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolvePropertyEditorValueConverters ( )
{
return ResolveTypes < IPropertyEditorValueConverter > ( ) ;
}
/// <summary>
/// Returns all available IDataType in application
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolveDataTypes ( )
{
2013-09-25 17:57:44 +10:00
return ResolveTypes < IDataType > ( ) ;
2012-11-27 13:27:33 -01:00
}
/// <summary>
/// Returns all available IMacroGuiRendering in application
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolveMacroRenderings ( )
{
return ResolveTypes < IMacroGuiRendering > ( ) ;
}
/// <summary>
/// Returns all available IPackageAction in application
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolvePackageActions ( )
{
return ResolveTypes < IPackageAction > ( ) ;
}
/// <summary>
/// Returns all available IAction in application
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolveActions ( )
{
return ResolveTypes < IAction > ( ) ;
}
2013-03-13 01:09:29 +04:00
/// <summary>
/// Returns all mapper types that have a MapperFor attribute defined
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolveAssignedMapperTypes ( )
{
return ResolveTypesWithAttribute < BaseMapper , MapperForAttribute > ( ) ;
}
2014-03-12 20:36:40 +11:00
2013-03-09 10:43:34 -01:00
/// <summary>
/// Returns all SqlSyntaxProviders with the SqlSyntaxProviderAttribute
/// </summary>
/// <returns></returns>
internal IEnumerable < Type > ResolveSqlSyntaxProviders ( )
{
return ResolveTypesWithAttribute < ISqlSyntaxProvider , SqlSyntaxProviderAttribute > ( ) ;
}
2012-11-27 13:27:33 -01:00
/// <summary>
/// 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.
/// </summary>
internal IEnumerable < Assembly > AssembliesToScan
{
get { return _assemblies ? ? ( _assemblies = TypeFinder . GetAssembliesWithKnownExclusions ( ) ) ; }
set { _assemblies = value ; }
}
/// <summary>
/// Used to resolve and create instances of the specified type based on the resolved/cached plugin types
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="throwException">set to true if an exception is to be thrown if there is an error during instantiation</param>
2013-11-19 17:41:48 +11:00
/// <param name="cacheResult"></param>
/// <param name="specificAssemblies"></param>
2012-11-27 13:27:33 -01:00
/// <returns></returns>
2013-11-19 17:41:48 +11:00
internal IEnumerable < T > FindAndCreateInstances < T > ( bool throwException = false , bool cacheResult = true , IEnumerable < Assembly > specificAssemblies = null )
2012-11-27 13:27:33 -01:00
{
2013-11-19 17:41:48 +11:00
var types = ResolveTypes < T > ( cacheResult , specificAssemblies ) ;
2012-11-27 13:27:33 -01:00
return CreateInstances < T > ( types , throwException ) ;
}
/// <summary>
/// Used to create instances of the specified type based on the resolved/cached plugin types
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="types"></param>
/// <param name="throwException">set to true if an exception is to be thrown if there is an error during instantiation</param>
/// <returns></returns>
internal IEnumerable < T > CreateInstances < T > ( IEnumerable < Type > types , bool throwException = false )
{
2015-01-16 15:47:44 +11:00
return _serviceProvider . CreateInstances < T > ( types , _logger . Logger , throwException ) ;
2012-11-27 13:27:33 -01:00
}
/// <summary>
/// Used to create an instance of the specified type based on the resolved/cached plugin types
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="type"></param>
/// <param name="throwException"></param>
/// <returns></returns>
internal T CreateInstance < T > ( Type type , bool throwException = false )
{
var instances = CreateInstances < T > ( new [ ] { type } , throwException ) ;
return instances . FirstOrDefault ( ) ;
}
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 > ( ) ;
2015-01-16 15:47:44 +11:00
using ( _logger . TraceDuration < PluginManager > (
2015-01-16 12:29:27 +11:00
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 ) ) )
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
}
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.
2013-12-06 14:06:53 +11:00
if ( fileCacheResult . Exception ! = null & & 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 )
{
//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 ;
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>
/// 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
/// </summary>
/// <param name="typeList"></param>
/// <param name="resolutionKind"> </param>
/// <param name="finder"></param>
/// <remarks>
/// THIS METHODS IS NOT THREAD SAFE
/// </remarks>
private void LoadViaScanningAndUpdateCacheFile < T > ( TypeList typeList , TypeResolutionKind resolutionKind , Func < IEnumerable < Type > > 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 < T > ( typeList . GetTypes ( ) , resolutionKind ) ;
}
2014-02-17 15:08:27 +11:00
#region Public Methods
2012-11-27 13:27:33 -01:00
/// <summary>
/// Generic method to find the specified type and cache the result
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></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>
/// Generic method to find the specified type that has an attribute and cache the result
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TAttribute"></typeparam>
/// <returns></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>
/// Generic method to find any type that has the specified attribute
/// </summary>
/// <typeparam name="TAttribute"></typeparam>
/// <returns></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 ) ;
2014-02-17 15:08:27 +11:00
}
#endregion
2012-11-27 13:27:33 -01:00
/// <summary>
/// Used for unit tests
/// </summary>
/// <returns></returns>
internal HashSet < TypeList > GetTypeLists ( )
{
return _types ;
}
#region Private classes / Enums
/// <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 )
{
//if the type is an attribute type we won't do the type check because typeof<T> 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 < 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>
/// This class is used simply to determine that a plugin was not found in the cache plugin list with the specified
/// TypeResolutionKind.
/// </summary>
2013-12-06 14:06:53 +11:00
internal class CachedPluginNotFoundInFileException : Exception
2012-11-27 13:27:33 -01:00
{
}
#endregion
}
2012-08-01 22:06:15 +06:00
}