#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
This commit is contained in:
Sipke Schoorstra
2015-08-12 15:13:03 +01:00
parent 4cd88cb9c9
commit 08a3285e4f
5 changed files with 70 additions and 22 deletions

View File

@@ -10,7 +10,6 @@ using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.Environment.ShellBuilders; using Orchard.Environment.ShellBuilders;
using Orchard.Tests.Environment.TestDependencies; using Orchard.Tests.Environment.TestDependencies;
using Orchard.Utility;
using Orchard.Utility.Extensions; using Orchard.Utility.Extensions;
namespace Orchard.Tests.Environment.ShellBuilders { 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)); 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() { private ShellSettings CreateShell() {
return new ShellSettings(); return new ShellSettings();
} }

View File

@@ -15,7 +15,11 @@ namespace Orchard.Environment.Extensions {
public static class ExtensionManagerExtensions { public static class ExtensionManagerExtensions {
public static IEnumerable<FeatureDescriptor> EnabledFeatures(this IExtensionManager extensionManager, ShellDescriptor descriptor) { public static IEnumerable<FeatureDescriptor> 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<FeatureDescriptor> EnabledFeatures(this IExtensionManager extensionManager, IEnumerable<ShellFeature> features) {
return extensionManager.AvailableFeatures().Where(fd => features.Any(sf => sf.Name == fd.Id));
} }
} }
} }

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers; using System.Web.Http.Controllers;
using System.Web.Mvc; using System.Web.Mvc;
using Autofac.Core; using Autofac.Core;
@@ -15,17 +14,6 @@ using Orchard.Environment.ShellBuilders.Models;
using Orchard.Logging; using Orchard.Logging;
namespace Orchard.Environment.ShellBuilders { namespace Orchard.Environment.ShellBuilders {
/// <summary>
/// Service at the host level to transform the cachable descriptor into the loadable blueprint.
/// </summary>
public interface ICompositionStrategy {
/// <summary>
/// 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.
/// </summary>
ShellBlueprint Compose(ShellSettings settings, ShellDescriptor descriptor);
}
public class CompositionStrategy : ICompositionStrategy { public class CompositionStrategy : ICompositionStrategy {
private readonly IExtensionManager _extensionManager; private readonly IExtensionManager _extensionManager;
@@ -40,14 +28,19 @@ namespace Orchard.Environment.ShellBuilders {
public ShellBlueprint Compose(ShellSettings settings, ShellDescriptor descriptor) { public ShellBlueprint Compose(ShellSettings settings, ShellDescriptor descriptor) {
Logger.Debug("Composing blueprint"); Logger.Debug("Composing blueprint");
var enabledFeatures = _extensionManager.EnabledFeatures(descriptor); var builtinFeatures = BuiltinFeatures().ToList();
var features = _extensionManager.LoadFeatures(enabledFeatures); 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")) if (descriptor.Features.Any(feature => feature.Name == "Orchard.Framework"))
features = BuiltinFeatures().Concat(features); features = builtinFeatures.Concat(features);
var excludedTypes = GetExcludedTypes(features); var excludedTypes = GetExcludedTypes(features);
var modules = BuildBlueprint(features, IsModule, BuildModule, excludedTypes); var modules = BuildBlueprint(features, IsModule, BuildModule, excludedTypes);
var dependencies = BuildBlueprint(features, IsDependency, (t, f) => BuildDependency(t, f, descriptor), excludedTypes); var dependencies = BuildBlueprint(features, IsDependency, (t, f) => BuildDependency(t, f, descriptor), excludedTypes);
var controllers = BuildBlueprint(features, IsController, BuildController, excludedTypes); var controllers = BuildBlueprint(features, IsController, BuildController, excludedTypes);
@@ -64,15 +57,40 @@ namespace Orchard.Environment.ShellBuilders {
}; };
Logger.Debug("Done composing blueprint."); 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; return result;
} }
private IEnumerable<string> ExpandDependencies(IDictionary<string, FeatureDescriptor> availableFeatures, IEnumerable<string> features) {
return ExpandDependenciesInternal(availableFeatures, features).Distinct();
}
private IEnumerable<string> ExpandDependenciesInternal(IDictionary<string, FeatureDescriptor> availableFeatures, IEnumerable<string> 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<string> GetExcludedTypes(IEnumerable<Feature> features) { private static IEnumerable<string> GetExcludedTypes(IEnumerable<Feature> features) {
var excludedTypes = new HashSet<string>(); var excludedTypes = new HashSet<string>();
// Identify replaced types // Identify replaced types.
foreach (Feature feature in features) { foreach (var feature in features) {
foreach (Type type in feature.ExportedTypes) { foreach (var type in feature.ExportedTypes) {
foreach (OrchardSuppressDependencyAttribute replacedType in type.GetCustomAttributes(typeof(OrchardSuppressDependencyAttribute), false)) { foreach (OrchardSuppressDependencyAttribute replacedType in type.GetCustomAttributes(typeof(OrchardSuppressDependencyAttribute), false)) {
excludedTypes.Add(replacedType.FullName); excludedTypes.Add(replacedType.FullName);
} }
@@ -159,7 +177,7 @@ namespace Orchard.Environment.ShellBuilders {
private static bool IsRecord(Type type) { private static bool IsRecord(Type type) {
return ((type.Namespace ?? "").EndsWith(".Models") || (type.Namespace ?? "").EndsWith(".Records")) && return ((type.Namespace ?? "").EndsWith(".Models") || (type.Namespace ?? "").EndsWith(".Records")) &&
type.GetProperty("Id") != null && type.GetProperty("Id") != null &&
(type.GetProperty("Id").GetAccessors() ?? Enumerable.Empty<MethodInfo>()).All(x => x.IsVirtual) && (type.GetProperty("Id").GetAccessors()).All(x => x.IsVirtual) &&
!type.IsSealed && !type.IsSealed &&
!type.IsAbstract && !type.IsAbstract &&
(!typeof(IContent).IsAssignableFrom(type) || typeof(ContentPartRecord).IsAssignableFrom(type)); (!typeof(IContent).IsAssignableFrom(type) || typeof(ContentPartRecord).IsAssignableFrom(type));

View File

@@ -0,0 +1,16 @@
using Orchard.Environment.Configuration;
using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.ShellBuilders.Models;
namespace Orchard.Environment.ShellBuilders {
/// <summary>
/// Service at the host level to transform the cachable descriptor into the loadable blueprint.
/// </summary>
public interface ICompositionStrategy {
/// <summary>
/// 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.
/// </summary>
ShellBlueprint Compose(ShellSettings settings, ShellDescriptor descriptor);
}
}

View File

@@ -148,6 +148,7 @@
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Environment\ShellBuilders\ICompositionStrategy.cs" />
<Compile Include="Localization\Services\ILocalizationStreamParser.cs" /> <Compile Include="Localization\Services\ILocalizationStreamParser.cs" />
<Compile Include="Localization\Services\LocalizationStreamParser.cs" /> <Compile Include="Localization\Services\LocalizationStreamParser.cs" />
<Compile Include="Security\ISslSettingsProvider.cs" /> <Compile Include="Security\ISslSettingsProvider.cs" />