Incremental change refactoring modularity

--HG--
branch : dev
This commit is contained in:
Louis DeJardin
2010-04-16 20:50:01 -07:00
parent 21f5d0f32a
commit ff96ad73ea
25 changed files with 320 additions and 70 deletions

View File

@@ -1,29 +1,133 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using Moq;
using NUnit.Framework;
using Orchard.Environment;
using Orchard.Environment.Topology;
using Orchard.Environment.Topology.Models;
using Orchard.Extensions;
using Orchard.Tests.ContentManagement.Records;
using Orchard.Tests.ContentManagement.Models;
using Orchard.Extensions.Models;
using Orchard.Tests.Utility;
namespace Orchard.Tests.Environment {
[TestFixture]
public class DefaultCompositionStrategyTests {
[Test]
public void ExpectedRecordsShouldComeBack() {
var extensionManager = new Moq.Mock<IExtensionManager>();
extensionManager.Setup(x => x.ActiveExtensions()).Returns(new[] {
new ExtensionEntry {
Descriptor = new ExtensionDescriptor { Name = "Test" },
ExportedTypes = new[] { typeof(GammaRecord), typeof(DeltaRecord), typeof(Delta) }
}
});
var strategy = new DefaultCompositionStrategy(extensionManager.Object);
var recordTypes = strategy.GetRecordDescriptors().Select(d => d.Type);
Assert.That(recordTypes.Count(), Is.Not.EqualTo(0));
Assert.That(recordTypes, Has.Some.EqualTo(typeof(DeltaRecord)));
Assert.That(recordTypes, Has.Some.EqualTo(typeof(GammaRecord)));
Assert.That(recordTypes, Has.None.EqualTo(typeof(Delta)));
private IContainer _container;
private IEnumerable<ExtensionDescriptor> _extensionDescriptors;
private IDictionary<string, IEnumerable<Type>> _featureTypes;
[SetUp]
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<DefaultCompositionStrategy>().As<ICompositionStrategy>();
builder.RegisterAutoMocking(MockBehavior.Strict);
_container = builder.Build();
_extensionDescriptors = Enumerable.Empty<ExtensionDescriptor>();
_featureTypes = new Dictionary<string, IEnumerable<Type>>();
_container.Mock<IExtensionManager>()
.Setup(x => x.AvailableExtensions())
.Returns(() => _extensionDescriptors);
_container.Mock<IExtensionManager>()
.Setup(x => x.LoadFeatures(It.IsAny<IEnumerable<FeatureDescriptor>>()))
.Returns((IEnumerable<FeatureDescriptor> x) => StubLoadFeatures(x));
}
private IEnumerable<Feature> StubLoadFeatures(IEnumerable<FeatureDescriptor> featureDescriptors) {
return featureDescriptors.Select(featureDescriptor => new Feature {
FeatureDescriptor = featureDescriptor,
ExportedTypes = _featureTypes[featureDescriptor.Name]
});
}
[Test]
public void TopologyIsNotNull() {
var descriptor = Build.TopologyDescriptor();
var compositionStrategy = _container.Resolve<ICompositionStrategy>();
var topology = compositionStrategy.Compose(descriptor);
Assert.That(topology, Is.Not.Null);
}
[Test]
public void DependenciesFromFeatureArePutIntoTopology() {
var descriptor = Build.TopologyDescriptor().WithFeatures("Foo", "Bar");
_extensionDescriptors = new[] {
Build.ExtensionDescriptor("Foo").WithFeatures("Foo"),
Build.ExtensionDescriptor("Bar").WithFeatures("Bar"),
};
_featureTypes["Foo"] = new[] { typeof(FooService1) };
_featureTypes["Bar"] = new[] { typeof(BarService1) };
var compositionStrategy = _container.Resolve<ICompositionStrategy>();
var topology = compositionStrategy.Compose(descriptor);
Assert.That(topology, Is.Not.Null);
Assert.That(topology.Dependencies.Count(), Is.EqualTo(2));
var foo = topology.Dependencies.SingleOrDefault(t => t.Type == typeof (FooService1));
Assert.That(foo, Is.Not.Null);
Assert.That(foo.Feature.FeatureDescriptor.Name, Is.EqualTo("Foo"));
var bar = topology.Dependencies.SingleOrDefault(t => t.Type == typeof(BarService1));
Assert.That(bar, Is.Not.Null);
Assert.That(bar.Feature.FeatureDescriptor.Name, Is.EqualTo("Bar"));
}
public interface IFooService : IDependency {
}
public class FooService1 : IFooService {
}
public interface IBarService : IDependency {
}
public class BarService1 : IBarService {
}
}
static class Build {
public static ShellTopologyDescriptor TopologyDescriptor() {
var descriptor = new ShellTopologyDescriptor {
EnabledFeatures = Enumerable.Empty<TopologyFeature>(),
};
return descriptor;
}
public static ShellTopologyDescriptor WithFeatures(this ShellTopologyDescriptor descriptor, params string[] names) {
descriptor.EnabledFeatures = descriptor.EnabledFeatures.Concat(
names.Select(name => new TopologyFeature { Name = name }));
return descriptor;
}
public static ExtensionDescriptor ExtensionDescriptor(string name) {
var descriptor = new ExtensionDescriptor {
Name = name,
DisplayName = name,
Features = Enumerable.Empty<FeatureDescriptor>(),
};
return descriptor;
}
public static ExtensionDescriptor WithFeatures(this ExtensionDescriptor descriptor, params string[] names) {
descriptor.Features = descriptor.Features.Concat(
names.Select(name => new FeatureDescriptor() { Name = name }));
return descriptor;
}
}
}

