mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
#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:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user