public static IWorkflow Instantiate(IWorkplan workplan, IWorkplanContext context) { // Prepare variables var places = new Dictionary <long, IPlace>(); var transitions = new List <ITransition>(); // Iterate over each step and find its connectors foreach (var step in workplan.Steps) { // Create transition var transition = step.CreateInstance(context); // Set inputs for (var i = 0; i < step.Inputs.Length; i++) { transition.Inputs[i] = GetPlace(step.Inputs[i], places); } // Set outputs for (int i = 0; i < step.Outputs.Length; i++) { transition.Outputs[i] = GetPlace(step.Outputs[i], places); } transitions.Add(transition); } return(new SimpleWorkflow(workplan, places.Values.ToList(), transitions)); }
private static IWorkflowEngine CreateEngine(IWorkplan workplan) { var engine = Workflow.CreateEngine(workplan, new NullContext()); engine.Completed += (sender, place) => { }; return(engine); }
/// <summary> /// Extracts the steps of the workplan by using the first output of any step (normally the success path) /// </summary> /// <param name="workplan">The workplan</param> /// <param name="startConnector">Connector for starting the extraction</param> /// <param name="endConnector">Connector for ending the extraction</param> /// <returns>A sorted list of steps for the path</returns> public static IReadOnlyList <IWorkplanStep> ExtractPath(this IWorkplan workplan, IConnector startConnector, IConnector endConnector) { if (!workplan.Connectors.Contains(startConnector)) { throw new ArgumentException("The start connector is not part of the workplan"); } if (!workplan.Connectors.Contains(endConnector)) { throw new ArgumentException("The end connector is not part of the workplan"); } var path = new List <IWorkplanStep>(); var currentConnector = startConnector; while (currentConnector != null) { var step = workplan.Steps.First(s => s.Inputs.Contains(currentConnector)); path.Add(step); var output = step.Outputs[0]; if (output == endConnector) { break; } currentConnector = output; } return(path); }
internal static WorkplanModel FromWorkplan(IWorkplan wp) { return(new WorkplanModel { Id = wp.Id, Name = wp.Name, Version = wp.Version, State = wp.State }); }
/// <summary> /// Extracts the steps of the workplan by using the first output of any step (normally the success path) /// </summary> /// <param name="workplan">The workplan</param> /// <param name="startConnector">Connector for starting the extraction</param> /// <returns>A sorted list of steps for the path</returns> public static IReadOnlyList <IWorkplanStep> ExtractPath(this IWorkplan workplan, IConnector startConnector) { var endConnector = workplan.Connectors.FirstOrDefault(c => c.Classification == NodeClassification.End); if (endConnector == null) { throw new ArgumentException("The workplan does not have any end connector"); } return(ExtractPath(workplan, startConnector, endConnector)); }
public WorkplanModel ConvertWorkplan(IWorkplan workplan) { var workplanDto = new WorkplanModel { Id = workplan.Id, Name = workplan.Name, Version = workplan.Version, State = workplan.State }; return(workplanDto); }
public PathPredictor(IWorkplan workplan) { // All inputs for each output to traverse the workplan in reverse var reversedRelations = ReverseRelations(workplan); _workplanAnalysis = reversedRelations.Keys.ToDictionary(key => key, key => new PossibleResults(key)); foreach (var connector in workplan.Connectors.Where(c => c.Classification.HasFlag(NodeClassification.Exit))) { Analyze(connector, connector, reversedRelations); } }
/// <summary> /// Validates the specified workplan. /// </summary> /// <param name="workplan">The workplan.</param> /// <exception cref="ValidationException"> /// Error during 'DeadEnd'-Validation /// or /// Error during 'InfiniteLoop'-Validation /// or /// Error during 'LoneWolf'-Validation /// or /// Error during 'LuckStreak'-Validation /// </exception> public static void Validate(this IWorkplan workplan) { const ValidationAspect aspects = ValidationAspect.DeadEnd | ValidationAspect.InfiniteLoop | ValidationAspect.LoneWolf | ValidationAspect.LuckyStreak; var result = Workflow.Validate(workplan, aspects); if (result.Success) { return; } foreach (var error in result.Errors) { throw new ValidationException(error.Print(workplan)); } }
/// <summary> /// Create step from another workflow /// </summary> protected SubWorkplanStep(IWorkplan workplan) { Workplan = workplan; // Step outputs are created from all exits of the sub workflow OutputDescriptions = (from connector in workplan.Connectors where connector.Classification.HasFlag(NodeClassification.Exit) select new OutputDescription { Name = connector.Name, MappingValue = connector.Id, OutputType = connector.Classification == NodeClassification.End ? OutputType.Success : OutputType.Failure }).ToArray(); Outputs = new IConnector[OutputDescriptions.Length]; }
/// <summary> /// Validate the workplan under different aspects. Aspects can be combined using '|' operator. /// </summary> /// <param name="workplan">Workplan to validate</param> /// <param name="aspects">Enum flag aspects to validate</param> /// <returns><remarks>True</remarks> if validation succeeded. Otherwise <remarks>false</remarks>.</returns> public static ValidationResult Validate(IWorkplan workplan, ValidationAspect aspects) { var valid = true; var errors = new List <ValidationError>(); foreach (var validator in Validators) { if (aspects.HasFlag(validator.TargetAspect)) { valid &= validator.Validate(workplan, errors); } } return(new ValidationResult { Success = valid, Errors = errors.ToArray() }); }
/// <summary> /// Reverse the relations from outputs to inputs to traverse the workplan /// in reverse and identify all possible path leading to a certain result /// </summary> /// <param name="workplan"></param> /// <returns></returns> private static IDictionary <long, HashSet <IWorkplanNode> > ReverseRelations(IWorkplan workplan) { // Create dictionary of node ids and possible inputs var reversedConnections = workplan.Connectors.Cast <IWorkplanNode>().Concat(workplan.Steps) .ToDictionary(c => c.Id, c => new HashSet <IWorkplanNode>()); foreach (var step in workplan.Steps) { // The input for each output is the step itself foreach (var output in step.Outputs) { reversedConnections[output.Id].Add(step); } // The input of the step ar its inputs reversedConnections[step.Id].AddRange(step.Inputs); } return(reversedConnections); }
/// <summary> /// Validate the given workplan /// </summary> public bool Validate(IWorkplan workplan, ICollection <ValidationError> errors) { foreach (var connector in workplan.Connectors) { // Find a step using the connector as input if (workplan.Steps.Any(step => step.Inputs.Contains(connector))) { continue; } // or if it is the end connector if (connector.Classification.HasFlag(NodeClassification.Exit)) { continue; } errors.Add(new DeadEndValidationError(connector.Id)); } return(errors.Count == 0); }
/// <summary> /// Validate the given workplan /// </summary> public bool Validate(IWorkplan workplan, ICollection <ValidationError> errors) { // Iterate over every step and find all steps without inputs or inputs that are not connected foreach (var step in workplan.Steps) { // Check if the step has inputs that a are used somewhere else as outputs if (step.Inputs.Length > 0 && step.Inputs.All(input => workplan.Steps.Any(otherStep => otherStep.Outputs.Contains(input)))) { continue; } // If we are linked to the start place we can skip too if (step.Inputs.Any(input => input.Classification.HasFlag(NodeClassification.Entry))) { continue; } errors.Add(new LoneWolfValidationError(step.Id)); } return(errors.Count == 0); }
/// <summary> /// Print error in readable format /// </summary> // TODO: Does it make sense to pass workplan to error public override string Print(IWorkplan workplan) { var targetStep = workplan.Steps.First(step => step.Id == PositionId); return($"Step {targetStep.Name} at position {PositionId} is unreachable!"); }
/// <summary> /// Print error in readable format /// </summary> public override string Print(IWorkplan workplan) { var connector = workplan.Connectors.First(c => c.Id == PositionId); return($"Connector {connector.Name} at position {PositionId} is not used as an input in any step!"); }
/// <summary> /// Create step from workplan /// </summary> public SubworkflowStep(IWorkplan workplan) : base(workplan) { }
/// <summary> /// Print error in readable format /// </summary> public override string Print(IWorkplan workplan) { var connector = workplan.Connectors.First(c => c.Id == PositionId); return(string.Format("Connector {0} at postion {1} is not used as an input in any step!", connector.Name, PositionId)); }
/// <summary> /// Print error in readable format /// </summary> // TODO: Does it make sense to pass workplan to error public abstract string Print(IWorkplan workplan);
/// <summary> /// Validate the workplan under different aspects. Aspects can be combined using '|' operator. /// </summary> /// <param name="workplan">Workplan to validate</param> /// <param name="aspects">Enum flag aspects to validate</param> /// <returns><remarks>True</remarks> if validation succeeded. Otherwise <remarks>false</remarks>.</returns> public static ValidationResult Validate(IWorkplan workplan, ValidationAspect aspects) { return(WorkflowValidation.Validate(workplan, aspects)); }
/// <summary> /// Create a path predictor for this workplan that can be used /// to monitor instances of the workplan. /// </summary> public static IPathPredictor PathPrediction(IWorkplan workplan) { return(new PathPredictor(workplan)); }
/// <summary> /// Create engine instance from workplan and context. The default factory will be used to instantiate /// the workflow. /// </summary> public static IWorkflowEngine CreateEngine(IWorkplan workplan, IWorkplanContext context) { return(CreateWorkflowEngine(WorkflowFactory.Instantiate(workplan, context), context)); }
/// <summary> /// Constructor to create workflow from places and transistions /// </summary> public SimpleWorkflow(IWorkplan workplan, IReadOnlyList <IPlace> places, IReadOnlyList <ITransition> transitions) { Workplan = workplan; Places = places; Transitions = transitions; }
/// <summary> /// Print error in readable format /// </summary> // TODO: Does it make sense to pass workplan to error public override string Print(IWorkplan workplan) { var targetStep = workplan.Steps.First(step => step.Id == PositionId); return(string.Format("Step {0} at position {1} is unreachable!", targetStep.Name, PositionId)); }