Added RecipesStep.

This recipe steps enables the execution of other recipes within the same ExecutionId.
This commit is contained in:
Sipke Schoorstra
2015-07-28 18:15:30 +01:00
parent dedded7422
commit 57692e073d
18 changed files with 125 additions and 26 deletions

View File

@@ -94,7 +94,7 @@ Features:
Enumerable.Empty<ShellParameter>());
var moduleStep = _container.Resolve<ModuleStep>();
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep { Name = "Module", Step = new XElement("SuperWiki") } };
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep( recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
@@ -120,7 +120,7 @@ Features:
");
var moduleStep = _container.Resolve<ModuleStep>();
var recipeContext = new RecipeContext { RecipeStep = new RecipeStep { Name = "Module", Step = new XElement("SuperWiki") } };
var recipeContext = new RecipeContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = recipeContext.RecipeStep };
recipeContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
@@ -145,7 +145,7 @@ Features:
});
var moduleStep = _container.Resolve<ModuleStep>();
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep { Name = "Module", Step = new XElement("SuperWiki") } };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));

View File

@@ -25,7 +25,7 @@ namespace Orchard.Tests.Modules.Recipes.RecipeHandlers {
var fakeRecipeStep = _container.Resolve<StubRecipeExecutionStep>();
var context = new RecipeContext {
RecipeStep = new RecipeStep { Name = "FakeRecipeStep", Step = new XElement("FakeRecipeStep")},
RecipeStep = new RecipeStep (recipeName: "FakeRecipe", name: "FakeRecipeStep", step: new XElement("FakeRecipeStep")),
ExecutionId = "12345"
};

View File

@@ -100,7 +100,7 @@ Features:
Enumerable.Empty<ShellParameter>());
var themeStep = _container.Resolve<ThemeStep>();
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep { Name = "Theme", Step = new XElement("SuperWiki") } };
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep (recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Theme.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
@@ -135,7 +135,7 @@ Features:
");
var themeStep = _container.Resolve<ThemeStep>();
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep { Name = "Theme", Step = new XElement("SuperWiki") } };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
Assert.Throws(typeof (InvalidOperationException), () => themeStep.Execute(recipeExecutionContext));
@@ -159,7 +159,7 @@ Features:
});
var themeStep = _container.Resolve<ThemeStep>();
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep { Name = "Theme", Step = new XElement("SuperWiki") } };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Theme.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));

View File

