| | | 1 | | using System.Text.Json; |
| | | 2 | | using Bpmn.Model.State; |
| | | 3 | | using Elsa.Workflows; |
| | | 4 | | |
| | | 5 | | namespace Elsa.Bpmn.Hosting; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// A BPMN scope's own memory: the interpreter's execution state, and the ledger of work this scope started. |
| | | 9 | | /// </summary> |
| | | 10 | | /// <remarks> |
| | | 11 | | /// Both live in the scope's <see cref="ActivityExecutionContext.Properties"/>, because that is where per-execution |
| | | 12 | | /// state belongs and because the handle-to-context map must survive anything that rewrites |
| | | 13 | | /// <see cref="ActivityExecutionContext.Tag"/>. Both are held as serialized JSON strings rather than as objects: |
| | | 14 | | /// the property bag is written through <c>PolymorphicObjectConverter</c>, which silently drops the type of a value |
| | | 15 | | /// whose type has no registered alias and hands back a loose <c>ExpandoObject</c> on the way in. |
| | | 16 | | /// </remarks> |
| | | 17 | | internal sealed class BpmnScopeMemory |
| | | 18 | | { |
| | | 19 | | /// <summary>The property key holding the interpreter's execution state.</summary> |
| | | 20 | | public const string ExecutionStatePropertyKey = "Bpmn:ExecutionState"; |
| | | 21 | | |
| | | 22 | | /// <summary>The property key holding this scope's work ledger.</summary> |
| | | 23 | | public const string WorkLedgerPropertyKey = "Bpmn:WorkLedger"; |
| | | 24 | | |
| | 2 | 25 | | private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.General); |
| | | 26 | | |
| | | 27 | | private readonly ActivityExecutionContext _context; |
| | | 28 | | |
| | 149 | 29 | | private BpmnScopeMemory(ActivityExecutionContext context, BpmnExecutionState? state, BpmnWorkLedger work) |
| | | 30 | | { |
| | 149 | 31 | | _context = context; |
| | 149 | 32 | | State = state; |
| | 149 | 33 | | Work = work; |
| | 149 | 34 | | } |
| | | 35 | | |
| | | 36 | | /// <summary>The state the interpreter last returned, or <c>null</c> before the scope has been started.</summary> |
| | 689 | 37 | | public BpmnExecutionState? State { get; set; } |
| | | 38 | | |
| | | 39 | | /// <summary>The work this scope has started and not finished.</summary> |
| | 761 | 40 | | public BpmnWorkLedger Work { get; } |
| | | 41 | | |
| | | 42 | | /// <summary>Reads a scope's memory from its property bag.</summary> |
| | | 43 | | public static BpmnScopeMemory Load(ActivityExecutionContext context) => |
| | 149 | 44 | | new(context, Read<BpmnExecutionState>(context, ExecutionStatePropertyKey), Read<BpmnWorkLedger>(context, WorkLed |
| | | 45 | | |
| | | 46 | | /// <summary>Reads a JSON string property, or returns <c>null</c> when it is absent.</summary> |
| | | 47 | | public static T? Read<T>(ActivityExecutionContext context, string key) where T : class => |
| | 528 | 48 | | context.Properties.TryGetValue(key, out var value) && value is string json && !string.IsNullOrWhiteSpace(json) |
| | 528 | 49 | | ? JsonSerializer.Deserialize<T>(json, SerializerOptions) |
| | 528 | 50 | | : null; |
| | | 51 | | |
| | | 52 | | /// <summary>Writes a value as a JSON string property.</summary> |
| | | 53 | | public static void Write<T>(ActivityExecutionContext context, string key, T value) => |
| | 446 | 54 | | context.Properties[key] = JsonSerializer.Serialize(value, SerializerOptions); |
| | | 55 | | |
| | | 56 | | /// <summary>Persists the execution state.</summary> |
| | | 57 | | public void SaveState() |
| | | 58 | | { |
| | 135 | 59 | | if (State is not null) |
| | 135 | 60 | | Write(_context, ExecutionStatePropertyKey, State); |
| | 135 | 61 | | } |
| | | 62 | | |
| | | 63 | | /// <summary>Persists the work ledger.</summary> |
| | 209 | 64 | | public void SaveWork() => Write(_context, WorkLedgerPropertyKey, Work); |
| | | 65 | | } |