Example #1
0
        private string GetFullPath(WorkflowStorageContext context, string key)
        {
            var containerPath = GetContainerPath(context);
            var activityId    = context.ActivityId;

            return($"${containerPath}/{activityId}/{key}.dat");
        }
        private async Task AddActivityOutputAsync(Engine engine, ActivityExecutionContext activityExecutionContext, CancellationToken cancellationToken)
        {
            var workflowExecutionContext = activityExecutionContext.WorkflowExecutionContext;
            var workflowInstance         = activityExecutionContext.WorkflowInstance;
            var workflowBlueprint        = workflowExecutionContext.WorkflowBlueprint;
            var activities = new Dictionary <string, object>();

            foreach (var activity in workflowBlueprint.Activities.Where(x => !string.IsNullOrWhiteSpace(x.Name)))
            {
                var activityType = await _activityTypeService.GetActivityTypeAsync(activity.Type, cancellationToken);

                var activityDescriptor = await _activityTypeService.DescribeActivityType(activityType, cancellationToken);

                var outputProperties      = activityDescriptor.OutputProperties;
                var storageProviderLookup = activity.PropertyStorageProviders;
                var activityModel         = new Dictionary <string, object?>();
                var storageContext        = new WorkflowStorageContext(workflowInstance, activity.Id);

                foreach (var property in outputProperties)
                {
                    var propertyName        = property.Name;
                    var storageProviderName = storageProviderLookup.GetItem(propertyName) ?? property.DefaultWorkflowStorageProvider;

                    activityModel[propertyName] = (Func <object?>)(() => _workflowStorageService.LoadAsync(storageProviderName, storageContext, propertyName, cancellationToken).Result);
                }

                activities[activity.Name !] = activityModel;
Example #3
0
        public override ValueTask DeleteAsync(WorkflowStorageContext context, CancellationToken cancellationToken = default)
        {
            var workflowInstanceId = context.WorkflowInstance.Id;

            _cacheSignal.TriggerToken(GetSignalKey(workflowInstanceId));
            return(new ValueTask());
        }
Example #4
0
        public override ValueTask DeleteAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken = default)
        {
            var state = GetData(context);

            state.Remove(key);
            return(new ValueTask());
        }
Example #5
0
        public virtual async Task <RunWorkflowResult> RunWorkflowAsync(
            IWorkflowBlueprint workflowBlueprint,
            WorkflowInstance workflowInstance,
            string?activityId   = default,
            WorkflowInput?input = default,
            CancellationToken cancellationToken = default)
        {
            using var loggingScope           = _logger.BeginScope(new Dictionary <string, object> { ["WorkflowInstanceId"] = workflowInstance.Id });
            using var workflowExecutionScope = _serviceScopeFactory.CreateScope();

            if (input?.Input != null)
            {
                var workflowStorageContext = new WorkflowStorageContext(workflowInstance, workflowBlueprint.Id);
                var inputStorageProvider   = _workflowStorageService.GetProviderByNameOrDefault(input.StorageProviderName);
                await inputStorageProvider.SaveAsync(workflowStorageContext, nameof(WorkflowInstance.Input), input.Input, cancellationToken);

                workflowInstance.Input = new WorkflowInputReference(inputStorageProvider.Name);
                await _mediator.Publish(new WorkflowInputUpdated(workflowInstance), cancellationToken);
            }

            var workflowExecutionContext = new WorkflowExecutionContext(workflowExecutionScope.ServiceProvider, workflowBlueprint, workflowInstance, input?.Input);
            var result = await RunWorkflowInternalAsync(workflowExecutionContext, activityId, cancellationToken);

            await workflowExecutionContext.WorkflowExecutionLog.FlushAsync(cancellationToken);

            return(result);
        }
Example #6
0
        private async ValueTask ApplyStoredObjectValuesAsync(ActivityExecutionContext context, object activity, CancellationToken cancellationToken, string parentName = null)
        {
            var properties       = activity.GetType().GetProperties().Where(IsActivityProperty).ToList();
            var nestedProperties = activity.GetType().GetProperties().Where(IsActivityObjectProperty).ToList();
            var propertyStorageProviderDictionary = context.ActivityBlueprint.PropertyStorageProviders;
            var workflowStorageContext            = new WorkflowStorageContext(context.WorkflowInstance, context.ActivityId);

            foreach (var property in properties)
            {
                var propertyName = parentName == null ? property.Name : $"{parentName}_{property.Name}";
                var attr         = property.GetCustomAttribute <ActivityPropertyAttributeBase>();
                var providerName = propertyStorageProviderDictionary.GetItem(propertyName) ?? attr.DefaultWorkflowStorageProvider;
                var value        = await _workflowStorageService.LoadAsync(providerName, workflowStorageContext, propertyName, cancellationToken);

                if (value != null)
                {
                    var typedValue = value.ConvertTo(property.PropertyType);
                    property.SetValue(activity, typedValue);
                }
            }

            foreach (var nestedProperty in nestedProperties)
            {
                var instance     = Activator.CreateInstance(nestedProperty.PropertyType);
                var propertyName = parentName == null ? nestedProperty.Name : $"{parentName}_{nestedProperty.Name}";
                await ApplyStoredObjectValuesAsync(context, instance, cancellationToken, propertyName);
            }
        }
Example #7
0
        public async ValueTask <WorkflowInputReference> SaveAsync(WorkflowInput input, WorkflowInstance workflowInstance, CancellationToken cancellationToken = default)
        {
            var workflowStorageContext = new WorkflowStorageContext(workflowInstance, workflowInstance.DefinitionId);
            var inputStorageProvider   = GetProviderByNameOrDefault(input.StorageProviderName);
            await inputStorageProvider.SaveAsync(workflowStorageContext, nameof(WorkflowInstance.Input), input.Input, cancellationToken);

            return(new WorkflowInputReference(inputStorageProvider.Name));
        }
Example #8
0
        public override ValueTask DeleteAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken = default)
        {
            var workflowInstanceId = context.WorkflowInstance.Id;
            var activityId         = context.ActivityId;
            var cacheKey           = GetKey(workflowInstanceId, activityId, key);

            _cache.Remove(cacheKey);
            return(new ValueTask());
        }
