From 08a3285e4f66e96c2a26d0bbfd256bc70aee3ba7 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 12 Aug 2015 15:13:03 +0100 Subject: [PATCH] #3132: Auto-enabling dependency features. This makes sure that any dependency of a feature that is currently not enabled gets enabled automatically before the shell is activated. This prevents potential autofac resolution issues if one service depends on another service provided by a feature that is disabled. Fixes #3132 --- .../ShellBuilders/CompositionStrategyTests.cs | 11 +++- .../Extensions/IExtensionManager.cs | 6 +- .../ShellBuilders/CompositionStrategy.cs | 58 ++++++++++++------- .../ShellBuilders/ICompositionStrategy.cs | 16 +++++ src/Orchard/Orchard.Framework.csproj | 1 + 5 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 src/Orchard/Environment/ShellBuilders/ICompositionStrategy.cs diff --git a/src/Orchard.Tests/Environment/ShellBuilders/CompositionStrategyTests.cs b/src/Orchard.Tests/Environment/ShellBuilders/CompositionStrategyTests.cs index d41f5d087..bb6780415 100644 --- a/src/Orchard.Tests/Environment/ShellBuilders/CompositionStrategyTests.cs +++ b/src/Orchard.Tests/Environment/ShellBuilders/CompositionStrategyTests.cs @@ -10,7 +10,6 @@ using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; using Orchard.Environment.ShellBuilders; using Orchard.Tests.Environment.TestDependencies; -using Orchard.Utility; using Orchard.Utility.Extensions; namespace Orchard.Tests.Environment.ShellBuilders { @@ -92,6 +91,16 @@ namespace Orchard.Tests.Environment.ShellBuilders { Assert.That(shellBlueprint.Dependencies.Count(x => x.Type == typeof(BetaDependency)), Is.EqualTo(1)); } + [Test] + public void ComposeReturnsBlueprintWithAutoEnabledDependencyFeatures() { + var shellSettings = CreateShell(); + var shellDescriptor = CreateShellDescriptor("Beta"); // Beta has a dependency on Alpha, but is not enabled initially. + var shellBlueprint = _compositionStrategy.Compose(shellSettings, shellDescriptor); + + Assert.That(shellBlueprint.Dependencies.Count(x => x.Type == typeof(AlphaDependency)), Is.EqualTo(1)); + Assert.That(shellDescriptor.Features.Count(x => x.Name == "Alpha"), Is.EqualTo(1)); + } + private ShellSettings CreateShell() { return new ShellSettings(); } diff --git a/src/Orchard/Environment/Extensions/IExtensionManager.cs b/src/Orchard/Environment/Extensions/IExtensionManager.cs index b79f0b95c..97fdbadea 100644 --- a/src/Orchard/Environment/Extensions/IExtensionManager.cs +++ b/src/Orchard/Environment/Extensions/IExtensionManager.cs @@ -15,7 +15,11 @@ namespace Orchard.Environment.Extensions { public static class ExtensionManagerExtensions { public static IEnumerable EnabledFeatures(this IExtensionManager extensionManager, ShellDescriptor descriptor) { - return extensionManager.AvailableFeatures().Where(fd => descriptor.Features.Any(sf => sf.Name == fd.Id)); + return EnabledFeatures(extensionManager, descriptor.Features); + } + + public static IEnumerable EnabledFeatures(this IExtensionManager extensionManager, IEnumerable features) { + return extensionManager.AvailableFeatures().Where(fd => features.Any(sf => sf.Name == fd.Id)); } } } diff --git a/src/Orchard/Environment/ShellBuilders/CompositionStrategy.cs b/src/Orchard/Environment/ShellBuilders/CompositionStrategy.cs index 1d91cae30..f0aebbbda 100644 --- a/src/Orchard/Environment/ShellBuilders/CompositionStrategy.cs +++ b/src/Orchard/Environment/ShellBuilders/CompositionStrategy.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Web.Http.Controllers; using System.Web.Mvc; using Autofac.Core; @@ -15,17 +14,6 @@ using Orchard.Environment.ShellBuilders.Models; using Orchard.Logging; namespace Orchard.Environment.ShellBuilders { - /// - /// Service at the host level to transform the cachable descriptor into the loadable blueprint. - /// - public interface ICompositionStrategy { - /// - /// Using information from the IExtensionManager, transforms and populates all of the - /// blueprint model the shell builders will need to correctly initialize a tenant IoC container. - /// - ShellBlueprint Compose(ShellSettings settings, ShellDescriptor descriptor); - } - public class CompositionStrategy : ICompositionStrategy { private readonly IExtensionManager _extensionManager; @@ -40,14 +28,19 @@ namespace Orchard.Environment.ShellBuilders { public ShellBlueprint Compose(ShellSettings settings, ShellDescriptor descriptor) { Logger.Debug("Composing blueprint"); - var enabledFeatures = _extensionManager.EnabledFeatures(descriptor); - var features = _extensionManager.LoadFeatures(enabledFeatures); + var builtinFeatures = BuiltinFeatures().ToList(); + var builtinFeatureDescriptors = builtinFeatures.Select(x => x.Descriptor).ToList(); + var availableFeatures = _extensionManager.AvailableFeatures().Concat(builtinFeatureDescriptors).ToDictionary(x => x.Id); + var enabledFeatures = _extensionManager.EnabledFeatures(descriptor).Select(x => x.Id).ToList(); + var expandedFeatures = ExpandDependencies(availableFeatures, descriptor.Features.Select(x => x.Name)).ToList(); + var autoEnabledDependencyFeatures = expandedFeatures.Except(enabledFeatures).Except(builtinFeatureDescriptors.Select(x => x.Id)).ToList(); + var featureDescriptors = _extensionManager.EnabledFeatures(expandedFeatures.Select(x => new ShellFeature { Name = x})).ToList(); + var features = _extensionManager.LoadFeatures(featureDescriptors); if (descriptor.Features.Any(feature => feature.Name == "Orchard.Framework")) - features = BuiltinFeatures().Concat(features); + features = builtinFeatures.Concat(features); var excludedTypes = GetExcludedTypes(features); - var modules = BuildBlueprint(features, IsModule, BuildModule, excludedTypes); var dependencies = BuildBlueprint(features, IsDependency, (t, f) => BuildDependency(t, f, descriptor), excludedTypes); var controllers = BuildBlueprint(features, IsController, BuildController, excludedTypes); @@ -64,15 +57,40 @@ namespace Orchard.Environment.ShellBuilders { }; Logger.Debug("Done composing blueprint."); + + if (autoEnabledDependencyFeatures.Any()) { + // Add any dependencies previously not enabled to the shell descriptor. + descriptor.Features = descriptor.Features.Concat(autoEnabledDependencyFeatures.Select(x => new ShellFeature { Name = x })).ToList(); + Logger.Information("Automatically enabled the following dependency features: {0}.", String.Join(", ", autoEnabledDependencyFeatures)); + } + return result; } + private IEnumerable ExpandDependencies(IDictionary availableFeatures, IEnumerable features) { + return ExpandDependenciesInternal(availableFeatures, features).Distinct(); + } + + private IEnumerable ExpandDependenciesInternal(IDictionary availableFeatures, IEnumerable features) { + foreach (var shellFeature in features) { + var feature = availableFeatures[shellFeature]; + + foreach (var childDependency in ExpandDependenciesInternal(availableFeatures, feature.Dependencies)) + yield return childDependency; + + foreach (var dependency in feature.Dependencies) + yield return dependency; + + yield return shellFeature; + } + } + private static IEnumerable GetExcludedTypes(IEnumerable features) { var excludedTypes = new HashSet(); - // Identify replaced types - foreach (Feature feature in features) { - foreach (Type type in feature.ExportedTypes) { + // Identify replaced types. + foreach (var feature in features) { + foreach (var type in feature.ExportedTypes) { foreach (OrchardSuppressDependencyAttribute replacedType in type.GetCustomAttributes(typeof(OrchardSuppressDependencyAttribute), false)) { excludedTypes.Add(replacedType.FullName); } @@ -159,7 +177,7 @@ namespace Orchard.Environment.ShellBuilders { private static bool IsRecord(Type type) { return ((type.Namespace ?? "").EndsWith(".Models") || (type.Namespace ?? "").EndsWith(".Records")) && type.GetProperty("Id") != null && - (type.GetProperty("Id").GetAccessors() ?? Enumerable.Empty()).All(x => x.IsVirtual) && + (type.GetProperty("Id").GetAccessors()).All(x => x.IsVirtual) && !type.IsSealed && !type.IsAbstract && (!typeof(IContent).IsAssignableFrom(type) || typeof(ContentPartRecord).IsAssignableFrom(type)); diff --git a/src/Orchard/Environment/ShellBuilders/ICompositionStrategy.cs b/src/Orchard/Environment/ShellBuilders/ICompositionStrategy.cs new file mode 100644 index 000000000..449dcd15e --- /dev/null +++ b/src/Orchard/Environment/ShellBuilders/ICompositionStrategy.cs @@ -0,0 +1,16 @@ +using Orchard.Environment.Configuration; +using Orchard.Environment.Descriptor.Models; +using Orchard.Environment.ShellBuilders.Models; + +namespace Orchard.Environment.ShellBuilders { + /// + /// Service at the host level to transform the cachable descriptor into the loadable blueprint. + /// + public interface ICompositionStrategy { + /// + /// Using information from the IExtensionManager, transforms and populates all of the + /// blueprint model the shell builders will need to correctly initialize a tenant IoC container. + /// + ShellBlueprint Compose(ShellSettings settings, ShellDescriptor descriptor); + } +} \ No newline at end of file diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index ee62487ba..050544824 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -148,6 +148,7 @@ +