From c43e87662c9ac12641d37e539770f878ca1ebc32 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 30 Jun 2010 18:18:11 -0700 Subject: [PATCH] Support for dynamic loading of module references --HG-- branch : dev --- .../ExtensionLoaderCoordinatorTests.cs | 17 ++ .../Extensions/ExtensionManagerTests.cs | 17 ++ .../Compilers/DefaultExtensionCompiler.cs | 30 ++- .../Compilers/DefaultProjectFileParser.cs | 12 +- .../Extensions/ExtensionLoaderCoordinator.cs | 171 +++++++++++++++--- .../Extensions/ExtensionLoadingContext.cs | 48 ++++- .../Loaders/DynamicExtensionLoader.cs | 29 ++- .../Extensions/Loaders/ExtensionLoaderBase.cs | 13 ++ .../Extensions/Loaders/IExtensionLoader.cs | 13 ++ .../Loaders/PrecompiledExtensionLoader.cs | 45 +++++ src/Orchard/Environment/IAssemblyBuilder.cs | 9 +- .../Dependencies/DefaultDependenciesFolder.cs | 39 +++- .../Dependencies/IDependenciesFolder.cs | 7 + .../VirtualPath/DefaultVirtualPathProvider.cs | 17 +- .../VirtualPath/IVirtualPathProvider.cs | 8 +- 15 files changed, 430 insertions(+), 45 deletions(-) diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs index 9c576aecc..bffa41b90 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionLoaderCoordinatorTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Autofac; using NUnit.Framework; using Orchard.Caching; @@ -56,10 +57,26 @@ namespace Orchard.Tests.Environment.Extensions { get { return this.GetType().Name; } } + public Assembly LoadReference(ReferenceDescriptor reference) { + throw new NotImplementedException(); + } + + public void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry) { + throw new NotImplementedException(); + } + + public void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry) { + throw new NotImplementedException(); + } + public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this }; } + public IEnumerable ProbeReferences(ExtensionDescriptor extensionDescriptor) { + throw new NotImplementedException(); + } + public ExtensionEntry Load(ExtensionDescriptor descriptor) { return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } }; } diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs index 92e6c1e61..5023852a7 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Autofac; using NUnit.Framework; using Orchard.Caching; @@ -55,10 +56,26 @@ namespace Orchard.Tests.Environment.Extensions { get { throw new NotImplementedException(); } } + public Assembly LoadReference(ReferenceDescriptor reference) { + throw new NotImplementedException(); + } + + public void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry) { + throw new NotImplementedException(); + } + + public void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry) { + throw new NotImplementedException(); + } + public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this }; } + public IEnumerable ProbeReferences(ExtensionDescriptor extensionDescriptor) { + throw new NotImplementedException(); + } + public ExtensionEntry Load(ExtensionDescriptor descriptor) { return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } }; } diff --git a/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs index 6ae511941..6f3afcce2 100644 --- a/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs +++ b/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs @@ -1,7 +1,9 @@ using System; using System.CodeDom; +using System.Collections.Generic; using System.IO; using System.Linq; +using Orchard.Environment.Extensions.Loaders; using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.VirtualPath; using Orchard.Localization; @@ -9,17 +11,24 @@ using Orchard.Logging; namespace Orchard.Environment.Extensions.Compilers { /// - /// Compile a C# extension into an assembly given a directory location + /// Compile an extension project file into an assembly /// public class DefaultExtensionCompiler : IExtensionCompiler { private readonly IVirtualPathProvider _virtualPathProvider; private readonly IProjectFileParser _projectFileParser; private readonly IDependenciesFolder _dependenciesFolder; + private readonly IEnumerable _loaders; + + public DefaultExtensionCompiler( + IVirtualPathProvider virtualPathProvider, + IProjectFileParser projectFileParser, + IDependenciesFolder dependenciesFolder, + IEnumerable loaders) { - public DefaultExtensionCompiler(IVirtualPathProvider virtualPathProvider, IProjectFileParser projectFileParser, IDependenciesFolder dependenciesFolder ) { _virtualPathProvider = virtualPathProvider; _projectFileParser = projectFileParser; _dependenciesFolder = dependenciesFolder; + _loaders = loaders; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -31,20 +40,33 @@ namespace Orchard.Environment.Extensions.Compilers { public void Compile(CompileExtensionContext context) { Logger.Information("Generate code for file \"{0}\"", context.VirtualPath); var moduleName = Path.GetFileNameWithoutExtension(context.VirtualPath); - if (_dependenciesFolder.GetDescriptor(moduleName) == null) + var dependencyDescriptor = _dependenciesFolder.GetDescriptor(moduleName); + if (dependencyDescriptor == null) return; try { using (var stream = _virtualPathProvider.OpenFile(context.VirtualPath)) { var descriptor = _projectFileParser.Parse(stream); + // Add source files var directory = _virtualPathProvider.GetDirectoryName(context.VirtualPath); foreach (var filename in descriptor.SourceFilenames.Select(f => _virtualPathProvider.Combine(directory, f))) { context.AssemblyBuilder.AddCodeCompileUnit(CreateCompileUnit(filename)); } + + // Add assembly references + foreach (var reference in dependencyDescriptor.References) { + var referenceTemp = reference; + var loader = _loaders.SingleOrDefault(l => l.Name == referenceTemp.LoaderName); + if (loader != null) { + var assembly = loader.LoadReference(reference); + if (assembly != null) + context.AssemblyBuilder.AddAssemblyReference(assembly); + } + } } } - catch(Exception e) { + catch (Exception e) { throw new OrchardCoreException(T("Error compiling module \"{0}\" from file \"{1}\"", moduleName, context.VirtualPath), e); } } diff --git a/src/Orchard/Environment/Extensions/Compilers/DefaultProjectFileParser.cs b/src/Orchard/Environment/Extensions/Compilers/DefaultProjectFileParser.cs index 2152bc3df..131c86594 100644 --- a/src/Orchard/Environment/Extensions/Compilers/DefaultProjectFileParser.cs +++ b/src/Orchard/Environment/Extensions/Compilers/DefaultProjectFileParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; @@ -40,7 +41,14 @@ namespace Orchard.Environment.Extensions.Compilers { .Elements(ns("ItemGroup")) .Elements(ns("Reference")) .Attributes("Include") - .Select(c => new ReferenceDescriptor { AssemblyName = c.Value }); + .Select(c => new ReferenceDescriptor { AssemblyName = ExtractAssemblyName(c.Value) }); + } + + private static string ExtractAssemblyName(string value) { + int index = value.IndexOf(','); + if (index < 0) + return value; + return value.Substring(0, index); } private static XName ns(string name) { diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index a26267211..3cb5802c8 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -5,6 +5,7 @@ using Orchard.Caching; using Orchard.Environment.Extensions.Loaders; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.Dependencies; +using Orchard.FileSystems.VirtualPath; using Orchard.Localization; using Orchard.Logging; @@ -12,19 +13,25 @@ namespace Orchard.Environment.Extensions { public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator { private readonly IDependenciesFolder _dependenciesFolder; private readonly IExtensionManager _extensionManager; + private readonly IVirtualPathProvider _virtualPathProvider; private readonly IEnumerable _loaders; private readonly IHostEnvironment _hostEnvironment; + private readonly IBuildManager _buildManager; public ExtensionLoaderCoordinator( IDependenciesFolder dependenciesFolder, IExtensionManager extensionManager, + IVirtualPathProvider virtualPathProvider, IEnumerable loaders, - IHostEnvironment hostEnvironment) { + IHostEnvironment hostEnvironment, + IBuildManager buildManager) { _dependenciesFolder = dependenciesFolder; _extensionManager = extensionManager; + _virtualPathProvider = virtualPathProvider; _loaders = loaders.OrderBy(l => l.Order); _hostEnvironment = hostEnvironment; + _buildManager = buildManager; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -36,50 +43,76 @@ namespace Orchard.Environment.Extensions { public void SetupExtensions() { Logger.Information("Loading extensions."); - var extensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList(); - var existingDependencies = _dependenciesFolder.LoadDescriptors().ToList(); - var deletedDependencies = existingDependencies.Where(e => !extensions.Any(e2 => StringComparer.OrdinalIgnoreCase.Equals(e2.Name, e.Name))).ToList(); - - var loadingContext = new ExtensionLoadingContext(); + var context = CreateLoadingContext(); // Notify all loaders about extensions removed from the web site - foreach (var dependency in deletedDependencies) { + foreach (var dependency in context.DeletedDependencies) { Logger.Information("Extension {0} has been removed from site", dependency.Name); foreach (var loader in _loaders) { if (dependency.LoaderName == loader.Name) { - loader.ExtensionRemoved(loadingContext, dependency); + loader.ExtensionRemoved(context, dependency); } } } // For all existing extensions in the site, ask each loader if they can // load that extension. - var newDependencies = new List(); - foreach (var extension in extensions) { - ProcessExtension(loadingContext, extension, existingDependencies, newDependencies); + foreach (var extension in context.AvailableExtensions) { + ProcessExtension(context, extension); } // Execute all the work need by "ctx" - ProcessContextCommands(loadingContext); + ProcessContextCommands(context); // And finally save the new entries in the dependencies folder - _dependenciesFolder.StoreDescriptors(newDependencies); + _dependenciesFolder.StoreDescriptors(context.NewDependencies); Logger.Information("Done loading extensions."); } - private void ProcessExtension( - ExtensionLoadingContext loadingContext, - ExtensionDescriptor extension, - IEnumerable existingDependencies, - List newDependencies) { + private ExtensionLoadingContext CreateLoadingContext() { + var availableExtensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList(); + var previousDependencies = _dependenciesFolder.LoadDescriptors().ToList(); + var availableExtensionsProbes = availableExtensions.SelectMany(extension => _loaders + .Select(loader => loader.Probe(extension)) + .Where(probe => probe != null)) + .GroupBy(e => e.Descriptor.Name) + .ToDictionary(g => g.Key, g => g.AsEnumerable() + .OrderByDescending(probe => probe.LastModificationTimeUtc) + .ThenBy(probe => probe.Loader.Order), StringComparer.OrdinalIgnoreCase); - var extensionProbes = _loaders - .Select(loader => loader.Probe(extension)) - .Where(probe => probe != null) - .OrderByDescending(probe => probe.LastModificationTimeUtc) - .ThenBy(probe => probe.Loader.Order) + var deletedDependencies = previousDependencies + .Where(e => !availableExtensions.Any(e2 => StringComparer.OrdinalIgnoreCase.Equals(e2.Name, e.Name))) .ToList(); + // Collect references for all modules + var references = + availableExtensions + .SelectMany(extension => _loaders.SelectMany(loader => loader.ProbeReferences(extension))) + .ToList(); + + var referencesByModule = references + .GroupBy(entry => entry.Descriptor.Name, StringComparer.OrdinalIgnoreCase) + .ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase); + + var referencesByName = references + .GroupBy(reference => reference.Name, StringComparer.OrdinalIgnoreCase) + .ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase); + + return new ExtensionLoadingContext { + AvailableExtensions = availableExtensions, + PreviousDependencies = previousDependencies, + DeletedDependencies = deletedDependencies, + AvailableExtensionsProbes = availableExtensionsProbes, + ReferencesByName = referencesByName, + ReferencesByModule = referencesByModule + }; + } + + private void ProcessExtension(ExtensionLoadingContext context, ExtensionDescriptor extension) { + + var extensionProbes = context.AvailableExtensionsProbes.ContainsKey(extension.Name) ? + context.AvailableExtensionsProbes[extension.Name] : + Enumerable.Empty(); if (Logger.IsEnabled(LogLevel.Debug)) { Logger.Debug("Loaders for extension \"{0}\": ", extension.Name); @@ -91,28 +124,110 @@ namespace Orchard.Environment.Extensions { } var activatedExtension = extensionProbes.FirstOrDefault(); - var previousDependency = existingDependencies.Where(d => StringComparer.OrdinalIgnoreCase.Equals(d.Name, extension.Name)).FirstOrDefault(); + var previousDependency = context.PreviousDependencies.Where(d => StringComparer.OrdinalIgnoreCase.Equals(d.Name, extension.Name)).FirstOrDefault(); if (activatedExtension == null) { Logger.Warning("No loader found for extension \"{0}\"!", extension.Name); } + var references = ProcessExtensionReferences(context, activatedExtension); + foreach (var loader in _loaders) { if (activatedExtension != null && activatedExtension.Loader.Name == loader.Name) { Logger.Information("Activating extension \"{0}\" with loader \"{1}\"", activatedExtension.Descriptor.Name, loader.Name); - loader.ExtensionActivated(loadingContext, extension); + loader.ExtensionActivated(context, extension); } else if (previousDependency != null && previousDependency.LoaderName == loader.Name) { Logger.Information("Deactivating extension \"{0}\" from loader \"{1}\"", previousDependency.Name, loader.Name); - loader.ExtensionDeactivated(loadingContext, extension); + loader.ExtensionDeactivated(context, extension); } } if (activatedExtension != null) { - newDependencies.Add(new DependencyDescriptor { + context.NewDependencies.Add(new DependencyDescriptor { Name = extension.Name, LoaderName = activatedExtension.Loader.Name, - VirtualPath = activatedExtension.VirtualPath + VirtualPath = activatedExtension.VirtualPath, + References = references + }); + } + } + + IEnumerable ProcessExtensionReferences(ExtensionLoadingContext context, ExtensionProbeEntry activatedExtension) { + if (activatedExtension == null) + return Enumerable.Empty(); + + var referenceNames = (context.ReferencesByModule.ContainsKey(activatedExtension.Descriptor.Name) ? + context.ReferencesByModule[activatedExtension.Descriptor.Name] : + Enumerable.Empty()) + .Select(r => r.Name) + .Distinct(StringComparer.OrdinalIgnoreCase); + + var referencesDecriptors = new List(); + foreach (var referenceName in referenceNames) { + ProcessExtensionReference(context, activatedExtension, referenceName, referencesDecriptors); + } + + return referencesDecriptors; + } + + private void ProcessExtensionReference(ExtensionLoadingContext context, + ExtensionProbeEntry activatedExtension, + string referenceName, + IList activatedReferences) { + + // Assemblies loaded by the BuildManager are ignored, since + // we don't want to update them and they are automatically + // referenced by the build manager + if (_buildManager.GetReferencedAssemblies().Any(a => StringComparer.OrdinalIgnoreCase.Equals(a.GetName().Name, referenceName))) + return; + + var references = context.ReferencesByName.ContainsKey(referenceName) ? + context.ReferencesByName[referenceName] : + Enumerable.Empty(); + + // Binary references + var bestBinaryReference = references + .Where(entry => !string.IsNullOrEmpty(entry.VirtualPath)) + .Select(entry => new { Entry = entry, LastWriteTimeUtc = _virtualPathProvider.GetFileLastWriteTimeUtc(entry.VirtualPath) }) + .OrderBy(e => e.LastWriteTimeUtc) + .ThenBy(e => e.Entry.Name).FirstOrDefault(); + + var probes = context.AvailableExtensionsProbes.ContainsKey(referenceName) ? + context.AvailableExtensionsProbes[referenceName] : + Enumerable.Empty(); + var bestProbe = probes.FirstOrDefault(); + + // Pick the best one of module vs binary + if (bestProbe != null && bestBinaryReference != null) { + if (bestProbe.LastModificationTimeUtc >= bestBinaryReference.LastWriteTimeUtc) { + bestBinaryReference = null; + } else { + bestProbe = null; + } + } + + // Activate the binary ref + if (bestBinaryReference != null) { + if (!context.ProcessedReferences.Contains(bestBinaryReference.Entry.Name)) { + context.ProcessedReferences.Add(bestBinaryReference.Entry.Name); + bestBinaryReference.Entry.Loader.ReferenceActivated(context, bestBinaryReference.Entry); + } + activatedReferences.Add(new ReferenceDescriptor { + LoaderName = bestBinaryReference.Entry.Loader.Name, + Name = bestBinaryReference.Entry.Name, + VirtualPath = bestBinaryReference.Entry.VirtualPath + }); + return; + } + + // Activated the module ref + if (bestProbe != null) + { + activatedReferences.Add(new ReferenceDescriptor { + LoaderName = bestProbe.Loader.Name, + Name = referenceName, + VirtualPath = bestProbe.VirtualPath }); } } diff --git a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs index 4baf04e30..1b80b0746 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoadingContext.cs @@ -1,17 +1,61 @@ using System; using System.Collections.Generic; +using System.Linq; +using Orchard.Environment.Extensions.Loaders; +using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.Dependencies; namespace Orchard.Environment.Extensions { public class ExtensionLoadingContext { public ExtensionLoadingContext() { + ProcessedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase); + ProcessedReferences = new HashSet(StringComparer.OrdinalIgnoreCase); DeleteActions = new List(); CopyActions = new List(); + NewDependencies = new List(); } - public IList DeleteActions { get; set; } - public IList CopyActions { get; set; } + public ISet ProcessedExtensions { get; private set; } + public ISet ProcessedReferences { get; private set; } + + public IList NewDependencies { get; private set; } + + public IList DeleteActions { get; private set; } + public IList CopyActions { get; private set; } public bool RestartAppDomain { get; set; } public bool ResetSiteCompilation { get; set; } + + /// + /// List of extensions (modules) present in the system + /// + public List AvailableExtensions { get; set; } + + /// + /// List of extensions (modules) that were loaded during a previous successful run + /// + public List PreviousDependencies { get; set; } + + /// + /// The list of extensions/modules that are were present in the previous successful run + /// and that are not present in the system anymore. + /// + public List DeletedDependencies { get; set; } + + /// + /// For every extension name, the list of loaders that can potentially load + /// that extension (in order of "best-of" applicable) + /// + public IDictionary> AvailableExtensionsProbes { get; set; } + + /// + /// For every reference name, list of potential loaders/locations + /// + public IDictionary> ReferencesByModule { get; set; } + + /// + /// For every extension name, list of references + /// + public IDictionary> ReferencesByName { get; set; } } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index b14e675e9..e867200ec 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -2,29 +2,35 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using Orchard.Caching; +using Orchard.Environment.Extensions.Compilers; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.VirtualPath; using Orchard.Logging; +using ReferenceDescriptor = Orchard.FileSystems.Dependencies.ReferenceDescriptor; namespace Orchard.Environment.Extensions.Loaders { public class DynamicExtensionLoader : ExtensionLoaderBase { private readonly IBuildManager _buildManager; private readonly IVirtualPathProvider _virtualPathProvider; private readonly IVirtualPathMonitor _virtualPathMonitor; + private readonly IProjectFileParser _projectFileParser; private static readonly ReloadWorkaround _reloadWorkaround = new ReloadWorkaround(); public DynamicExtensionLoader( IBuildManager buildManager, IVirtualPathProvider virtualPathProvider, IVirtualPathMonitor virtualPathMonitor, - IDependenciesFolder dependenciesFolder) + IDependenciesFolder dependenciesFolder, + IProjectFileParser projectFileParser) : base(dependenciesFolder) { _buildManager = buildManager; _virtualPathProvider = virtualPathProvider; _virtualPathMonitor = virtualPathMonitor; + _projectFileParser = projectFileParser; Logger = NullLogger.Instance; } @@ -74,6 +80,27 @@ namespace Orchard.Environment.Extensions.Loaders { } } + public override IEnumerable ProbeReferences(ExtensionDescriptor descriptor) { + string projectPath = GetProjectPath(descriptor); + if (projectPath == null) + return Enumerable.Empty(); + + using(var stream = _virtualPathProvider.OpenFile(projectPath)) { + var projectFile = _projectFileParser.Parse(stream); + + return projectFile.References.Select(r => new ExtensionReferenceEntry { + Descriptor = descriptor, + Loader = this, + Name = r.AssemblyName, + VirtualPath = null + }); + } + } + + public override Assembly LoadReference(ReferenceDescriptor reference) { + return _buildManager.GetCompiledAssembly(reference.VirtualPath); + } + public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { string projectPath = GetProjectPath(descriptor); if (projectPath == null) diff --git a/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs b/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs index eb6ebdfd2..cae0ce53d 100644 --- a/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs +++ b/src/Orchard/Environment/Extensions/Loaders/ExtensionLoaderBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Orchard.Caching; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.Dependencies; @@ -16,6 +17,14 @@ namespace Orchard.Environment.Extensions.Loaders { public abstract int Order { get; } public string Name { get { return this.GetType().Name; } } + public virtual IEnumerable ProbeReferences(ExtensionDescriptor descriptor) { + return Enumerable.Empty(); + } + + public virtual Assembly LoadReference(ReferenceDescriptor reference) { + return null; + } + public abstract ExtensionProbeEntry Probe(ExtensionDescriptor descriptor); public ExtensionEntry Load(ExtensionDescriptor descriptor) { @@ -26,9 +35,13 @@ namespace Orchard.Environment.Extensions.Loaders { return null; } + public virtual void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry) { } + public virtual void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry) { } + public virtual void ExtensionActivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { } public virtual void ExtensionDeactivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { } public virtual void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) { } + public virtual void Monitor(ExtensionDescriptor extension, Action monitor) { } protected abstract ExtensionEntry LoadWorker(ExtensionDescriptor descriptor); diff --git a/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs index 2f2bd8765..138e01dcc 100644 --- a/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/IExtensionLoader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using Orchard.Caching; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.Dependencies; @@ -12,10 +13,22 @@ namespace Orchard.Environment.Extensions.Loaders { public DateTime LastModificationTimeUtc { get; set; } } + public class ExtensionReferenceEntry { + public ExtensionDescriptor Descriptor { get; set; } + public IExtensionLoader Loader { get; set; } + public string Name { get; set; } + public string VirtualPath { get; set; } + } + public interface IExtensionLoader { int Order { get; } string Name { get; } + IEnumerable ProbeReferences(ExtensionDescriptor extensionDescriptor); + Assembly LoadReference(ReferenceDescriptor reference); + void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry); + void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry); + ExtensionProbeEntry Probe(ExtensionDescriptor descriptor); ExtensionEntry Load(ExtensionDescriptor descriptor); diff --git a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs index 2a57d3c3b..dd726cd0f 100644 --- a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Reflection; using Orchard.Caching; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.Dependencies; @@ -93,6 +95,28 @@ namespace Orchard.Environment.Extensions.Loaders { } } + public override void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry) { + if (string.IsNullOrEmpty(referenceEntry.VirtualPath)) + return; + + string sourceFileName = _virtualPathProvider.MapPath(referenceEntry.VirtualPath); + + // Copy the assembly if it doesn't exist or if it is older than the source file. + bool copyAssembly = + !_assemblyProbingFolder.AssemblyExists(referenceEntry.Name) || + File.GetLastWriteTimeUtc(sourceFileName) > _assemblyProbingFolder.GetAssemblyDateTimeUtc(referenceEntry.Name); + + if (copyAssembly) { + context.CopyActions.Add(() => _assemblyProbingFolder.StoreAssembly(referenceEntry.Name, sourceFileName)); + + // We need to restart the appDomain if the assembly is loaded + if (IsAssemblyLoaded(referenceEntry.Name)) { + Logger.Information("ReferenceActivated: Reference \"{0}\" is activated with newer file and its assembly is loaded, forcing AppDomain restart", referenceEntry.Name); + context.RestartAppDomain = true; + } + } + } + public override void Monitor(ExtensionDescriptor descriptor, Action monitor) { string assemblyPath = GetAssemblyPath(descriptor); if (assemblyPath != null) { @@ -101,6 +125,23 @@ namespace Orchard.Environment.Extensions.Loaders { } } + public override IEnumerable ProbeReferences(ExtensionDescriptor descriptor) { + var assemblyPath = GetAssemblyPath(descriptor); + if (assemblyPath == null) + return Enumerable.Empty(); + + return _virtualPathProvider + .ListFiles(_virtualPathProvider.GetDirectoryName(assemblyPath)) + .Where(s => StringComparer.OrdinalIgnoreCase.Equals(Path.GetExtension(s), ".dll")) + .Where(s => !StringComparer.OrdinalIgnoreCase.Equals(Path.GetFileNameWithoutExtension(s), descriptor.Name)) + .Select(path => new ExtensionReferenceEntry { + Descriptor = descriptor, + Loader = this, + Name = Path.GetFileNameWithoutExtension(path), + VirtualPath = path + } ); + } + public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { var assemblyPath = GetAssemblyPath(descriptor); if (assemblyPath == null) @@ -114,6 +155,10 @@ namespace Orchard.Environment.Extensions.Loaders { }; } + public override Assembly LoadReference(ReferenceDescriptor reference) { + return _assemblyProbingFolder.LoadAssembly(reference.Name); + } + protected override ExtensionEntry LoadWorker(ExtensionDescriptor descriptor) { var assembly = _assemblyProbingFolder.LoadAssembly(descriptor.Name); if (assembly == null) diff --git a/src/Orchard/Environment/IAssemblyBuilder.cs b/src/Orchard/Environment/IAssemblyBuilder.cs index 055fb1caa..6a059ab3f 100644 --- a/src/Orchard/Environment/IAssemblyBuilder.cs +++ b/src/Orchard/Environment/IAssemblyBuilder.cs @@ -1,9 +1,12 @@ -using System.CodeDom; +using System; +using System.CodeDom; +using System.Reflection; using System.Web.Compilation; namespace Orchard.Environment { public interface IAssemblyBuilder { void AddCodeCompileUnit(CodeCompileUnit compileUnit); + void AddAssemblyReference(Assembly assembly); } public class AspNetAssemblyBuilder : IAssemblyBuilder { @@ -18,5 +21,9 @@ namespace Orchard.Environment { public void AddCodeCompileUnit(CodeCompileUnit compileUnit) { _assemblyBuilder.AddCodeCompileUnit(_buildProvider, compileUnit); } + + public void AddAssemblyReference(Assembly assembly) { + _assemblyBuilder.AddAssemblyReference(assembly); + } } } \ No newline at end of file diff --git a/src/Orchard/FileSystems/Dependencies/DefaultDependenciesFolder.cs b/src/Orchard/FileSystems/Dependencies/DefaultDependenciesFolder.cs index b42d1d46d..08a26a328 100644 --- a/src/Orchard/FileSystems/Dependencies/DefaultDependenciesFolder.cs +++ b/src/Orchard/FileSystems/Dependencies/DefaultDependenciesFolder.cs @@ -69,9 +69,12 @@ namespace Orchard.FileSystems.Dependencies { .Select(e => new DependencyDescriptor { Name = elem(e, "ModuleName"), VirtualPath = elem(e, "VirtualPath"), - LoaderName = elem(e, "LoaderName") - }) - .ToList(); + LoaderName = elem(e, "LoaderName"), + References = e.Elements(ns("References")).Elements(ns("Reference")).Select(r => new ReferenceDescriptor { + Name = elem(r, "Name"), + LoaderName = elem(r, "LoaderName"), + VirtualPath = elem(r, "VirtualPath") + })}).ToList(); } } @@ -83,7 +86,13 @@ namespace Orchard.FileSystems.Dependencies { var elements = dependencies.Select(d => new XElement("Dependency", new XElement(ns("ModuleName"), d.Name), new XElement(ns("VirtualPath"), d.VirtualPath), - new XElement(ns("LoaderName"), d.LoaderName))); + new XElement(ns("LoaderName"), d.LoaderName), + new XElement(ns("References"), d.References + .Select(r => new XElement(ns("Reference"), + new XElement(ns("Name"), r.Name), + new XElement(ns("LoaderName"), r.LoaderName), + new XElement(ns("VirtualPath"), r.VirtualPath))).ToArray()))); + document.Root.Add(elements); using (var stream = _appDataFolder.CreateFile(persistancePath)) { @@ -99,7 +108,27 @@ namespace Orchard.FileSystems.Dependencies { } private class DependencyDescriptorComparer : EqualityComparer { + private readonly ReferenceDescriptorComparer _referenceDescriptorComparer = new ReferenceDescriptorComparer(); + public override bool Equals(DependencyDescriptor x, DependencyDescriptor y) { + return + StringComparer.OrdinalIgnoreCase.Equals(x.Name, y.Name) && + StringComparer.OrdinalIgnoreCase.Equals(x.LoaderName, y.LoaderName) && + StringComparer.OrdinalIgnoreCase.Equals(x.VirtualPath, y.VirtualPath) && + x.References.SequenceEqual(y.References, _referenceDescriptorComparer); + } + + public override int GetHashCode(DependencyDescriptor obj) { + return + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name) ^ + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.LoaderName) ^ + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.VirtualPath) ^ + obj.References.Aggregate(0, (a, entry) => a + _referenceDescriptorComparer.GetHashCode(entry)); + } + } + + private class ReferenceDescriptorComparer : EqualityComparer { + public override bool Equals(ReferenceDescriptor x, ReferenceDescriptor y) { return StringComparer.OrdinalIgnoreCase.Equals(x.Name, y.Name) && StringComparer.OrdinalIgnoreCase.Equals(x.LoaderName, y.LoaderName) && @@ -107,7 +136,7 @@ namespace Orchard.FileSystems.Dependencies { } - public override int GetHashCode(DependencyDescriptor obj) { + public override int GetHashCode(ReferenceDescriptor obj) { return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.LoaderName) ^ diff --git a/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs b/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs index 3b51e98af..097542b2d 100644 --- a/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs +++ b/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs @@ -6,6 +6,13 @@ namespace Orchard.FileSystems.Dependencies { public string Name { get; set; } public string LoaderName { get; set; } public string VirtualPath { get; set; } + public IEnumerable References { get; set; } + } + + public class ReferenceDescriptor { + public string Name { get; set; } + public string LoaderName { get; set; } + public string VirtualPath { get; set; } } public interface IDependenciesFolder : IVolatileProvider { diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs index f414b5b97..0883058ea 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathProvider.cs @@ -1,4 +1,7 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Web.Hosting; namespace Orchard.FileSystems.VirtualPath { @@ -7,6 +10,14 @@ namespace Orchard.FileSystems.VirtualPath { return Path.GetDirectoryName(virtualPath).Replace(Path.DirectorySeparatorChar, '/'); } + public IEnumerable ListFiles(string path) { + return HostingEnvironment.VirtualPathProvider.GetDirectory(path).Files.OfType().Select(f => f.VirtualPath); + } + + public IEnumerable ListDirectories(string path) { + return HostingEnvironment.VirtualPathProvider.GetDirectory(path).Directories.OfType().Select(d => d.VirtualPath); + } + public string Combine(params string[] paths) { return Path.Combine(paths).Replace(Path.DirectorySeparatorChar, '/'); } @@ -19,6 +30,10 @@ namespace Orchard.FileSystems.VirtualPath { return File.CreateText(MapPath(virtualPath)); } + public DateTime GetFileLastWriteTimeUtc(string virtualPath) { + return File.GetLastWriteTimeUtc(MapPath(virtualPath)); + } + public string MapPath(string virtualPath) { return HostingEnvironment.MapPath(virtualPath); } diff --git a/src/Orchard/FileSystems/VirtualPath/IVirtualPathProvider.cs b/src/Orchard/FileSystems/VirtualPath/IVirtualPathProvider.cs index e941c227f..2fa00229f 100644 --- a/src/Orchard/FileSystems/VirtualPath/IVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/VirtualPath/IVirtualPathProvider.cs @@ -1,4 +1,6 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; using Orchard.Caching; namespace Orchard.FileSystems.VirtualPath { @@ -9,9 +11,13 @@ namespace Orchard.FileSystems.VirtualPath { bool FileExists(string virtualPath); Stream OpenFile(string virtualPath); StreamWriter CreateText(string virtualPath); + DateTime GetFileLastWriteTimeUtc(string virtualPath); bool DirectoryExists(string virtualPath); void CreateDirectory(string virtualPath); string GetDirectoryName(string virtualPath); + + IEnumerable ListFiles(string path); + IEnumerable ListDirectories(string path); } } \ No newline at end of file