Example #9
0
        public override ValueTask <object?> LoadAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken = default)
        {
            var workflowInstanceId = context.WorkflowInstance.Id;
            var activityId         = context.ActivityId;
            var cacheKey           = GetKey(workflowInstanceId, activityId, key);
            var value = _cache.TryGetValue(cacheKey, out var v) ? v : default;

            return(new ValueTask <object?>(value));
        }
Example #10
0
        public override ValueTask SaveAsync(WorkflowStorageContext context, string key, object?value, CancellationToken cancellationToken = default)
        {
            var workflowInstanceId = context.WorkflowInstance.Id;
            var activityId         = context.ActivityId;
            var cacheKey           = GetKey(workflowInstanceId, activityId, key);
            var options            = new MemoryCacheEntryOptions();

            options.ExpirationTokens.Add(_cacheSignal.GetToken(GetSignalKey(workflowInstanceId)));
            _cache.Set(cacheKey, value, options);

            return(new ValueTask());
        }
Example #11
0
        private async ValueTask StoreAppliedValuesAsync(ActivityExecutionContext context, IActivity activity, CancellationToken cancellationToken)
        {
            var properties = activity.GetType().GetProperties().Where(IsActivityProperty).ToList();
            var propertyStorageProviderDictionary = context.ActivityBlueprint.PropertyStorageProviders;
            var workflowStorageContext            = new WorkflowStorageContext(context.WorkflowInstance, context.ActivityId);

            foreach (var property in properties)
            {
                var value        = property.GetValue(activity);
                var providerName = propertyStorageProviderDictionary.GetItem(property.Name);
                await _workflowStorageService.SaveAsync(providerName, workflowStorageContext, property.Name, value, cancellationToken);
            }
        }
