diff --git a/src/Orchard.Tests/Environment/DefaultOrchardHostTests.cs b/src/Orchard.Tests/Environment/DefaultOrchardHostTests.cs index a0380c066..35804a700 100644 --- a/src/Orchard.Tests/Environment/DefaultOrchardHostTests.cs +++ b/src/Orchard.Tests/Environment/DefaultOrchardHostTests.cs @@ -77,7 +77,10 @@ namespace Orchard.Tests.Environment { return Enumerable.Empty(); } - public ShellTopology_Obsolete GetExtensionsTopology() { + public IEnumerable LoadFeature(string featureName) { + throw new NotImplementedException(); + } + throw new NotImplementedException(); } diff --git a/src/Orchard.Tests/Extensions/ExtensionManagerTests.cs b/src/Orchard.Tests/Extensions/ExtensionManagerTests.cs index a92225d89..90b562569 100644 --- a/src/Orchard.Tests/Extensions/ExtensionManagerTests.cs +++ b/src/Orchard.Tests/Extensions/ExtensionManagerTests.cs @@ -1,8 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Autofac; using NUnit.Framework; using Orchard.Extensions; +using Orchard.Extensions.Loaders; +using Orchard.Tests.Extensions.ExtensionTypes; using Yaml.Grammar; namespace Orchard.Tests.Extensions { @@ -48,6 +51,20 @@ namespace Orchard.Tests.Extensions { } } + public class StubLoaders : IExtensionLoader { + #region Implementation of IExtensionLoader + + public int Order { + get { return 1; } + } + + public ExtensionEntry Load(ExtensionDescriptor descriptor) { + return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } }; + } + + #endregion + } + [Test] public void AvailableExtensionsShouldFollowCatalogLocations() { @@ -190,5 +207,78 @@ features: Assert.That(!type.IsAbstract); } } + + [Test] + public void ExtensionManagerShouldThrowIfFeatureDoesNotExist() { + Assert.Throws(() => _manager.LoadFeature("NoSuchFeature")); + } + + [Test] + public void ExtensionManagerTestFeatureAttribute() { + var extensionManager = new Moq.Mock(); + extensionManager.Setup(x => x.ActiveExtensions()).Returns(new[] { + new ExtensionEntry { + Descriptor = new ExtensionDescriptor { + Name = "Module", + Features = new[] { + new FeatureDescriptor { Name = "Module", ExtensionName = "Module" }, + new FeatureDescriptor { Name = "TestFeature", ExtensionName = "Module" } + }}, + ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } + }}); + + foreach (var type in extensionManager.Object.ActiveExtensions().SelectMany(x => x.ExportedTypes)) { + foreach (OrchardFeatureAttribute featureAttribute in type.GetCustomAttributes(typeof(OrchardFeatureAttribute), false)) { + Assert.That(featureAttribute.FeatureName, Is.EqualTo("TestFeature")); + } + } + } + + [Test] + public void ExtensionManagerLoadFeatureReturnsTypesFromSpecificFeaturesWithFeatureAttribute() { + var extensionLoader = new StubLoaders(); + var extensionFolder = new StubFolders(); + + extensionFolder.Manifests.Add("TestModule", @" +name: TestModule +version: 1.0.3 +orchardversion: 1 +features: + TestModule: + Description: My test module for Orchard. + TestFeature: + Description: Contains the Phi type. +"); + + ExtensionManager extensionManager = new ExtensionManager(new []{extensionFolder}, new [] {extensionLoader}); + + foreach (var type in extensionManager.LoadFeature("TestFeature")) { + Assert.That(type == typeof(Phi)); + } + } + + [Test] + public void ExtensionManagerLoadFeatureDoesNotReturnTypesFromNonMatchingFeatures() { + var extensionLoader = new StubLoaders(); + var extensionFolder = new StubFolders(); + + extensionFolder.Manifests.Add("TestModule", @" +name: TestModule +version: 1.0.3 +orchardversion: 1 +features: + TestModule: + Description: My test module for Orchard. + TestFeature: + Description: Contains the Phi type. +"); + + ExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }); + + foreach (var type in extensionManager.LoadFeature("TestModule")) { + Assert.That(type != typeof(Phi)); + Assert.That((type == typeof(Alpha) || (type == typeof(Beta)))); + } + } } } diff --git a/src/Orchard.Tests/Extensions/ExtensionTypes/StubTypes.cs b/src/Orchard.Tests/Extensions/ExtensionTypes/StubTypes.cs new file mode 100644 index 000000000..8bf533a42 --- /dev/null +++ b/src/Orchard.Tests/Extensions/ExtensionTypes/StubTypes.cs @@ -0,0 +1,13 @@ +using Orchard.Extensions; + +namespace Orchard.Tests.Extensions.ExtensionTypes { + public class Alpha { + } + + public class Beta { + } + + [OrchardFeature("TestFeature")] + public class Phi { + } +} diff --git a/src/Orchard.Tests/Mvc/Routes/StandardExtensionRouteProviderTests.cs b/src/Orchard.Tests/Mvc/Routes/StandardExtensionRouteProviderTests.cs index 9199276a5..25173e2c8 100644 --- a/src/Orchard.Tests/Mvc/Routes/StandardExtensionRouteProviderTests.cs +++ b/src/Orchard.Tests/Mvc/Routes/StandardExtensionRouteProviderTests.cs @@ -54,7 +54,10 @@ namespace Orchard.Tests.Mvc.Routes { }; } - public ShellTopology_Obsolete GetExtensionsTopology() { + public IEnumerable LoadFeature(string featureName) { + throw new NotImplementedException(); + } + throw new NotImplementedException(); } diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 8e7b4f994..11806c807 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -165,6 +165,7 @@ + diff --git a/src/Orchard/Extensions/ExtensionManager.cs b/src/Orchard/Extensions/ExtensionManager.cs index 5f065c1b3..4516d1fb3 100644 --- a/src/Orchard/Extensions/ExtensionManager.cs +++ b/src/Orchard/Extensions/ExtensionManager.cs @@ -33,7 +33,8 @@ namespace Orchard.Extensions { Logger = NullLogger.Instance; } - + // This method does not load extension types, simply parses extension manifests from + // the filesystem. public IEnumerable AvailableExtensions() { var availableExtensions = new List(); foreach (var folder in _folders) { @@ -44,6 +45,14 @@ namespace Orchard.Extensions { return availableExtensions; } + // This method loads types from extensions into the ExtensionEntry array. + public IEnumerable ActiveExtensions() { + if (_activeExtensions == null) { + _activeExtensions = BuildActiveExtensions().ToList(); + } + return _activeExtensions; + } + private static ExtensionDescriptor GetDescriptorForExtension(string name, IExtensionFolders folder) { string extensionType = folder is ThemeFolders ? "Theme" : "Module"; var parseResult = folder.ParseManifest(name); @@ -94,17 +103,50 @@ namespace Orchard.Extensions { return featureDescriptors; } - public IEnumerable ActiveExtensions() { - if (_activeExtensions == null) { - _activeExtensions = BuildActiveExtensions().ToList(); - } - return _activeExtensions; - } - - public ShellTopology_Obsolete GetExtensionsTopology() { + public ShellTopology GetExtensionsTopology() { var types = ActiveExtensions().SelectMany(x => x.ExportedTypes); types = types.Concat(typeof(IOrchardHost).Assembly.GetExportedTypes()); - return new ShellTopology_Obsolete { Types = types.Where(t => t.IsClass && !t.IsAbstract) }; + return new ShellTopology { Types = types.Where(t => t.IsClass && !t.IsAbstract) }; + } + + public IEnumerable LoadFeature(string featureName) { + string extensionName = GetExtensionForFeature(featureName); + if (extensionName == null) throw new ArgumentException(T("Feature ") + featureName + T(" was not found in any of the installed extensions")); + var extension = ActiveExtensions().Where(x => x.Descriptor.Name == extensionName).FirstOrDefault(); + if (extension == null) throw new InvalidOperationException(T("Extension ") + extensionName + T(" is not active")); + + var extensionTypes = extension.ExportedTypes.Where(t => t.IsClass && !t.IsAbstract); + var featureTypes = new List(); + + foreach (var type in extensionTypes) { + string sourceFeature = GetSourceFeatureNameForType(type, extensionName); + if (String.Equals(sourceFeature, featureName, StringComparison.OrdinalIgnoreCase)) { + featureTypes.Add(type); + } + } + + return featureTypes; + } + + private static string GetSourceFeatureNameForType(Type type, string extensionName) { + foreach (OrchardFeatureAttribute featureAttribute in type.GetCustomAttributes(typeof(OrchardFeatureAttribute), false)) { + return featureAttribute.FeatureName; + } + return extensionName; + } + + private string GetExtensionForFeature(string featureName) { + foreach (var extensionDescriptor in AvailableExtensions()) { + if (String.Equals(extensionDescriptor.Name, featureName, StringComparison.OrdinalIgnoreCase)) { + return extensionDescriptor.Name; + } + foreach (var feature in extensionDescriptor.Features) { + if (String.Equals(feature.Name, featureName, StringComparison.OrdinalIgnoreCase)) { + return extensionDescriptor.Name; + } + } + } + return null; } public void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle) { diff --git a/src/Orchard/Extensions/IExtensionManager.cs b/src/Orchard/Extensions/IExtensionManager.cs index 71f617fe0..bcc5cb00f 100644 --- a/src/Orchard/Extensions/IExtensionManager.cs +++ b/src/Orchard/Extensions/IExtensionManager.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Web; namespace Orchard.Extensions { public interface IExtensionManager { IEnumerable AvailableExtensions(); IEnumerable ActiveExtensions(); - ShellTopology_Obsolete GetExtensionsTopology(); + IEnumerable LoadFeature(string featureName); void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle); void UninstallExtension(string extensionType, string extensionName); } diff --git a/src/Orchard/Extensions/OrchardFeatureAttribute.cs b/src/Orchard/Extensions/OrchardFeatureAttribute.cs new file mode 100644 index 000000000..aab5dabe3 --- /dev/null +++ b/src/Orchard/Extensions/OrchardFeatureAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Orchard.Extensions { + [AttributeUsage(AttributeTargets.Class)] + public class OrchardFeatureAttribute : Attribute { + public OrchardFeatureAttribute(string text) { + FeatureName = text; + } + + public string FeatureName { get; set; } + } +} diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 53c921c4b..402ed275e 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -181,6 +181,7 @@ +