BPMN Script Task

BPMN Script Tasks can be used in process diagrams for a PROCEED engine.

General Aspects

You can use pure JavaScript to write functionalities for the process. Thereby, you also have access to a bunch of APIs for accessing process variables or trigger network requests (see API).

At the end of the script there needs to be a return, ending the script. The return may contain a variable with an value which will update the process variable. A Script Task can also end by a semantic error, which will be caught by an attached boundary Error or Escalation event. This is done by throwing an BpmnError or an BpmnEscalation.

If an error is thrown but not caught within the BPMN process, or if the script itself has an error (e.g. syntax error), the token will end its execution.. (This only stops the running token, not the whole process.)

The code of a script task is usually executed within a sandboxed JavaScript environment, so that it is not able to influence the PROCEED Engine.
The supported ECMAScript version depends on the used Node.js version which runs the PROCEED Engine, but at least does ECMAScript Version 8 is supported.

Example

The following example is a BPMN process describing the workflow of an online shop which offers to ship products to their customers: The process starts with a received order, then the order gets processed and finally shipped to a customer.

Using the attached boundary elements Error and Escalation, the process can handle exceptional behavior when processing the order: If there is an escalation triggered because of an unusual high amount of ordered products, the customer will be informed of a late shipment. If there occurs an error while processing, the customer is informed that the order got canceled.

BPMN Process

Example Script Code

<bpmn2:scriptTask id="Task_1pdn5o1" scriptFormat="application/javascript">
    <bpmn2:script>
        <![CDATA[
            const amount = variable.get('amount'); // amount of the ordered products
            const cost = variable.get('cost'); // cost of the ordered products
            
            if (cost < 50) {
                // update the costs
                try {
                    // add shipping costs to total cost
                    variable.set('cost', cost+5);
                } catch(err) {
                    log.info('Error occured while processing order: ' + err);
                    throw new BpmnError('Error occured', 'Can not update cost');
                }
            }

            if (amount > 100) {
                // execute alternative flow to the attached escalation boundary-event
                throw new BpmnEscalation('late shipment', 'The order is too much');
            }
        ]]>
    </bpmn2:script>
</bpmn2:scriptTask>

API

variable

In a Script Task it is possible to read and modify the process variables. This can be done via the object variable.

When setting variables, it is required to use only data types which are supported by JSON. Variables are not allowed to contain undefined, functions or cyclical objects. Not following these requirements, the Script Task will throw an error. Due to this, setting of a variable should be done in a try-catch block.

API Access to the process variables.
.set( "<name>", <value> ) Updates value of variable
.get( "<name>" ) Returns value of variable
.getAll() Returns object containing all variables and values

log

Logging in the PROCEED Engine can be done via the object log. Depending on the log level and the Engine configuration, the logged message will maybe shown in the console.

API Write a message to the logging system of the Engine.
.trace( "<message>" )
.debug( "<message>" )
.info( "<message>" )
.warn( "<message>" )
.error( "<message>" )

console

Logging can also done via the object console. Using this way, the message will be redirected to the logging system of the engine and potentially shown in the console. Furthermore, console supports a few other functions:

API Write a message to the logging system of the Engine.
.log( "<message>" )
.trace( "<message>" )
.debug( "<message>" )
.info( "<message>" )
.warn( "<message>" )
.error( "<message>" )
.time( "<label>" ) Starts a timer containing label parameter
.timeEnd( "<label>" ) Ends a timer with given label parameter and displays the result in console

setProgress

The engine holds information about the current progress of a flow Node. Inside of a Script Task, the current progress can be set via the function setProgress, which accepts a value between 0 and 100 describing the percentage.

API
setProgress(<progress>) Set progress of a Script Task, progress between 0 and 100

Services

There are special functionalities for accessing system resources. They can be accessed via services by calling getService( 'service' ). Services are provided by the PROCEED Engine. The following services are currently offered:

getService('capabilities')

