I recently worked on a project where users needed the ability to cancel the process and any child process at any point in the process. After doing some digging on K2 Community and the K2 Knowledge Center, I was able to piece together the essential parts to get my processes to reach a completed state when canceled by the end user.
The first thing I did was build a SmartBox SmartObject called CancelProcessKeys that I could use for any process that might need to cancelled. The SmartObject was straight forward with a few properties for the record ID (autonumber), process ID, parent process ID, process name (for giggles), and the serial number (which is a string).
Building the process
Setting up the process for cancel at anytime is not all that difficult. From the start box I have two parallel paths; one for the main process flow and the other to handle the cancellation of the process. In the cancel activity (Wait to Cancel) I have three events: one SmartObject event, and two server events (code). Also note that I am using Visual Studio for this process rather than K2 Studio or K2 designer because I need to write some custom code for the server events.
For the SmartObject event, I am calling the create method on the CancelProcessKeys SmartObject to save the process ID, process name, and the serial number for the activity destination instance. I return the record ID for the SmartObject and save to the process data for later use.
Next are the server events. The first server event, Wait to Cancel, is an Asynchronous Server Event. The Asynchronous Server Event is used to basically pause process execution until another system programmatically tells the process engine to continue the process flow. To make a server event asynchronous the item event code needs to be modified (right click event -> view code –> event item). Then add the following line of code:
The next server event, Go To Cancel Process, does exactly what is says except that we use the code behind to do so rather than allowing the activity to compete naturally. The line between the two activities is needed to avoid build and deployment errors. The code to add to this event is as follows:
Here we are instructing the workflow server to go to the specified activity AND to expire all activities, meaning any outstanding user tasks will be cancelled. The next activity contains a SmartObject event to delete the cancel process keys record because the process has been cancelled.
Next, I set the activity destination rule and use ‘Plan per slot (no destinations)’ since there aren’t any users interacting with the process in this activity. To select the destination rule options, the wizard needs to be run in advanced mode. I also set the number of slots to be created to 1.
Now that the process is essentially setup, I need something that is going to finish out my Asynchronous Server Events programmatically. To do so, I created a custom assembly and used the API to complete the server event. Take a look at this post on K2 community for the code. I also installed the assembly into GAC as recommended in the post. Once I had the assembly installed into GAC, I exposed it as a SmartObject (FinishK2ServerEvent) using the Endpoints Assembly Service object. Also take a look at this knowledge base article on the K2 site for further explanation about the Asynchronous Server Event and how to setup using the server event. The article also contains code to complete the server event.
The other thing to note here is that the K2 service account will need process rights on the process/processes you want to cancel using this method. Set the process rights (Server Event) accordingly using K2 workspace.
Cancel Child Processes
For this example, I am using an IPC event to start a child process from the parent. The child process is setup in the same manner as the parent process with the parallel process to the cancel activity with the SmartObject and Server events. The only difference is in the SmartObject event, where I also set the Parent Process ID. This is so I can find all the cancel serial numbers for the child processes that are related to the parent that is being cancelled.
In the parent process I have another activity after the cleanup activity to cancel any child processes. I followed this KB article to set this activity up to execute the FinishK2ServerEvent SmartObject event for as many times there are child processes. I ran the destination rule wizard in advanced mode again and select the same ‘Plan per slot (no destination)’ option. On the next screen, rather than specifying the number of slots, I use the ‘Select a list field…’ option and use the list method of the CancelProcessKeys SmartObject to get a list of child process serial numbers for the current process ID and return the CancelSerialNumber field, which is then stored in the Activity Instance Destination data field. I also added a condition on the line rule leading to the Cancel Children activity to check if there are child records related to the parent before trying to reach the activity or execute any server events.
Then in the SmartObject event, I call the FinishK2ServerEvent SmartObject and pass in the Activity Destination Instance data as the cancel serial number to cancel each child process.
Now when the end user cancels the parent process, all the child processes are cancelled as well.
To test the cancel configuration, I setup a SmartForm to trigger the cancel event. I fetch the serial number for the parent workflow to send into the Finish
K2ServerEvent SmartObject when the cancel button is clicked. We can see by using the View Flow of the processes that the parent and the child have both reached a completed state after the Cancel button is clicked on the SmartForm.