Part 5: Stage And Case Completion
Overview
In this part we are focussing on a very important part in CMMN: how to complete stages and case models.
There are many ways this can be influenced from using the auto-completion rule, over how plan items are configured within a stage or the case model. But in this part we want to get to know the more hidden aspects of how to influence stage and case completion.
We have already seen some aspects of it in earlier parts like a bit of auto-completion or impact on parent completion rules, but in this part we want to dig deep in various aspects and combinations thereof.
Regular completion
In this example we are quickly looking into regular completion of a stage or case model: whenever there are no more plan items left or in other words, all of them have been completed, then a stage or case model simply completes as there is no more work to be done.
Here is an example of such a stage / case:
If you complete Task A, then Stage A automatically completes, even without auto-completion turned on as there is no more non-completed plan item in that stage. Completing Task B then also completes the case as there is no more other stage or plan items available to be considered.
But what if we add another task into the stage (or the case model, would not make any difference) which has manual activation? Like this:
As you might guess, we can now complete both Task A and Task B like we did before, but the stage and hence the case will not yet complete. We first need to manually activate Task C and then complete it to finish the stage and case.
Auto-completion
As Task C has manual activation, this might be due to the fact that it is optional and up to the user to decide if it is needed or not. But in default mode for completion, we always have to start it at some time and complete it. There is otherwise no way to let the stage complete.
Now here is where the auto-complete rule comes into play. Turning auto-complete on for a stage or even the case model has the following meaning:
Whenever there is no more active plan items in a stage or the case model (wherever the auto-completion rule is used), it will complete automatically.
So let's turn on auto-complete for Stage A and re-run the same example (auto-completion turned on is indicated with a black rectangle):
Completing Task A and Task B now directly completes the case or completing Task A directly completes Stage A as there is no more active plan item to wait for. So the manually activated Task C never is considered waiting for - unless you manually start it. But this can only be done whenever Stage A is still active, meaning you would have to start Task C BEFORE Task A is completed, otherwise there is no more time as the stage automatically completes.
Auto-completion combined with required
Wrapping up: auto-completion automatically completes a stage or a case model as soon as there is no more active plan item contained in it AND there is no required plan item, which is not yet completed as well.
This means would we turn on the required flag on our manually activated plan item, it would prevent the stage from being completed, even having auto-completion turned on, if it was not completed.
Turning on the required flag (indicated by the exclamation point) basically brings us back to the same result as not using the auto-completion rule at all: we need to complete both tasks in Stage A before it can be completed. So you ask where is the difference? Well, typically, stages will contain way more plan items and using combinations with auto-completion, manual activation and required can help you achieve what you need: maybe one of the two manually activated plan items actually need to be completed (hence required), the other not, it should just be up to the user to decide when to start and complete them.
Auto-completion combined with enabled
Making things yet a bit more interesting, we add a new task to the stage, Task D and depending on some rules, based on case data, that task might or might not be needed. We can use an entry-sentry with a condition to model this behavior.
Going back one step, we remove the required flag from Task C as well as the auto-completion rule of the stage to find out about the overall behavior now.
If we add a condition initially resolving to false
, Task D will stay in available state unless that condition becomes true
at some point in time which
would result in the task becoming active and in need to be completed.
If you complete all the tasks in the case, even manually starting Task C and completing it will never complete Stage A as our new Task D will
remain in available state unless that condition becomes true
at some point.
This is a good time to introduce Flowable Inspect as a tool. If playing with complexity in a case model, Inspect can be a tremendous help by
inspecting the case data, the full execution tree as well as the plan items currently at play within the case.
If you open Inspect on an active case and choosing the Plan item instances view, you will see our Task D being in available state (yellow)
and hence Stage A still being active and far from being completed.
There is no other way than manually terminate the case to close it as unless we can make that condition true
there is no other way to complete Stage A.
But what if that condition never becomes true
? How can we complete Stage A nonetheless?
Yes you guessed it - auto-completion to the rescue!
Turning on auto-completion and running the case again leads us to the following behavior (if the condition resolves to false
):
- completing Task A will already complete the stage
- manually starting Task C before Task A is completed needs it to be completed before the stage completes of course
- completing Task B afterwards will also complete the case
We learned that the auto-completion rule will ignore non-required plan items in either enabled (like with the manually activated Task C) or available state (like Task D) and will complete automatically once all active and required plan items are completed.
Manual completion of a stage with user listeners
But wait a second, what if this is NOT the required behavior? What if we want to even have a chance to decide whether to manually start Task C or maybe
wait for an event to occur before we know, that Task D is not needed, and we can move on?
This is very hard to model with just auto-completion, sentries, the required flag and manual activation in our toolbox, so we need some more control.
This is where a user listener can be of a great help.
Instead of using the auto-complete rule for a stage, we add a user listener to manually complete the stage and move on. After adding a user listener, add
an exit sentry (the black diamond) to the edge of the stage and connect the user listener with it.
Your model should now look something like this:
Once you deploy and start this version of the model, we can figure out some interesting facts about the user listener:
- a user listener is exposed in the default UI of Flowable using an action button with the same name as the user listener, if the Action name property is not used
- our user listener is in available state which means that it can be triggered at any time we want
- even if we still have active plan items (tasks) open in the stage, we can still trigger the user listener
- whenever we hit that Complete stage user listener, the stage is terminated and if Task B is completed as well, the case finishes
Alright, one requirement we have achieved already: it is now fully up to the user when that stage is left, and we have all the time we need to decide whether we need to start Task C or even wait for the condition on Task D to resolve to true. It is in our control.
But there is one problem still there: we can even move on, if there are still active tasks to be completed, the user listener will not care, we can always trigger it. Maybe this is exactly what we want, but most likely it is not. We will go into details a bit later, but let's extend the case into another direction first.
Connecting stages
After Stage A is completed, we want to move on to the next stage, Stage B. We add a new second stage, name it Stage B, and we even move Task B
into that new stage.
To connect the two stages, we drag an entry sentry from Stage A onto Stage B to only start the second stage once the first one is completed.
So far so good, our model should now look like this:
Once we start a case and complete Task A and Stage A through the user listener (regardless of what tasks we completed before triggering the user listener),
we kind of get stuck. What happened?
Why was Stage B not started? When consulting Flowable Inspect again, we see Stage B in available state. This means, it was never started as it
has an entry-sentry guarding it from being started unintentionally.
Now, what does this entry-sentry actually mean? It is connected to Stage A and if we select the connector, we can see which event type it is bound to. By
default, it will be listening to the complete event and then trigger the sentry it is connected to.
Obviously, that sentry never got triggered, so we need to dig a bit deeper to find out, what was happening.
Looking into the Audit of the History tab in the Flowable default UI shows this picture:
As expected Task A is in completed state and of course the complete stage user listener as well, but wait, what about the stage and the other tasks?
They are all in terminated state! It makes sense for Task C and Task D as they have never been completed, but Stage A also is in
terminated state!
Looking at the spec of CMMN, it reveals that an exit sentry actually DOES terminate a stage or even a case, if bound to it.
But maybe this is not exactly what we want.
We could change the event on the connector from complete (which is the default) to exit and use the same user listener and exit sentry. If you change the connector to listening to the exit event and try it out again, you will see Stage B and hence Task B being activated and you will be able to complete the case as expected. However, Stage A still remains in terminated state. Assuming you want to have a clean history, this is not what we want.
As a rule of thumb, a stage should go in completed state, if it was left in a sense of completeness and not in a sense of terminating or breaking it. We will see a small example later on illustrating the difference.
Using the exit event type of a sentry
Although outside the CMMN spec, Flowable allows you to define how an exit sentry should actually terminate a stage or a plan item or whatever it is bound to. By default, as we learned, it will use the exit event type, but we can overrule it and set it to complete instead.
If you selet the exit sentry in the CMMN case model, you find the Exit event type property. You can choose between exit (default), complete and force complete.
Now things are getting more interesting. We first try the complete event type. Also make sure that the connector to Stage A is set back to complete should you have set it to exit in the last step.
Running that sample again, reveals the following behavior:
- if we complete all active plan items (like Task A for sure and Task C as well if it was manually activated), we can trigger the user listener
- if the user listener is triggered, Stage B is activated now as we actually complete Stage A and not exit it as before
- triggering the user listener, if there is still an active plan item (e.g. if Task A was not yet completed) will lead into an exception
If we use complete as the exit event type, the user listener can only be triggered in a state where auto-complete would actually complete as well, meaning there is no more active plan item to be completed first and no required one left.
Looking again into the Audit section of the History tab in the Flowable UI, we can see that Stage A was actually left in completed state and not terminated as before, so exactly what we wanted.
There are two possibilities to overcome this undesired behavior of running into an exception. We could use force complete instead of complete, this would
end the stage no matter its current state and still leave it in completed state.
This might make sense, if there is a situation where you actually can terminate active work and still leave the stage in completed state, but most likely, this
is not what you need.
A good pattern is to use the complete exit event type, if from a business perspective, you want to look at the stage to be completed rather than terminated.
But we don't want to run into the exception and explain the users to only use that trigger to manually complete the stage if it is actually completable. So
there is another mechanism we can use: the available condition of a listener. Any listener, not just the user listener, supports an available condition which
needs resolve to true
in order for the listener to become available.
Using the exit event type complete, we can add ${cmmn:isStageCompletable()}
as the expression for the available condition of the user event listener. This
is a built-in function (hence the cmmn:
prefix) and will return true
if the parent container (in our case Stage A) of the listener is in a completable
state.
Running again with this changes shows, that we only see the user listener, if there is no more active plan item (no more tasks to complete) in Stage A. It
also takes Task C into consideration, if it was manually started and not yet completed or if Task D is activated as well, should its entry condition
evaluate to true
.
Combining complete and exit event types
Looking at a more sophisticated example where we actually want to combine both, exit and complete event type on an exit sentry (not the same of course).
Here is an example case model:
Let's walk through the different parts of the case model and look at some important details:
- we have three stages: Prepare, Review and Execute, each one following the previous one
- the Prepare stage is connected through an entry sentry to the Review stage, listening to the complete event (as we learned previously)
- the same for the Review stage being connected through the complete event / entry sentry to the Execute stage
- but we also have a loop back from the Review stage to the Prepare stage, should we need to go back to prepare while in review
- as we have a Review task as well as a manually activated Extended Review task, we choose not to use auto-completion, but rather manually send to execute or prepare again
- as we have two exit sentries on the same stage, we need to make sure, one is firing the complete event and the other one is firing exit
- from a business perspective, closing up review and sending the case to Execute actually means we have completed the stage and hence the exit sentry connected to the send to execute user listener needs to be configured to send a complete event and not an exit
- as opposed to the send to execute action, the prepare again user listener is not completing the stage, but terminates it and repeats the Prepare stage again, so we use the exit event type on the exit sentry
- the entry sentry connecting the Review and Execute stage is listening on the complete event and the entry sentry connecting the Review and Prepare stage is listening to the exit event, this way, we don't need any conditions and we either leave the Review stage in completed state, when moving on, or in terminated stage, when in need to repeat and go back to the Prepare stage
- also closely look at the repetition flag of both the Prepare and Review stage, as we can go back and repeat the preparing, we need to activate the repetition flag on both stages as they might be activated more than once
- we need to add the already used expression
${cmmn:isStageCompletable()}
as the available condition on the send to execute user listener as we need to wait to show that option, until the Review task is completed and the optional Extended Review task as well, should it be activated - we don't need a condition on the prepare again user listener though, as we might want to go back to preparation, even if the review is not yet fully done
- once we are back in Prepare stage the second or even third time, things repeat the same way: you can complete preparation and move on to review and if ok, send the case to execution or repeat back to preparation
Pay attention to the entry sentry without any connector on the Prepare stage. Why would we even need it? Well, as we have an entry sentry with a connector
from the Review stage as a loop back, should we need to go back to prepare again, the Prepare stage has an entry sentry attached to it which will prevent
it from being started automatically, when the case starts.
As soon as there is at least one entry sentry on a plan item (a stage is a plan item too), it will NOT be started automatically, but only if one of its
entry sentries is triggered.
This is why we need to add an entry sentry without a connector and without any condition to let the first stage be activated once the case is started.
But wait, if we keep it this way, the Prepare stage will always be activated again, once completed as it has repetition and no further condition on this entry sentry. Unfortunately, there is currently no other way than using a state variable and a condition so we only start it once and then whenever we go back from the Review stage.
On the initial entry sentry without any connector, we will add a condition having the following expression: ${!vars:exists(preparationStarted)}
. You can
either directly add it or use the condition builder to create it for you. We use a variable named preparationStarted and check, if it is not yet existing.
At the start of the case, this variable does not yet exist, so the stage Prepare is activated exactly as we want.
Now we still need to initialize that variable once we have entered the Prepare stage so it does not trigger again later. Select the Prepare stage and open
the Lifecycle listeners property and add a new listener where the source state is Any and the target stage is active. This listener will trigger
whenever that stage plan item goes into active state. As the expresssion, you can add ${cmmnRuntimeService.setVariable(root.id, 'preparationStarted', true)}
which will set our preparation variable to true
, so the next time our initial entry sentry gets evaluated, the condition will always be false
.
We will add a new feature to use a connector between the case model and such an initial entry sentry to avoid having to use such state variables. But in the meantime, this mechanism might be use in different places to control the triggering of sentries according to specific conditions.
Cancel the case using either complete or terminate
What if we are (back) in the Prepare stage and we feel we just need to drop the case and not even try again to send it to Review and Execute again?
There are basically two options, let's look at the most obvious one first by using an exit sentry directly added to the case model through a user listener while being in the Prepare stage:
By adding a user listener to the Prepare stage and connecting it to an exit sentry on the case model with event type exit and no available condition on the user listener will allow us to actually terminate the case (cancel it) at any time during the preparation. As we used the default exit type on the exit sentry we will terminate the Prepare stage as well as the case itself and leave it in terminated state, which most likely matches the business view on that case model as well.
First thing we might want to consider is moving the cancel case user listener outside the Prepare stage directly to the case model. This would allow us to cancel (terminate) the case at any time during its execution, even while in Review or even Execute stage. But in most cases, this does not make sense, to allow a case being canceled while already moved too far, in our case beyond the Prepare stage. But if necessary for whatever reason, this would be the way to achieve it.
Did you deploy and get stuck in the Prepare stage, even when completing the Prepare task?
This is due to the fact that a user listener is a plan item too that needs to be considered when looking at a stage to be completable. In our case, we did
not use auto-completion for the Prepare stage and hence we can only terminate the case using the cancel case option.
We can solve this in two ways: either use the auto-complete option for the Prepare stage, or use a more sophisticated way, we will look into right now.
Imagine there is way more to be done in the Prepare stage than a simple user task, maybe some optional work, ad-hoc stuff or whatever prevents us from using the auto-complete option as we have seen in our previous examples.
There is a way more fine-grained option to let the case engine know how to treat the completion of the stage by using the Impact on parent completion option.
The Impact on parent completion option on any plan item allows you to define how that plan item should be looked at when the case engine is evaluating a
stage or the case model for automatically completing it. We already learned that by using the auto-complete option, we can ignore all non-required and
non-active plan items to complete a stage or the case model, but this is not working, when using the regular way to complete a stage.
In our case, we could add another user listener to move on to the Review stage, but we want this to be done automatically by the case engine.
That's where the Impact on parent completion comes into play: by default, the behavior is exactly as the CMMN spec is saying and we already learned in the
previous examples here. But in the Flowable case engine, we have way more options.
In our case, we don't want the Prepare stage to be prevented from completion by the user listener, so we choose ignore as the impact on the stage
completion. This way, whenever the case engine examines the stage for completing it, it will only look at the user task, but not the user listener.
There is another option as mentioned before to achieve the same goal. It will leave the case in a slightly different state and is using an exit sentry on the
Prepare stage (not the case model), when trying to cancel the case.
In order for it to work and cancel the case, we need to enable auto-completion on the case model. Once we trigger that cancel case user listener, it will
terminate the Prepare stage and as there is no more active plan item / stage and by using auto-completion, the case will complete.
Although the same result, this option will leave the case in completed state, not terminated as with option one and maybe this is exactly what we want, but
most likely, option one is better as it leaves the case in terminated state as we never actually completed it.
Playing with the 'impact on parent completion' property
We have already introduced the Impact on parent completion property in the previous example. However, we want to have a closer look at all the options it provides as they allow you to configure the way the case engine is looking at such a plan item in great detail.
As a recap, look at the last note around that property to get the basics around it.
Let's have a look at this theoretical example case model:
We have several tasks (plan items) in our stage. Of course the example would also work with any other plan items than user tasks, like process tasks. But out of simplicity, we are just using user tasks as plan items, as they are really easy to play with in the default UI of Flowable Work.
Here is a quick example of the various plan items (user tasks):
- Task A is just a regular plan item with no special behavior, it will be created and directly activated once its parent (the stage) is started
- Task B has manual activation turned on, which means, it will be created in enabled state and only be activated, once manually started
- Task C is a regular plan item too (like Task A), but it has repetition turned on, which means whenever it is completed, it is created and activated right away again
- Task D has both repetition and manual activation, it is started in enabled sate and once manually activated and completed, will again be created in enabled state
- Task E through Task H are all the same like Task A through Task D, but they all have an entry sentry with a condition, so will be created in available state
until their condition becomes
true
to move to the next state, which is active or enabled for manually activated plan items
When started, the Plan item instances view in Flowable Inspect is showing the following state of the case and its plant items:
As expected, all the plan items are in the state when we initially start the case. For an easy playing with the case, we can create a simple task form with checkboxes for all the task entry sentry conditions and attach it to Task C. As this task is always started again when completed, we can switch on and off all those conditions and see what happens.
Once started and played with all the tasks, we see that there is no way we can complete the stage, even if we complete all the tasks, turn off all the conditions for the entry sentries, there is still no way to complete as for instance, Task C is always started again, once completed.
Now let's see how we can determine the behavior of each one of them whenever the case engine is evaluating, whether the stage can be completed.
Completion options for Task A
As Task A is a regular user task with no specifics, we keep it that way, which means: it is started automatically and once completed, it will not prevent the stage from completion.
Completion options for Task B
But what about Task B? By default, we would need it to be manually started at some point and then completed to no longer prevent the stage from being completed.
Now if this is not what we want, we can set the Impact on parent completion to either ignore or ignore if available or enabled.
What is the difference you asked? If we choose ignore, that task will ALWAYS be ignored, even if manually started and still in active state! If we have
a plan item, that should not be looked at when evaluating parent completion, this is the way to go. As an example, the Case page plan item has this set as
the default (behind the scenes, as it will never be completed and hence should not have an impact on parent completion.
But if you want to ignore it only, when not yet manually started (in enabled state), use the second option, ignore if available or enabled, to only ignore it
if not active and not manually started. This will make this plan item a truly optional one.
Completion options for Task C
With Task C, things are getting a bit more interesting: this one will always be created and activated again, once completed. Now as just talked about before,
we could set its Impact on parent completion property to ignore, to not have any impact on the parent at all. But most likely this is not what we want.
But what if we want to ignore it, after it has been completed at least ones? This might make way more sense. Think of an edit task, where you can (repeatedly)
edit some values, but you have to edit them at least ones.
By using ignore after first completion as the option, the parent (our stage) will no longer consider this plan item, once it was completed at least one time,
even if it will be active afterwards again.
Completion options for Task D
Task D is like Task B and Task C combined, so whenever we have manually started it and completed, it will again go into enabled state.
Here multiple options might make sense:
- ignore if available or enabled - will ignore it, if it is not started right now, but if started, it will need to be completed
- ignore after first completion - like we saw before, with this option, Task D will be ignored, once it was completed at least one time, regardless of its current state
- ignore after first completion if available or enabled - this one is making sure the plan item is at least completed one time and is currently not started (active) again, you need to complete it one time at least and if started, you always have to complete it, even if it was already completed before
Completion options for Task E
Task E is similar to Task B, but instead of manually deciding whether to start the plan item, it is decided based on the condition of the entry sentry.
This will start the plan item in state enabled state and we might use the ignore if available or enabled option to ignore the plan item, if it was
never started (the condition never became true
).
Completion options for Task F
Task F
is actually a combination of Task B and Task E as it combines manual activation with an entry sentry and a condition. Here we have two interesting
options for the parent completion: we can use ignore if available, this way, it will only be ignored, if the condition never became true
, but as soon as
the condition on the entry sentry is true
, the plan item state is changed from available to enabled and wait for the manual activation. As soon as this
happens, you would need to manually start the task at some point and of course complete it.
But by choosing ignore if available or enabled, we can completely ignore it, as long as it was not manually started or the condition never became true
.
Completion options for Task G
We could look at Task G pretty similar as we did with Task C in regard to the repetition option. But first, we need to understand a very specific rule
according the CMMN spec for a plan item with both repetition and an entry sentry with a condition. The spec notes that as long as the condition is satisfied
(resolves to true
) the plan item should be started, if it has repetition on. This would actually result in an endless loop as long as the condition is not
turned off at some point. So normally, this does not make sense. Maybe you saw the Max instance count property being set to one by default. This property
is there exactly to prevent this from happening, as even if the condition remains true
. there will only be one instance created.
If you want more than one instance being created at once, but have full control on how many of them, use the Collection variable and if you do, you can even give all the plan item instances their own context by using the Element variable and / or Element index variable property. This is very similar to the multi instance behavior in BPMN.
Coming back to the Impact on parent completion options, here, ignore if available, ignore after first completion or even ignore after first completion if available or enabled might make sense as we saw in Task D as well.
Completion options for Task H
Task H is very similar to Task G, even here with the unlimited instance count option, we can create an endless loop, if we don't use the
Collection variable option to create more than one instance with a good control on how many.
So ignore if available will ignore the plan item, if its condition never became true
, ignore if available or enabled will even ignore it, if the condition
became true
, but the task was never manually started. The option ignore after first completion will only ignore it, if completed at least ones (and
obviously the condition needed to become true
and then it was manually activated). Using the option ignore after first completion if available or enabled
will ignore it, if at least completed ones and currently, there is no more active instance, otherwise it would need to be completed as well, even if completed
before.
Summary
As you can see with all the examples and explanation in this part of the CMMN series, there is a lot of options and fine-grained mechanisms available for you to define the exact behavior of the case engine, when it comes down to how a stage or the case model gets completed or is prevented from being completed.
Even though we added quite a lot of extra properties and options compared with the CMMN spec, we believe this is necessary to have all options available in the area of stage and case model completion.