Example #12
0
        public async ValueTask <FluidValue> ProcessAsync(FluidValue input, FilterArguments arguments, TemplateContext context)
        {
            var activityName             = input.ToStringValue();
            var activityPropertyName     = arguments.Values.First().ToStringValue();
            var activityExecutionContext = (ActivityExecutionContext)context.Model;
            var activityBlueprint        = activityExecutionContext.WorkflowExecutionContext.GetActivityBlueprintByName(activityName) !;
            var activityId          = activityBlueprint.Id;
            var storageProviderName = activityBlueprint.PropertyStorageProviders.GetItem(activityPropertyName);
            var storageContext      = new WorkflowStorageContext(activityExecutionContext.WorkflowExecutionContext.WorkflowInstance, activityId);
            var value = await _workflowStorageService.LoadAsync(storageProviderName, storageContext, activityPropertyName);

            return(new ObjectValue(value));
        }
Example #13
0
        public async ValueTask <object?> LoadAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken = default)
        {
            var inputReference = workflowInstance.Input;

            if (inputReference == null)
            {
                return(null);
            }

            var context = new WorkflowStorageContext(workflowInstance, workflowInstance.DefinitionId);

            return(await LoadAsync(inputReference.ProviderName, context, nameof(WorkflowInstance.Input), cancellationToken));
        }
        private async Task SavePropertyAsync(ActivityExecutionContext context, string propertyName, object?value, CancellationToken cancellationToken)
        {
            var propertyStorageProviderDictionary = context.ActivityBlueprint.PropertyStorageProviders;
            var providerName           = propertyStorageProviderDictionary.GetItem(propertyName);
            var workflowStorageContext = new WorkflowStorageContext(context.WorkflowInstance, context.ActivityId);

            await _workflowStorageService.SaveAsync(providerName, workflowStorageContext, propertyName, value, cancellationToken);

            // By convention, properties named "Output" will be stored as the workflow output.
            if (propertyName == "Output")
            {
                context.WorkflowInstance.Output = new WorkflowOutputReference(providerName, context.ActivityId);
            }
        }
Example #15
0
        private async ValueTask ApplyStoredValuesAsync(ActivityExecutionContext context, IActivity activity, CancellationToken cancellationToken)
        {
            var properties = activity.GetType().GetProperties().Where(IsActivityProperty).ToList();
            var propertyStorageProviderDictionary = context.ActivityBlueprint.PropertyStorageProviders;
            var workflowStorageContext            = new WorkflowStorageContext(context.WorkflowInstance, context.ActivityId);

            foreach (var property in properties)
            {
                var providerName = propertyStorageProviderDictionary.GetItem(property.Name);
                var value        = await _workflowStorageService.LoadAsync(providerName, workflowStorageContext, property.Name, cancellationToken);

                if (value != null)
                {
                    var typedValue = value.ConvertTo(property.PropertyType);
                    property.SetValue(activity, typedValue);
                }
            }
        }
Example #16
0
        public override async ValueTask <object?> LoadAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken = default)
        {
            var path = GetFullPath(context, key);

            if (!await _blobStorage.ExistsAsync(path, cancellationToken))
            {
                return(null);
            }

            var blob = await _blobStorage.GetBlobAsync(path, cancellationToken);

            var contentType = blob.Metadata.GetItem("ContentType") ?? "Json";

            if (contentType == "Json")
            {
                var json = await _blobStorage.ReadTextAsync(path, cancellationToken : cancellationToken);

                return(JsonConvert.DeserializeObject(json, _serializerSettings));
            }

            return(await _blobStorage.ReadBytesAsync(path, cancellationToken));
        }
