Added support for multiple recipe steps of the same type and added unit tests.

Enables execution of multiple child recipes in a particular sequence. This is useful for the RecipesStep for example, where you want to execute a child recipe first, then execute some commands and import some content, then execute a second child recipe.
This commit is contained in:
Sipke Schoorstra
2015-07-30 14:26:01 +01:00
parent a7d2777478
commit 73170a0d63
17 changed files with 147 additions and 69 deletions

View File

@@ -159,6 +159,7 @@
<Compile Include="Packaging\Services\FileBasedProjectSystemTests.cs" /> <Compile Include="Packaging\Services\FileBasedProjectSystemTests.cs" />
<Compile Include="Packaging\Services\FolderUpdaterTests.cs" /> <Compile Include="Packaging\Services\FolderUpdaterTests.cs" />
<Compile Include="Packaging\Services\PackageInstallerTests.cs" /> <Compile Include="Packaging\Services\PackageInstallerTests.cs" />
<Compile Include="Recipes\RecipeHandlers\RecipeParserTest.cs" />
<Compile Include="Recipes\RecipeHandlers\RecipeExecutionStepHandlerTest.cs" /> <Compile Include="Recipes\RecipeHandlers\RecipeExecutionStepHandlerTest.cs" />
<Compile Include="Recipes\RecipeHandlers\ModuleStepTest.cs" /> <Compile Include="Recipes\RecipeHandlers\ModuleStepTest.cs" />
<Compile Include="Recipes\RecipeHandlers\ThemeStepTest.cs" /> <Compile Include="Recipes\RecipeHandlers\ThemeStepTest.cs" />
@@ -336,6 +337,12 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Recipes\Services\FoldersData\Sample2\Module.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Recipes\Services\FoldersData\Sample2\Recipes\duplicate-steps.recipe.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -94,7 +94,7 @@ Features:
Enumerable.Empty<ShellParameter>()); Enumerable.Empty<ShellParameter>());
var moduleStep = _container.Resolve<ModuleStep>(); var moduleStep = _container.Resolve<ModuleStep>();
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep( recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) }; var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep(id: "1", recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki")); recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test")); recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
@@ -120,7 +120,7 @@ Features:
"); ");
var moduleStep = _container.Resolve<ModuleStep>(); var moduleStep = _container.Resolve<ModuleStep>();
var recipeContext = new RecipeContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) }; var recipeContext = new RecipeContext { RecipeStep = new RecipeStep(id: "1", recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = recipeContext.RecipeStep }; var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = recipeContext.RecipeStep };
recipeContext.RecipeStep.Step.Add(new XAttribute("repository", "test")); recipeContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
@@ -145,7 +145,7 @@ Features:
}); });
var moduleStep = _container.Resolve<ModuleStep>(); var moduleStep = _container.Resolve<ModuleStep>();
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) }; var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(id: "1", recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki")); recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test")); 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 fakeRecipeStep = _container.Resolve<StubRecipeExecutionStep>();
var context = new RecipeContext { var context = new RecipeContext {
RecipeStep = new RecipeStep (recipeName: "FakeRecipe", name: "FakeRecipeStep", step: new XElement("FakeRecipeStep")), RecipeStep = new RecipeStep (id: "1", recipeName: "FakeRecipe", name: "FakeRecipeStep", step: new XElement("FakeRecipeStep")),
ExecutionId = "12345" ExecutionId = "12345"
}; };

View File

