UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

216 lines • 13.2 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConditionalBoundaryEventHandler = void 0; const async_lock_1 = __importDefault(require("async-lock")); const inversify_1 = require("inversify"); const processcube_engine_sdk_1 = require("@5minds/processcube_engine_sdk"); const index_1 = require("../../../Contracts/index"); const index_2 = require("../../../Tools/DatabaseAdaptersSequelize/index"); const EventAggregator_1 = require("../../../Tools/EventAggregator"); const EventMiddlewareHandler_1 = require("../../../Tools/EventMiddlewareHandler"); const ProcessInstance_1 = require("../../ProcessInstance"); const FlowNodeHandlerFactory_1 = require("../FlowNodeHandlerFactory"); const BoundaryEventHandler_1 = require("./BoundaryEventHandler"); const asyncLocker = new async_lock_1.default({ maxPending: 1000000 }); let ConditionalBoundaryEventHandler = class ConditionalBoundaryEventHandler extends BoundaryEventHandler_1.BoundaryEventHandler { eventSubscriptions; constructor(eventMiddlewareHandler, flowNodeInstanceDatabaseAdapter, flowNodeHandlerFactory, boundaryEventModel, processInstance, processTokenOfAttachedFlowNode) { super(eventMiddlewareHandler, flowNodeInstanceDatabaseAdapter, flowNodeHandlerFactory, boundaryEventModel, processInstance, processTokenOfAttachedFlowNode, 'conditional_boundary_event_handler'); this.eventSubscriptions = []; } async execute(onTriggeredCallback, attachedFlowNodeInstanceId, decoratedFlowNodeRejectFunc) { try { this.logger.debug(`Initializing Conditional BoundaryEvent on FlowNodeInstance ${attachedFlowNodeInstanceId}`); const typeSpecificData = { type: index_1.FlowNodeInstanceDataTypes.catchEvent, eventName: this.boundaryEventModel.conditionalEventDefinition?.id, }; await this.initialize(attachedFlowNodeInstanceId, undefined, typeSpecificData); await this.waitForConditionFulfillment(onTriggeredCallback); this.cancelEventSubscriptions(); return this.handleNextFlowNodes(); } catch (error) { this.cancelEventSubscriptions(); // persistOnError would fail, if the Boundary>Event Instance was not previously persisted. // This can happen, if running the pre-script fails. // Running "persistOnEnter" again is actually safe, because a repeated call doesn't do anything. await this.persistOnEnter(); await this.persistOnError(error); decoratedFlowNodeRejectFunc(error); } } async resume(boundaryEventInstance, onTriggeredCallback, attachedFlowNodeInstanceId, decoratedFlowNodeRejectFunc, flowNodeInstances) { this.boundaryEventInstance = boundaryEventInstance; this.eventWasTriggered = this.boundaryEventWasTriggered(flowNodeInstances); if (this.eventWasTriggered && this.boundaryEventModel.cancelActivity) { return this.handleNextFlowNodes(flowNodeInstances); } try { await this.initialize(attachedFlowNodeInstanceId, boundaryEventInstance); if (this.boundaryEventInstance?.state === processcube_engine_sdk_1.FlowNodeInstanceState.canceled) { return; } let resumingPromises = []; const waitForConditionFulfillmentPromise = this.waitForConditionFulfillment(onTriggeredCallback); resumingPromises.push(waitForConditionFulfillmentPromise); if (this.eventWasTriggered) { resumingPromises.push(this.handleNextFlowNodes(flowNodeInstances)); } await Promise.all(resumingPromises); this.cancelEventSubscriptions(); return this.handleNextFlowNodes(); } catch (error) { this.cancelEventSubscriptions(); await this.persistOnError(error); decoratedFlowNodeRejectFunc(error); return; } } async cancel() { this.cancelEventSubscriptions(); if (!this.boundaryEventModel.cancelActivity && this.eventWasTriggered) { this.executionFinishCallback?.(); } await super.cancel(); } // TODO: // Conditional Boundary Events will only ever be triggered once, regardless of whether they are interrupting or not. // This is because Event Sources for a Conditional Event are not deterministic. // Basically, a Conditional Boundary Event runs whenever ANYTHING in the process happens. // Theoretically, this could lead to a Non-interrupting Conditional Boundary Event being triggered countless times, as soon as the condition is fulfilled once. // This is hardly a desirable behavior. So, until we have a way to limit the trigger count in a useful way, Conditional Boundary Events will only ever trigger once. async waitForConditionFulfillment(onTriggeredCallback) { this.logger.debug(`IntermediateConditionalCatchEvent instance ${this.boundaryEventInstanceId} waiting for condition to be fulfilled"`); const onConditionFulfilled = async () => { this.eventWasTriggered = true; this.cancelEventSubscriptions(); const nextFlowNode = this.getNextFlowNode(); await this.runPostScript(); const eventData = { boundaryInstanceId: this.boundaryEventInstanceId, nextFlowNode: nextFlowNode, interruptHandler: this.boundaryEventModel.cancelActivity, eventPayload: this.processToken, canTriggerMultipleTimes: false, }; await this.saveTokenInDataObjects(eventData.eventPayload, this.boundaryEventModel.id); this.sendBoundaryEventTriggeredNotification(this.boundaryEventModel.conditionalEventDefinition.id); onTriggeredCallback(eventData); }; const isConditionFulfilled = await this.executeAndCheckCondition(); if (isConditionFulfilled) { onConditionFulfilled(); return; } return new Promise((resolve) => { if (!this.boundaryEventModel.cancelActivity) { this.executionFinishCallback = resolve; } const onTokenMayHaveChanged = async (event, eventName) => { EventAggregator_1.EventAggregator.acknowledgeEvent(event.eventId); const isConditionFulfilled = await asyncLocker.acquire(`ConditionalBoundaryEventHandler-${this.boundaryEventInstanceId}-CheckCondition`, async () => { if (this.eventWasTriggered) { return false; } if (event.processInstanceId !== this.processInstance.getProcessInstanceId() && !(eventName === index_1.eventAggregatorSettings.messagePaths.correlationMetadataChanged && event.correlationId === this.processInstance.getCorrelationId())) { return false; } return await this.executeAndCheckCondition(); }); if (isConditionFulfilled) { this.triggeredByFlowNodeInstanceId = event.flowNodeInstanceId; onConditionFulfilled(); return resolve(); } }; this.eventSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.activityFinished, onTokenMayHaveChanged)); this.eventSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.intermediateCatchEventFinished, onTokenMayHaveChanged)); this.eventSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.intermediateThrowEventTriggered, onTokenMayHaveChanged)); this.eventSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.tokenPayloadChangeFinished, onTokenMayHaveChanged)); this.eventSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.correlationMetadataChanged, onTokenMayHaveChanged)); this.eventSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.processInstanceMetadataChanged, onTokenMayHaveChanged)); this.eventSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.processInstanceOwnerChanged.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstance.getProcessInstanceId()), onTokenMayHaveChanged)); }); } async cancelEventSubscriptions() { this.eventSubscriptions.forEach(EventAggregator_1.EventAggregator.unsubscribe); this.eventSubscriptions = []; } async executeAndCheckCondition() { const conditionExpression = this.boundaryEventModel.conditionalEventDefinition?.condition; try { if (!conditionExpression) { const conditionMissingError = new processcube_engine_sdk_1.BadRequestError('Condition is missing.'); conditionMissingError.additionalInformation = { ...this.getAdditionalInformationForError(), }; throw conditionMissingError; } const result = await this.processInstance.executeRuntimeExpression({ expression: this.boundaryEventModel.conditionalEventDefinition.condition, currentFlowNode: this.boundaryEventModel, currentToken: this.processToken, previousFlowNode: this.processInstance.getProcessModelFacade().getPreviousFlowNodesFor(this.boundaryEventModel)?.pop(), allowNonObjectResults: true, }); if (result === true) { this.logger.debug(`ConditionalBoundaryEvent instance ${this.boundaryEventInstanceId} fulfilled`); return true; } if (result != false) { const invalidExpressionError = new processcube_engine_sdk_1.BadRequestError('Condition did not evaluate to a boolean expression'); invalidExpressionError.additionalInformation = { ...this.getAdditionalInformationForError(), }; throw invalidExpressionError; } return false; } catch (err) { err.additionalInformation = { flowNodeInstanceId: this.boundaryEventInstanceId, flowNodeType: this.boundaryEventModel.bpmnType, flowNodeId: this.boundaryEventModel.id, processModelId: this.processInstance.getProcessModelId(), embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(), processInstanceId: this.processInstance.getProcessInstanceId(), correlationId: this.processInstance.getCorrelationId(), }; this.logger.error(err); throw err; } } getAdditionalInformationForError() { return { flowNodeId: this.boundaryEventModel.id, flowNodeInstanceId: this.boundaryEventInstanceId, processModelId: this.processInstance.getProcessModelId(), embeddedProcessModelId: this.processInstance.getEmbeddedProcessModelId(), processInstanceId: this.processInstance.getProcessInstanceId(), correlationId: this.processInstance.getCorrelationId(), }; } }; exports.ConditionalBoundaryEventHandler = ConditionalBoundaryEventHandler; exports.ConditionalBoundaryEventHandler = ConditionalBoundaryEventHandler = __decorate([ (0, inversify_1.injectable)(), __metadata("design:paramtypes", [EventMiddlewareHandler_1.EventMiddlewareHandler, index_2.FlowNodeInstanceDatabaseAdapter, FlowNodeHandlerFactory_1.FlowNodeHandlerFactory, Object, ProcessInstance_1.ProcessInstance, Object]) ], ConditionalBoundaryEventHandler); //# sourceMappingURL=ConditionalBoundaryEventHandler.js.map