@@ -16,6 +16,7 @@ else {
<table class="items" style="width: auto;">
<thead>
<tr>
<th>@T("Recipe")</th>
<th>@T("Step")</th>
<th>@T("Executed")</th>
<th>@T("Result")</th>
@@ -25,6 +26,7 @@ else {
<tbody>
@foreach (var step in Model.Result.Steps) {
<tr>
<td>@(!String.IsNullOrWhiteSpace(step.RecipeName) ? step.RecipeName : T("Untitled").ToString())</td>
<td>@step.StepName</td>
<td>@step.IsCompleted</td>
<td>@if (step.IsSuccessful) { @T("Successful") } else if (step.IsCompleted) { <strong>@T("Failed")</strong> }</td>

View File

@@ -253,11 +253,12 @@ namespace Orchard.Packaging.Controllers {
// Enable the features and its dependencies using recipes, so that they are run after the module's recipes
var recipe = new Recipe {
Name = "Test",
RecipeSteps = featureIds.Select(
x => new RecipeStep {
Name = "Feature",
Step = new XElement("Feature", new XAttribute("enable", x))
})
x => new RecipeStep(
recipeName: "Test",
name: "Feature",
step: new XElement("Feature", new XAttribute("enable", x))))
};
_recipeManager.Execute(recipe);

View File

@@ -91,7 +91,7 @@ namespace Orchard.Recipes.Commands {
Context.Output.WriteLine(T(" Successful: {0}", result.IsSuccessful));
foreach (var step in result.Steps) {
Context.Output.WriteLine(T(" Step: {0}", step.StepName));
Context.Output.WriteLine(T(" Step: {0} ({1})", step.StepName, step.RecipeName));
Context.Output.WriteLine(T(" Completed: {0}", step.IsCompleted));
Context.Output.WriteLine(T(" Successful: {0}", step.IsSuccessful));
if (!String.IsNullOrEmpty(step.ErrorMessage))

View File

@@ -6,10 +6,11 @@ namespace Orchard.Recipes {
SchemaBuilder.CreateTable("RecipeStepResultRecord", table => table
.Column<int>("Id", c => c.PrimaryKey().Identity())
.Column<string>("ExecutionId", c => c.WithLength(128).NotNull())
.Column<string>("RecipeName", c => c.WithLength(256))
.Column<string>("StepName", c => c.WithLength(256).NotNull())
.Column<bool>("IsCompleted", c => c.NotNull())
.Column<bool>("IsSuccessful", c => c.NotNull())
.Column<string>("ErrorMessage", c => c.Unlimited().Nullable())
.Column<string>("ErrorMessage", c => c.Unlimited())
);
SchemaBuilder.AlterTable("RecipeStepResultRecord", table => {

View File

@@ -2,6 +2,7 @@
public class RecipeStepResultRecord {
public virtual int Id { get; set; }
public virtual string ExecutionId { get; set; }
public virtual string RecipeName { get; set; }
public virtual string StepName { get; set; }
public virtual bool IsCompleted { get; set; }
public virtual bool IsSuccessful { get; set; }

View File

@@ -55,6 +55,9 @@
<HintPath>..\..\..\..\lib\log4net\log4net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="NHibernate">
<HintPath>..\..\..\..\lib\nhibernate\NHibernate.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Web.ApplicationServices" />
@@ -89,6 +92,7 @@
<Compile Include="Providers\Builders\RecipeMetadataStep.cs" />
<Compile Include="Providers\Builders\SettingsStep.cs" />
<Compile Include="Providers\Executors\CommandStep.cs" />
<Compile Include="Providers\Executors\RecipesStep.cs" />
<Compile Include="ViewModels\ContentExecutionStepViewModel.cs" />
<Compile Include="Providers\Executors\ContentStep.cs" />
<Compile Include="Providers\Executors\ContentDefinitionStep.cs" />

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.Providers.Executors {
public class RecipesStep : RecipeExecutionStep {
private readonly IRecipeHarvester _recipeHarvester;
private readonly IRecipeStepQueue _recipeStepQueue;
private readonly IRepository<RecipeStepResultRecord> _recipeStepResultRecordRepository;
private readonly ISessionLocator _sessionLocator;
public RecipesStep(
IRecipeHarvester recipeHarvester,
IRecipeStepQueue recipeStepQueue,
IRepository<RecipeStepResultRecord> recipeStepResultRecordRepository,
ISessionLocator sessionLocator) {
_recipeHarvester = recipeHarvester;
_recipeStepQueue = recipeStepQueue;
_recipeStepResultRecordRepository = recipeStepResultRecordRepository;
_sessionLocator = sessionLocator;
}
public override string Name { get { return "Recipes"; } }
/*
<Recipes>
<Recipe ExtensionId="Orchard.Setup" Name="Core" />
</Recipes>
*/
public override void Execute(RecipeExecutionContext context) {
var recipeElements = context.RecipeStep.Step.Elements();
var recipesDictionary = new Dictionary<string, IDictionary<string, Recipe>>();
var session = _sessionLocator.For(typeof(RecipeStepResultRecord));
foreach (var recipeElement in recipeElements) {
var extensionId = recipeElement.Attr("ExtensionId");
var recipeName = recipeElement.Attr("Name");
var recipes = recipesDictionary.ContainsKey(extensionId) ? recipesDictionary[extensionId] : default(IDictionary<string, Recipe>);
if (recipes == null) {
recipes = recipesDictionary[extensionId] = HarvestRecipes(extensionId);
}
var recipe = recipes.ContainsKey(recipeName) ? recipes[recipeName] : default(Recipe);
if (recipe == null) {
Logger.Error(String.Format("No recipe named {0} was found for extension {1}", recipeName, extensionId));
continue;
}
EnqueueRecipe(session, context.ExecutionId, recipe);
}
}
private void EnqueueRecipe(ISession session, string executionId, Recipe recipe) {
foreach (var recipeStep in recipe.RecipeSteps) {
_recipeStepQueue.Enqueue(executionId, recipeStep);
_recipeStepResultRecordRepository.Create(new RecipeStepResultRecord {
ExecutionId = executionId,
RecipeName = recipe.Name,
StepName = recipeStep.Name
});
}
}
private IDictionary<string, Recipe> HarvestRecipes(string extensionId) {
return _recipeHarvester.HarvestRecipes(extensionId).ToDictionary(x => x.Name);
}
}
}

View File

@@ -45,8 +45,9 @@ namespace Orchard.Recipes.Services {
foreach (var recipeStep in recipe.RecipeSteps) {
_recipeStepQueue.Enqueue(executionId, recipeStep);
_recipeStepResultRecordRepository.Create(new RecipeStepResultRecord() {
_recipeStepResultRecordRepository.Create(new RecipeStepResultRecord {
ExecutionId = executionId,
RecipeName = recipe.Name,
StepName = recipeStep.Name
});
}

View File

@@ -52,7 +52,7 @@ namespace Orchard.Recipes.Services {
}
// Recipe step.
else {
var recipeStep = new RecipeStep { Name = element.Name.LocalName, Step = element };
var recipeStep = new RecipeStep(recipeName: recipe.Name, name: element.Name.LocalName, step: element );
recipeSteps.Add(recipeStep);
}
}

View File

@@ -26,7 +26,8 @@ namespace Orchard.Recipes.Services {
ExecutionId = executionId,
Steps =
from record in records
select new RecipeStepResult() {
select new RecipeStepResult {
RecipeName = record.RecipeName,
StepName = record.StepName,
IsCompleted = record.IsCompleted,
IsSuccessful = record.IsSuccessful,

View File

@@ -29,7 +29,7 @@ namespace Orchard.Recipes.Services {
Logger = NullLogger.Instance;
}
public ILogger Logger;
public ILogger Logger { get; set; }
public void ScheduleWork(string executionId) {
var shellDescriptor = _shellDescriptorManager.GetShellDescriptor();

View File

@@ -44,11 +44,11 @@ namespace Orchard.Recipes.Services {
recipeHandler.ExecuteRecipeStep(recipeContext);
}
UpdateStepResultRecord(executionId, nextRecipeStep.Name, isSuccessful: true);
UpdateStepResultRecord(executionId, nextRecipeStep.RecipeName, nextRecipeStep.Name, isSuccessful: true);
_recipeExecuteEventHandler.RecipeStepExecuted(executionId, recipeContext);
}
catch (Exception ex) {
UpdateStepResultRecord(executionId, nextRecipeStep.Name, isSuccessful: false, errorMessage: ex.Message);
UpdateStepResultRecord(executionId, nextRecipeStep.RecipeName, nextRecipeStep.Name, isSuccessful: false, errorMessage: ex.Message);
Logger.Error(ex, "Recipe execution failed because the step '{0}' failed.", nextRecipeStep.Name);
while (_recipeStepQueue.Dequeue(executionId) != null);
var message = T("Recipe execution with ID {0} failed because the step '{1}' failed to execute. The following exception was thrown:\n{2}\nRefer to the error logs for more information.", executionId, nextRecipeStep.Name, ex.Message);
@@ -65,12 +65,15 @@ namespace Orchard.Recipes.Services {
return true;
}
private void UpdateStepResultRecord(string executionId, string stepName, bool isSuccessful, string errorMessage = null) {
private void UpdateStepResultRecord(string executionId, string recipeName, string stepName, bool isSuccessful, string errorMessage = null) {
var query =
from record in _recipeStepResultRecordRepository.Table
where record.ExecutionId == executionId && record.StepName == stepName
select record;
if (!String.IsNullOrWhiteSpace(recipeName))
query = from record in query where record.RecipeName == recipeName select record;
var stepResultRecord = query.Single();
stepResultRecord.IsCompleted = true;

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Orchard.ContentManagement;
using Orchard.FileSystems.AppData;
using Orchard.Localization;
using Orchard.Logging;
@@ -25,6 +26,7 @@ namespace Orchard.Recipes.Services {
public void Enqueue(string executionId, RecipeStep step) {
Logger.Information("Enqueuing recipe step '{0}'.", step.Name);
var recipeStepElement = new XElement("RecipeStep");
recipeStepElement.Attr("RecipeName", step.RecipeName);
recipeStepElement.Add(new XElement("Name", step.Name));
recipeStepElement.Add(step.Step);
@@ -52,11 +54,9 @@ namespace Orchard.Recipes.Services {
// string to xelement
var stepElement = XElement.Parse(_appDataFolder.ReadFile(stepPath));
var stepName = stepElement.Element("Name").Value;
var recipeName = stepElement.Attr("RecipeName");
Logger.Information("Dequeuing recipe step '{0}'.", stepName);
recipeStep = new RecipeStep {
Name = stepName,
Step = stepElement.Element(stepName)
};
recipeStep = new RecipeStep(recipeName: recipeName, name: stepName, step: stepElement.Element(stepName));
_appDataFolder.DeleteFile(stepPath);
}

View File

@@ -2,7 +2,14 @@
namespace Orchard.Recipes.Models {
public class RecipeStep {
public string Name { get; set; }
public XElement Step { get; set; }
public RecipeStep(string recipeName, string name, XElement step) {
RecipeName = recipeName;
Name = name;
Step = step;
}
public string RecipeName { get; private set; }
public string Name { get; private set; }
public XElement Step { get; private set; }
}
}

View File

@@ -2,6 +2,7 @@
namespace Orchard.Recipes.Models {
public class RecipeStepResult {
public string RecipeName { get; set; }
public string StepName { get; set; }
public bool IsCompleted { get; set; }
public bool IsSuccessful { get; set; }