Пример #1
0
 public IEnumerable <WorkflowConnectionEntity> PreviousConnections(IWorkflowNodeEntity node)
 {
     return(PreviousGraph.RelatedTo(node).SelectMany(a => a.Value));
 }
Пример #2
0
    public void Validate(List <WorkflowIssue> issuesContainer, Action <WorkflowGatewayEntity, WorkflowGatewayDirection> changeDirection)
    {
        List <WorkflowIssue> issues = issuesContainer;

        if (Events.Count(a => a.Value.Type.IsStart()) == 0)
        {
            issues.AddError(null, WorkflowValidationMessage.SomeStartEventIsRequired.NiceToString());
        }

        if (Workflow.MainEntityStrategies.Any(a => a == WorkflowMainEntityStrategy.SelectByUser || a == WorkflowMainEntityStrategy.Clone))
        {
            if (Events.Count(a => a.Value.Type == WorkflowEventType.Start) == 0)
            {
                issues.AddError(null,
                                WorkflowValidationMessage.NormalStartEventIsRequiredWhenThe0Are1Or2.NiceToString(
                                    Workflow.MainEntityStrategies.GetType().NiceName(),
                                    WorkflowMainEntityStrategy.SelectByUser.NiceToString(),
                                    WorkflowMainEntityStrategy.Clone.NiceToString()));
            }
        }

        if (Events.Count(a => a.Value.Type == WorkflowEventType.Start) > 1)
        {
            foreach (var e in Events.Where(a => a.Value.Type == WorkflowEventType.Start))
            {
                issues.AddError(e.Value, WorkflowValidationMessage.MultipleStartEventsAreNotAllowed.NiceToString());
            }
        }

        var finishEventCount = Events.Count(a => a.Value.Type.IsFinish());

        if (finishEventCount == 0)
        {
            issues.AddError(null, WorkflowValidationMessage.FinishEventIsRequired.NiceToString());
        }

        Events.Values.ToList().ForEach(e =>
        {
            var fanIn  = PreviousConnections(e).Count();
            var fanOut = NextConnections(e).Count();

            if (e.Type.IsStart())
            {
                if (fanIn > 0)
                {
                    issues.AddError(e, WorkflowValidationMessage._0HasInputs.NiceToString(e));
                }
                if (fanOut == 0)
                {
                    issues.AddError(e, WorkflowValidationMessage._0HasNoOutputs.NiceToString(e));
                }
                if (fanOut > 1)
                {
                    issues.AddError(e, WorkflowValidationMessage._0HasMultipleOutputs.NiceToString(e));
                }

                if (fanOut == 1)
                {
                    var nextConn = NextConnections(e).SingleEx();

                    if (e.Type == WorkflowEventType.Start && !(nextConn.To is WorkflowActivityEntity))
                    {
                        issues.AddError(e, WorkflowValidationMessage.StartEventNextNodeShouldBeAnActivity.NiceToString());
                    }
                }
            }

            if (e.Type.IsFinish())
            {
                if (fanIn == 0)
                {
                    issues.AddError(e, WorkflowValidationMessage._0HasNoInputs.NiceToString(e));
                }
                if (fanOut > 0)
                {
                    issues.AddError(e, WorkflowValidationMessage._0HasOutputs.NiceToString(e));
                }
            }

            if (e.Type.IsScheduledStart())
            {
                var schedule = e.ScheduledTask();

                if (schedule == null)
                {
                    issues.AddError(e, WorkflowValidationMessage._0IsTimerStartAndSchedulerIsMandatory.NiceToString(e));
                }

                var wet = e.WorkflowEventTask();

                if (wet == null)
                {
                    issues.AddError(e, WorkflowValidationMessage._0IsTimerStartAndTaskIsMandatory.NiceToString(e));
                }
                else if (wet.TriggeredOn != TriggeredOn.Always)
                {
                    if (wet.Condition?.Script == null || !wet.Condition.Script.Trim().HasText())
                    {
                        issues.AddError(e, WorkflowValidationMessage._0IsConditionalStartAndTaskConditionIsMandatory.NiceToString(e));
                    }
                }
            }

            if (e.Type.IsTimer())
            {
                var boundaryOutput = NextConnections(e).Only();

                if (boundaryOutput == null || boundaryOutput.Type != ConnectionType.Normal)
                {
                    if (e.Type == WorkflowEventType.IntermediateTimer)
                    {
                        issues.AddError(e, WorkflowValidationMessage.IntermediateTimer0ShouldHaveOneOutputOfType1.NiceToString(e, ConnectionType.Normal.NiceToString()));
                    }
                    else
                    {
                        var parentActivity = Activities.Values.Where(a => a.BoundaryTimers.Contains(e)).SingleEx();
                        issues.AddError(e, WorkflowValidationMessage.BoundaryTimer0OfActivity1ShouldHaveExactlyOneConnectionOfType2.NiceToString(e, parentActivity, ConnectionType.Normal.NiceToString()));
                    }
                }

                if (e.Type == WorkflowEventType.IntermediateTimer && !e.Name.HasText())
                {
                    issues.AddError(e, WorkflowValidationMessage.IntermediateTimer0ShouldHaveName.NiceToString(e));
                }
            }
        });

        Gateways.Values.ToList().ForEach(g =>
        {
            var fanIn  = PreviousConnections(g).Count();
            var fanOut = NextConnections(g).Count();
            if (fanIn == 0)
            {
                issues.AddError(g, WorkflowValidationMessage._0HasNoInputs.NiceToString(g));
            }
            if (fanOut == 0)
            {
                issues.AddError(g, WorkflowValidationMessage._0HasNoOutputs.NiceToString(g));
            }

            if (fanIn == 1 && fanOut == 1)
            {
                issues.AddError(g, WorkflowValidationMessage._0HasJustOneInputAndOneOutput.NiceToString(g));
            }

            var newDirection = fanOut == 1 ? WorkflowGatewayDirection.Join : WorkflowGatewayDirection.Split;
            if (g.Direction != newDirection)
            {
                changeDirection(g, newDirection);
            }

            if (g.Direction == WorkflowGatewayDirection.Split)
            {
                if (g.Type == WorkflowGatewayType.Exclusive || g.Type == WorkflowGatewayType.Inclusive)
                {
                    if (NextConnections(g).Any(c => c.Type == ConnectionType.Decision))
                    {
                        List <WorkflowActivityEntity> previousActivities = new List <WorkflowActivityEntity>();

                        PreviousGraph.DepthExploreConnections(g, (prev, conn, next) =>
                        {
                            if (next is WorkflowActivityEntity a)
                            {
                                previousActivities.Add(a);
                                return(false);
                            }

                            return(true);
                        });

                        foreach (var act in previousActivities.Where(a => a.Type != WorkflowActivityType.Decision))
                        {
                            issues.AddError(act, WorkflowValidationMessage.Activity0ShouldBeDecision.NiceToString(act));
                        }
                    }
                }

                switch (g.Type)
                {
                case WorkflowGatewayType.Exclusive:
                    if (NextConnections(g).OrderByDescending(a => a.Order).Skip(1).Any(c => c.Type == ConnectionType.Normal && c.Condition == null))
                    {
                        issues.AddError(g, WorkflowValidationMessage.Gateway0ShouldHasConditionOrDecisionOnEachOutputExceptTheLast.NiceToString(g));
                    }
                    break;

                case WorkflowGatewayType.Inclusive:
                    if (NextConnections(g).Count(c => c.Type == ConnectionType.Normal && c.Condition == null) != 1)
                    {
                        issues.AddError(g, WorkflowValidationMessage.InclusiveGateway0ShouldHaveOneConnectionWithoutCondition.NiceToString(g));
                    }

                    break;

                case WorkflowGatewayType.Parallel:
                    if (NextConnections(g).Count() == 0)
                    {
                        issues.AddError(g, WorkflowValidationMessage.ParallelSplit0ShouldHaveAtLeastOneConnection.NiceToString(g));
                    }

                    if (NextConnections(g).Any(a => a.Type != ConnectionType.Normal || a.Condition != null))
                    {
                        issues.AddError(g, WorkflowValidationMessage.ParallelSplit0ShouldHaveOnlyNormalConnectionsWithoutConditions.NiceToString(g));
                    }
                    break;

                default:
                    break;
                }
            }
        });

        var starts = Events.Values.Where(a => a.Type.IsStart()).ToList();

        TrackId        = starts.ToDictionary(a => (IWorkflowNodeEntity)a, a => 0);
        TrackCreatedBy = new Dictionary <int, IWorkflowNodeEntity> {
            { 0, null ! }
        };


        Queue <IWorkflowNodeEntity> queue = new Queue <IWorkflowNodeEntity>();

        queue.EnqueueRange(starts);
        while (queue.Count > 0)
        {
            IWorkflowNodeEntity node = queue.Dequeue();

            var nextConns = NextConnections(node).ToList(); //Clone;
            if (node is WorkflowActivityEntity wa)
            {
                foreach (var bt in wa.BoundaryTimers.Where(a => a.Type == WorkflowEventType.BoundaryInterruptingTimer))
                {
                    TrackId[bt] = TrackId[wa];
                    queue.Enqueue(bt);
                }

                foreach (var bt in wa.BoundaryTimers.Where(a => a.Type == WorkflowEventType.BoundaryForkTimer))
                {
                    var newTrackId = TrackCreatedBy.Count + 1;
                    TrackCreatedBy.Add(newTrackId, bt);
                    TrackId[bt] = TrackId[wa];
                    queue.Enqueue(bt);
                }
            }

            foreach (var con in nextConns)
            {
                if (ContinueExplore(con))
                {
                    queue.Enqueue(con.To);
                }
            }
        }

        bool IsSplitActivity(WorkflowActivityEntity wa)
        {
            return(wa.BoundaryTimers.Any(bt => bt.Type == WorkflowEventType.BoundaryForkTimer));
        }

        bool ContinueExplore(WorkflowConnectionEntity conn)
        {
            IWorkflowNodeEntity prev = conn.From;
            IWorkflowNodeEntity next = conn.To;

            var prevTrackId = TrackId.GetOrThrow(prev);
            int newTrackId;

            if (IsParallelGateway(prev, WorkflowGatewayDirection.Split))
            {
                if (IsParallelGateway(next, WorkflowGatewayDirection.Join))
                {
                    newTrackId = prevTrackId;
                }
                else
                {
                    newTrackId = TrackCreatedBy.Count + 1;
                    TrackCreatedBy.Add(newTrackId, (WorkflowGatewayEntity)prev);
                }
            }
            else if (prev is WorkflowActivityEntity act && IsSplitActivity(act) ||
                     prev is WorkflowEventEntity we && we.Type == WorkflowEventType.BoundaryInterruptingTimer &&
                     IsSplitActivity(this.Activities.GetOrThrow(we.BoundaryOf !)))
            {
                if (IsParallelGateway(next, WorkflowGatewayDirection.Join))
                {
                    newTrackId = prevTrackId;
                }
                else
                {
                    var activity = (prev as WorkflowActivityEntity) ??
                                   this.Activities.GetOrThrow(((WorkflowEventEntity)prev).BoundaryOf !);

                    var mainTrackId = NextConnections(activity)
                                      .Concat(activity.BoundaryTimers.Where(a => a.Type == WorkflowEventType.BoundaryInterruptingTimer).SelectMany(we => NextConnections(we)))
                                      .Select(c => TrackId.TryGetS(c.To))
                                      .Where(c => c != null)
                                      .Distinct()
                                      .SingleOrDefaultEx();

                    if (mainTrackId.HasValue)
                    {
                        newTrackId = mainTrackId.Value;
                    }
                    else
                    {
                        newTrackId = TrackCreatedBy.Count + 1;
                        TrackCreatedBy.Add(newTrackId, activity);
                    }
                }
            }
Пример #3
0
        public List <string> Validate(Action <WorkflowGatewayEntity, WorkflowGatewayDirection> changeDirection)
        {
            List <string> errors = new List <string>();

            if (Events.Count(a => a.Value.Type.IsStart()) == 0)
            {
                errors.Add(WorkflowValidationMessage.SomeStartEventIsRequired.NiceToString());
            }

            if (Workflow.MainEntityStrategy != WorkflowMainEntityStrategy.CreateNew)
            {
                if (Events.Count(a => a.Value.Type == WorkflowEventType.Start) == 0)
                {
                    errors.Add(WorkflowValidationMessage.NormalStartEventIsRequiredWhenThe0Are1Or2.NiceToString(
                                   Workflow.MainEntityStrategy.GetType().NiceName(),
                                   WorkflowMainEntityStrategy.SelectByUser.NiceToString(),
                                   WorkflowMainEntityStrategy.Both.NiceToString()));
                }
            }

            if (Events.Count(a => a.Value.Type == WorkflowEventType.Start) > 1)
            {
                errors.Add(WorkflowValidationMessage.MultipleStartEventsAreNotAllowed.NiceToString());
            }

            var finishEventCount = Events.Count(a => a.Value.Type.IsFinish());

            if (finishEventCount == 0)
            {
                errors.Add(WorkflowValidationMessage.FinishEventIsRequired.NiceToString());
            }

            Events.Values.ToList().ForEach(e =>
            {
                var fanIn  = PreviousGraph.RelatedTo(e).Count;
                var fanOut = NextGraph.RelatedTo(e).Count;

                if (e.Type.IsStart())
                {
                    if (fanIn > 0)
                    {
                        errors.Add(WorkflowValidationMessage._0HasInputs.NiceToString(e));
                    }
                    if (fanOut == 0)
                    {
                        errors.Add(WorkflowValidationMessage._0HasNoOutputs.NiceToString(e));
                    }
                    if (fanOut > 1)
                    {
                        errors.Add(WorkflowValidationMessage._0HasMultipleOutputs.NiceToString(e));
                    }

                    if (fanOut == 1)
                    {
                        var nextConn = NextGraph.RelatedTo(e).Single().Value;

                        if (e.Type == WorkflowEventType.Start && !(nextConn.To is WorkflowActivityEntity))
                        {
                            errors.Add(WorkflowValidationMessage.StartEventNextNodeShouldBeAnActivity.NiceToString());
                        }
                    }
                }

                if (e.Type.IsFinish())
                {
                    if (fanIn == 0)
                    {
                        errors.Add(WorkflowValidationMessage._0HasNoInputs.NiceToString(e));
                    }
                    if (fanOut > 0)
                    {
                        errors.Add(WorkflowValidationMessage._0HasOutputs.NiceToString(e));
                    }
                }

                if (e.Type.IsTimerStart())
                {
                    var schedule = e.ScheduledTask();

                    if (schedule == null)
                    {
                        errors.Add(WorkflowValidationMessage._0IsTimerStartAndSchedulerIsMandatory.NiceToString(e));
                    }

                    var wet = e.WorkflowEventTask();

                    if (wet == null)
                    {
                        errors.Add(WorkflowValidationMessage._0IsTimerStartAndTaskIsMandatory.NiceToString(e));
                    }
                    else if (wet.TriggeredOn != TriggeredOn.Always)
                    {
                        if (wet.Condition?.Script == null || !wet.Condition.Script.Trim().HasText())
                        {
                            errors.Add(WorkflowValidationMessage._0IsConditionalStartAndTaskConditionIsMandatory.NiceToString(e));
                        }
                    }
                }
            });

            Gateways.Values.ToList().ForEach(g =>
            {
                var fanIn  = PreviousGraph.RelatedTo(g).Count;
                var fanOut = NextGraph.RelatedTo(g).Count;
                if (fanIn == 0)
                {
                    errors.Add(WorkflowValidationMessage._0HasNoInputs.NiceToString(g));
                }
                if (fanOut == 0)
                {
                    errors.Add(WorkflowValidationMessage._0HasNoOutputs.NiceToString(g));
                }

                if (fanIn == 1 && fanOut == 1)
                {
                    errors.Add(WorkflowValidationMessage._0HasJustOneInputAndOneOutput.NiceToString(g));
                }

                var newDirection = fanOut == 1 ? WorkflowGatewayDirection.Join : WorkflowGatewayDirection.Split;
                if (g.Direction != newDirection)
                {
                    changeDirection(g, newDirection);
                }

                if (g.Direction == WorkflowGatewayDirection.Split)
                {
                    if (g.Type == WorkflowGatewayType.Exclusive || g.Type == WorkflowGatewayType.Inclusive)
                    {
                        if (NextGraph.RelatedTo(g).OrderByDescending(a => a.Value.Order).Any(c => c.Value.DecisonResult != null))
                        {
                            List <WorkflowActivityEntity> previousActivities = new List <WorkflowActivityEntity>();

                            PreviousGraph.DepthExploreConnections(g, (prev, conn, next) =>
                            {
                                if (next is WorkflowActivityEntity a)
                                {
                                    previousActivities.Add(a);
                                    return(false);
                                }

                                return(true);
                            });

                            foreach (var act in previousActivities.Where(a => a.Type != WorkflowActivityType.Decision))
                            {
                                errors.Add(WorkflowValidationMessage.Activity0ShouldBeDecision.NiceToString(act));
                            }
                        }
                    }

                    if (g.Type == WorkflowGatewayType.Exclusive && NextGraph.RelatedTo(g).OrderByDescending(a => a.Value.Order).Skip(1).Any(c => c.Value.DecisonResult == null && c.Value.Condition == null))
                    {
                        errors.Add(WorkflowValidationMessage.Gateway0ShouldHasConditionOrDecisionOnEachOutputExceptTheLast.NiceToString(g));
                    }

                    if (g.Type == WorkflowGatewayType.Inclusive && NextGraph.RelatedTo(g).Any(c => c.Value.DecisonResult == null && c.Value.Condition == null))
                    {
                        errors.Add(WorkflowValidationMessage.Gateway0ShouldHasConditionOnEachOutput.NiceToString(g));
                    }
                }
            });

            var starts = Events.Values.Where(a => a.Type.IsStart()).ToList();

            TrackId        = starts.ToDictionary(a => (IWorkflowNodeEntity)a, a => 0);
            TrackCreatedBy = new Dictionary <int, WorkflowGatewayEntity> {
                { 0, null }
            };

            ParallelWorkflowPairs = new Dictionary <WorkflowGatewayEntity, WorkflowGatewayEntity>();

            starts.ForEach(st =>
                           NextGraph.BreadthExploreConnections(st,
                                                               (prev, conn, next) =>
            {
                var prevTrackId = TrackId.GetOrThrow(prev);
                int newTrackId;

                if (IsParallelGateway(prev, WorkflowGatewayDirection.Split))
                {
                    if (IsParallelGateway(next, WorkflowGatewayDirection.Join))
                    {
                        newTrackId = prevTrackId;
                    }
                    else
                    {
                        newTrackId = TrackCreatedBy.Count + 1;
                        TrackCreatedBy.Add(newTrackId, (WorkflowGatewayEntity)prev);
                    }
                }
                else
                {
                    if (IsParallelGateway(next, WorkflowGatewayDirection.Join))
                    {
                        var split = TrackCreatedBy.TryGetC(prevTrackId);
                        if (split == null)
                        {
                            errors.Add(WorkflowValidationMessage._0CanNotBeConnectedToAParallelJoinBecauseHasNoPreviousParallelSplit.NiceToString(prev));
                            return(false);
                        }

                        ParallelWorkflowPairs[split] = (WorkflowGatewayEntity)next;

                        newTrackId = TrackId.GetOrThrow(split);
                    }
                    else
                    {
                        newTrackId = prevTrackId;
                    }
                }

                if (TrackId.ContainsKey(next))
                {
                    if (TrackId[next] != newTrackId)
                    {
                        errors.Add(WorkflowValidationMessage._0Track1CanNotBeConnectedTo2Track3InsteadOfTrack4.NiceToString(prev, prevTrackId, next, TrackId[next], newTrackId));
                    }

                    return(false);
                }
                else
                {
                    TrackId[next] = newTrackId;
                    return(true);
                }
            })
                           );

            Action <WorkflowActivityEntity, IWorkflowTransitionTo> ValidateTransition = (WorkflowActivityEntity wa, IWorkflowTransitionTo item) =>
            {
                var activity0CanNotXTo1Because2 = (item is WorkflowJumpEmbedded || item is WorkflowScriptPartEmbedded) ?
                                                  WorkflowValidationMessage.Activity0CanNotJumpTo1Because2 :
                                                  WorkflowValidationMessage.Activity0CanNotTimeoutTo1Because2;

                var to =
                    item.To is Lite <WorkflowActivityEntity>?(IWorkflowNodeEntity)Activities.TryGetC((Lite <WorkflowActivityEntity>)item.To) :
                        item.To is Lite <WorkflowGatewayEntity>?(IWorkflowNodeEntity)Gateways.TryGetC((Lite <WorkflowGatewayEntity>)item.To) :
                            item.To is Lite <WorkflowEventEntity>?(IWorkflowNodeEntity)Events.TryGetC((Lite <WorkflowEventEntity>)item.To) : null;

                if (to == null)
                {
                    errors.Add(activity0CanNotXTo1Because2.NiceToString(wa, item.To, WorkflowValidationMessage.IsNotInWorkflow.NiceToString()));
                }

                if (to is WorkflowEventEntity && ((WorkflowEventEntity)to).Type.IsStart())
                {
                    errors.Add(activity0CanNotXTo1Because2.NiceToString(wa, item.To, WorkflowValidationMessage.IsStart.NiceToString()));
                }

                if (to is WorkflowActivityEntity && to == wa)
                {
                    errors.Add(activity0CanNotXTo1Because2.NiceToString(wa, item.To, WorkflowValidationMessage.IsSelfJumping.NiceToString()));
                }

                if (TrackId.GetOrThrow(to) != TrackId.GetOrThrow(wa))
                {
                    errors.Add(activity0CanNotXTo1Because2.NiceToString(wa, item.To, WorkflowValidationMessage.IsInDifferentParallelTrack.NiceToString()));
                }
            };


            foreach (var wa in Activities.Values)
            {
                var fanIn  = PreviousGraph.RelatedTo(wa).Count;
                var fanOut = NextGraph.RelatedTo(wa).Count;

                if (fanIn == 0)
                {
                    errors.Add(WorkflowValidationMessage._0HasNoInputs.NiceToString(wa));
                }
                if (fanOut == 0)
                {
                    errors.Add(WorkflowValidationMessage._0HasNoOutputs.NiceToString(wa));
                }
                if (fanOut > 1)
                {
                    errors.Add(WorkflowValidationMessage._0HasMultipleOutputs.NiceToString(wa));
                }

                if (fanOut == 1 && wa.Type == WorkflowActivityType.Decision)
                {
                    var nextConn = NextGraph.RelatedTo(wa).Single().Value;
                    if (!(nextConn.To is WorkflowGatewayEntity) || ((WorkflowGatewayEntity)nextConn.To).Type == WorkflowGatewayType.Parallel)
                    {
                        errors.Add(WorkflowValidationMessage.Activity0WithDecisionTypeShouldGoToAnExclusiveOrInclusiveGateways.NiceToString(wa));
                    }
                }

                if (wa.Reject != null)
                {
                    var prevs = PreviousGraph.IndirectlyRelatedTo(wa, kvp => !(kvp.Key is WorkflowActivityEntity));
                    if (prevs.Any(a => a is WorkflowEventEntity && ((WorkflowEventEntity)a).Type.IsStart()))
                    {
                        errors.Add(WorkflowValidationMessage.Activity0CanNotRejectToStart.NiceToString(wa));
                    }

                    if (prevs.Any(a => IsParallelGateway(a)))
                    {
                        errors.Add(WorkflowValidationMessage.Activity0CanNotRejectToParallelGateway.NiceToString(wa));
                    }
                }

                if (wa.Timeout != null)
                {
                    ValidateTransition(wa, wa.Timeout);
                }

                if (wa.Script != null)
                {
                    ValidateTransition(wa, wa.Script);
                }

                foreach (var item in wa.Jumps)
                {
                    ValidateTransition(wa, item);
                }
            }

            if (errors.HasItems())
            {
                this.TrackCreatedBy        = null;
                this.TrackId               = null;
                this.ParallelWorkflowPairs = null;
            }

            return(errors);
        }