Implementing feature priorities

--HG--
branch : dev
This commit is contained in:
Andre Rodrigues
2011-03-20 17:16:41 -07:00
parent f4fadb2eee
commit b67a348267
9 changed files with 208 additions and 19 deletions

View File

@@ -506,9 +506,7 @@ OrchardVersion: 1
Name: Alpha Name: Alpha
Version: 1.0.3 Version: 1.0.3
OrchardVersion: 1 OrchardVersion: 1
Features: Dependencies: Gamma
Alpha:
Dependencies: Gamma
"); ");
moduleExtensionFolder.Manifests.Add("Beta", @" moduleExtensionFolder.Manifests.Add("Beta", @"
@@ -520,23 +518,139 @@ OrchardVersion: 1
Name: Gamma Name: Gamma
Version: 1.0.3 Version: 1.0.3
OrchardVersion: 1 OrchardVersion: 1
Features: Dependencies: Beta
Gamma:
Dependencies: Beta
"); ");
moduleExtensionFolder.Manifests.Add("Classic", @" moduleExtensionFolder.Manifests.Add("Classic", @"
Name: Classic Name: Classic
Version: 1.0.3 Version: 1.0.3
OrchardVersion: 1 OrchardVersion: 1
Features: Dependencies: Alpha
Classic:
Dependencies: Alpha
"); ");
IExtensionManager extensionManager = new ExtensionManager(new[] { moduleExtensionFolder, themeExtensionFolder }, new[] { extensionLoader }, new StubCacheManager()); IExtensionManager extensionManager = new ExtensionManager(new[] { moduleExtensionFolder, themeExtensionFolder }, new[] { extensionLoader }, new StubCacheManager());
var features = extensionManager.AvailableFeatures(); var features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Alpha<Classic<")); Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Alpha<Classic<"));
} }
[Test]
public void FeatureDescriptorsAreInDependencyAndPriorityOrder() {
var extensionLoader = new StubLoaders();
var extensionFolder = new StubFolders();
// Check that priorities apply correctly on items on the same level of dependencies and are overwritten by dependencies
extensionFolder.Manifests.Add("Alpha", @"
Name: Alpha
Version: 1.0.3
OrchardVersion: 1
Dependencies: Gamma
Priority:2"); // More important than Gamma but will get overwritten by the dependency
extensionFolder.Manifests.Add("Beta", @"
Name: Beta
Version: 1.0.3
OrchardVersion: 1
Priority:2");
extensionFolder.Manifests.Add("Foo", @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:1");
extensionFolder.Manifests.Add("Gamma", @"
Name: Gamma
Version: 1.0.3
OrchardVersion: 1
Dependencies: Beta, Foo
Priority:3");
IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
var features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Foo<Beta<Gamma<Alpha<"));
// Change priorities and see that it reflects properly
extensionFolder.Manifests["Foo"] = @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:3";
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Foo<Gamma<Alpha<"));
// Remove dependency on Beta and see that it moves down the list since no one depends on it anymore
extensionFolder.Manifests["Gamma"] = @"
Name: Gamma
Version: 1.0.3
OrchardVersion: 1
Dependencies: Beta
Priority:3";
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Foo<Gamma<Alpha<"));
// Change Foo to depend on Gamma and see that it moves to a new position (same dependencies as alpha but lower priority)
extensionFolder.Manifests["Foo"] = @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:3
Dependencies: Gamma";
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Alpha<Foo<"));
// Update Foo to a higher priority than alpha
extensionFolder.Manifests["Foo"] = @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:1
Dependencies: Gamma";
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Foo<Alpha<"));
}
[Test]
public void FeatureDescriptorsAreInPriorityOrder() {
var extensionLoader = new StubLoaders();
var extensionFolder = new StubFolders();
// Check that priorities apply correctly on items on the same level of dependencies and are overwritten by dependencies
extensionFolder.Manifests.Add("Alpha", @"
Name: Alpha
Version: 1.0.3
OrchardVersion: 1
Priority:4"); // More important than Gamma but will get overwritten by the dependency
extensionFolder.Manifests.Add("Beta", @"
Name: Beta
Version: 1.0.3
OrchardVersion: 1
Priority:3");
extensionFolder.Manifests.Add("Foo", @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:1");
extensionFolder.Manifests.Add("Gamma", @"
Name: Gamma
Version: 1.0.3
OrchardVersion: 1
Priority:2");
IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
var features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Foo<Gamma<Beta<Alpha<"));
}
} }
} }

View File

@@ -37,7 +37,7 @@ namespace Orchard.DisplayManagement.Descriptors {
var alterations = builderFactory.BuildAlterations() var alterations = builderFactory.BuildAlterations()
.Where(alteration => IsModuleOrRequestedTheme(alteration, themeName)) .Where(alteration => IsModuleOrRequestedTheme(alteration, themeName))
.OrderByDependencies(AlterationHasDependency); .OrderByDependenciesAndPriorities(AlterationHasDependency, GetPriority);
var descriptors = alterations.GroupBy(alteration => alteration.ShapeType, StringComparer.OrdinalIgnoreCase) var descriptors = alterations.GroupBy(alteration => alteration.ShapeType, StringComparer.OrdinalIgnoreCase)
.Select(group => group.Aggregate( .Select(group => group.Aggregate(
@@ -54,6 +54,10 @@ namespace Orchard.DisplayManagement.Descriptors {
}); });
} }
private static int GetPriority(ShapeAlteration shapeAlteration) {
return shapeAlteration.Feature.Descriptor.Priority;
}
private static bool AlterationHasDependency(ShapeAlteration item, ShapeAlteration subject) { private static bool AlterationHasDependency(ShapeAlteration item, ShapeAlteration subject) {
return ExtensionManager.HasDependency(item.Feature.Descriptor, subject.Feature.Descriptor); return ExtensionManager.HasDependency(item.Feature.Descriptor, subject.Feature.Descriptor);
} }

View File

@@ -199,9 +199,10 @@ namespace Orchard.Environment.Extensions {
.ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase); .ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase);
var sortedAvailableExtensions = var sortedAvailableExtensions =
availableExtensions.OrderByDependencies( availableExtensions.OrderByDependenciesAndPriorities(
(item, dep) => referencesByModule.ContainsKey(item.Id) && (item, dep) => referencesByModule.ContainsKey(item.Id) &&
referencesByModule[item.Id].Any(r => StringComparer.OrdinalIgnoreCase.Equals(dep.Id, r.Name))) referencesByModule[item.Id].Any(r => StringComparer.OrdinalIgnoreCase.Equals(dep.Id, r.Name)),
(item) => item.Priority)
.ToList(); .ToList();
return new ExtensionLoadingContext { return new ExtensionLoadingContext {

View File

@@ -39,7 +39,11 @@ namespace Orchard.Environment.Extensions {
public IEnumerable<FeatureDescriptor> AvailableFeatures() { public IEnumerable<FeatureDescriptor> AvailableFeatures() {
return _cacheManager.Get("...", ctx => return _cacheManager.Get("...", ctx =>
AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependencies(HasDependency).ToReadOnlyCollection()); AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependenciesAndPriorities(HasDependency, GetPriority).ToReadOnlyCollection());
}
internal static int GetPriority(FeatureDescriptor featureDescriptor) {
return featureDescriptor.Priority;
} }
/// <summary> /// <summary>

View File

@@ -100,6 +100,7 @@ namespace Orchard.Environment.Extensions.Folders {
AntiForgery = GetValue(manifest, "AntiForgery"), AntiForgery = GetValue(manifest, "AntiForgery"),
Zones = GetValue(manifest, "Zones"), Zones = GetValue(manifest, "Zones"),
BaseTheme = GetValue(manifest, "BaseTheme"), BaseTheme = GetValue(manifest, "BaseTheme"),
Priority = int.Parse(GetValue(manifest, "Priority") ?? "0")
}; };
extensionDescriptor.Features = GetFeaturesForExtension(manifest, extensionDescriptor); extensionDescriptor.Features = GetFeaturesForExtension(manifest, extensionDescriptor);
@@ -183,6 +184,9 @@ namespace Orchard.Environment.Extensions.Folders {
case "FeatureDescription": case "FeatureDescription":
manifest.Add("FeatureDescription", field[1]); manifest.Add("FeatureDescription", field[1]);
break; break;
case "Priority":
manifest.Add("Priority", field[1]);
break;
case "Features": case "Features":
manifest.Add("Features", reader.ReadToEnd()); manifest.Add("Features", reader.ReadToEnd());
break; break;
@@ -200,6 +204,7 @@ namespace Orchard.Environment.Extensions.Folders {
FeatureDescriptor defaultFeature = new FeatureDescriptor { FeatureDescriptor defaultFeature = new FeatureDescriptor {
Id = extensionDescriptor.Id, Id = extensionDescriptor.Id,
Name = extensionDescriptor.Name, Name = extensionDescriptor.Name,
Priority = extensionDescriptor.Priority,
Description = GetValue(manifest, "FeatureDescription") ?? GetValue(manifest, "Description") ?? string.Empty, Description = GetValue(manifest, "FeatureDescription") ?? GetValue(manifest, "Description") ?? string.Empty,
Dependencies = ParseFeatureDependenciesEntry(GetValue(manifest, "Dependencies")), Dependencies = ParseFeatureDependenciesEntry(GetValue(manifest, "Dependencies")),
Extension = extensionDescriptor, Extension = extensionDescriptor,
@@ -229,6 +234,7 @@ namespace Orchard.Environment.Extensions.Folders {
if (featureDescriptorId == extensionDescriptor.Id) { if (featureDescriptorId == extensionDescriptor.Id) {
featureDescriptor = defaultFeature; featureDescriptor = defaultFeature;
featureDescriptor.Name = extensionDescriptor.Name; featureDescriptor.Name = extensionDescriptor.Name;
featureDescriptor.Priority = extensionDescriptor.Priority;
} }
else { else {
featureDescriptor = new FeatureDescriptor { featureDescriptor = new FeatureDescriptor {
@@ -256,6 +262,9 @@ namespace Orchard.Environment.Extensions.Folders {
case "Category": case "Category":
featureDescriptor.Category = featureField[1]; featureDescriptor.Category = featureField[1];
break; break;
case "Priority":
featureDescriptor.Priority = int.Parse(featureField[1]);
break;
case "Dependencies": case "Dependencies":
featureDescriptor.Dependencies = ParseFeatureDependenciesEntry(featureField[1]); featureDescriptor.Dependencies = ParseFeatureDependenciesEntry(featureField[1]);
break; break;

View File

@@ -29,6 +29,7 @@ namespace Orchard.Environment.Extensions.Models {
public string AntiForgery { get; set; } public string AntiForgery { get; set; }
public string Zones { get; set; } public string Zones { get; set; }
public string BaseTheme { get; set; } public string BaseTheme { get; set; }
public int Priority { get; set; }
public IEnumerable<FeatureDescriptor> Features { get; set; } public IEnumerable<FeatureDescriptor> Features { get; set; }
} }

View File

@@ -13,6 +13,7 @@ namespace Orchard.Environment.Extensions.Models {
public string Name { get; set; } public string Name { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Category { get; set; } public string Category { get; set; }
public int Priority { get; set; }
public IEnumerable<string> Dependencies { get; set; } public IEnumerable<string> Dependencies { get; set; }
} }
} }

View File

@@ -15,27 +15,82 @@ namespace Orchard.Utility {
/// is a dependency of another, this algorithm will return the collection of elements sorted /// is a dependency of another, this algorithm will return the collection of elements sorted
/// so that a given element in the sequence doesn't depend on any other element further in the sequence. /// so that a given element in the sequence doesn't depend on any other element further in the sequence.
/// </summary> /// </summary>
public static IEnumerable<T> OrderByDependencies<T>(this IEnumerable<T> elements, Func<T, T, bool> hasDependency) { public static IEnumerable<T> OrderByDependenciesAndPriorities<T>(this IEnumerable<T> elements, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
var population = elements.Select(d => new Linkage<T> { var population = elements.Select(d => new Linkage<T> {
Element = d Element = d
}).ToArray(); }).OrderBy(item => getPriority(item.Element)).ToArray(); // Performing an initial sorting by priorities may optimize performance
var result = new List<T>(); var result = new List<T>();
foreach (var item in population) { foreach (var item in population) {
Add(item, result, population, hasDependency); Add(item, result, population, hasDependency, getPriority);
} }
// shift elements forward as possible within priorities and dependencies
for (int i = 1; i < result.Count; i++) {
int bestPosition = BestPriorityPosition(result, i, hasDependency, getPriority);
SwitchAndShift(result, i, bestPosition);
}
return result; return result;
} }
private static void Add<T>(Linkage<T> item, ICollection<T> list, IEnumerable<Linkage<T>> population, Func<T, T, bool> hasDependency) { private static void Add<T>(Linkage<T> item, ICollection<T> list, IEnumerable<Linkage<T>> population, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
if (item.Used) if (item.Used)
return; return;
item.Used = true; item.Used = true;
foreach (var dependency in population.Where(dep => hasDependency(item.Element, dep.Element))) { foreach (var dependency in population.Where(dep => hasDependency(item.Element, dep.Element))) {
Add(dependency, list, population, hasDependency); Add(dependency, list, population, hasDependency, getPriority);
} }
list.Add(item.Element); list.Add(item.Element);
} }
private static int BestPriorityPosition<T>(List<T> list, int index, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
int bestPriority = getPriority(list[index]);
int bestIndex = index;
for (int i = index - 1 ; i >= 0 ; i--) {
if (hasDependency(list[index], list[i])) {
return bestIndex;
}
int currentPriority = getPriority(list[i]);
if (currentPriority >= bestPriority) {
bestIndex = i;
}
}
return bestIndex;
}
/// <summary>
/// Advances an element within the list from an initial position to a final position with a lower index.
/// </summary>
/// <typeparam name="T">The type of each element.</typeparam>
/// <param name="list">the list of elements.</param>
/// <param name="initialPosition">The initial position within the list.</param>
/// <param name="finalPosition">The final position within the list.</param>
/// <returns>True if any change was made; false otherwise.</returns>
private static bool SwitchAndShift<T>(List<T> list, int initialPosition, int finalPosition) {
if (initialPosition < finalPosition) {
throw new ArgumentException("finalPosition");
}
if (initialPosition != finalPosition) {
T temp = list[initialPosition];
for (; initialPosition > finalPosition; initialPosition--) {
list[initialPosition] = list[initialPosition - 1];
}
list[finalPosition] = temp;
return true;
}
return false;
}
} }
} }