Support for dynamic loading of module references

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-06-30 18:18:11 -07:00
parent 21328875f3
commit c43e87662c
15 changed files with 430 additions and 45 deletions

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Autofac; using Autofac;
using NUnit.Framework; using NUnit.Framework;
using Orchard.Caching; using Orchard.Caching;
@@ -56,10 +57,26 @@ namespace Orchard.Tests.Environment.Extensions {
get { return this.GetType().Name; } 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) { public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this }; return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
} }
public IEnumerable<ExtensionReferenceEntry> ProbeReferences(ExtensionDescriptor extensionDescriptor) {
throw new NotImplementedException();
}
public ExtensionEntry Load(ExtensionDescriptor descriptor) { public ExtensionEntry Load(ExtensionDescriptor descriptor) {
return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } }; return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } };
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Autofac; using Autofac;
using NUnit.Framework; using NUnit.Framework;
using Orchard.Caching; using Orchard.Caching;
@@ -55,10 +56,26 @@ namespace Orchard.Tests.Environment.Extensions {
get { throw new NotImplementedException(); } 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) { public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this }; return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
} }
public IEnumerable<ExtensionReferenceEntry> ProbeReferences(ExtensionDescriptor extensionDescriptor) {
throw new NotImplementedException();
}
public ExtensionEntry Load(ExtensionDescriptor descriptor) { public ExtensionEntry Load(ExtensionDescriptor descriptor) {
return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } }; return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } };
} }

View File

