Questions and Answers
Process Logging (Script Task)
JavaScript Script Format
Declare and use a logger in the 'Script' field or use the standard println function.
var logger = java.util.logging.Logger;
var log = logger.getLogger('MY_JS_LOGGER');
log.info('Im logging INFO');
Groovy Script Format
import org.slf4j.*
Logger logger = LoggerFactory.getLogger('MyScriptLogger');
logger.info('Hello World');
Analyzing Process Execution Errors
Various errors may occur during process execution that are not apparent during the initial process review in monitoring.
To avoid situations where errors are hard to identify, set additional wait points.
Example with a Gateway Condition Error
There is a process that, at first glance, seems to have no issues with execution. The process is waiting for a task to be completed.
If we enter the task and try to process it, we'll see an error - a 500 response code for the task processing method. This means that after processing the task, an exception occurs during the process execution that cannot be handled. Since there are no additional wait points set in the process (the user task has the default one), the process, when an exception occurs, rolls back the transaction to the last wait point, which is the task.
In such cases, it is necessary to check the execution service log. This can be done through Kibana or by directly exporting logs from the service.
At the end of this log, you may encounter the following error (you can see the full error description and stack trace in the log file):
ERROR: condition expression returns non-Boolean: result has class java.lang.String and not java.lang.Boolean
This error indicates that the Conditional Expression returns a result of type "String," but it should be of type "Boolean" (true/false). Such an error can occur in places where conditional expressions are used—on branches/joins or on an element: intermediate conditional event.
In our process, this error could only occur in two places, namely, the two remaining gateways. By checking variables and conditions on each branch,
In this specific situation, an error occurred at the gateway after the "Get the list of vacation requests" step, specifically when checking the vacationRequestStatusId variable, which in this process version had the type "String" (as seen in the variable list in the screenshot above). You can download the process diagram and check why the error occurred.
Process Execution Stuck at the Kafka Message Consumption Cube
In the business process, there is a step of subscribing to a Kafka topic. During execution, if there is a message in Kafka, the process may hang. This means that the correlation identifiers for the message in Kafka and the process are different. It is necessary to correctly configure the correlation mechanism, described below.
Error When Starting a Process Instance
Option 1
When starting a business process instance (calling the POST method), you may receive an HTTP 500 response code. You can parse the errorMessage parameter or check the MS logs. The specified parameter will contain a message like:
Internal exception, please see server log, message: Output topic «input-topic» not registered
This error indicates that the "input-topic" topic is not registered for sending (output) messages in the business process execution microservice. A similar error can occur for subscription topics.
Option 2
The situation arises when transforming an existing (copied) process, where the default start step is changed to a message start step.
Conditions that led to the error:
- Upon the initial publication of the business process, the name and identifier of the process pool remain unchanged.
- In subsequent version changes to the diagram and publication, change the name and identifier of the process pool.
ENGINE-13030 Cannot correlate a message with name 'qhldrq-vacationrequest-command-processstart' to a single process definition. 2 process definitions match the correlations keys: CorrelationSet [businessKey=null, processInstanceId=null, processDefinitionId=null, correlationKeys=null, localCorrelationKeys=null, tenantId=null, isTenantIdSet=false, isExecutionsOnly=false]
Error summary: Two published processes with the same start element subscribed to the same topic and waiting for messages in Kafka to start.
This error can be corrected, but it is best to eliminate the root cause that led to it.
To fix the problem, it will be sufficient not to start the first version of the process AND in the service's database, find the act_ru_event_subscr
table, and delete the record with the old process identifier, which is no longer relevant. This way, the subscription of the starting process to the topic is removed, leaving only the current one.
The actions described above will fix the problem, but it may reoccur. To address this, it is necessary to identify the problematic version of the business process (input data: diagram identifier):
- In the "Process Designer" module's database, in the
diagram_item
table, find all records related to the process pool.
select *
from qbpmdesigner.q_bpm_diagram_item qbdi
where diagram_version_id in (
select id
from qbpmdesigner.q_bpm_diagram_version qbdv
where diagram_id = <Diagram Record Identifier>
)
and type = 'process'
- Using the
system_name
column, identify the system name of the outdated process pool. - If there are multiple records for the outdated process pool, remember all values from the
diagram_version_id
column – this is the "bad" process version. - Delete all existing connections in all tables of the "Process Designer" module database, including the
act_*
tables (Camunda system tables).
After completing these actions, the error will be considered resolved.
Message Correlation
Processes may involve asynchronous interaction through Kafka. Process instance(s) can respond to incoming messages using the correlation key - correlationId
. Published messages in Kafka are matched with the process instance - message correlation.
A message is not sent directly to an instance. Message correlation is based on subscriptions that contain message name
and correlation key
.
There are two possible implementation approaches (the first one works by default).
-
Automatic All settings for the microservice remain unchanged, and the correlation key is generated automatically and placed in the
correlationId
parameter.The key is generated automatically when starting a process instance. In the "Send Message" cube, when sending a message, the key is automatically placed in both the body and the header.
Sending a message to Kafka is done for the consumer system, and if the main process instance subscribes to the response message, the consumer system in the response message must return the correlation key unchanged.
When there is a suitable message in Kafka, the process instance compares its generation key with the one that came from the message (checking the message's ownership to the instance).🚫If the message lacks the correlation key or does not match the process instance's correlation key, the instance will hang on the message receiving cube. If successful processing cannot be guaranteed, it is advisable to set a boundary timer event (timeout).
-
Manual
The correlation key can be generated/set manually during process design. To do this, in the settings of the qbpetqbpmplayer service, in the
${QBPM_CORRELATION_KEYS:dqMessageGuid}
parameter, specify the field name for the correlation key.When sending a message to Kafka, the specified parameter with the correlation key will be placed in both the body and the header of the message.
To exclude the correlationId and businessKey fields from the message, you need to set the QBPM_IS_DISABLE_UNUSED_OPERATIONS: true parameter in Q.BPM Player
Reserved Platform Events
Message Header Name | Event Description | Message Body | Initiator -> Receiver |
---|---|---|---|
dq-q-bpm-task-event | Event on user task change (creation, update, closure) - passing data about the performer | Human task → Player | |
taskActionEventTopic | Task action event, tasks send information to the player. | Human task → Player | |
taskCreateEventTopic | Event on task creation | Player → Human task | |
taskCancelEventTopic | Event on task cancellation in case of process termination (kill) | Human task → Player | |
dq-q-bpm-decision-definition | Event with the description of a specific rule on publication | Player → Designer | |
dq-q-bpm-decision-requirements-definition | Event with the description of a DMN diagram (decision tree) on publication | Player → Designer | |
dq-q-bpm-decision-evaluation | Event about executed business rule | Player → Designer | |
dq-q-bpm-diagram-deploy-request | Event about publication from the designer to the player | Designer → Player | |
dq-q-bpm-diagram-deploy-response | Event about the fact of publication from the player to the designer | Player → Designer | |
dq-q-bpm-diagram-process-definition | Event from the player to the designer, during which process pools in the diagram are highlighted and linked to the diagram | Player → Designer | |
dq-q-bpm-diagram-version | Event from the designer to Kafka on manual saving (floppy disk button in the editor) of the diagram | Designer → All | |
dq-q-bpm-process-instance-protocol | Protocol on process instance execution. On start and completion. | Player → Cockpit | |
dq-q-bpm-process-activity-protocol | Protocol on process step instance execution. On start and completion. | Player → Cockpit | |
dsOnAfterProcessDefinitionCreate | Event on process appearance (player sends to cockpit on publication) | Player → Cockpit | |
dsOnProcessActivityEvent | Event on each node of the process in the diagram from the player | Player → Cockpit | |
dsOnProcessInstanceEvent | When a process instance appears and completes. Process start status = ACTIVE, and process completion status = COMPLETED. | Player → Cockpit | |
dq-q-bpm-incident | Event on a failed process that is suspended for user intervention | Player → Cockpit |
Data Type Conversion in JavaScript Business Process Scripts
The business process context stores data in variables with Java object types. To process objects with JavaScript functions, it is necessary to convert Java objects obtained from the process context into JavaScript objects. For this conversion in script nodes of the process, use the methods of the ObjectMapper object.
writeValueAsString(variable <object>) // converting an object to a string
readValue(someVariableAsStringOutput, java.util.LinkedHashMap.class) // converting a string variable to a Java object of the specified type (in this case, LinkedHashMap)
To work with JavaScript objects in a business process script node, it is necessary to first convert a string variable to a JavaScript object:
JSON.parse(someVariableAsString)
To convert a JavaScript object to a string, you need to use the method:
JSON.stringify(someVariableAsObject)
The sequence of necessary transformations in the process script to obtain a Java object from the process context, process the data as a JavaScript object, and pass it back to the process context as a Java object:
// Getting a variable from the process context
var someVariable = execution.getVariable("someVariable");
// Converting the obtained object to a JSON string
var someVariableAsString = objectMapper.writeValueAsString(someVariable);
// Converting the JSON string to a JavaScript object
var someVariableAsObject = JSON.parse(someVariableAsString);
//
// Executing script logic with the JavaScript object
//
// Converting the JavaScript object to a string
var someVariableAsStringOutput = JSON.stringify(someVariableAsObject);
// Converting the string to a Java object (java.util.LinkedHashMap)
var someVariableAsMapOutput = objectMapper.readValue(someVariableAsStringOutput, java.util.LinkedHashMap.class);
// Passing the variable to the process context
execution.setVariable("someVariable", someVariableAsMapOutput);
Creating a Widget and Using it in a Custom Task Template
To create a widget, you need to follow the instructions (opens in a new tab).
Interaction Modes between the Widget and the Main Task Processing Form
-
Task processing actions are controlled on the main form.
This option is recommended for simple custom task processing forms that provide information to users for viewing only. The widget itself only provides visual representation. No control actions or additional logic need to be implemented in the application component.
To display information on the form received from the process in the custom task, you need to add the
data(@Input() data: Task = {};)
property to the widget code. In this case, all task data and parameters passed from the bpmn process will be available in the data object.To achieve this, the widget needs to add:
- the actions property;
- the taskActionTriggered, widgetInfo, and cancel events;
- in ngOnInit (the moment the widget is initialized), you need to pass the event to widgetInfo to notify the main form of the current widget's operating mode:
ngOnInit
@Input() actions: MenuItem[]; @Output() widgetInfo = new EventEmitter<TaskProcessingWidgetInfo>(); @Output() taskActionTriggered = new EventEmitter<TaskProcessingResultAction>(); @Output() cancel = new EventEmitter<string>(); ngOnInit(): void { this.widgetInfo.emit({ usesActionButtons: true // true - control buttons are implemented on the widget side, false - control buttons should be provided by the main task processing form }); }
-
Controlling actions on the widget side
This option is intended for implementing more complex interaction with the user during custom task processing. The widget receives a list of actions from the main form and independently manages the layout, logic for the availability of these actions, and other application logic executed when the user selects a specific action.
To achieve this, the widget needs to add:
- the actions property;
- the taskActionTriggered, widgetInfo, and cancel events;
- in ngOnInit (the moment the widget is initialized), you need to pass the event to widgetInfo to notify the main form of the current widget's operating mode:
ngOnInit
@Input() actions: MenuItem[]; @Output() widgetInfo = new EventEmitter<TaskProcessingWidgetInfo>(); @Output() taskActionTriggered = new EventEmitter<TaskProcessingResultAction>(); @Output() cancel = new EventEmitter<string>(); ngOnInit(): void { this.widgetInfo.emit({ usesActionButtons: true // true - control buttons are implemented on the widget side, false - control buttons should be provided by the main task processing form }); }
The widget can initiate an event signaling the completion of task processing by the user and transfer control to the main form, which will mark the user task as "Completed". To do this, it is necessary to propagate the event to taskActionTriggered.
onActionButtonClicked(): void { this.taskActionTriggered.emit({ id: 'DONE', // The id field from the selected actions item. Anything not included in actions is ignored, as such an action cannot be processed in the bpmn process. comment: 'Some comment' // User comment or additional information (optional field) }); }
The widget can also trigger an event to cancel the processing of the user task (in this case, the processing form will be closed without performing any actions on the task). To do this, you need to pass the event to "cancel."
onCancel(): void { this.cancel.emit(); }
To ensure the interaction of the task processing widget with the common task processing form, the widget must comply with the contract provided by the TaskProcessingWidgetComponent interface. To do this, it is necessary to connect the library npm i @diasoft/q-bpm-human-task-widget or add "@diasoft/q-bpm-human-task-widget": "0.0.9" to the package.json file.
export interface TaskProcessingWidgetComponent { actions: MenuItem[]; // Set or list of actions available for task processing data: Task; // Data of the processed task taskActionTriggered: EventEmitter<TaskProcessingResultAction>; // Event caught by the common task processing form. This event should be triggered when processing the task based on user action. }
Example of implementing a widget
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { MenuItem } from 'primeng/api';
import { TaskProcessingAction } from './task-processing-action.model';
import {
Task,
TaskProcessingResultAction,
TaskProcessingWidgetComponent,
TaskProcessingWidgetInfo
} from '@diasoft/q-bpm-human-task-widget'; // Library import
@Component({
selector: 'app-task-processing-widget',
templateUrl: './task-processing-default-widget.component.html',
styleUrls: ['./task-processing-default-widget.component.scss']
})
export class TaskProcessingDefaultWidgetComponent implements TaskProcessingWidgetComponent, OnInit {
private readonly defaultActions: TaskProcessingAction[] = [
{ label: 'Process', id: 'NEXT' }
];
// implement the component
private actionsInner: TaskProcessingAction[] = this.defaultActions;
@Input() set actions(actions: any) {
this.actionsInner = actions || [];
this.actionButtonItems = this.getActionButtonItems();
if (actions.length > 0) {
this.taskProcessingAction = this.mapActionToSplitButtonItem(actions[0]);
}
}; // Get task actions
@Input() data: Task = {}; // Get other task information for widget logic
@Output() taskActionTriggered = new EventEmitter<TaskProcessingResultAction>(); // Send event
@Output() cancel = new EventEmitter<string>();
@Output() widgetInfo = new EventEmitter<TaskProcessingWidgetInfo>();
actionButtonItems: MenuItem[] = this.getActionButtonItems();
taskProcessingAction: MenuItem = this.mapActionToSplitButtonItem(this.defaultActions[0]);
form: FormGroup = this.fb.group({
comment: ['']
});
showActionsButtons = true;
constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {
}
ngOnInit(): void {
this.widgetInfo.emit({
usesActionButtons: this.showActionsButtons
});
}
getActionButtonItems(): MenuItem[] {
return this.actionsInner.map(action => this.mapActionToSplitButtonItem(action));
}
mapActionToSplitButtonItem(action: TaskProcessingAction): MenuItem {
return ({
label: action.label,
id: action.id,
command: () => this.onActionSelected(action.id)
});
}
onCancel(): void {
this.cancel.emit();
}
onActionSelected(actionId: string): void {
const action = this.actionsInner.find(act => act.id === actionId);
if (action) {
this.taskProcessingAction = this.mapActionToSplitButtonItem(action);
}
}
onActionButtonClicked(): void {
if (this.taskProcessingAction?.id) {
this.taskActionTriggered.emit({
id: this.taskProcessingAction.id,
comment: this.form.controls.comment.value
});
}
}
}
export interface Task {
id?: any;
priority?: number; // task priority
templateSysName?: string; // template system name
templateName?: string; // template name
objectType?: string;
version?: number; // version
maxProcessTime?: number; // maximum task processing date
maxExecutionTime?: number; // maximum task execution time
taskForm?: string;
assignAlgorithm?: string; // assignment algorithm
state?: string; // state
stateName?: string;
createDate?: Date;
endDate?: Date;
assignDate?: Date; // assignment date
assignGroupType?: string;
assignGroupName?: string;
assignId?: number;
assignName?: string;
assigneeId?: number;
details?: TaskDetails; // task parameters from BPM process context
activityName?: string;
activityId?: string;
processInstanceId?: string;
processDefinitionId?: string;
processDefinitionKey?: string;
processDefinitionName?: string;
processDefinitionVersion?: string;
comment?: string; // task comment
serviceName?: string; // name of the task initiator service
}
Using the widget in the custom task template
Now it remains to insert the ready-made widget into the custom task template, which will be displayed on the custom task processing form.
In the "Task Processing Widget" field, you need to specify the widget component address in the format <ui-service name>
:<component name>
:<widget name>
.
Common mistakes when executing a business process
Authorization error when calling via rest-connector
"error":"invalid_token","errorDescription":"Full authentication is required to access this resource"
Solution: Enable authorization authorization (opens in a new tab) in the Q.BPM Player settings
When calling DMN
Internal exception, please see server log, message: no decision definition deployed with key <someKey> and tenant-id 'prod': decisionDefinition is null
Solution: Check the ID in the DMN. It must match the system name of the business rule
When using a User Task die
ENGINE-03051 There was an exception while invoking the TaskListener. Message: 'Cannot deserialize value of type `java.util.ArrayList` from Object value (token `JsonToken.START_OBJECT`) at [Source: UNKNOWN; line: -1, column: -1]'
Solution: Check the script on the cube where the error occurred. Invalid syntax.
Process execution stops
Query return 2 results instead of max 1
Solution: Check the act_ru_event_subscr table in Q.BPM Player for duplicate records.
Process Instances is not opened for viewing
<someNamespace>api/null/null/engine-rest/history/variable-instance?<processInstanceId>
Http failure response for <someNamespace>/api/null/null/engine-rest/process-definition/<processDefinitionId>/xml: 404 Not Found
Solution: Check Q.BPM Cockpit logs for ERROR. Republish the process. Update the version of Q.BPM Cockpit and Q.BPM Player
Business Processes and Process Instances are not displayed in Process Monitoring Solution: Check the general Kafka settings on the stand and in Process Monitoring. If the Kafka parameter values are without prefixes, messages for Process Monitoring can be intercepted by another namespace that looks at the same Kafka. We recommend configuring the CHANNEL_PREFIX parameter in the general settings of the stand. A service restart will be required.