@@ -0,0 +1,29 @@
using System.Linq;
using Autofac;
using NUnit.Framework;
using Orchard.Recipes.Services;
namespace Orchard.Tests.Modules.Recipes.RecipeHandlers {
[TestFixture]
public class RecipeParserTest {
protected IContainer _container;
[SetUp]
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<RecipeParser>().As<IRecipeParser>();
_container = builder.Build();
}
[Test]
public void ParsingRecipeYieldsUniqueIdsForSteps() {
var recipeText = @"<Orchard><Foo /><Bar /><Baz /></Orchard>";
var recipeParser = _container.Resolve<IRecipeParser>();
var recipe = recipeParser.ParseRecipe(recipeText);
// Assert that each step has a unique ID.
Assert.IsTrue(recipe.RecipeSteps.GroupBy(x => x.Id).All(y => y.Count() == 1));
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
Name: Le plug-in français
Author: Bertrand Le Roy
Description:
This plug-in replaces
'the' with 'le'
Version: 1.4.1
Tags: plug-in, français, the, le
homepage: http://weblogs.asp.net/bleroy

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<Orchard>
<Recipe>
<Name>Duplicate Steps</Name>
</Recipe>
<Recipes />
<Recipes />
<Recipes />
</Orchard>

View File

@@ -1,61 +1,52 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
using Autofac; using Autofac;
using Moq; using Moq;
using NHibernate;
using NUnit.Framework; using NUnit.Framework;
using Orchard.Caching; using Orchard.Caching;
using Orchard.ContentManagement.Records;
using Orchard.Data; using Orchard.Data;
using Orchard.Environment.Extensions; using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Folders; using Orchard.Environment.Extensions.Folders;
using Orchard.Environment.Extensions.Loaders; using Orchard.Environment.Extensions.Loaders;
using Orchard.FileSystems.AppData; using Orchard.FileSystems.AppData;
using Orchard.FileSystems.WebSite; using Orchard.FileSystems.WebSite;
using Orchard.Recipes.Events;
using Orchard.Recipes.Models; using Orchard.Recipes.Models;
using Orchard.Recipes.Services; using Orchard.Recipes.Services;
using Orchard.Services; using Orchard.Services;
using Orchard.Tests.Environment.Extensions;
using Orchard.Tests.Stubs; using Orchard.Tests.Stubs;
using Orchard.Recipes.Events;
using System;
using System.Linq.Expressions;
namespace Orchard.Tests.Modules.Recipes.Services { namespace Orchard.Tests.Modules.Recipes.Services {
[TestFixture] [TestFixture]
public class RecipeManagerTests { public class RecipeManagerTests : DatabaseEnabledTestsBase {
private IContainer _container;
private IRecipeManager _recipeManager; private IRecipeManager _recipeManager;
private IRecipeHarvester _recipeHarvester; private IRecipeHarvester _recipeHarvester;
private IRecipeParser _recipeParser; private IRecipeParser _recipeParser;
private IExtensionFolders _folders; private IExtensionFolders _folders;
private ISessionFactory _sessionFactory;
private ISession _session;
private const string DataPrefix = "Orchard.Tests.Modules.Recipes.Services.FoldersData."; private const string DataPrefix = "Orchard.Tests.Modules.Recipes.Services.FoldersData.";
private string _tempFolderName; private string _tempFolderName;
[TestFixtureSetUp] protected override IEnumerable<Type> DatabaseTypes {
public void InitFixture() { get { yield return typeof (RecipeStepResultRecord); }
var databaseFileName = System.IO.Path.GetTempFileName();
_sessionFactory = DataUtility.CreateSessionFactory(
databaseFileName,
typeof(ContentTypeRecord),
typeof(ContentItemRecord),
typeof(ContentItemVersionRecord),
typeof(RecipeStepResultRecord));
} }
[SetUp] public override void Register(ContainerBuilder builder) {
public void Init() {
_tempFolderName = Path.GetTempFileName(); _tempFolderName = Path.GetTempFileName();
File.Delete(_tempFolderName); File.Delete(_tempFolderName);
var assembly = GetType().Assembly; var assembly = GetType().Assembly;
foreach (var name in assembly.GetManifestResourceNames()) { foreach (var name in assembly.GetManifestResourceNames())
if (name.StartsWith(DataPrefix)) { {
if (name.StartsWith(DataPrefix))
{
string text; string text;
using (var stream = assembly.GetManifestResourceStream(name)) { using (var stream = assembly.GetManifestResourceStream(name))
{
using (var reader = new StreamReader(stream)) using (var reader = new StreamReader(stream))
text = reader.ReadToEnd(); text = reader.ReadToEnd();
@@ -73,15 +64,16 @@ namespace Orchard.Tests.Modules.Recipes.Services {
var targetPath = Path.Combine(_tempFolderName, relativePath); var targetPath = Path.Combine(_tempFolderName, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
using (var stream = new FileStream(targetPath, FileMode.Create)) { using (var stream = new FileStream(targetPath, FileMode.Create))
using (var writer = new StreamWriter(stream)) { {
using (var writer = new StreamWriter(stream))
{
writer.Write(text); writer.Write(text);
} }
} }
} }
} }
var builder = new ContainerBuilder();
var harvester = new ExtensionHarvester(new StubCacheManager(), new StubWebSiteFolder(), new Mock<ICriticalErrorProvider>().Object); var harvester = new ExtensionHarvester(new StubCacheManager(), new StubWebSiteFolder(), new Mock<ICriticalErrorProvider>().Object);
_folders = new ModuleFolders(new[] { _tempFolderName }, harvester); _folders = new ModuleFolders(new[] { _tempFolderName }, harvester);
builder.RegisterType<RecipeManager>().As<IRecipeManager>(); builder.RegisterType<RecipeManager>().As<IRecipeManager>();
@@ -97,21 +89,23 @@ namespace Orchard.Tests.Modules.Recipes.Services {
builder.RegisterType<StubAsyncTokenProvider>().As<IAsyncTokenProvider>(); builder.RegisterType<StubAsyncTokenProvider>().As<IAsyncTokenProvider>();
builder.RegisterInstance(_folders).As<IExtensionFolders>(); builder.RegisterInstance(_folders).As<IExtensionFolders>();
builder.RegisterInstance(new Mock<IRecipeExecuteEventHandler>().Object); builder.RegisterInstance(new Mock<IRecipeExecuteEventHandler>().Object);
builder.RegisterType<Environment.Extensions.ExtensionManagerTests.StubLoaders>().As<IExtensionLoader>(); builder.RegisterType<ExtensionManagerTests.StubLoaders>().As<IExtensionLoader>();
builder.RegisterType<RecipeParser>().As<IRecipeParser>(); builder.RegisterType<RecipeParser>().As<IRecipeParser>();
builder.RegisterType<StubWebSiteFolder>().As<IWebSiteFolder>(); builder.RegisterType<StubWebSiteFolder>().As<IWebSiteFolder>();
builder.RegisterType<CustomRecipeHandler>().As<IRecipeHandler>(); builder.RegisterType<CustomRecipeHandler>().As<IRecipeHandler>();
builder.RegisterInstance(new StubRecipeStepResultRecordRepository()).As<IRepository<RecipeStepResultRecord>>(); }
public override void Init() {
base.Init();
_container = builder.Build();
_recipeManager = _container.Resolve<IRecipeManager>(); _recipeManager = _container.Resolve<IRecipeManager>();
_recipeParser = _container.Resolve<IRecipeParser>(); _recipeParser = _container.Resolve<IRecipeParser>();
_recipeHarvester = _container.Resolve<IRecipeHarvester>(); _recipeHarvester = _container.Resolve<IRecipeHarvester>();
} }
[TearDown] public override void Cleanup() {
public void Term() {
Directory.Delete(_tempFolderName, true); Directory.Delete(_tempFolderName, true);
base.Cleanup();
} }
[Test] [Test]
@@ -168,6 +162,38 @@ namespace Orchard.Tests.Modules.Recipes.Services {
Assert.That(CustomRecipeHandler.AttributeValue == "value1"); Assert.That(CustomRecipeHandler.AttributeValue == "value1");
} }
[Test]
public void ExecuteUpdatesStepResults()
{
var recipes = (List<Recipe>)_recipeHarvester.HarvestRecipes("Sample1");
var sampleRecipe = recipes.First();
var steps = sampleRecipe.RecipeSteps.ToArray();
_recipeManager.Execute(sampleRecipe);
var stepResultRepository = _container.Resolve<IRepository<RecipeStepResultRecord>>();
var stepResults = stepResultRepository.Table.ToArray();
Assert.That(stepResults.Count(), Is.EqualTo(steps.Count()));
Assert.IsTrue(stepResults.All(x => x.IsCompleted));
}
[Test]
public void CanExecuteSameStepMultipleTimes()
{
var recipes = (List<Recipe>)_recipeHarvester.HarvestRecipes("Sample2");
var recipe = recipes.Single(x => x.Name == "Duplicate Steps");
var steps = recipe.RecipeSteps.ToArray();
_recipeManager.Execute(recipe);
var stepResultRepository = _container.Resolve<IRepository<RecipeStepResultRecord>>();
var stepResults = stepResultRepository.Table.ToArray();
Assert.That(stepResults.Count(), Is.EqualTo(steps.Count()));
Assert.IsTrue(stepResults.All(x => x.IsCompleted));
}
} }
public class StubStepQueue : IRecipeStepQueue { public class StubStepQueue : IRecipeStepQueue {
@@ -182,22 +208,6 @@ namespace Orchard.Tests.Modules.Recipes.Services {
} }
} }
public class StubRecipeStepResultRecordRepository : IRepository<RecipeStepResultRecord> {
private List<RecipeStepResultRecord> _records = new List<RecipeStepResultRecord>();
public IQueryable<RecipeStepResultRecord> Table { get { return _records.AsQueryable(); } }
public void Copy(RecipeStepResultRecord source, RecipeStepResultRecord target) { }
public int Count(Expression<Func<RecipeStepResultRecord, bool>> predicate) { return _records.Count; }
public void Create(RecipeStepResultRecord entity) { _records.Add(entity); }
public void Delete(RecipeStepResultRecord entity) { _records.Remove(entity); }
public IEnumerable<RecipeStepResultRecord> Fetch(Expression<Func<RecipeStepResultRecord, bool>> predicate) { throw new NotImplementedException(); }
public IEnumerable<RecipeStepResultRecord> Fetch(Expression<Func<RecipeStepResultRecord, bool>> predicate, Action<Orderable<RecipeStepResultRecord>> order) { throw new NotImplementedException(); }
public IEnumerable<RecipeStepResultRecord> Fetch(Expression<Func<RecipeStepResultRecord, bool>> predicate, Action<Orderable<RecipeStepResultRecord>> order, int skip, int count) { throw new NotImplementedException(); }
public void Flush() { }
public RecipeStepResultRecord Get(Expression<Func<RecipeStepResultRecord, bool>> predicate) { throw new NotImplementedException(); }
public RecipeStepResultRecord Get(int id) { throw new NotImplementedException(); }
public void Update(RecipeStepResultRecord entity) { }
}
public class StubRecipeScheduler : IRecipeScheduler { public class StubRecipeScheduler : IRecipeScheduler {
private readonly IRecipeStepExecutor _recipeStepExecutor; private readonly IRecipeStepExecutor _recipeStepExecutor;
@@ -212,7 +222,7 @@ namespace Orchard.Tests.Modules.Recipes.Services {
public class CustomRecipeHandler : IRecipeHandler { public class CustomRecipeHandler : IRecipeHandler {
public static string AttributeValue; public static string AttributeValue;
public string[] _handles = { "Module", "Theme", "Migration", "Custom1", "Custom2", "Command", "Metadata", "Feature", "Settings" }; public string[] _handles = { "Module", "Theme", "Migration", "Custom1", "Custom2", "Command", "Metadata", "Feature", "Settings", "Recipes" };
public void ExecuteRecipeStep(RecipeContext recipeContext) { public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (_handles.Contains(recipeContext.RecipeStep.Name)) { if (_handles.Contains(recipeContext.RecipeStep.Name)) {

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@@ -255,7 +256,8 @@ namespace Orchard.Packaging.Controllers {
var recipe = new Recipe { var recipe = new Recipe {
Name = "Test", Name = "Test",
RecipeSteps = featureIds.Select( RecipeSteps = featureIds.Select(
x => new RecipeStep( (i,x) => new RecipeStep(
id: i.ToString(CultureInfo.InvariantCulture),
recipeName: "Test", recipeName: "Test",
name: "Feature", name: "Feature",
step: new XElement("Feature", new XAttribute("enable", x)))) step: new XElement("Feature", new XAttribute("enable", x))))

View File

@@ -7,6 +7,7 @@ namespace Orchard.Recipes {
.Column<int>("Id", c => c.PrimaryKey().Identity()) .Column<int>("Id", c => c.PrimaryKey().Identity())
.Column<string>("ExecutionId", c => c.WithLength(128).NotNull()) .Column<string>("ExecutionId", c => c.WithLength(128).NotNull())
.Column<string>("RecipeName", c => c.WithLength(256)) .Column<string>("RecipeName", c => c.WithLength(256))
.Column<string>("StepId", c => c.WithLength(32).NotNull())
.Column<string>("StepName", c => c.WithLength(256).NotNull()) .Column<string>("StepName", c => c.WithLength(256).NotNull())
.Column<bool>("IsCompleted", c => c.NotNull()) .Column<bool>("IsCompleted", c => c.NotNull())
.Column<bool>("IsSuccessful", c => c.NotNull()) .Column<bool>("IsSuccessful", c => c.NotNull())

View File

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

View File

@@ -65,6 +65,7 @@ namespace Orchard.Recipes.Providers.Executors {
_recipeStepResultRecordRepository.Create(new RecipeStepResultRecord { _recipeStepResultRecordRepository.Create(new RecipeStepResultRecord {
ExecutionId = executionId, ExecutionId = executionId,
RecipeName = recipe.Name, RecipeName = recipe.Name,
StepId = recipeStep.Id,
StepName = recipeStep.Name StepName = recipeStep.Name
}); });
} }

View File

@@ -48,6 +48,7 @@ namespace Orchard.Recipes.Services {
_recipeStepResultRecordRepository.Create(new RecipeStepResultRecord { _recipeStepResultRecordRepository.Create(new RecipeStepResultRecord {
ExecutionId = executionId, ExecutionId = executionId,
RecipeName = recipe.Name, RecipeName = recipe.Name,
StepId = recipeStep.Id,
StepName = recipeStep.Name StepName = recipeStep.Name
}); });
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using Orchard.Logging; using Orchard.Logging;
@@ -11,6 +12,7 @@ namespace Orchard.Recipes.Services {
public Recipe ParseRecipe(XDocument recipeDocument) { public Recipe ParseRecipe(XDocument recipeDocument) {
var recipe = new Recipe(); var recipe = new Recipe();
var recipeSteps = new List<RecipeStep>(); var recipeSteps = new List<RecipeStep>();
var stepId = 0;
foreach (var element in recipeDocument.Root.Elements()) { foreach (var element in recipeDocument.Root.Elements()) {
// Recipe metadata. // Recipe metadata.
@@ -52,7 +54,7 @@ namespace Orchard.Recipes.Services {
} }
// Recipe step. // Recipe step.
else { else {
var recipeStep = new RecipeStep(recipeName: recipe.Name, name: element.Name.LocalName, step: element ); var recipeStep = new RecipeStep(id: (++stepId).ToString(CultureInfo.InvariantCulture), recipeName: recipe.Name, name: element.Name.LocalName, step: element );
recipeSteps.Add(recipeStep); recipeSteps.Add(recipeStep);
} }
} }

View File

@@ -11,18 +11,18 @@ namespace Orchard.Recipes.Services {
private readonly IRecipeStepQueue _recipeStepQueue; private readonly IRecipeStepQueue _recipeStepQueue;
private readonly IEnumerable<IRecipeHandler> _recipeHandlers; private readonly IEnumerable<IRecipeHandler> _recipeHandlers;
private readonly IRecipeExecuteEventHandler _recipeExecuteEventHandler; private readonly IRecipeExecuteEventHandler _recipeExecuteEventHandler;
private readonly IRepository<RecipeStepResultRecord> _recipeStepResultRecordRepository; private readonly IRepository<RecipeStepResultRecord> _recipeStepResultRepository;
public RecipeStepExecutor( public RecipeStepExecutor(
IRecipeStepQueue recipeStepQueue, IRecipeStepQueue recipeStepQueue,
IEnumerable<IRecipeHandler> recipeHandlers, IEnumerable<IRecipeHandler> recipeHandlers,
IRecipeExecuteEventHandler recipeExecuteEventHandler, IRecipeExecuteEventHandler recipeExecuteEventHandler,
IRepository<RecipeStepResultRecord> recipeStepResultRecordRepository) { IRepository<RecipeStepResultRecord> recipeStepResultRepository) {
_recipeStepQueue = recipeStepQueue; _recipeStepQueue = recipeStepQueue;
_recipeHandlers = recipeHandlers; _recipeHandlers = recipeHandlers;
_recipeExecuteEventHandler = recipeExecuteEventHandler; _recipeExecuteEventHandler = recipeExecuteEventHandler;
_recipeStepResultRecordRepository = recipeStepResultRecordRepository; _recipeStepResultRepository = recipeStepResultRepository;
} }
public bool ExecuteNextStep(string executionId) { public bool ExecuteNextStep(string executionId) {
@@ -44,11 +44,11 @@ namespace Orchard.Recipes.Services {
recipeHandler.ExecuteRecipeStep(recipeContext); recipeHandler.ExecuteRecipeStep(recipeContext);
} }
UpdateStepResultRecord(executionId, nextRecipeStep.RecipeName, nextRecipeStep.Name, isSuccessful: true); UpdateStepResultRecord(executionId, nextRecipeStep.RecipeName, nextRecipeStep.Id, nextRecipeStep.Name, isSuccessful: true);
_recipeExecuteEventHandler.RecipeStepExecuted(executionId, recipeContext); _recipeExecuteEventHandler.RecipeStepExecuted(executionId, recipeContext);
} }
catch (Exception ex) { catch (Exception ex) {
UpdateStepResultRecord(executionId, nextRecipeStep.RecipeName, nextRecipeStep.Name, isSuccessful: false, errorMessage: ex.Message); UpdateStepResultRecord(executionId, nextRecipeStep.RecipeName, nextRecipeStep.Id, nextRecipeStep.Name, isSuccessful: false, errorMessage: ex.Message);
Logger.Error(ex, "Recipe execution failed because the step '{0}' failed.", nextRecipeStep.Name); Logger.Error(ex, "Recipe execution failed because the step '{0}' failed.", nextRecipeStep.Name);
while (_recipeStepQueue.Dequeue(executionId) != null); 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); 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,10 +65,10 @@ namespace Orchard.Recipes.Services {
return true; return true;
} }
private void UpdateStepResultRecord(string executionId, string recipeName, string stepName, bool isSuccessful, string errorMessage = null) { private void UpdateStepResultRecord(string executionId, string recipeName, string stepId, string stepName, bool isSuccessful, string errorMessage = null) {
var query = var query =
from record in _recipeStepResultRecordRepository.Table from record in _recipeStepResultRepository.Table
where record.ExecutionId == executionId && record.StepName == stepName where record.ExecutionId == executionId && record.StepId == stepId && record.StepName == stepName
select record; select record;
if (!String.IsNullOrWhiteSpace(recipeName)) if (!String.IsNullOrWhiteSpace(recipeName))
@@ -80,7 +80,7 @@ namespace Orchard.Recipes.Services {
stepResultRecord.IsSuccessful = isSuccessful; stepResultRecord.IsSuccessful = isSuccessful;
stepResultRecord.ErrorMessage = errorMessage; stepResultRecord.ErrorMessage = errorMessage;
_recipeStepResultRecordRepository.Update(stepResultRecord); _recipeStepResultRepository.Update(stepResultRecord);
} }
} }
} }

View File

@@ -26,6 +26,7 @@ namespace Orchard.Recipes.Services {
public void Enqueue(string executionId, RecipeStep step) { public void Enqueue(string executionId, RecipeStep step) {
Logger.Information("Enqueuing recipe step '{0}'.", step.Name); Logger.Information("Enqueuing recipe step '{0}'.", step.Name);
var recipeStepElement = new XElement("RecipeStep"); var recipeStepElement = new XElement("RecipeStep");
recipeStepElement.Attr("Id", step.Id);
recipeStepElement.Attr("RecipeName", step.RecipeName); recipeStepElement.Attr("RecipeName", step.RecipeName);
recipeStepElement.Add(new XElement("Name", step.Name)); recipeStepElement.Add(new XElement("Name", step.Name));
recipeStepElement.Add(step.Step); recipeStepElement.Add(step.Step);
@@ -54,9 +55,10 @@ namespace Orchard.Recipes.Services {
// string to xelement // string to xelement
var stepElement = XElement.Parse(_appDataFolder.ReadFile(stepPath)); var stepElement = XElement.Parse(_appDataFolder.ReadFile(stepPath));
var stepName = stepElement.Element("Name").Value; var stepName = stepElement.Element("Name").Value;
var stepId = stepElement.Attr("Id");
var recipeName = stepElement.Attr("RecipeName"); var recipeName = stepElement.Attr("RecipeName");
Logger.Information("Dequeuing recipe step '{0}'.", stepName); Logger.Information("Dequeuing recipe step '{0}'.", stepName);
recipeStep = new RecipeStep(recipeName: recipeName, name: stepName, step: stepElement.Element(stepName)); recipeStep = new RecipeStep(id: stepId, recipeName: recipeName, name: stepName, step: stepElement.Element(stepName));
_appDataFolder.DeleteFile(stepPath); _appDataFolder.DeleteFile(stepPath);
} }

View File

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