@@ -1,7 +1,9 @@
using System; using System;
using System.CodeDom; using System.CodeDom;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Orchard.Environment.Extensions.Loaders;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.VirtualPath;
using Orchard.Localization; using Orchard.Localization;
@@ -9,17 +11,24 @@ using Orchard.Logging;
namespace Orchard.Environment.Extensions.Compilers { namespace Orchard.Environment.Extensions.Compilers {
/// <summary> /// <summary>
/// Compile a C# extension into an assembly given a directory location /// Compile an extension project file into an assembly
/// </summary> /// </summary>
public class DefaultExtensionCompiler : IExtensionCompiler { public class DefaultExtensionCompiler : IExtensionCompiler {
private readonly IVirtualPathProvider _virtualPathProvider; private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IProjectFileParser _projectFileParser; private readonly IProjectFileParser _projectFileParser;
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
private readonly IEnumerable<IExtensionLoader> _loaders;
public DefaultExtensionCompiler(
IVirtualPathProvider virtualPathProvider,
IProjectFileParser projectFileParser,
IDependenciesFolder dependenciesFolder,
IEnumerable<IExtensionLoader> loaders) {
public DefaultExtensionCompiler(IVirtualPathProvider virtualPathProvider, IProjectFileParser projectFileParser, IDependenciesFolder dependenciesFolder ) {
_virtualPathProvider = virtualPathProvider; _virtualPathProvider = virtualPathProvider;
_projectFileParser = projectFileParser; _projectFileParser = projectFileParser;
_dependenciesFolder = dependenciesFolder; _dependenciesFolder = dependenciesFolder;
_loaders = loaders;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
@@ -31,17 +40,30 @@ namespace Orchard.Environment.Extensions.Compilers {
public void Compile(CompileExtensionContext context) { public void Compile(CompileExtensionContext context) {
Logger.Information("Generate code for file \"{0}\"", context.VirtualPath); Logger.Information("Generate code for file \"{0}\"", context.VirtualPath);
var moduleName = Path.GetFileNameWithoutExtension(context.VirtualPath); var moduleName = Path.GetFileNameWithoutExtension(context.VirtualPath);
if (_dependenciesFolder.GetDescriptor(moduleName) == null) var dependencyDescriptor = _dependenciesFolder.GetDescriptor(moduleName);
if (dependencyDescriptor == null)
return; return;
try { try {
using (var stream = _virtualPathProvider.OpenFile(context.VirtualPath)) { using (var stream = _virtualPathProvider.OpenFile(context.VirtualPath)) {
var descriptor = _projectFileParser.Parse(stream); var descriptor = _projectFileParser.Parse(stream);
// Add source files
var directory = _virtualPathProvider.GetDirectoryName(context.VirtualPath); var directory = _virtualPathProvider.GetDirectoryName(context.VirtualPath);
foreach (var filename in descriptor.SourceFilenames.Select(f => _virtualPathProvider.Combine(directory, f))) { foreach (var filename in descriptor.SourceFilenames.Select(f => _virtualPathProvider.Combine(directory, f))) {
context.AssemblyBuilder.AddCodeCompileUnit(CreateCompileUnit(filename)); 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) {

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
@@ -40,7 +41,14 @@ namespace Orchard.Environment.Extensions.Compilers {
.Elements(ns("ItemGroup")) .Elements(ns("ItemGroup"))
.Elements(ns("Reference")) .Elements(ns("Reference"))
.Attributes("Include") .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) { private static XName ns(string name) {

View File

@@ -5,6 +5,7 @@ using Orchard.Caching;
using Orchard.Environment.Extensions.Loaders; using Orchard.Environment.Extensions.Loaders;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Logging; using Orchard.Logging;
@@ -12,19 +13,25 @@ namespace Orchard.Environment.Extensions {
public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator { public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator {
private readonly IDependenciesFolder _dependenciesFolder; private readonly IDependenciesFolder _dependenciesFolder;
private readonly IExtensionManager _extensionManager; private readonly IExtensionManager _extensionManager;
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IEnumerable<IExtensionLoader> _loaders; private readonly IEnumerable<IExtensionLoader> _loaders;
private readonly IHostEnvironment _hostEnvironment; private readonly IHostEnvironment _hostEnvironment;
private readonly IBuildManager _buildManager;
public ExtensionLoaderCoordinator( public ExtensionLoaderCoordinator(
IDependenciesFolder dependenciesFolder, IDependenciesFolder dependenciesFolder,
IExtensionManager extensionManager, IExtensionManager extensionManager,
IVirtualPathProvider virtualPathProvider,
IEnumerable<IExtensionLoader> loaders, IEnumerable<IExtensionLoader> loaders,
IHostEnvironment hostEnvironment) { IHostEnvironment hostEnvironment,
IBuildManager buildManager) {
_dependenciesFolder = dependenciesFolder; _dependenciesFolder = dependenciesFolder;
_extensionManager = extensionManager; _extensionManager = extensionManager;
_virtualPathProvider = virtualPathProvider;
_loaders = loaders.OrderBy(l => l.Order); _loaders = loaders.OrderBy(l => l.Order);
_hostEnvironment = hostEnvironment; _hostEnvironment = hostEnvironment;
_buildManager = buildManager;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
@@ -36,50 +43,76 @@ namespace Orchard.Environment.Extensions {
public void SetupExtensions() { public void SetupExtensions() {
Logger.Information("Loading extensions."); Logger.Information("Loading extensions.");
var extensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList(); var context = CreateLoadingContext();
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();
// Notify all loaders about extensions removed from the web site // 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); Logger.Information("Extension {0} has been removed from site", dependency.Name);
foreach (var loader in _loaders) { foreach (var loader in _loaders) {
if (dependency.LoaderName == loader.Name) { 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 // For all existing extensions in the site, ask each loader if they can
// load that extension. // load that extension.
var newDependencies = new List<DependencyDescriptor>(); foreach (var extension in context.AvailableExtensions) {
foreach (var extension in extensions) { ProcessExtension(context, extension);
ProcessExtension(loadingContext, extension, existingDependencies, newDependencies);
} }
// Execute all the work need by "ctx" // Execute all the work need by "ctx"
ProcessContextCommands(loadingContext); ProcessContextCommands(context);
// And finally save the new entries in the dependencies folder // And finally save the new entries in the dependencies folder
_dependenciesFolder.StoreDescriptors(newDependencies); _dependenciesFolder.StoreDescriptors(context.NewDependencies);
Logger.Information("Done loading extensions."); Logger.Information("Done loading extensions.");
} }
private void ProcessExtension( private ExtensionLoadingContext CreateLoadingContext() {
ExtensionLoadingContext loadingContext, var availableExtensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList();
ExtensionDescriptor extension, var previousDependencies = _dependenciesFolder.LoadDescriptors().ToList();
IEnumerable<DependencyDescriptor> existingDependencies, var availableExtensionsProbes = availableExtensions.SelectMany(extension => _loaders
List<DependencyDescriptor> newDependencies) {
var extensionProbes = _loaders
.Select(loader => loader.Probe(extension)) .Select(loader => loader.Probe(extension))
.Where(probe => probe != null) .Where(probe => probe != null))
.GroupBy(e => e.Descriptor.Name)
.ToDictionary(g => g.Key, g => g.AsEnumerable()
.OrderByDescending(probe => probe.LastModificationTimeUtc) .OrderByDescending(probe => probe.LastModificationTimeUtc)
.ThenBy(probe => probe.Loader.Order) .ThenBy(probe => probe.Loader.Order), StringComparer.OrdinalIgnoreCase);
var deletedDependencies = previousDependencies
.Where(e => !availableExtensions.Any(e2 => StringComparer.OrdinalIgnoreCase.Equals(e2.Name, e.Name)))
.ToList(); .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<ExtensionProbeEntry>();
if (Logger.IsEnabled(LogLevel.Debug)) { if (Logger.IsEnabled(LogLevel.Debug)) {
Logger.Debug("Loaders for extension \"{0}\": ", extension.Name); Logger.Debug("Loaders for extension \"{0}\": ", extension.Name);
@@ -91,28 +124,110 @@ namespace Orchard.Environment.Extensions {
} }
var activatedExtension = extensionProbes.FirstOrDefault(); 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) { if (activatedExtension == null) {
Logger.Warning("No loader found for extension \"{0}\"!", extension.Name); Logger.Warning("No loader found for extension \"{0}\"!", extension.Name);
} }
var references = ProcessExtensionReferences(context, activatedExtension);
foreach (var loader in _loaders) { foreach (var loader in _loaders) {
if (activatedExtension != null && activatedExtension.Loader.Name == loader.Name) { if (activatedExtension != null && activatedExtension.Loader.Name == loader.Name) {
Logger.Information("Activating extension \"{0}\" with loader \"{1}\"", activatedExtension.Descriptor.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) { else if (previousDependency != null && previousDependency.LoaderName == loader.Name) {
Logger.Information("Deactivating extension \"{0}\" from loader \"{1}\"", previousDependency.Name, 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) { if (activatedExtension != null) {
newDependencies.Add(new DependencyDescriptor { context.NewDependencies.Add(new DependencyDescriptor {
Name = extension.Name, Name = extension.Name,
LoaderName = activatedExtension.Loader.Name, LoaderName = activatedExtension.Loader.Name,
VirtualPath = activatedExtension.VirtualPath VirtualPath = activatedExtension.VirtualPath,
References = references
});
}
}
IEnumerable<ReferenceDescriptor> ProcessExtensionReferences(ExtensionLoadingContext context, ExtensionProbeEntry activatedExtension) {
if (activatedExtension == null)
return Enumerable.Empty<ReferenceDescriptor>();
var referenceNames = (context.ReferencesByModule.ContainsKey(activatedExtension.Descriptor.Name) ?
context.ReferencesByModule[activatedExtension.Descriptor.Name] :
Enumerable.Empty<ExtensionReferenceEntry>())
.Select(r => r.Name)
.Distinct(StringComparer.OrdinalIgnoreCase);
var referencesDecriptors = new List<ReferenceDescriptor>();
foreach (var referenceName in referenceNames) {
ProcessExtensionReference(context, activatedExtension, referenceName, referencesDecriptors);
}
return referencesDecriptors;
}
private void ProcessExtensionReference(ExtensionLoadingContext context,
ExtensionProbeEntry activatedExtension,
string referenceName,
IList<ReferenceDescriptor> 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<ExtensionReferenceEntry>();
// 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<ExtensionProbeEntry>();
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
}); });
} }
} }

View File

@@ -1,17 +1,61 @@
using System; using System;
using System.Collections.Generic; 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 { namespace Orchard.Environment.Extensions {
public class ExtensionLoadingContext { public class ExtensionLoadingContext {
public ExtensionLoadingContext() { public ExtensionLoadingContext() {
ProcessedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
ProcessedReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
DeleteActions = new List<Action>(); DeleteActions = new List<Action>();
CopyActions = new List<Action>(); CopyActions = new List<Action>();
NewDependencies = new List<DependencyDescriptor>();
} }
public IList<Action> DeleteActions { get; set; } public ISet<string> ProcessedExtensions { get; private set; }
public IList<Action> CopyActions { get; set; } public ISet<string> ProcessedReferences { get; private set; }
public IList<DependencyDescriptor> NewDependencies { get; private set; }
public IList<Action> DeleteActions { get; private set; }
public IList<Action> CopyActions { get; private set; }
public bool RestartAppDomain { get; set; } public bool RestartAppDomain { get; set; }
public bool ResetSiteCompilation { get; set; } public bool ResetSiteCompilation { get; set; }
/// <summary>
/// List of extensions (modules) present in the system
/// </summary>
public List<ExtensionDescriptor> AvailableExtensions { get; set; }
/// <summary>
/// List of extensions (modules) that were loaded during a previous successful run
/// </summary>
public List<DependencyDescriptor> PreviousDependencies { get; set; }
/// <summary>
/// The list of extensions/modules that are were present in the previous successful run
/// and that are not present in the system anymore.
/// </summary>
public List<DependencyDescriptor> DeletedDependencies { get; set; }
/// <summary>
/// For every extension name, the list of loaders that can potentially load
/// that extension (in order of "best-of" applicable)
/// </summary>
public IDictionary<string, IOrderedEnumerable<ExtensionProbeEntry>> AvailableExtensionsProbes { get; set; }
/// <summary>
/// For every reference name, list of potential loaders/locations
/// </summary>
public IDictionary<string, IEnumerable<ExtensionReferenceEntry>> ReferencesByModule { get; set; }
/// <summary>
/// For every extension name, list of references
/// </summary>
public IDictionary<string, IEnumerable<ExtensionReferenceEntry>> ReferencesByName { get; set; }
} }
} }

View File

@@ -2,29 +2,35 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions.Compilers;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.VirtualPath;
using Orchard.Logging; using Orchard.Logging;
using ReferenceDescriptor = Orchard.FileSystems.Dependencies.ReferenceDescriptor;
namespace Orchard.Environment.Extensions.Loaders { namespace Orchard.Environment.Extensions.Loaders {
public class DynamicExtensionLoader : ExtensionLoaderBase { public class DynamicExtensionLoader : ExtensionLoaderBase {
private readonly IBuildManager _buildManager; private readonly IBuildManager _buildManager;
private readonly IVirtualPathProvider _virtualPathProvider; private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IVirtualPathMonitor _virtualPathMonitor; private readonly IVirtualPathMonitor _virtualPathMonitor;
private readonly IProjectFileParser _projectFileParser;
private static readonly ReloadWorkaround _reloadWorkaround = new ReloadWorkaround(); private static readonly ReloadWorkaround _reloadWorkaround = new ReloadWorkaround();
public DynamicExtensionLoader( public DynamicExtensionLoader(
IBuildManager buildManager, IBuildManager buildManager,
IVirtualPathProvider virtualPathProvider, IVirtualPathProvider virtualPathProvider,
IVirtualPathMonitor virtualPathMonitor, IVirtualPathMonitor virtualPathMonitor,
IDependenciesFolder dependenciesFolder) IDependenciesFolder dependenciesFolder,
IProjectFileParser projectFileParser)
: base(dependenciesFolder) { : base(dependenciesFolder) {
_buildManager = buildManager; _buildManager = buildManager;
_virtualPathProvider = virtualPathProvider; _virtualPathProvider = virtualPathProvider;
_virtualPathMonitor = virtualPathMonitor; _virtualPathMonitor = virtualPathMonitor;
_projectFileParser = projectFileParser;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
} }
@@ -74,6 +80,27 @@ namespace Orchard.Environment.Extensions.Loaders {
} }
} }
public override IEnumerable<ExtensionReferenceEntry> ProbeReferences(ExtensionDescriptor descriptor) {
string projectPath = GetProjectPath(descriptor);
if (projectPath == null)
return Enumerable.Empty<ExtensionReferenceEntry>();
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) { public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
string projectPath = GetProjectPath(descriptor); string projectPath = GetProjectPath(descriptor);
if (projectPath == null) if (projectPath == null)

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
@@ -16,6 +17,14 @@ namespace Orchard.Environment.Extensions.Loaders {
public abstract int Order { get; } public abstract int Order { get; }
public string Name { get { return this.GetType().Name; } } public string Name { get { return this.GetType().Name; } }
public virtual IEnumerable<ExtensionReferenceEntry> ProbeReferences(ExtensionDescriptor descriptor) {
return Enumerable.Empty<ExtensionReferenceEntry>();
}
public virtual Assembly LoadReference(ReferenceDescriptor reference) {
return null;
}
public abstract ExtensionProbeEntry Probe(ExtensionDescriptor descriptor); public abstract ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
public ExtensionEntry Load(ExtensionDescriptor descriptor) { public ExtensionEntry Load(ExtensionDescriptor descriptor) {
@@ -26,9 +35,13 @@ namespace Orchard.Environment.Extensions.Loaders {
return null; 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 ExtensionActivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { }
public virtual void ExtensionDeactivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { } public virtual void ExtensionDeactivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { }
public virtual void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) { } public virtual void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) { }
public virtual void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor) { } public virtual void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor) { }
protected abstract ExtensionEntry LoadWorker(ExtensionDescriptor descriptor); protected abstract ExtensionEntry LoadWorker(ExtensionDescriptor descriptor);

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.Dependencies;
@@ -12,10 +13,22 @@ namespace Orchard.Environment.Extensions.Loaders {
public DateTime LastModificationTimeUtc { get; set; } 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 { public interface IExtensionLoader {
int Order { get; } int Order { get; }
string Name { get; } string Name { get; }
IEnumerable<ExtensionReferenceEntry> ProbeReferences(ExtensionDescriptor extensionDescriptor);
Assembly LoadReference(ReferenceDescriptor reference);
void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry);
void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceEntry referenceEntry);
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor); ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
ExtensionEntry Load(ExtensionDescriptor descriptor); ExtensionEntry Load(ExtensionDescriptor descriptor);

View File

@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies; 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<IVolatileToken> monitor) { public override void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
string assemblyPath = GetAssemblyPath(descriptor); string assemblyPath = GetAssemblyPath(descriptor);
if (assemblyPath != null) { if (assemblyPath != null) {
@@ -101,6 +125,23 @@ namespace Orchard.Environment.Extensions.Loaders {
} }
} }
public override IEnumerable<ExtensionReferenceEntry> ProbeReferences(ExtensionDescriptor descriptor) {
var assemblyPath = GetAssemblyPath(descriptor);
if (assemblyPath == null)
return Enumerable.Empty<ExtensionReferenceEntry>();
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) { public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
var assemblyPath = GetAssemblyPath(descriptor); var assemblyPath = GetAssemblyPath(descriptor);
if (assemblyPath == null) 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) { protected override ExtensionEntry LoadWorker(ExtensionDescriptor descriptor) {
var assembly = _assemblyProbingFolder.LoadAssembly(descriptor.Name); var assembly = _assemblyProbingFolder.LoadAssembly(descriptor.Name);
if (assembly == null) if (assembly == null)

View File

@@ -1,9 +1,12 @@
using System.CodeDom; using System;
using System.CodeDom;
using System.Reflection;
using System.Web.Compilation; using System.Web.Compilation;
namespace Orchard.Environment { namespace Orchard.Environment {
public interface IAssemblyBuilder { public interface IAssemblyBuilder {
void AddCodeCompileUnit(CodeCompileUnit compileUnit); void AddCodeCompileUnit(CodeCompileUnit compileUnit);
void AddAssemblyReference(Assembly assembly);
} }
public class AspNetAssemblyBuilder : IAssemblyBuilder { public class AspNetAssemblyBuilder : IAssemblyBuilder {
@@ -18,5 +21,9 @@ namespace Orchard.Environment {
public void AddCodeCompileUnit(CodeCompileUnit compileUnit) { public void AddCodeCompileUnit(CodeCompileUnit compileUnit) {
_assemblyBuilder.AddCodeCompileUnit(_buildProvider, compileUnit); _assemblyBuilder.AddCodeCompileUnit(_buildProvider, compileUnit);
} }
public void AddAssemblyReference(Assembly assembly) {
_assemblyBuilder.AddAssemblyReference(assembly);
}
} }
} }

View File

@@ -69,9 +69,12 @@ namespace Orchard.FileSystems.Dependencies {
.Select(e => new DependencyDescriptor { .Select(e => new DependencyDescriptor {
Name = elem(e, "ModuleName"), Name = elem(e, "ModuleName"),
VirtualPath = elem(e, "VirtualPath"), VirtualPath = elem(e, "VirtualPath"),
LoaderName = elem(e, "LoaderName") LoaderName = elem(e, "LoaderName"),
}) References = e.Elements(ns("References")).Elements(ns("Reference")).Select(r => new ReferenceDescriptor {
.ToList(); 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", var elements = dependencies.Select(d => new XElement("Dependency",
new XElement(ns("ModuleName"), d.Name), new XElement(ns("ModuleName"), d.Name),
new XElement(ns("VirtualPath"), d.VirtualPath), 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); document.Root.Add(elements);
using (var stream = _appDataFolder.CreateFile(persistancePath)) { using (var stream = _appDataFolder.CreateFile(persistancePath)) {
@@ -99,7 +108,27 @@ namespace Orchard.FileSystems.Dependencies {
} }
private class DependencyDescriptorComparer : EqualityComparer<DependencyDescriptor> { private class DependencyDescriptorComparer : EqualityComparer<DependencyDescriptor> {
private readonly ReferenceDescriptorComparer _referenceDescriptorComparer = new ReferenceDescriptorComparer();
public override bool Equals(DependencyDescriptor x, DependencyDescriptor y) { 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<ReferenceDescriptor> {
public override bool Equals(ReferenceDescriptor x, ReferenceDescriptor y) {
return return
StringComparer.OrdinalIgnoreCase.Equals(x.Name, y.Name) && StringComparer.OrdinalIgnoreCase.Equals(x.Name, y.Name) &&
StringComparer.OrdinalIgnoreCase.Equals(x.LoaderName, y.LoaderName) && 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 return
StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name) ^
StringComparer.OrdinalIgnoreCase.GetHashCode(obj.LoaderName) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.LoaderName) ^

View File

@@ -6,6 +6,13 @@ namespace Orchard.FileSystems.Dependencies {
public string Name { get; set; } public string Name { get; set; }
public string LoaderName { get; set; } public string LoaderName { get; set; }
public string VirtualPath { get; set; } public string VirtualPath { get; set; }
public IEnumerable<ReferenceDescriptor> 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 { public interface IDependenciesFolder : IVolatileProvider {

View File

@@ -1,4 +1,7 @@
using System.IO; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Hosting; using System.Web.Hosting;
namespace Orchard.FileSystems.VirtualPath { namespace Orchard.FileSystems.VirtualPath {
@@ -7,6 +10,14 @@ namespace Orchard.FileSystems.VirtualPath {
return Path.GetDirectoryName(virtualPath).Replace(Path.DirectorySeparatorChar, '/'); return Path.GetDirectoryName(virtualPath).Replace(Path.DirectorySeparatorChar, '/');
} }
public IEnumerable<string> ListFiles(string path) {
return HostingEnvironment.VirtualPathProvider.GetDirectory(path).Files.OfType<VirtualFile>().Select(f => f.VirtualPath);
}
public IEnumerable<string> ListDirectories(string path) {
return HostingEnvironment.VirtualPathProvider.GetDirectory(path).Directories.OfType<VirtualDirectory>().Select(d => d.VirtualPath);
}
public string Combine(params string[] paths) { public string Combine(params string[] paths) {
return Path.Combine(paths).Replace(Path.DirectorySeparatorChar, '/'); return Path.Combine(paths).Replace(Path.DirectorySeparatorChar, '/');
} }
@@ -19,6 +30,10 @@ namespace Orchard.FileSystems.VirtualPath {
return File.CreateText(MapPath(virtualPath)); return File.CreateText(MapPath(virtualPath));
} }
public DateTime GetFileLastWriteTimeUtc(string virtualPath) {
return File.GetLastWriteTimeUtc(MapPath(virtualPath));
}
public string MapPath(string virtualPath) { public string MapPath(string virtualPath) {
return HostingEnvironment.MapPath(virtualPath); return HostingEnvironment.MapPath(virtualPath);
} }

View File

@@ -1,4 +1,6 @@
using System.IO; using System;
using System.Collections.Generic;
using System.IO;
using Orchard.Caching; using Orchard.Caching;
namespace Orchard.FileSystems.VirtualPath { namespace Orchard.FileSystems.VirtualPath {
@@ -9,9 +11,13 @@ namespace Orchard.FileSystems.VirtualPath {
bool FileExists(string virtualPath); bool FileExists(string virtualPath);
Stream OpenFile(string virtualPath); Stream OpenFile(string virtualPath);
StreamWriter CreateText(string virtualPath); StreamWriter CreateText(string virtualPath);
DateTime GetFileLastWriteTimeUtc(string virtualPath);
bool DirectoryExists(string virtualPath); bool DirectoryExists(string virtualPath);
void CreateDirectory(string virtualPath); void CreateDirectory(string virtualPath);
string GetDirectoryName(string virtualPath); string GetDirectoryName(string virtualPath);
IEnumerable<string> ListFiles(string path);
IEnumerable<string> ListDirectories(string path);
} }
} }