Example #17
0
        public override async ValueTask SaveAsync(WorkflowStorageContext context, string key, object?value, CancellationToken cancellationToken = default)
        {
            if (value == null)
            {
                return;
            }

            var path = GetFullPath(context, key);

            if (value is Stream stream)
            {
                await _blobStorage.WriteAsync(path, stream, cancellationToken : cancellationToken);

                var blob = await _blobStorage.GetBlobAsync(path, cancellationToken);

                blob.Metadata["ContentType"] = "Binary";
                await _blobStorage.SetBlobAsync(blob, cancellationToken);
            }
            else if (value is byte[] bytes)
            {
                await _blobStorage.WriteAsync(path, bytes, cancellationToken : cancellationToken);

                var blob = await _blobStorage.GetBlobAsync(path, cancellationToken);

                blob.Metadata["ContentType"] = "Binary";
                await _blobStorage.SetBlobAsync(blob, cancellationToken);
            }
            else
            {
                var json      = JsonConvert.SerializeObject(value, _serializerSettings);
                var jsonBytes = Encoding.UTF8.GetBytes(json);
                await _blobStorage.WriteAsync(path, jsonBytes, cancellationToken : cancellationToken);

                var blob = await _blobStorage.GetBlobAsync(path, cancellationToken);

                blob.Metadata["ContentType"] = "Json";
                await _blobStorage.SetBlobAsync(blob, cancellationToken);
            }
        }
        public async ValueTask <object?> LoadAsync(string?providerName, WorkflowStorageContext context, string key, CancellationToken cancellationToken = default)
        {
            var provider = GetProviderByNameOrDefault(providerName);

            return(await provider.LoadAsync(context, key, cancellationToken));
        }
 public async ValueTask SaveAsync(string?providerName, WorkflowStorageContext context, string key, object?value, CancellationToken cancellationToken = default)
 {
     var provider = GetProviderByNameOrDefault(providerName);
     await provider.SaveAsync(context, key, value, cancellationToken);
 }
