UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

797 lines • 42.3 kB
"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