internal static bool HasScriptWithEntityUse(this WorkflowConfiguration workflowConfiguration) { var hasActivityScriptWithEntityUse = workflowConfiguration.States .SelectMany(s => s.Activities).Any(a => !string.IsNullOrEmpty(a.Script) && a.Script.Contains("entity")); if (hasActivityScriptWithEntityUse) { return(true); } var hasEventHandlerScriptWithEntityUse = workflowConfiguration.States .SelectMany(s => s.Events).Any(e => !string.IsNullOrEmpty(e.Script) && e.Script.Contains("entity")); if (hasEventHandlerScriptWithEntityUse) { return(true); } var hasTransitionScriptWithEntityUse = workflowConfiguration.States .SelectMany(s => s.Transitions).Any(t => !string.IsNullOrEmpty(t.ConditionScript) && t.ConditionScript.Contains("entity")); if (hasTransitionScriptWithEntityUse) { return(true); } return(false); }
internal static StateConfiguration GetStateConfigurationByCode(this WorkflowConfiguration workflowConfiguration, string code) { var stateConfiguration = workflowConfiguration.States.SingleOrDefault(s => string.Equals(s.Code, code, StringComparison.OrdinalIgnoreCase)); if (null == stateConfiguration) { throw new WorkflowException($"State [{code}] was not found in workflow [{workflowConfiguration.Code}]"); } return(stateConfiguration); }
internal static IEndpointConfiguration FindEndpointConfiguration(this WorkflowConfiguration workflowConfiguration, string code) { Guard.ArgumentNotNull(workflowConfiguration, nameof(workflowConfiguration)); Guard.ArgumentNotNull(workflowConfiguration.RuntimeConfiguration, nameof(workflowConfiguration.RuntimeConfiguration)); Guard.ArgumentNotNull(workflowConfiguration.RuntimeConfiguration.EndpointConfigurations, nameof(workflowConfiguration.RuntimeConfiguration.EndpointConfigurations)); foreach (var endpointConfigurationKvp in workflowConfiguration.RuntimeConfiguration.EndpointConfigurations) { var regex = new Regex(endpointConfigurationKvp.Key, RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (regex.Match(code).Success) { return(endpointConfigurationKvp.Value); } } throw new WorkflowException($"Endpoint configuration for code [${code}] was not found for workflow [ID=${workflowConfiguration.Id}]"); }
internal static StateConfiguration GetStateConfigurationByType(this WorkflowConfiguration workflowConfiguration, StateTypeConfiguration stateType) { return(workflowConfiguration.States.Single(s => s.Type == stateType)); }
internal static EventConfiguration GetEventConfigurationByType(this WorkflowConfiguration workflowConfiguration, EventTypeConfiguration eventType) { return(workflowConfiguration.Events.FirstOrDefault(e => e.Type == eventType)); }
internal static EventConfiguration GetEventConfigurationByCode(this WorkflowConfiguration workflowConfiguration, string code) { return(workflowConfiguration.Events.FirstOrDefault(e => string.Equals(code, e.Code, StringComparison.OrdinalIgnoreCase))); }
internal static StateConfiguration GetFailedStateConfiguration(this WorkflowConfiguration workflowConfiguration) { return(workflowConfiguration.States.Single(s => s.Type == StateTypeConfiguration.Failed)); }
/// <summary> /// Validate workflow configuration against a number of invariants /// </summary> internal static void ValidateWorkflowConfiguration(WorkflowConfiguration workflowConfiguration) { WorkflowConfigurationException workflowConfigurationException = null; // 1. make sure that workflow has exactly one initial state var initialStateCount = workflowConfiguration.States.Count(s => s.Type == StateTypeConfiguration.Initial); if (initialStateCount != 1) { AddValidationMessage(ref workflowConfigurationException, $"Workflow [{workflowConfiguration.Code}] must have exactly one initial state"); } // 2. make sure that workflow has exactly one failed state var failedStateCount = workflowConfiguration.States.Count(s => s.Type == StateTypeConfiguration.Initial); if (failedStateCount != 1) { AddValidationMessage(ref workflowConfigurationException, $"Workflow [{workflowConfiguration.Code}] must have exactly one failed state"); } // 3. there are no state code duplicates var hasDuplicateState = workflowConfiguration.States .Any(s => workflowConfiguration.States.Any(si => si != s && string.Equals(si.Code, s.Code, StringComparison.OrdinalIgnoreCase))); if (hasDuplicateState) { AddValidationMessage(ref workflowConfigurationException, $"Workflow [{workflowConfiguration.Code}] must have unique states"); } // 4. make sure that workflow has zero or exactly one parentToNestedInitial event var parentToNestedInitialEventCount = workflowConfiguration.Events.Count(e => e.Type == EventTypeConfiguration.ParentToNestedInitial); if (parentToNestedInitialEventCount > 1) { AddValidationMessage(ref workflowConfigurationException, $"Workflow [{workflowConfiguration.Code}] must have zero or exactly one [{EventTypeConfiguration.ParentToNestedInitial:G}] event"); } // 5. there are no event code duplicates var hasDuplicateEvents = workflowConfiguration.Events .Any(e => workflowConfiguration.Events.Any(ei => ei != e && string.Equals(ei.Code, e.Code, StringComparison.OrdinalIgnoreCase))); if (hasDuplicateEvents) { AddValidationMessage(ref workflowConfigurationException, $"Workflow [{workflowConfiguration.Code}] must have unique events"); } // 6. do not allow state to have events and incoming asynchronousImmediate transitions simultaneously var statesWithEvents = workflowConfiguration.States.Where(s => s.Events.Any()).ToList(); var transitionsToStateWithEvents = workflowConfiguration.States.SelectMany(s => s.Transitions) .Where(t => t.Type == TransitionTypeConfiguration.AsynchronousImmediate) .Where(t => statesWithEvents.Any(s => s.Code == t.MoveToState)); if (transitionsToStateWithEvents.Any()) { AddValidationMessage(ref workflowConfigurationException, $"Workflow [{workflowConfiguration.Code}] has state(s) with events and incoming async transitions"); } // 7. do not allow duplicated event declaration for the same state var hasStatesWithDuplicatedEvent = statesWithEvents .Any(s => s.Events.Any(e => s.Events .Any(ei => ei != e && string.Equals(ei.Code, e.Code, StringComparison.OrdinalIgnoreCase)))); if (hasStatesWithDuplicatedEvent) { AddValidationMessage(ref workflowConfigurationException, $"Workflow [{workflowConfiguration.Code}] has state(s) with duplicated events"); } // 8. make sure that activity -> retryPolicy -> onFailureTransitionToUse refers to an existing transition within the state var statesWithCustomFailureTransitions = workflowConfiguration.States .Where(s => s.Activities.Any(a => !string.IsNullOrEmpty(a.RetryPolicy.OnFailureTransitionToUse))).ToList(); if (statesWithCustomFailureTransitions.Any()) { var hasMissConfiguredCustomFailureTransitions = statesWithCustomFailureTransitions .Any(s => s.Activities .Any(a => !string.IsNullOrEmpty(a.RetryPolicy.OnFailureTransitionToUse) && !s.Transitions .Any(t => string.Equals(t.MoveToState, a.RetryPolicy.OnFailureTransitionToUse)))); if (hasMissConfiguredCustomFailureTransitions) { AddValidationMessage(ref workflowConfigurationException, $"Workflow [{workflowConfiguration.Code}] has activity(s) with configured 'onFailureTransitionToUse' but state has no such transition"); } } if (null != workflowConfigurationException) { throw workflowConfigurationException; } }
public WorkflowConfiguration Parse(string content) { try { using (var textReader = new StringReader(content)) using (var xmlReader = XmlReader.Create(textReader)) { var xDocument = XDocument.Load(xmlReader); Guard.ArgumentNotNull(xDocument.Root, "document"); var idString = ParseAttributeString(xDocument.Root, "id"); var @class = ParseAttributeString(xDocument.Root, "class"); var code = ParseAttributeString(xDocument.Root, "code"); var name = ParseAttributeString(xDocument.Root, "name"); var versionString = ParseAttributeString(xDocument.Root, "version"); if (!Guid.TryParse(idString, out var id)) { throw new Exception($"Can not convert [{idString}] to a Guid"); } if (!Version.TryParse(versionString, out var version)) { throw new Exception($"Can not convert [{versionString}] to a version"); } Log.Verbose("Starting reading workflow [{code}], [{name}], [{version}] from XML", code, name, version); var workflowConfiguration = new WorkflowConfiguration(id, @class, code, name, version); var events = xDocument.Root.Element("events"); if (events != null) { foreach (var eventElement in events.Elements("event")) { var @event = ParseEvent(eventElement); workflowConfiguration.Events.Add(@event); } } var states = xDocument.Root.Element("states"); if (null != states) { foreach (var stateElement in states.Elements("state")) { var state = ParseState(stateElement); workflowConfiguration.States.Add(state); } } Log.Verbose("Workflow [{code}], [{name}], [{version}] has been read from XML", code, name, version); WorkflowConfigurationValidator.ValidateWorkflowConfiguration(workflowConfiguration); return(workflowConfiguration); } } catch (WorkflowConfigurationException) { throw; } catch (Exception ex) { throw new WorkflowConfigurationException("An error has occurred during reading XML workflow", ex); } }