RecipeJournal implementation.

- IStorageProvider based journal implementation for the recipe pipeline.
- Was needed as the existing reporting capability was found insufficient.

--HG--
branch : recipe
This commit is contained in:
Suha Can
2011-02-16 17:16:15 -08:00
parent 8f699ea418
commit 4218725a77
6 changed files with 127 additions and 17 deletions

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
@@ -68,6 +67,7 @@ namespace Orchard.Tests.Modules.Recipes.Services {
builder.RegisterType<RecipeHarvester>().As<IRecipeHarvester>();
builder.RegisterType<RecipeStepExecutor>().As<IRecipeStepExecutor>();
builder.RegisterType<StubStepQueue>().As<IRecipeStepQueue>().InstancePerLifetimeScope();
builder.RegisterType<StubRecipeJournal>().As<IRecipeJournal>();
builder.RegisterType<StubRecipeScheduler>().As<IRecipeScheduler>();
builder.RegisterType<ExtensionManager>().As<IExtensionManager>();
builder.RegisterType<StubAppDataFolder>().As<IAppDataFolder>();
@@ -145,6 +145,28 @@ namespace Orchard.Tests.Modules.Recipes.Services {
}
}
public class StubRecipeJournal : IRecipeJournal {
public void ExecutionStart(string executionId) {
}
public void ExecutionComplete(string executionId) {
}
public void ExecutionFailed(string executionId) {
}
public void WriteJournalEntry(string executionId, string message) {
}
public RecipeJournal GetRecipeJournal(string executionId) {
return new RecipeJournal();
}
public RecipeStatus GetRecipeStatus(string executionId) {
return RecipeStatus.Complete;
}
}
public class StubStepQueue : IRecipeStepQueue {
readonly Queue<RecipeStep> _queue = new Queue<RecipeStep>();
@@ -171,8 +193,12 @@ namespace Orchard.Tests.Modules.Recipes.Services {
public class CustomRecipeHandler : IRecipeHandler {
public static string AttributeValue;
public string[] _handles = {"Module", "Theme", "Migration", "CleanUpInactive", "Custom1", "Custom2", "Command", "Metadata", "Feature", "Settings"};
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (_handles.Contains(recipeContext.RecipeStep.Name)) {
recipeContext.Executed = true;
}
if (recipeContext.RecipeStep.Name == "Custom1") {
foreach (var attribute in recipeContext.RecipeStep.Step.Attributes().Where(attribute => attribute.Name == "attr1")) {
AttributeValue = attribute.Value;

View File

@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using Orchard.FileSystems.Media;
using Orchard.Localization;
using Orchard.Logging;
@@ -7,6 +10,7 @@ using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public class RecipeJournalManager : IRecipeJournal {
private readonly IStorageProvider _storageProvider;
private readonly string _recipeJournalFolder = "RecipeJournal" + Path.DirectorySeparatorChar;
public RecipeJournalManager(IStorageProvider storageProvider) {
_storageProvider = storageProvider;
@@ -18,36 +22,103 @@ namespace Orchard.Recipes.Services {
public Localizer T { get; set; }
ILogger Logger { get; set; }
public void StartExecution(string executionId) {
throw new NotImplementedException();
public void ExecutionStart(string executionId) {
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
xElement.Element("Status").Value = "Started";
WriteJournal(executionJournal, xElement);
}
public void ExecutionComplete(string executionId) {
throw new NotImplementedException();
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
xElement.Element("Status").Value = "Complete";
WriteJournal(executionJournal, xElement);
}
public void ExecutionFailed(string executionId) {
throw new NotImplementedException();
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
xElement.Element("Status").Value = "Failed";
WriteJournal(executionJournal, xElement);
}
public void WriteJournalEntry(string executionId, string message) {
throw new NotImplementedException();
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
var journalEntry = new XElement("Message", message);
xElement.Add(journalEntry);
WriteJournal(executionJournal, xElement);
}
public RecipeJournal GetRecipeJournal(string executionId) {
throw new NotImplementedException();
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
var journal = new RecipeJournal { ExecutionId = executionId };
var messages = new List<JournalMessage>();
journal.Status = ReadStatusFromJournal(xElement);
foreach (var message in xElement.Elements("Message")) {
messages.Add(new JournalMessage {Message = message.Value});
}
journal.Messages = messages;
return journal;
}
public RecipeStatus GetRecipeStatus(string executionId) {
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
return ReadStatusFromJournal(xElement);
}
private IStorageFile GetJournalFile(string executionId) {
IStorageFile journalFile;
var journalPath = Path.Combine(_recipeJournalFolder, executionId);
try {
journalFile = _storageProvider.GetFile(executionId);
_storageProvider.TryCreateFolder(_recipeJournalFolder);
journalFile = _storageProvider.GetFile(journalPath);
}
catch (ArgumentException) {
journalFile = _storageProvider.CreateFile(executionId);
journalFile = _storageProvider.CreateFile(journalPath);
var recipeStepElement = new XElement("RecipeJournal");
recipeStepElement.Add(new XElement("Status", "Unknown"));
WriteJournal(journalFile, recipeStepElement);
}
return journalFile;
}
private static string ReadJournal(IStorageFile executionJournal) {
using (var stream = executionJournal.OpenRead()) {
using (var streamReader = new StreamReader(stream)) {
return streamReader.ReadToEnd();
}
}
}
private static void WriteJournal(IStorageFile journalFile, XElement journal) {
string content = journal.ToString();
using (var stream = journalFile.OpenWrite()) {
using (var tw = new StreamWriter(stream)) {
tw.Write(content);
}
}
}
private static RecipeStatus ReadStatusFromJournal(XElement xElement) {
switch (xElement.Element("Status").Value) {
case "Started":
return RecipeStatus.Started;
case "Complete":
return RecipeStatus.Complete;
case "Failed":
return RecipeStatus.Failed;
default:
return RecipeStatus.Unknown;
}
}
}
}

View File

@@ -7,10 +7,12 @@ namespace Orchard.Recipes.Services {
public class RecipeManager : IRecipeManager {
private readonly IRecipeStepQueue _recipeStepQueue;
private readonly IRecipeScheduler _recipeScheduler;
private readonly IRecipeJournal _recipeJournal;
public RecipeManager(IRecipeStepQueue recipeStepQueue, IRecipeScheduler recipeScheduler) {
public RecipeManager(IRecipeStepQueue recipeStepQueue, IRecipeScheduler recipeScheduler, IRecipeJournal recipeJournal) {
_recipeStepQueue = recipeStepQueue;
_recipeScheduler = recipeScheduler;
_recipeJournal = recipeJournal;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
@@ -24,7 +26,8 @@ namespace Orchard.Recipes.Services {
return null;
var executionId = Guid.NewGuid().ToString("n");
// TODO: Run each step inside a transaction boundary.
_recipeJournal.ExecutionStart(executionId);
foreach (var recipeStep in recipe.RecipeSteps) {
_recipeStepQueue.Enqueue(executionId, recipeStep);
}

View File

@@ -7,10 +7,12 @@ using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public class RecipeStepExecutor : IRecipeStepExecutor {
private readonly IRecipeStepQueue _recipeStepQueue;
private readonly IRecipeJournal _recipeJournal;
private readonly IEnumerable<IRecipeHandler> _recipeHandlers;
public RecipeStepExecutor(IRecipeStepQueue recipeStepQueue, IEnumerable<IRecipeHandler> recipeHandlers) {
public RecipeStepExecutor(IRecipeStepQueue recipeStepQueue, IRecipeJournal recipeJournal, IEnumerable<IRecipeHandler> recipeHandlers) {
_recipeStepQueue = recipeStepQueue;
_recipeJournal = recipeJournal;
_recipeHandlers = recipeHandlers;
Logger = NullLogger.Instance;
@@ -23,8 +25,10 @@ namespace Orchard.Recipes.Services {
public bool ExecuteNextStep(string executionId) {
var nextRecipeStep= _recipeStepQueue.Dequeue(executionId);
if (nextRecipeStep == null) {
_recipeJournal.ExecutionComplete(executionId);
return false;
}
_recipeJournal.WriteJournalEntry(executionId, string.Format("Executing step {0}.", nextRecipeStep.Name));
var recipeContext = new RecipeContext { RecipeStep = nextRecipeStep, Executed = false };
try {
foreach (var recipeHandler in _recipeHandlers) {
@@ -34,11 +38,15 @@ namespace Orchard.Recipes.Services {
catch(Exception exception) {
Logger.Error(exception, "Recipe execution {0} was cancelled because a step failed to execute", executionId);
while (_recipeStepQueue.Dequeue(executionId) != null) ;
_recipeJournal.ExecutionFailed(executionId);
return false;
}
if (!recipeContext.Executed) {
Logger.Error("Could not execute recipe step '{0}' because the recipe handler was not found.", recipeContext.RecipeStep.Name);
while (_recipeStepQueue.Dequeue(executionId) != null) ;
_recipeJournal.ExecutionFailed(executionId);
return false;
}
return true;

View File

@@ -3,7 +3,7 @@
namespace Orchard.Recipes.Models {
public class RecipeJournal {
public string ExecutionId { get; set; }
public RecipeJournalStatus Status { get; set; }
public RecipeStatus Status { get; set; }
public IEnumerable<JournalMessage> Messages { get; set; }
}
@@ -11,8 +11,9 @@ namespace Orchard.Recipes.Models {
public string Message { get; set; }
}
public enum RecipeJournalStatus {
Running,
public enum RecipeStatus {
Unknown,
Started,
Complete,
Failed
}

View File

@@ -2,10 +2,11 @@
namespace Orchard.Recipes.Services {
public interface IRecipeJournal : IDependency {
void StartExecution(string executionId);
void ExecutionStart(string executionId);
void ExecutionComplete(string executionId);
void ExecutionFailed(string executionId);
void WriteJournalEntry(string executionId, string message);
RecipeJournal GetRecipeJournal(string executionId);
RecipeStatus GetRecipeStatus(string executionId);
}
}