@5minds/processcube_engine
Version:
The ProcessCube Engine. Stores and executes BPMNs.
797 lines • 42.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActivityHandler = void 0;
const uuid = __importStar(require("uuid"));
const processcube_engine_sdk_1 = require("@5minds/processcube_engine_sdk");
const EventAggregator_1 = __importDefault(require("../../../Tools/EventAggregator"));
const ErrorBoundaryEventHandler_1 = require("../BoundaryEventHandlers/ErrorBoundaryEventHandler");
const ExecutionStrategies_1 = require("./ExecutionStrategies");
/**
* This is the base handler for all Activities and Tasks.
*/
class ActivityHandler {
flowNodeInstanceDatabaseAdapter;
flowNodeHandlerFactory;
attachedBoundaryEventHandlers = {};
processInstance;
_flowNode;
_previousFlowNodeInstanceId;
_multiInstanceMetadataId;
startToken;
endToken;
batchPersistOnEnterRequests = [];
batchPersistOnEnterResolves = [];
processEventSubscriptions = [];
allBoundaryEventExecutionPromises = [];
errorBoundaryEventExecutionPromises = {};
activityExecutionStrategy;
logger;
loggingMetaData = {};
loggerNamespace = 'activity_handler';
abortController;
waitForResumption;
constructor(flowNodeInstanceDatabaseAdapter, flowNodeHandlerFactory, processInstance, flowNode) {
this.flowNodeInstanceDatabaseAdapter = flowNodeInstanceDatabaseAdapter;
this.flowNodeHandlerFactory = flowNodeHandlerFactory;
this.processInstance = processInstance;
this._flowNode = flowNode;
this._multiInstanceMetadataId = uuid.v4();
this.logger = new processcube_engine_sdk_1.Logger(this.loggerNamespace, this.loggingMetaData);
this.activityExecutionStrategy = this.getActivityExecutionStrategy();
this.abortController = new AbortController();
}
get viewModel() {
return processcube_engine_sdk_1.FlowNodeViewModelFactory.getViewModel(this.flowNode, {
processModelId: this.processInstance.getProcessModelId(),
processModelName: this.processInstance.getProcessModelName(),
});
}
get flowNode() {
return this._flowNode;
}
get multiInstanceMetadataId() {
return this._multiInstanceMetadataId;
}
get previousFlowNodeInstanceId() {
return this._previousFlowNodeInstanceId;
}
get flowNodeInstanceId() {
return this.activityExecutionStrategy.flowNodeInstanceId;
}
get abortSignal() {
return this.abortController.signal;
}
getInstanceId() {
return this.activityExecutionStrategy.flowNodeInstanceId;
}
async abort() {
this.abortController.abort();
}
async cancel() {
await this.activityExecutionStrategy.cancel();
this.abortController.abort();
}
async execute(previousFlowNodeInstanceId, previousResult, executionCanceledCallback) {
if (this.processInstance.getState() && this.processInstance.getState() !== processcube_engine_sdk_1.ProcessInstanceState.running) {
return undefined;
}
try {
const triggeredBoundaryEventExecutions = [];
this._previousFlowNodeInstanceId = previousFlowNodeInstanceId;
this.startToken = previousResult ?? {};
const triggeredBoundaryEventExecutionsFromBeforeExecute = await this.beforeExecution();
const triggeredBoundaryEventExecutionsFromExecute = await this.runExecution(executionCanceledCallback);
if (triggeredBoundaryEventExecutionsFromBeforeExecute && Array.isArray(triggeredBoundaryEventExecutionsFromBeforeExecute)) {
triggeredBoundaryEventExecutions.push(...triggeredBoundaryEventExecutionsFromBeforeExecute);
}
if (triggeredBoundaryEventExecutionsFromExecute && Array.isArray(triggeredBoundaryEventExecutionsFromExecute)) {
triggeredBoundaryEventExecutions.push(...triggeredBoundaryEventExecutionsFromExecute);
}
const nextFlowNode = await this.afterExecution();
try {
const allNextFlowNodePromises = [];
allNextFlowNodePromises.push(...triggeredBoundaryEventExecutions);
if (nextFlowNode) {
allNextFlowNodePromises.push(this.handleNextFlowNode(nextFlowNode));
}
await Promise.all([...allNextFlowNodePromises, this.detachAllBoundaryEvents()]);
}
catch (error) {
return await this.handleSubsequentError(error);
}
}
finally {
await this.detachAllBoundaryEvents();
this.allBoundaryEventExecutionPromises = [];
}
}
async resume(flowNodeInstancesForHandler, allFlowNodeInstances, resumptionCanceledCallback) {
try {
const flowNodeInstancesForHandlerAsArray = Array.isArray(flowNodeInstancesForHandler) ? flowNodeInstancesForHandler : [flowNodeInstancesForHandler];
await this.loadMultiInstanceDataFromResumableFlowNodeInstances(flowNodeInstancesForHandlerAsArray);
await this.beforeResuming(allFlowNodeInstances);
const triggeredBoundaryEventExecutions = await this.runResuming(flowNodeInstancesForHandlerAsArray, allFlowNodeInstances, resumptionCanceledCallback);
const nextFlowNode = await this.afterExecution();
try {
const allNextFlowNodePromises = [];
allNextFlowNodePromises.push(...triggeredBoundaryEventExecutions);
if (nextFlowNode) {
allNextFlowNodePromises.push(this.handleNextFlowNode(nextFlowNode, allFlowNodeInstances));
}
await Promise.all([...allNextFlowNodePromises, this.detachAllBoundaryEvents()]);
}
catch (error) {
return await this.handleSubsequentError(error);
}
}
finally {
await this.detachAllBoundaryEvents();
this.allBoundaryEventExecutionPromises = [];
}
}
isMultiInstanceType() {
return [processcube_engine_sdk_1.Model.Activities.LoopMarker.Sequential, processcube_engine_sdk_1.Model.Activities.LoopMarker.Parallel, processcube_engine_sdk_1.Model.Activities.LoopMarker.Loop].includes(this.flowNode.loopMarker);
}
async batchPersistOnEnterRequest(request, limit, resolve) {
this.batchPersistOnEnterRequests.push(request);
this.batchPersistOnEnterResolves.push(resolve);
if (this.batchPersistOnEnterRequests.length >= limit) {
await this.flowNodeInstanceDatabaseAdapter.batchPersistOnEnter(this.batchPersistOnEnterRequests);
this.batchPersistOnEnterRequests = [];
this.batchPersistOnEnterResolves.forEach((resolve) => resolve());
}
}
async runOutgoingDataObjectExpressions(token) {
this.abortSignal.throwIfAborted();
const dataObjectOutputReferences = this.processInstance.getProcessModelFacade().getOutputDataObjectReferencesForFlowNode(this.flowNode.id);
if (dataObjectOutputReferences.length === 0) {
return;
}
const dataObjectValueCollection = {};
for (const dataObjectReference of dataObjectOutputReferences) {
const dataOutputAssociation = this.flowNode.dataOutputAssociations.find((entry) => entry.targetRef === dataObjectReference.id);
const dataObjectValue = dataOutputAssociation.dataSource?.length > 0 ? await this.parseDataObjectExpression(dataOutputAssociation.dataSource, dataObjectReference.id, token) : token;
dataObjectValueCollection[dataObjectReference.id] = dataObjectValue;
this.processInstance.getDataObjectFacade().storeValue(dataObjectReference.id, dataObjectValue);
}
return dataObjectValueCollection;
}
async beforeExecution() {
let triggeredBoundaryEventExecutions = [];
try {
await this.ensureIsNotMultiInstanceAtEventBasedGateway();
await this.subscribeToProcessInstanceEvents();
await this.attachBoundaryEvents();
await this.runPreScript();
await this.saveMultiInstanceData();
await this.parseAdditionalActivityProperties();
this.ensureMultiInstanceInputIsValid();
}
catch (error) {
await this.disposeEventSubscriptions();
await this.detachNonErrorBoundaryEvents();
await this.handleBeforeExecutionError(error);
triggeredBoundaryEventExecutions = this.allBoundaryEventExecutionPromises
.filter((execution) => execution.boundaryEventHandler.getState() === processcube_engine_sdk_1.FlowNodeInstanceState.finished || execution.boundaryEventHandler.eventWasTriggered)
.map((boundaryEventExecutionPromise) => boundaryEventExecutionPromise.executionPromise);
}
return triggeredBoundaryEventExecutions;
}
async runExecution(executionCanceledCallback) {
let triggeredBoundaryEventExecutions = [];
try {
this.endToken = await this.executeByStrategy(this.startToken, executionCanceledCallback);
if (this.isMultiInstanceType()) {
try {
this.endToken = (await this.runPostScript(this.endToken)) ?? this.endToken;
}
catch (error) {
const isTerminationOrAbortion = await this.isAbortedOrTerminatedError(error);
if (!isTerminationOrAbortion) {
await this.activityExecutionStrategy.endWithPostError(error);
}
throw error;
}
}
}
catch (error) {
await this.disposeEventSubscriptions();
await this.detachNonErrorBoundaryEvents();
await this.handleExecutionError(error);
}
finally {
triggeredBoundaryEventExecutions = this.allBoundaryEventExecutionPromises
.filter((execution) => execution.boundaryEventHandler.getState() === processcube_engine_sdk_1.FlowNodeInstanceState.finished || execution.boundaryEventHandler.eventWasTriggered)
.map((boundaryEventExecutionPromise) => boundaryEventExecutionPromise.executionPromise);
}
return triggeredBoundaryEventExecutions;
}
async afterExecution() {
try {
this.abortSignal.throwIfAborted();
if (this.isMultiInstanceType()) {
const dataObjectValues = await this.runOutgoingDataObjectExpressions(this.endToken);
await this.persistDataObjectValues(dataObjectValues);
}
await this.saveMultiInstanceData();
const nextFlowNode = await this.getNextFlowNode();
return nextFlowNode;
}
catch (error) {
await this.handleAfterExecutionError(error);
}
finally {
await this.disposeEventSubscriptions();
}
return undefined;
}
async beforeResuming(allFlowNodeInstances) {
try {
await this.subscribeToProcessInstanceEvents();
await this.parseAdditionalActivityProperties();
}
catch (error) {
await this.disposeEventSubscriptions();
await this.handleBeforeExecutionError(error);
}
}
async loadMultiInstanceDataFromResumableFlowNodeInstances(flowNodeInstancesForHandler) {
const flowNodeInstanceForHandler = flowNodeInstancesForHandler[0];
this.startToken = flowNodeInstanceForHandler.multiInstanceStartToken;
this.endToken = flowNodeInstanceForHandler.multiInstanceEndToken;
this._multiInstanceMetadataId = flowNodeInstanceForHandler.multiInstanceMetadataId;
this._previousFlowNodeInstanceId = flowNodeInstanceForHandler.previousFlowNodeInstanceId;
}
async runResuming(flowNodeInstances, allFlowNodeInstances, resumptionCanceledCallback) {
let triggeredBoundaryEventExecutions = [];
try {
let resolver;
this.waitForResumption = new Promise((resolve) => {
resolver = resolve;
});
const resumingPromise = this.activityExecutionStrategy.resume(flowNodeInstances, this.startToken, resumptionCanceledCallback);
await this.attachBoundaryEvents(allFlowNodeInstances);
resolver();
this.endToken = await resumingPromise;
if (this.isMultiInstanceType()) {
this.endToken = (await this.runPostScript(this.endToken)) ?? this.endToken;
}
}
catch (error) {
await this.disposeEventSubscriptions();
await this.detachNonErrorBoundaryEvents();
await this.handleExecutionError(error);
}
finally {
triggeredBoundaryEventExecutions = this.allBoundaryEventExecutionPromises
.filter((execution) => execution.boundaryEventHandler.getState() === processcube_engine_sdk_1.FlowNodeInstanceState.finished || execution.boundaryEventHandler.eventWasTriggered)
.map((boundaryEventExecutionPromise) => boundaryEventExecutionPromise.executionPromise);
}
return triggeredBoundaryEventExecutions;
}
async getNextFlowNode() {
const nextFlowNodes = this.processInstance.getProcessModelFacade().getNextFlowNodesFor(this.flowNode);
if (nextFlowNodes == null || nextFlowNodes.length === 0) {
return;
}
if (nextFlowNodes.length > 1) {
this.throwMultipleSubsequentFlowNodesError(nextFlowNodes);
}
return nextFlowNodes[0];
}
async subscribeToProcessInstanceEvents() {
this.abortSignal.throwIfAborted();
this.processEventSubscriptions.push(this.subscribeToProcessKilledEvent());
this.processEventSubscriptions.push(this.subscribeToTerminateEndEventReached());
this.processEventSubscriptions.push(this.subscribeToProcessError());
}
subscribeToProcessKilledEvent() {
return this.processInstance.onKillProcess(async (message) => {
const killerId = message?.killedBy?.userId ?? undefined;
const errorMsg = `Process was killed by user \`${killerId}\``;
const processKilledError = new processcube_engine_sdk_1.GoneError(errorMsg);
processKilledError.additionalInformation = {
processInstanceWasTerminated: true,
killedBy: message.killedBy,
};
await this.activityExecutionStrategy.terminate();
this.abortController.abort(processKilledError);
});
}
subscribeToProcessError() {
return this.processInstance.onProcessError(async (message) => {
const payloadIsDefined = message != undefined;
this.endToken = payloadIsDefined ? message.currentToken : {};
const error = new processcube_engine_sdk_1.InternalServerError('ProcessInstance encountered an error!');
error.additionalInformation = message.currentToken;
this.abortController.abort();
await this.activityExecutionStrategy.endWithTermination();
await this.disposeEventSubscriptions();
});
}
subscribeToTerminateEndEventReached() {
return this.processInstance.onTerminateEndEventReached(async (message) => {
this.endToken = message.currentToken ?? {};
this.logger.debug('Cancelling Flow Node Execution, because a Terminate End Event has been reached.');
this.abortController.abort();
await this.activityExecutionStrategy.cancel();
await this.disposeEventSubscriptions();
});
}
async disposeEventSubscriptions() {
this.processEventSubscriptions.forEach((subscription) => EventAggregator_1.default.unsubscribe(subscription));
}
async handleSubsequentError(error) {
const shouldExit = await this.isAbortedOrTerminatedError(error);
if (shouldExit) {
return;
}
this.abortController.abort(error);
throw error;
}
async handleBeforeExecutionError(error) {
const shouldExit = await this.isAbortedOrTerminatedError(error);
if (shouldExit) {
return;
}
await this.activityExecutionStrategy.endWithPreError(error, true);
return this.runErrorBoundaryEventsForError(error);
}
async handleExecutionError(error) {
const shouldExit = await this.isAbortedOrTerminatedError(error);
if (shouldExit) {
return;
}
await this.activityExecutionStrategy.endWithTermination();
return this.runErrorBoundaryEventsForError(error);
}
async handleAfterExecutionError(error) {
const shouldExit = await this.isAbortedOrTerminatedError(error);
if (shouldExit) {
return;
}
this.abortController.abort(error);
await this.activityExecutionStrategy.endWithPostError(error);
throw error;
}
async runErrorBoundaryEventsForError(error) {
const errorBoundaryEvents = Object.values(this.attachedBoundaryEventHandlers).filter((handler) => handler instanceof ErrorBoundaryEventHandler_1.ErrorBoundaryEventHandler && handler.canHandleError(error));
const noErrorBoundaryEventsAvailable = !errorBoundaryEvents || errorBoundaryEvents.length === 0;
if (noErrorBoundaryEventsAvailable) {
this.abortController.abort(error);
throw error;
}
try {
let executionPromisesToAwait = [];
for (const errorBoundaryEvent of errorBoundaryEvents) {
const errorHandlerId = errorBoundaryEvent.getInstanceId();
errorBoundaryEvent.trigger(error);
executionPromisesToAwait.push(this.errorBoundaryEventExecutionPromises[errorHandlerId]);
}
await Promise.all(executionPromisesToAwait);
this.abortController.abort();
return;
}
catch (errorFromBoundaryEventChain) {
this.abortController.abort(errorFromBoundaryEventChain);
throw errorFromBoundaryEventChain;
}
}
async isAbortedOrTerminatedError(error) {
const isAbortError = error.name === 'AbortError' || error.type === 'abort';
if (isAbortError) {
return true;
}
const isTerminationEvent = error.additionalInformation?.processInstanceWasTerminated === true;
if (isTerminationEvent) {
await this.detachAllBoundaryEvents();
throw error;
}
return false;
}
async saveMultiInstanceData() {
await this.flowNodeInstanceDatabaseAdapter.saveMultiInstanceMetadata(this.multiInstanceMetadataId, this.startToken, this.endToken);
}
async persistDataObjectValues(dataObjectValues) {
this.abortSignal.throwIfAborted();
return this.flowNodeInstanceDatabaseAdapter.saveDataObjectInstances({
processDefinitionId: this.processInstance.getProcessDefinitionId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
processModelId: this.processInstance.getProcessModelId(),
embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(),
dataObjectValues: dataObjectValues,
flowNodeInstanceId: this.activityExecutionStrategy.flowNodeInstanceId,
});
}
async handleNextFlowNode(nextFlowNode, allFlowNodeInstancesToResume) {
this.abortSignal.throwIfAborted();
const nextFlowNodeHandler = this.flowNodeHandlerFactory.create(nextFlowNode, this.processInstance);
const flowNodeInstanceToResume = allFlowNodeInstancesToResume ? this.findNextFlowNodeInstance(allFlowNodeInstancesToResume, nextFlowNode.id) : undefined;
if (flowNodeInstanceToResume) {
return nextFlowNodeHandler.resume(flowNodeInstanceToResume, allFlowNodeInstancesToResume);
}
await nextFlowNodeHandler.execute(this.flowNodeInstanceId, this.endToken);
}
findNextFlowNodeInstance(allFlowNodeInstances, nextFlowNodeId) {
return allFlowNodeInstances.find((instance) => {
// ParallelJoinGateways always have multiple "previousFlowNodeInstanceIds", separated by ";" (i.e.: ID1;ID2;ID3 etc)
let instanceFollowedCurrentFlowNode = instance.previousFlowNodeInstanceId?.indexOf(this.flowNodeInstanceId) > -1;
if (this.flowNodeInstanceId.includes(';') && instance.previousFlowNodeInstanceId?.includes(';')) {
const sortedFlowNodeInstanceId = this.flowNodeInstanceId.split(';').sort().join(';');
const sortedPreviousFlowNodeInstanceId = instance.previousFlowNodeInstanceId.split(';').sort().join(';');
instanceFollowedCurrentFlowNode = sortedPreviousFlowNodeInstanceId.indexOf(sortedFlowNodeInstanceId) > -1;
}
const flowNodeIdsMatch = instance.flowNodeId === nextFlowNodeId;
return instanceFollowedCurrentFlowNode && flowNodeIdsMatch;
});
}
async parseDataObjectExpression(expression, dataObjectId, token) {
try {
const payload = await this.processInstance.executeRuntimeExpression({
expression: expression,
currentFlowNode: this.flowNode,
currentToken: token,
dataObjectId: dataObjectId,
allowNonObjectResults: false,
});
return payload;
}
catch (error) {
const parserError = new processcube_engine_sdk_1.BadRequestError(`Failed to execute DataObject expression!`, error.category);
const additionalInformation = {
expression: expression,
dataObjectId: dataObjectId,
currentDataObjectValue: this.processInstance.getDataObjectFacade().getValue(dataObjectId) ?? {},
correlationId: this.processInstance.getCorrelationId(),
flowNodeInstanceId: this.flowNodeInstanceId,
flowNodeId: this.flowNode.id,
processInstanceId: this.processInstance.getProcessInstanceId(),
originalErrorMessage: error.message,
};
parserError.additionalInformation = additionalInformation;
this.logger.error(parserError.message, additionalInformation);
throw parserError;
}
}
async runPreScript() {
this.abortSignal.throwIfAborted();
const preScript = this.flowNode.extensionElements?.camundaExtensionProperties?.find((prop) => prop.name === 'engine.runPreScript')?.value;
if (!preScript) {
return;
}
try {
const result = await this.processInstance.executeRuntimeExpression({
expression: preScript,
currentFlowNode: this.flowNode,
currentToken: this.startToken,
previousFlowNode: this.processInstance.getProcessModelFacade().getPreviousFlowNodesFor(this.flowNode)?.pop(),
allowNonObjectResults: false,
});
this.startToken = result ?? {};
}
catch (error) {
const errorCategoryPrefix = error.category ? `Failure in ${error.category}: ` : '';
this.logger.error(`${errorCategoryPrefix}Error in Pre Script`, {
err: (0, processcube_engine_sdk_1.serializeJson)(error),
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
processModelId: this.processInstance.getProcessModelId(),
embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(),
flowNodeId: this.flowNode.id,
flowNodeInstanceId: this.flowNodeInstanceId,
});
const sanitizedMessage = error.message.replace(errorCategoryPrefix, '');
const preScriptError = new processcube_engine_sdk_1.BadRequestError(`Failure in process: Error in Pre Script: ${sanitizedMessage}`, error.category);
preScriptError.additionalInformation = {
originalError: error.message,
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
processModelId: this.processInstance.getProcessModelId(),
embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(),
flowNodeId: this.flowNode.id,
flowNodeInstanceId: this.flowNodeInstanceId,
};
throw preScriptError;
}
}
async runPostScript(token) {
this.abortSignal.throwIfAborted();
const postScript = this.flowNode.extensionElements?.camundaExtensionProperties?.find((prop) => prop.name === 'engine.runPostScript')?.value;
if (!postScript) {
return;
}
try {
const result = await this.processInstance.executeRuntimeExpression({
expression: postScript,
currentFlowNode: this.flowNode,
currentToken: token,
previousFlowNode: this.processInstance.getProcessModelFacade().getPreviousFlowNodesFor(this.flowNode)?.pop(),
allowNonObjectResults: false,
});
return result ?? {};
}
catch (error) {
const errorCategoryPrefix = error.category ? `Failure in ${error.category}: ` : '';
this.logger.error(`${errorCategoryPrefix}Error in Post Script`, {
err: (0, processcube_engine_sdk_1.serializeJson)(error),
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
processModelId: this.processInstance.getProcessModelId(),
embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(),
flowNodeId: this.flowNode.id,
flowNodeInstanceId: this.flowNodeInstanceId,
});
const sanitizedMessage = error.message.replace(errorCategoryPrefix, '');
const postScriptError = new processcube_engine_sdk_1.BadRequestError(`Failure in process: Error in Post Script: ${sanitizedMessage}`, error.category);
postScriptError.additionalInformation = {
originalError: error.message,
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
processModelId: this.processInstance.getProcessModelId(),
embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(),
flowNodeId: this.flowNode.id,
flowNodeInstanceId: this.flowNodeInstanceId,
};
throw postScriptError;
}
}
async executeByStrategy(token, executionCanceledCallback) {
this.abortSignal.throwIfAborted();
return await this.activityExecutionStrategy.execute(token, executionCanceledCallback);
}
async attachBoundaryEvents(flowNodeInstances) {
this.abortSignal.throwIfAborted();
const boundaryEventModels = this.processInstance.getProcessModelFacade().getBoundaryEventsFor(this.flowNode);
const noBoundaryEventsFound = boundaryEventModels?.length === 0;
if (noBoundaryEventsFound) {
return;
}
// Create a handler for each attached BoundaryEvent and store it in the internal collection.
for (const boundaryEventModel of boundaryEventModels) {
this.logger.trace(`Attaching${boundaryEventModel.cancelActivity ? ' interrupting' : ''} Boundary Event '${boundaryEventModel.id}'`);
await this.createAndRunBoundaryEventHandler(boundaryEventModel, flowNodeInstances);
}
}
async createAndRunBoundaryEventHandler(boundaryEventModel, flowNodeInstances) {
const boundaryEventHandler = this.flowNodeHandlerFactory.createBoundaryEventHandler(boundaryEventModel, this.processInstance, this.startToken);
let executionPromise;
const onBoundaryEventTriggeredCallback = async (eventData) => {
try {
await this.waitForResumption;
this.logger.debug(`Boundary Event '${boundaryEventHandler.getModelId()}' instance '${boundaryEventHandler.getInstanceId()}' was triggered.`);
if (eventData.interruptHandler) {
await this.cancelExecution(eventData);
}
if (!eventData.canTriggerMultipleTimes) {
delete this.attachedBoundaryEventHandlers[boundaryEventHandler.getInstanceId()];
boundaryEventHandler.finish();
}
await executionPromise;
}
catch (error) {
return this.handleExecutionError(error);
}
};
const flowNodeInstance = flowNodeInstances?.find((entry) => {
return entry.flowNodeId === boundaryEventModel.id && entry.previousFlowNodeInstanceId === this.multiInstanceMetadataId;
});
if (flowNodeInstance) {
executionPromise = boundaryEventHandler.resume(flowNodeInstance, onBoundaryEventTriggeredCallback, this.multiInstanceMetadataId, this.handleExecutionError.bind(this), flowNodeInstances);
}
else {
executionPromise = boundaryEventHandler.execute(onBoundaryEventTriggeredCallback, this.multiInstanceMetadataId, this.handleExecutionError.bind(this));
}
if (boundaryEventModel.eventType === processcube_engine_sdk_1.EventType.errorEvent) {
this.errorBoundaryEventExecutionPromises[boundaryEventHandler.getInstanceId()] = executionPromise;
}
this.allBoundaryEventExecutionPromises.push({
executionPromise: executionPromise,
boundaryEventHandler: boundaryEventHandler,
});
this.attachedBoundaryEventHandlers[boundaryEventHandler.getInstanceId()] = boundaryEventHandler;
}
/**
* Used for interactive tasks, to prevent interrupting a task, which has already produced a result and is effectively finished.
* ErrorBoundaryEvents must be kept alive, until "persistOnExit" has completed, so that errors in post scripts, or data associations can be handled.
* All other Boundary Event Types must be canceled before that. See also https://github.com/atlas-engine/Engine/issues/1223
*/
async detachNonErrorBoundaryEvents() {
await Promise.all(Object.keys(this.attachedBoundaryEventHandlers)
.filter((handlerInstanceId) => !(this.attachedBoundaryEventHandlers[handlerInstanceId] instanceof ErrorBoundaryEventHandler_1.ErrorBoundaryEventHandler))
.map(async (handlerInstanceId) => {
const handler = this.attachedBoundaryEventHandlers[handlerInstanceId];
this.logger.trace(`Detaching Boundary Event '${handler.getModelId()}' instance ${handlerInstanceId}`, {
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
});
delete this.attachedBoundaryEventHandlers[handlerInstanceId];
await handler.finish();
}));
}
async detachAllBoundaryEvents() {
if (Object.keys(this.attachedBoundaryEventHandlers).length > 0) {
this.logger.trace(`Detaching Boundary Events (${Object.keys(this.attachedBoundaryEventHandlers).length} Boundary Events are currently attached)`, {
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
});
}
for (const [handlerInstanceId, handler] of Object.entries(this.attachedBoundaryEventHandlers)) {
this.logger.trace(`Detaching Boundary Event '${handler.getModelId()}' instance ${handlerInstanceId}`, {
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
});
delete this.attachedBoundaryEventHandlers[handlerInstanceId];
await handler.finish();
}
}
async cancelExecution(eventData) {
this.logger.debug(`Boundary Event was interrupting. Cancelling execution.`);
if (eventData?.eventPayload) {
this.endToken = eventData.eventPayload;
}
this.abortController.abort();
await this.activityExecutionStrategy.cancel();
}
throwMultipleSubsequentFlowNodesError(nextFlowNodes) {
this.abortSignal.throwIfAborted();
const msg = `Activity \`${this.flowNode.id}\` is followed by multiple Flow Nodes. This is not allowed. Activities must only be followed by a single Flow Node.`;
const multipleOutgoingFlowsError = new processcube_engine_sdk_1.BadRequestError(msg, 'process');
multipleOutgoingFlowsError.additionalInformation = {
nextFlowNodes: nextFlowNodes.map((nextFlowNode) => {
return {
flowNodeId: nextFlowNode.id,
connectingSequenceFlowId: this.processInstance.getProcessModelFacade().getSequenceFlowBetween(this.flowNode, nextFlowNode),
};
}),
};
throw multipleOutgoingFlowsError;
}
async ensureIsNotMultiInstanceAtEventBasedGateway() {
// The modeler only allows receive tasks to follow an event based gateway, which makes things a lot easier.
if (this.flowNode.bpmnType !== processcube_engine_sdk_1.BpmnType.receiveTask) {
return;
}
if (!(this.flowNode.loopMarker === processcube_engine_sdk_1.Model.Activities.LoopMarker.Sequential ||
this.flowNode.loopMarker === processcube_engine_sdk_1.Model.Activities.LoopMarker.Parallel ||
this.flowNode.loopMarker === processcube_engine_sdk_1.Model.Activities.LoopMarker.Loop)) {
return;
}
const previousFlowNodeInstance = await this.flowNodeInstanceDatabaseAdapter.findByInstanceId(this.previousFlowNodeInstanceId);
if (previousFlowNodeInstance?.flowNodeType === processcube_engine_sdk_1.BpmnType.eventBasedGateway) {
const error = new processcube_engine_sdk_1.BadRequestError(`Executing Multi Instance Receive Tasks with an Event Based Gateway is not supported.`);
error.additionalInformation = {
processDefinitionId: this.processInstance.getProcessDefinitionId(),
processModelId: this.processInstance.getProcessModelId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
correlationId: this.processInstance.getCorrelationId(),
flowNodeInstanceId: this.flowNodeInstanceId,
flowNodeId: this.flowNode.id,
flowNodeName: this.flowNode.name,
multiInstanceType: this.flowNode.loopMarker,
};
throw error;
}
}
ensureMultiInstanceInputIsValid() {
if (!(this.flowNode.loopMarker === processcube_engine_sdk_1.Model.Activities.LoopMarker.Sequential || this.flowNode.loopMarker === processcube_engine_sdk_1.Model.Activities.LoopMarker.Parallel)) {
return;
}
const startTokenAsArray = Array.isArray(this.startToken) ? this.startToken : [this.startToken];
if (startTokenAsArray.length === 0) {
throw new processcube_engine_sdk_1.BadRequestError(`Error while trying to execute ${this.flowNode.loopMarker} Multi Instance Task: Input data is empty.`, 'process');
}
}
getActivityExecutionStrategy() {
switch (this.flowNode.loopMarker) {
case processcube_engine_sdk_1.Model.Activities.LoopMarker.Sequential:
return new ExecutionStrategies_1.SequentialActivityExecutionStrategy(this.flowNodeInstanceDatabaseAdapter, this, this.processInstance);
case processcube_engine_sdk_1.Model.Activities.LoopMarker.Parallel:
return new ExecutionStrategies_1.ParallelActivityExecutionStrategy(this.flowNodeInstanceDatabaseAdapter, this, this.processInstance);
case processcube_engine_sdk_1.Model.Activities.LoopMarker.Loop:
return new ExecutionStrategies_1.LoopActivityExecutionStrategy(this.flowNodeInstanceDatabaseAdapter, this, this.processInstance);
default:
return new ExecutionStrategies_1.DefaultActivityExecutionStrategy(this.flowNodeInstanceDatabaseAdapter, this, this.processInstance);
}
}
async parseAdditionalActivityProperties() {
this.activityExecutionStrategy.timeoutBetweenLoopIterations = await this.parseTimeoutBetweenLoopIterations();
this.activityExecutionStrategy.maxLoopIterations = await this.parseMaxLoopIterations();
}
async parseTimeoutBetweenLoopIterations() {
const canUseTimeoutBetweenLoopIterations = this.flowNode.loopMarker === processcube_engine_sdk_1.Model.Activities.LoopMarker.Loop || this.flowNode.loopMarker === processcube_engine_sdk_1.Model.Activities.LoopMarker.Sequential;
const hasTimeoutBetweenLoopIterationsDefined = this.flowNode.timeoutBetweenLoopIterations;
if (canUseTimeoutBetweenLoopIterations && hasTimeoutBetweenLoopIterationsDefined) {
return this.parseNumberValueFromExpression(this.flowNode.timeoutBetweenLoopIterations, 'engine.setTimeoutBetweenLoopIterations');
}
return undefined;
}
async parseMaxLoopIterations() {
const canUseMaxLoopIterations = this.flowNode.loopMarker === processcube_engine_sdk_1.Model.Activities.LoopMarker.Loop;
const hasMaxLoopIterationsDefined = this.flowNode.maxLoopIterations;
if (canUseMaxLoopIterations && hasMaxLoopIterationsDefined) {
return this.parseNumberValueFromExpression(this.flowNode.maxLoopIterations, 'engine.setMaxLoopIterations');
}
return undefined;
}
async parseNumberValueFromExpression(expression, propertyName) {
try {
const result = await this.processInstance.executeRuntimeExpression({
expression: expression,
currentFlowNode: this.flowNode,
currentToken: this.startToken,
previousFlowNode: this.processInstance.getProcessModelFacade().getPreviousFlowNodesFor(this.flowNode)?.pop(),
allowNonObjectResults: true,
});
const resultIsNumber = typeof result === 'number';
if (resultIsNumber) {
return result;
}
const parsedNumber = parseInt(result);
return parsedNumber;
}
catch (error) {
const errorCategoryPrefix = error.category ? `Failure in ${error.category}: ` : '';
this.logger.error(`${errorCategoryPrefix}Error while parsing additionalProperty ${propertyName}`, {
err: (0, processcube_engine_sdk_1.serializeJson)(error),
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
processModelId: this.processInstance.getProcessModelId(),
embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(),
flowNodeId: this.flowNode.id,
flowNodeInstanceId: this.flowNodeInstanceId,
property: propertyName,
});
const sanitizedMessage = error.message.replace(errorCategoryPrefix, '');
const parsingError = new processcube_engine_sdk_1.BadRequestError(`Failure in process: Error while parsing additionalProperty ${propertyName}: ${sanitizedMessage}`, error.category);
parsingError.additionalInformation = {
originalError: error.message,
correlationId: this.processInstance.getCorrelationId(),
processInstanceId: this.processInstance.getProcessInstanceId(),
processModelId: this.processInstance.getProcessModelId(),
embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(),
flowNodeId: this.flowNode.id,
flowNodeInstanceId: this.flowNodeInstanceId,
property: propertyName,
};
throw parsingError;
}
}
}
exports.ActivityHandler = ActivityHandler;
//# sourceMappingURL=ActivityHandler.js.map