API Access to the systems capabilities
.startCapability( „<Capability-Name>“, [<{ Parameter-Object }>], [<clb function>] )
Return value: if clb then null else Promise
Call the capability Function and give a list of parameters.
E.g.
await startCapability( „PhotographAction“, { height: 80, width: 80 } )
E.g. Variant 1:
await startCapability( „PhotographAction“, { height: { value: 80, unit: “px” } )
Needed for the Management System to determine the correct Capability Description if there are multiple Description of the same Capability kind (e.g. two times “PhotoAction”) with different Parameter units, e.g. “cm” and “pt”
E.g. Variant 2:
await startCapability( „https://schema.org/PhotographAction“, { “https://schema.org/height”: 80 } )
Only needed for the Management System to determine the correct Capability Description if there are multiple descriptions of the same capability kind or its parameter (e.g. “height” from “schema.org/height” and from “vocab.org/height”)

getService('network')

Inside of a Script Task it is possible to send HTTP requests to a given URL. The network functions are asynchronous and it is returned a Promise. The promise resolves if the request was successful and rejects if not. For a successful request the response-object of the request looks as follows: {<{response}>, <{body}>}

API Send network requests
.get( "<url>", [<{options}>]) Send GET-Request to URL with given options (e.g. headers for request-header)
.post( "<url>", [<{options}>]) Send POST-Request to URL with given options (e.g. formData to transmit data in body of request)
.put( "<url>", [<{options}>]) Send PUT-Request to URL with given options
.delete( "<url>", [<{options}>]) Send DELETE-Request to URL with given options
.head( "<url>", [<{options}>]) Send HEAD-Request to URL with given options

Trigger Events

throw new BpmnError( 'reference', 'explanation' )

API Send network requests
'reference' A String that is matched to the corresponding errorCode of the error element (prio 1) or to the name attribute of the attached BPMN boundary error event (prio 2). If there is no match but an attached event without a name, then the flow is given to this Event. See Error and Escalation Event description
'explanation' A String that is stored in the logging system of the Engine.

throw new BpmnEscalation('reference', 'explanation' );

API Send network requests
'reference' A String that is matched to the corresponding escalationCode of the escalation element (prio 1) or to the name attribute of the attached BPMN boundary escalation event (prio 2). If there is no match but an attached event without a name, then the flow is given to this Event. See Error and Escalation Event description
'explanation' A String that is stored in the logging system of the Engine.

Example

<bpmn2:scriptTask id="Task_1pdn5o1" scriptFormat="javascript" proceed:version="1587405670122">
    <bpmn2:script>
        <![CDATA[
            const capabilities = getService("capabilities");
            const network = getService("network");
            
            log.info( "Script started" );

            const amount = variable.get("amount");
            log.info( "The value of the process variable _amount_ currently is: " + amount );
            variable.set("amount", ++amount);
            log.info( "Increased variable _amount_ by 1" );



            // send get-request using given url and store response body in variable
            network.get('https://example.org/123', {})
            .then(value => {
                const {response, body} = value;
                log.info(`Successful request with response: ${response.statusCode}`);
                variable.set('requestedData', body);
            })
            .catch(error => log.error(`Failed request: ${error}`))
            

            // send put-request using given url and data
            const exampleText = variable.get('exampleText');

            network.put('https://example.org/456', {formData: exampleText, headers: {'Content-Type': 'text/plain'}})
            .then(value => log.info(`Successful request with response: ${value.response.statusCode}`))
            .catch(error => log.error(`Failed request: ${error}`))

            // set current progress of script task to 50%
            setProgress(50);

            const image = await capabilities.startCapability( "takePhoto" );

            log.info( "Photo taken" );

            // set variable with new value
            return { photo: image };
        ]]>
    </bpmn2:script>
    <bpmn2:extensionElements>
        <proceed:capabilities>
            <proceed:capability>takePhoto</proceed:capability>
        </proceed:capabilities>
    </bpmn2:extensionElements>
</bpmn2:scriptTask>