using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.LogPersistence;
using Elsa.Workflows.Management.Options;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.State;
using Humanizer;
using Microsoft.Extensions.Options;
namespace Elsa.Workflows.Runtime;
///
public class DefaultActivityExecutionMapper : IActivityExecutionMapper
{
private readonly IOptions _options;
private readonly IDictionary _logPersistenceStrategies;
public DefaultActivityExecutionMapper(IOptions options, ILogPersistenceStrategyService logPersistenceStrategyService)
{
_options = options;
_logPersistenceStrategies = logPersistenceStrategyService.ListStrategies().ToDictionary(x => x.GetType().GetSimpleAssemblyQualifiedName(), x => x);
}
private const string LegacyLogPersistenceModeKey = "logPersistenceMode";
private const string LogPersistenceStrategyKey = "logPersistenceStrategy";
///
public async Task MapAsync(ActivityExecutionContext source)
{
/* The following legacy JSON structure is expected to be found in the custom properties of the workflow and activity:
* {
* "logPersistenceMode": {
* "default": "default",
* "inputs": { k : v },
* "outputs": { k: v }
* }
* }
*/
/* The following JSON structure is expected to be found in the custom properties of the workflow and activity:
* {
* "logPersistenceStrategy": {
* "default": "null",
* "inputs": { k : v },
* "outputs": { k: v }
* }
* }
*/
var cancellationToken = source.WorkflowExecutionContext.CancellationToken;
var workflow = (Workflow?)source.GetAncestors().FirstOrDefault(x => x.Activity is Workflow)?.Activity ?? source.WorkflowExecutionContext.Workflow;
var workflowPersistenceProperty = await GetDefaultPersistenceModeAsync(workflow.CustomProperties, () => _options.Value.LogPersistenceMode, cancellationToken);
var activityPersistencePropertyDefault = await GetDefaultPersistenceModeAsync(source.Activity.CustomProperties, () => workflowPersistenceProperty, cancellationToken);
var legacyActivityPersistenceProperties = source.Activity.CustomProperties.GetValueOrDefault>(LegacyLogPersistenceModeKey, () => new Dictionary());
var activityPersistenceProperties = source.Activity.CustomProperties.GetValueOrDefault>(LogPersistenceStrategyKey, () => new Dictionary());
var payload = GetPayload(source);
var outputs = GetOutputs(source);
outputs = await StorePropertyUsingPersistenceMode(
outputs,
legacyActivityPersistenceProperties!.GetValueOrDefault("outputs", () => new Dictionary())!,
activityPersistenceProperties!.GetValueOrDefault("outputs", () => new Dictionary())!,
activityPersistencePropertyDefault,
cancellationToken);
var inputs = await StorePropertyUsingPersistenceMode(
source.ActivityState,
legacyActivityPersistenceProperties!.GetValueOrDefault("inputs", () => new Dictionary())!,
activityPersistenceProperties!.GetValueOrDefault("inputs", () => new Dictionary())!,
activityPersistencePropertyDefault,
cancellationToken);
return new ActivityExecutionRecord
{
Id = source.Id,
ActivityId = source.Activity.Id,
ActivityNodeId = source.NodeId,
WorkflowInstanceId = source.WorkflowExecutionContext.Id,
ActivityType = source.Activity.Type,
ActivityName = source.Activity.Name,
ActivityState = inputs,
Outputs = outputs,
Properties = source.Properties,
Payload = payload,
Exception = ExceptionState.FromException(source.Exception),
ActivityTypeVersion = source.Activity.Version,
StartedAt = source.StartedAt,
HasBookmarks = source.Bookmarks.Any(),
Status = GetAggregateStatus(source),
CompletedAt = source.CompletedAt
};
}
private async Task GetDefaultPersistenceModeAsync(IDictionary customProperties, Func defaultFactory, CancellationToken cancellationToken)
{
var legacyProperties = customProperties.GetValueOrDefault>(LegacyLogPersistenceModeKey, () => new Dictionary());
var properties = customProperties.GetValueOrDefault>(LogPersistenceStrategyKey, () => new Dictionary());
var defaultPersistenceStrategyTypeName = properties!.GetValueOrDefault("default");
if (defaultPersistenceStrategyTypeName == null)
{
var legacyPersistencePropertyDefault = legacyProperties!.GetValueOrDefault("default", defaultFactory);
if (legacyPersistencePropertyDefault == LogPersistenceMode.Inherit)
return defaultFactory();
return legacyPersistencePropertyDefault;
}
var strategy = _logPersistenceStrategies.TryGetValue(defaultPersistenceStrategyTypeName, out var v) ? v : null;
if (strategy == null)
return defaultFactory();
var strategyContext = new LogPersistenceStrategyContext(cancellationToken);
return await strategy.GetPersistenceModeAsync(strategyContext);
}
private async Task> StorePropertyUsingPersistenceMode(
IDictionary state,
IDictionary persistenceModeConfiguration,
IDictionary persistenceStrategyConfiguration,
LogPersistenceMode defaultLogPersistenceMode,
CancellationToken cancellationToken)
{
var result = new Dictionary();
foreach (var value in state)
{
var propKey = value.Key.Camelize();
var strategy = persistenceStrategyConfiguration.GetValueOrDefault(propKey) is string strategyTypeName ? _logPersistenceStrategies.TryGetValue(strategyTypeName, out var v) ? v : null : null;
var mode = defaultLogPersistenceMode;
if (strategy != null)
{
var strategyContext = new LogPersistenceStrategyContext(cancellationToken);
mode = await strategy.GetPersistenceModeAsync(strategyContext);
}
else
{
mode = persistenceModeConfiguration.GetValueOrDefault(propKey, () => defaultLogPersistenceMode);
}
if (mode == LogPersistenceMode.Include || mode == LogPersistenceMode.Inherit && defaultLogPersistenceMode is LogPersistenceMode.Include or LogPersistenceMode.Inherit)
result.Add(value.Key, value.Value);
}
return result;
}
private static ActivityStatus GetAggregateStatus(ActivityExecutionContext context)
{
// If any child activity is faulted, the aggregate status is faulted.
var descendantContexts = context.GetDescendants().ToList();
if (descendantContexts.Any(x => x.Status == ActivityStatus.Faulted))
return ActivityStatus.Faulted;
return context.Status;
}
private static IDictionary GetPayload(ActivityExecutionContext source)
{
var outcomes = source.JournalData.TryGetValue("Outcomes", out var resultValue) ? resultValue as string[] : default;
var payload = new Dictionary();
if (outcomes != null)
payload.Add("Outcomes", outcomes);
return payload;
}
// Get the outputs of the activity execution context.
private static IDictionary GetOutputs(ActivityExecutionContext source)
{
var activity = source.Activity;
var expressionExecutionContext = source.ExpressionExecutionContext;
var activityDescriptor = source.ActivityDescriptor;
var outputDescriptors = activityDescriptor.Outputs;
var outputs = outputDescriptors.ToDictionary(x => x.Name, x =>
{
if (x.IsSerializable == false)
return "(not serializable)";
var cachedValue = activity.GetOutput(expressionExecutionContext, x.Name);
if (cachedValue != default)
return cachedValue;
if (x.ValueGetter(activity) is Output output && source.TryGet(output.MemoryBlockReference(), out var outputValue))
return outputValue;
return default;
});
return outputs;
}
}