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,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;