Example #20
0
        public virtual async Task <RunWorkflowResult> RunWorkflowAsync(
            IWorkflowBlueprint workflowBlueprint,
            WorkflowInstance workflowInstance,
            string?activityId   = default,
            WorkflowInput?input = default,
            CancellationToken cancellationToken = default)
        {
            using var loggingScope           = _logger.BeginScope(new Dictionary <string, object> { ["WorkflowInstanceId"] = workflowInstance.Id });
            using var workflowExecutionScope = _serviceScopeFactory.CreateScope();

            if (input?.Input != null)
            {
                var workflowStorageContext = new WorkflowStorageContext(workflowInstance, workflowBlueprint.Id);
                var inputStorageProvider   = _workflowStorageService.GetProviderByNameOrDefault(input.StorageProviderName);
                await inputStorageProvider.SaveAsync(workflowStorageContext, nameof(WorkflowInstance.Input), input.Input, cancellationToken);

                workflowInstance.Input = new WorkflowInputReference(inputStorageProvider.Name);
                await _mediator.Publish(new WorkflowInputUpdated(workflowInstance), cancellationToken);
            }

            var workflowExecutionContext = new WorkflowExecutionContext(workflowExecutionScope.ServiceProvider, workflowBlueprint, workflowInstance, input?.Input);

            if (!string.IsNullOrWhiteSpace(workflowInstance.ContextId))
            {
                var loadContext = new LoadWorkflowContext(workflowExecutionContext);
                workflowExecutionContext.WorkflowContext = await _workflowContextManager.LoadContext(loadContext, cancellationToken);
            }

            // If the workflow instance has a CurrentActivity, it means the workflow instance is being retried.
            var currentActivity = workflowInstance.CurrentActivity;

            if (currentActivity != null)
            {
                activityId = currentActivity.ActivityId;
            }

            var activity = activityId != null?workflowBlueprint.GetActivity(activityId) : default;

            // Give application a chance to prevent workflow from executing.
            var validateWorkflowExecution = new ValidateWorkflowExecution(workflowExecutionContext, activity);
            await _mediator.Publish(validateWorkflowExecution, cancellationToken);

            if (!validateWorkflowExecution.CanExecuteWorkflow)
            {
                _logger.LogInformation("Workflow execution prevented for workflow {WorkflowInstanceId}", workflowInstance.Id);
                return(new RunWorkflowResult(workflowInstance, activityId, false));
            }

            await _mediator.Publish(new WorkflowExecuting(workflowExecutionContext), cancellationToken);

            RunWorkflowResult runWorkflowResult;

            switch (workflowExecutionContext.Status)
            {
            case WorkflowStatus.Idle:
                runWorkflowResult = await BeginWorkflow(workflowExecutionContext, activity, cancellationToken);

                if (!runWorkflowResult.Executed)
                {
                    if (workflowInstance.WorkflowStatus != WorkflowStatus.Faulted)
                    {
                        _logger.LogDebug("Workflow {WorkflowInstanceId} cannot begin from an idle state (perhaps it needs a specific input)", workflowInstance.Id);
                        return(runWorkflowResult);
                    }
                }

                break;

            case WorkflowStatus.Running:
                await RunWorkflowAsync(workflowExecutionContext, cancellationToken);

                runWorkflowResult = new RunWorkflowResult(workflowInstance, activityId, true);
                break;

            case WorkflowStatus.Suspended:
                runWorkflowResult = await ResumeWorkflowAsync(workflowExecutionContext, activity !, cancellationToken);

                if (!runWorkflowResult.Executed)
                {
                    _logger.LogDebug("Workflow {WorkflowInstanceId} cannot be resumed from a suspended state (perhaps it needs a specific input)", workflowInstance.Id);
                    return(runWorkflowResult);
                }

                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            await _mediator.Publish(new WorkflowExecuted(workflowExecutionContext), cancellationToken);

            var statusEvent = workflowExecutionContext.Status switch
            {
                WorkflowStatus.Cancelled => new WorkflowCancelled(workflowExecutionContext),
                WorkflowStatus.Finished => new WorkflowCompleted(workflowExecutionContext),
                WorkflowStatus.Faulted => new WorkflowFaulted(workflowExecutionContext),
                WorkflowStatus.Suspended => new WorkflowSuspended(workflowExecutionContext),
                _ => default(INotification)
            };

            if (statusEvent != null)
            {
                await _mediator.Publish(statusEvent, cancellationToken);
            }

            await _mediator.Publish(new WorkflowExecutionFinished(workflowExecutionContext), cancellationToken);

            return(runWorkflowResult);
        }
Example #21
0
 public override ValueTask DeleteAsync(WorkflowStorageContext context, CancellationToken cancellationToken = default)
 {
     context.WorkflowInstance.ActivityData.Remove(context.ActivityId !);
     return(new ValueTask());
 }
Example #22
0
 public abstract ValueTask SaveAsync(WorkflowStorageContext context, string key, object?value, CancellationToken cancellationToken = default);
Example #23
0
 public abstract ValueTask DeleteAsync(WorkflowStorageContext context, CancellationToken cancellationToken = default);
Example #24
0
 public abstract ValueTask <object?> LoadAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken     = default);
Example #25
0
 public override async ValueTask DeleteAsync(WorkflowStorageContext context, CancellationToken cancellationToken = default)
 {
     var path = GetContainerPath(context);
     await _blobStorage.DeleteAsync(path, cancellationToken);
 }
Example #26
0
 public override async ValueTask DeleteAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken = default)
 {
     var path = GetFullPath(context, key);
     await _blobStorage.DeleteAsync(path, cancellationToken);
 }
 public async ValueTask DeleteAsync(string?providerName, WorkflowStorageContext context, CancellationToken cancellationToken = default)
 {
     var provider = GetProviderByNameOrDefault(providerName);
     await provider.DeleteAsync(context, cancellationToken);
 }
Example #28
0
 private IDictionary <string, object?> GetData(WorkflowStorageContext context) => context.WorkflowInstance.ActivityData.GetItem(context.ActivityId !, () => new Dictionary <string, object?>());
Example #29
0
 private string GetContainerPath(WorkflowStorageContext context) => context.WorkflowInstance.Id;
Example #30
0
        public override ValueTask <object?> LoadAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken = default)
        {
            var value = GetState(context, key);

            return(new ValueTask <object?>(value));
        }