View File

@@ -14,6 +14,7 @@ using Orchard.Environment.Configuration;
using Orchard.Environment.ShellBuilders;
using Orchard.Environment.Topology;
using Orchard.Environment.Topology.Models;
using Orchard.Extensions.Models;
using Orchard.Mvc;
using Orchard.Mvc.ModelBinders;
using Orchard.Mvc.Routes;
@@ -78,6 +79,10 @@ namespace Orchard.Tests.Environment {
return Enumerable.Empty<ExtensionDescriptor>();
}
public IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> features) {
throw new NotImplementedException();
}
public IEnumerable<ExtensionEntry> ActiveExtensions() {
return Enumerable.Empty<ExtensionEntry>();
}

View File

@@ -1,17 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Autofac;
using Autofac.Core.Registration;
using Moq;
using NUnit.Framework;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Environment.ShellBuilders;
using Orchard.Environment.Topology;
using Orchard.Environment.Topology.Models;
using Orchard.Tests.Utility;
namespace Orchard.Tests.Environment.ShellBuilders {
[TestFixture]
@@ -22,7 +17,7 @@ namespace Orchard.Tests.Environment.ShellBuilders {
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<DefaultShellContextFactory>().As<IShellContextFactory>();
builder.RegisterSource(new MockServiceSource());
builder.RegisterAutoMocking();
_container = builder.Build();
}
@@ -60,39 +55,4 @@ namespace Orchard.Tests.Environment.ShellBuilders {
Assert.That(context.Shell, Is.SameAs(shellLifetimeScope.Resolve<IOrchardShell>()));
}
}
class MockServiceSource : IRegistrationSource {
public IEnumerable<IComponentRegistration> RegistrationsFor(
Service service,
Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) {
var swt = service as IServiceWithType;
if (swt == null)
yield break;
var st = swt.ServiceType;
if (st.IsGenericType && st.GetGenericTypeDefinition() == typeof(Mock<>)) {
yield return RegistrationBuilder.ForType(st).SingleInstance().CreateRegistration();
}
else if (st.IsInterface) {
yield return RegistrationBuilder.ForDelegate(
(ctx, p) => {
Trace.WriteLine(string.Format("Mocking {0}", st));
var mt = typeof(Mock<>).MakeGenericType(st);
var m = (Mock)ctx.Resolve(mt);
return m.Object;
})
.As(service)
.SingleInstance()
.CreateRegistration();
}
}
}
public static class MockExtensions {
public static Mock<T> Mock<T>(this IContainer container) where T : class {
return container.Resolve<Mock<T>>();
}
}
}

View File

