public string StoreWorkflow(WorkflowObj wfObj) { try { var wf = (from w in Workflows where w.Name == wfObj.Name && w.Version == ConvertVersion(wfObj.Version) select w).SingleOrDefault(); if (wf == null) { wf = new Workflow { Name = wfObj.Name, Version = ConvertVersion(wfObj.Version), // Description = acObj.Description, Json = JsonConvert.SerializeObject(wfObj) }; Workflows.InsertOnSubmit(wf); } wf.Json = JsonConvert.SerializeObject(wfObj); SubmitChanges(ConflictMode.FailOnFirstConflict); return("\"OK\""); } catch (Exception ex) { return("\"" + ex.Message + "\""); } }
private static void AttemptCleanup(Database db, Execution execution, WorkflowObj workflow, List <History> decisions) { if (ExState.Create(execution.ExecutionState.State) == ExState.Cleanup) { StopWorkflow(execution, decisions); return; } try { var cleanupStartTask = workflow.Tasks.SingleOrDefault(t => t.ActivityName == "cleanup"); if (cleanupStartTask == null) { StopWorkflow(execution, decisions); return; } var cleanupTaskId = cleanupStartTask.Outflows.Single(o => o.Name == "Out").Target; var cleanupTask = workflow.Tasks.Single(t => t.TaskId == cleanupTaskId); decisions.Add(new WorkflowCleanupStartedEvent()); CreateTaskScheduledEvent(db, execution, cleanupTask, decisions); } catch (Exception ex) { log.Error("Exception thrown from cleanup", ex); StopWorkflow(execution, decisions); } }
/// <summary> /// Two stage workflow execution start. /// First create and initialise the workflow variables /// Then schedule the first task /// If this process is interrupted then the variables /// </summary> /// <param name="db"></param> /// <param name="workflow"></param> /// <param name="execution"></param> /// <param name="history"></param> /// <param name="decisions"></param> private static void ProcessWorkflowExecutionStartedEvent(Database db, WorkflowObj workflow, Execution execution, History history, List <History> decisions) { var evt = WorkflowExecutionStartedEvent.Create(history); CreateVariables(db, evt, execution, workflow); // Ensure that the variables are set up for the first task // This should never fail due to conflict db.SubmitChanges(); // Scheduling the first task var startTaskId = workflow.Tasks.Single(t => t.ActivityName == "start").Outflows.Single(o => o.Name == "Out").Target; var startTask = workflow.Tasks.Single(t => t.TaskId == startTaskId); CreateTaskScheduledEvent(db, execution, startTask, decisions); }
/// <summary> /// When a task fails, follow the error outflow if one exists else fail the workflow /// </summary> /// <param name="db"></param> /// <param name="workflow"></param> /// <param name="execution"></param> /// <param name="history"></param> /// <param name="decisions"></param> private static void ProcessActivityTaskFailedEvent(Database db, WorkflowObj workflow, Execution execution, History history, List <History> decisions) { // There should(!) be no contention for the data modified in this process var evt = ActivityTaskFailedEvent.Create(history); var se = ActivityTaskScheduledEvent.Create(db.GetHistoryEvent(execution.ExecutionId, evt.SchedulingEventId)); var failedTask = workflow.Tasks.Single(t => t.TaskId == se.TaskId); var nextTaskId = failedTask.FailOutflow?.Target; if (!string.IsNullOrEmpty(nextTaskId)) { var nextTask = workflow.Tasks.Single(t => t.TaskId == nextTaskId); CreateTaskScheduledEvent(db, execution, nextTask, decisions); } decisions.Add(new WorkflowExecutionFailedEvent { Reason = $"Task {se.TaskId} failed with no recovery action defined" }); AttemptCleanup(db, execution, workflow, decisions); }
/// <summary> /// Create all workflow variables and initialise them from the workflow input data /// </summary> /// <param name="db"></param> /// <param name="evt"></param> /// <param name="execution"></param> /// <param name="workflow"></param> private static void CreateVariables(Database db, WorkflowExecutionStartedEvent evt, Execution execution, WorkflowObj workflow) { foreach (var vdefn in workflow.Variables) { // Get the initial value of the variable JToken value = null; if (!string.IsNullOrWhiteSpace(vdefn.Value.Path)) { value = evt.Input.SelectToken(vdefn.Value.Path); if (value == null) // The path expression failed { if (vdefn.Value.Required) { throw new ApplicationException("Uninitialised required variable - " + vdefn.Key); } } // Empty default is equivalent to an explicit null if (IsNullOrUndefined(value) && !string.IsNullOrWhiteSpace(vdefn.Value.Default)) { value = JToken.Parse(vdefn.Value.Default); } } // Empty literal is equivalent to an explicit null else if (!string.IsNullOrWhiteSpace(vdefn.Value.Lit)) { value = JToken.Parse(vdefn.Value.Lit); } // TODO: Check datatypes for validity db.Variables.InsertOnSubmit(new Variable { ExecutionId = execution.ExecutionId, Name = vdefn.Key, Json = JsonConvert.SerializeObject(value) }); } }
/// <summary> /// Process task completion by scheduling the next task or completing the workflow /// </summary> /// <param name="db"></param> /// <param name="workflow"></param> /// <param name="execution"></param> /// <param name="history"></param> /// <param name="decisions"></param> private static void ProcessActivityTaskCompletedEvent(Database db, WorkflowObj workflow, Execution execution, History history, List <History> decisions) { // There should(!) be no contention for the data modified in this process var evt = ActivityTaskCompletedEvent.Create(history); var se = ActivityTaskScheduledEvent.Create(db.GetHistoryEvent(execution.ExecutionId, evt.SchedulingEventId)); var completedTask = workflow.Tasks.Single(t => t.TaskId == se.TaskId); // Default task outflow var outflow = "Out"; // Update variables if (evt.Result != null) { // Update the variables if there are any normal results (not prefixed with "$") if (evt.Result.Properties().Any(p => !p.Name.StartsWith("$"))) { var variables = db.Variables.Where(v => v.Execution == execution).ToArray(); foreach (var o in completedTask.Outputs.Where(o => o.Value.Var != null)) { // If the activity has not returned a value for an output then we don't update the mapped variable // In general it is probably best if activities return values for all outputs to avoid confusion JToken value; if (evt.Result.TryGetValue(o.Key, out value)) { variables.Single(v => v.Name == o.Value.Var).Json = value.ToString(Formatting.None); } } } // Get the correct outflow JToken outflowToken; if (evt.Result.TryGetValue("$outflow", out outflowToken)) { if (outflowToken.Type == JTokenType.String) { outflow = (string)outflowToken; } else { throw new ApplicationException("Task outflow identifier must be a string"); } } } var nextTaskId = completedTask.Outflows.Single(o => o.Name == outflow).Target; var nextTask = workflow.Tasks.Single(t => t.TaskId == nextTaskId); // A task with no outflows is an end if (nextTask.Outflows.Length == 0) { Console.WriteLine($"Execution state = {execution.ExecutionState.State}"); if (ExState.Create(execution.ExecutionState.State) != ExState.Cleanup) { CreateWorkflowCompletedEvent(execution, decisions); } AttemptCleanup(db, execution, workflow, decisions); } else { CreateTaskScheduledEvent(db, execution, nextTask, decisions); } }