@@ -5,6 +5,7 @@ using Autofac;
using NUnit.Framework;
using Orchard.Extensions;
using Orchard.Extensions.Loaders;
using Orchard.Extensions.Models;
using Orchard.Tests.Extensions.ExtensionTypes;
using Yaml.Grammar;

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Web;
using System.Web.Routing;
using NUnit.Framework;
using Orchard.Extensions.Models;
using Orchard.Mvc.Routes;
using Orchard.Extensions;
@@ -39,6 +40,10 @@ namespace Orchard.Tests.Mvc.Routes {
throw new NotImplementedException();
}
public IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> features) {
throw new NotImplementedException();
}
public IEnumerable<ExtensionEntry> ActiveExtensions() {
yield return new ExtensionEntry {
Descriptor = new ExtensionDescriptor {

View File

@@ -165,6 +165,7 @@
<Compile Include="Environment\OrchardStarterTests.cs" />
<Compile Include="Environment\ShellBuilders\DefaultShellContainerFactoryTests.cs" />
<Compile Include="Environment\ShellBuilders\DefaultShellContextFactoryTests.cs" />
<Compile Include="Utility\ContainerExtensions.cs" />
<Compile Include="Environment\ShellBuilders\SetupShellContainerFactoryTests.cs" />
<Compile Include="Environment\TestDependencies\TestDependency.cs" />
<Compile Include="Environment\Topology\DefaultTopologyDescriptorCacheTests.cs" />

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Moq;
namespace Orchard.Tests.Utility {
public static class ContainerExtensions {
public static Mock<T> Mock<T>(this IContainer container) where T : class {
return container.Resolve<Mock<T>>();
}
public static void RegisterAutoMocking(this ContainerBuilder builder) {
builder.RegisterSource(new AutoMockSource(MockBehavior.Default));
}
public static void RegisterAutoMocking(this ContainerBuilder builder, MockBehavior behavior) {
builder.RegisterSource(new AutoMockSource(behavior));
}
class AutoMockSource : IRegistrationSource {
private readonly MockBehavior _behavior;
public AutoMockSource(MockBehavior behavior) {
_behavior = behavior;
}
public IEnumerable<IComponentRegistration> RegistrationsFor(
Service service,
Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) {
var swt = service as IServiceWithType;
if (swt == null)
yield break;
var st = swt.ServiceType;
if (st.IsGenericType && st.GetGenericTypeDefinition() == typeof(Mock<>)) {
yield return RegistrationBuilder.ForType(st)
.SingleInstance()
.WithParameter("behavior", _behavior)
.CreateRegistration();
}
else if (st.IsInterface) {
yield return RegistrationBuilder.ForDelegate(
(ctx, p) => {
Trace.WriteLine(string.Format("Mocking {0}", st));
var mt = typeof(Mock<>).MakeGenericType(st);
var m = (Mock)ctx.Resolve(mt);
return m.Object;
})
.As(service)
.SingleInstance()
.CreateRegistration();
}
}
}
}
}

View File

@@ -2,10 +2,14 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using Autofac.Core;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
using Orchard.Environment.Topology;
using Orchard.Environment.Topology.Models;
using Orchard.Extensions;
using Orchard.Extensions.Models;
using Orchard.Utility.Extensions;
using Orchard.Extensions.Records;
@@ -23,13 +27,83 @@ namespace Orchard.Environment {
public string Prefix { get; set; }
}
public class DefaultCompositionStrategy : ICompositionStrategy_Obsolete {
public class DefaultCompositionStrategy : ICompositionStrategy, ICompositionStrategy_Obsolete {
private readonly IExtensionManager _extensionManager;
public DefaultCompositionStrategy(IExtensionManager extensionManager) {
_extensionManager = extensionManager;
}
public ShellTopology Compose(ShellTopologyDescriptor topologyDescriptor) {
var featureDescriptors = _extensionManager.AvailableExtensions()
.SelectMany(extensionDescriptor => extensionDescriptor.Features)
.Where(featureDescriptor => IsFeatureEnabledInTopology(featureDescriptor, topologyDescriptor));
var features = _extensionManager.LoadFeatures(featureDescriptors);
return new ShellTopology {
Modules = BuildTopology<ModuleTopology>(features, IsModule, BuildModule),
Dependencies = BuildTopology<DependencyTopology>(features, IsDependency, BuildDependency),
Controllers = BuildTopology<ControllerTopology>(features, IsController, BuildController),
Records = BuildTopology<RecordTopology>(features, IsRecord, BuildRecord),
};
}
private static bool IsFeatureEnabledInTopology(FeatureDescriptor featureDescriptor, ShellTopologyDescriptor topologyDescriptor) {
return topologyDescriptor.EnabledFeatures.Any(topologyFeature => topologyFeature.Name == featureDescriptor.Name);
}
private static IEnumerable<T> BuildTopology<T>(
IEnumerable<Feature> features,
Func<Type, bool> predicate,
Func<Type, Feature, T> selector) {
return features.SelectMany(
feature => feature.ExportedTypes
.Where(predicate)
.Select(type => selector(type, feature)))
.ToArray();
}
private static bool IsModule(Type type) {
return typeof(IModule).IsAssignableFrom(type);
}
private static ModuleTopology BuildModule(Type type, Feature feature) {
return new ModuleTopology { Type = type, Feature = feature };
}
private static bool IsDependency(Type type) {
return typeof(IDependency).IsAssignableFrom(type);
}
private static DependencyTopology BuildDependency(Type type, Feature feature) {
return new DependencyTopology {Type = type, Feature = feature};
}
private static bool IsController(Type type) {
return typeof(IController).IsAssignableFrom(type);
}
private static ControllerTopology BuildController(Type type, Feature feature) {
return new ControllerTopology { Type = type, Feature = feature };
}
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<MethodInfo>()).All(x => x.IsVirtual) &&
!type.IsSealed &&
!type.IsAbstract &&
(!typeof(IContent).IsAssignableFrom(type) || typeof(ContentPartRecord).IsAssignableFrom(type));;
}
private static RecordTopology BuildRecord(Type type, Feature feature) {
return new RecordTopology { Type = type, Feature = feature };
}
public IEnumerable<Type> GetModuleTypes() {
return _extensionManager.GetExtensionsTopology().Types.Where(t => typeof(IModule).IsAssignableFrom(t));
}
@@ -70,5 +144,6 @@ namespace Orchard.Environment {
!type.IsAbstract &&
(!typeof(IContent).IsAssignableFrom(type) || typeof(ContentPartRecord).IsAssignableFrom(type));
}
}
}

View File

@@ -2,10 +2,10 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using Orchard.Extensions.Models;
using Orchard.Logging;
using Orchard.Mvc.ModelBinders;
using Orchard.Mvc.Routes;
using Orchard.Extensions;
using Orchard.Utility;
namespace Orchard.Environment {

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Orchard.Environment.Configuration;
using Orchard.Extensions;
using Orchard.Extensions.Models;
namespace Orchard.Environment.Topology.Models {
public class ShellTopology {
@@ -14,6 +15,7 @@ namespace Orchard.Environment.Topology.Models {
public class ShellTopologyItem {
public Type Type { get; set; }
public Feature Feature { get; set; }
public ExtensionDescriptor ExtensionDescriptor { get; set; }
public FeatureDescriptor FeatureDescriptor { get; set; }
public ExtensionEntry ExtensionEntry { get; set; }

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Orchard.Extensions.Models;
namespace Orchard.Extensions {
public class ExtensionEntry {

View File

@@ -6,6 +6,7 @@ using ICSharpCode.SharpZipLib.Zip;
using Orchard.Environment;
using Orchard.Extensions.Helpers;
using Orchard.Extensions.Loaders;
using Orchard.Extensions.Models;
using Orchard.Localization;
using Orchard.Logging;
using Yaml.Grammar;
@@ -45,6 +46,10 @@ namespace Orchard.Extensions {
return availableExtensions;
}
public IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> features) {
throw new NotImplementedException();
}
// This method loads types from extensions into the ExtensionEntry array.
public IEnumerable<ExtensionEntry> ActiveExtensions() {
if (_activeExtensions == null) {
@@ -52,6 +57,7 @@ namespace Orchard.Extensions {
}
return _activeExtensions;
}
private static ExtensionDescriptor GetDescriptorForExtension(string name, IExtensionFolders folder) {
string extensionType = folder is ThemeFolders ? "Theme" : "Module";

View File

@@ -1,13 +1,17 @@
using System;
using System.Collections.Generic;
using System.Web;
using Orchard.Extensions.Models;
namespace Orchard.Extensions {
public interface IExtensionManager {
IEnumerable<ExtensionDescriptor> AvailableExtensions();
IEnumerable<Type> LoadFeature(string featureName);
IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> features);
IEnumerable<ExtensionEntry> ActiveExtensions();
ShellTopology_Obsolete GetExtensionsTopology();
IEnumerable<Type> LoadFeature(string featureName);
void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle);
void UninstallExtension(string extensionType, string extensionName);
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
using Orchard.Extensions.Models;
namespace Orchard.Extensions.Loaders {
public class AreaExtensionLoader : IExtensionLoader {

View File

@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
using Orchard.Extensions.Models;
namespace Orchard.Extensions.Loaders {
public class CoreExtensionLoader : IExtensionLoader {

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Web.Compilation;
using System.Web.Hosting;
using Orchard.Extensions.Models;
namespace Orchard.Extensions.Loaders {
public class DynamicExtensionLoader : IExtensionLoader {

View File

@@ -1,4 +1,6 @@
namespace Orchard.Extensions.Loaders {
using Orchard.Extensions.Models;
namespace Orchard.Extensions.Loaders {
public interface IExtensionLoader {
int Order { get; }
ExtensionEntry Load(ExtensionDescriptor descriptor);

View File

@@ -1,4 +1,6 @@
namespace Orchard.Extensions.Loaders {
using Orchard.Extensions.Models;
namespace Orchard.Extensions.Loaders {
public class PrecompiledExtensionLoader : IExtensionLoader {
public int Order { get { return 3; } }

View File

@@ -2,6 +2,7 @@
using System.Reflection;
using System.Web.Compilation;
using System.Web.Hosting;
using Orchard.Extensions.Models;
namespace Orchard.Extensions.Loaders {
public class ReferencedExtensionLoader : IExtensionLoader {

View File

@@ -0,0 +1,4 @@
namespace Orchard.Extensions.Models {
public class Extension {
}
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Orchard.Extensions {
namespace Orchard.Extensions.Models {
public class ExtensionDescriptor {
/// <summary>
/// Virtual path base, "~/Themes", "~/Modules", or "~/Core"
@@ -29,4 +29,4 @@ namespace Orchard.Extensions {
public IEnumerable<FeatureDescriptor> Features { get; set; }
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
namespace Orchard.Extensions.Models {
public class Feature {
public FeatureDescriptor FeatureDescriptor { get; set; }
public Extension Extension { get; set; }
public IEnumerable<Type> ExportedTypes { get; set; }
}
}

View File

@@ -189,8 +189,10 @@
<Compile Include="Events\IEventBusHandler.cs" />
<Compile Include="Extensions\AreaFolders.cs" />
<Compile Include="Extensions\ExtensionFolders.cs" />
<Compile Include="Extensions\FeatureDescriptor.cs" />
<Compile Include="Extensions\Models\Extension.cs" />
<Compile Include="Extensions\Models\FeatureDescriptor.cs" />
<Compile Include="Extensions\Loaders\AreaExtensionLoader.cs" />
<Compile Include="Extensions\Models\Feature.cs" />
<Compile Include="Extensions\OrchardFeatureAttribute.cs" />
<Compile Include="Extensions\Records\ExtensionRecord.cs" />
<Compile Include="Extensions\ShellTopology.cs" />
@@ -271,7 +273,7 @@
<Compile Include="Data\TransactionManager.cs" />
<Compile Include="Environment\IOrchardShellEvents.cs" />
<Compile Include="Environment\OrchardServices.cs" />
<Compile Include="Extensions\ExtensionDescriptor.cs" />
<Compile Include="Extensions\Models\ExtensionDescriptor.cs" />
<Compile Include="Extensions\ExtensionEntry.cs" />
<Compile Include="IOrchardServices.cs" />
<Compile Include="Themes\ThemeFilter.cs" />

View File

@@ -1,6 +1,7 @@
using System.IO;
using System.Linq;
using Orchard.Extensions;
using Orchard.Extensions.Models;
namespace Orchard.Themes {
public static class ExtensionManagerExtensions {