UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

210 lines • 12.8 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.InclusiveJoinGatewayHandler = void 0; const inversify_1 = require("inversify"); const lodash_1 = require("lodash"); const processcube_engine_sdk_1 = require("@5minds/processcube_engine_sdk"); const index_1 = require("../../../Contracts/InternalMessages/index"); const index_2 = require("../../../Tools/DatabaseAdaptersSequelize/index"); const EventAggregator_1 = __importDefault(require("../../../Tools/EventAggregator")); const EventMiddlewareHandler_1 = require("../../../Tools/EventMiddlewareHandler"); const ProcessInstance_1 = require("../../ProcessInstance"); const FlowNodeHandlerFactory_1 = require("../FlowNodeHandlerFactory"); const GatewayHandler_1 = require("./GatewayHandler"); const filterNewestInstanceFromFlowNode = (flowNodeInstances) => { const newestFlowNodeInstances = []; for (const flowNodeInstance of flowNodeInstances) { const isAlreadyIncluded = newestFlowNodeInstances.map((fni) => fni.flowNodeId).includes(flowNodeInstance.flowNodeId); if (!isAlreadyIncluded) { newestFlowNodeInstances.push(flowNodeInstance); } } return newestFlowNodeInstances; }; let InclusiveJoinGatewayHandler = class InclusiveJoinGatewayHandler extends GatewayHandler_1.GatewayHandler { isInterrupted = false; relevantFlowNodeIds; incomingFlowNodeIds; relevantEventsSubscriptions = []; constructor(eventMiddlewareHandler, flowNodeHandlerFactory, flowNodeInstanceDatabaseAdapter, inclusiveGatewayModel, processInstance) { super(eventMiddlewareHandler, flowNodeHandlerFactory, flowNodeInstanceDatabaseAdapter, inclusiveGatewayModel, processInstance, 'inclusive_join_gateway'); const relevantFlowNodes = this.processInstance.getProcessModelFacade().traversePreviousFlowNodesFor(this.inclusiveGateway); this.relevantFlowNodeIds = relevantFlowNodes.map((flowNode) => flowNode.id).filter((flowNodeId) => flowNodeId !== this.inclusiveGateway.id); const incomingFlowNodes = this.processInstance.getProcessModelFacade().getPreviousFlowNodesFor(this.inclusiveGateway, true); this.incomingFlowNodeIds = incomingFlowNodes.map((flowNode) => flowNode.id).filter((flowNodeId) => flowNodeId !== this.inclusiveGateway.id); } get inclusiveGateway() { return this.flowNode; } async beforeExecute(resolveFunction, rejectFunction, resumedFlowNodeInstances) { // Safety check to prevent a handler to be resolved and called after it was already finished. if (this.isInterrupted) { return; } if (this.processEventSubscriptions.length == 0) { this.processEventSubscriptions.push(this.subscribeToProcessKilledEvent(rejectFunction)); this.processEventSubscriptions.push(this.subscribeToTerminateEndEventReached(resolveFunction)); this.processEventSubscriptions.push(this.subscribeToProcessError(rejectFunction)); } if (!this.relevantEventsSubscriptions || this.relevantEventsSubscriptions.length === 0) { this.subscribeToRelevantFlowNodeEvents(); } if (resumedFlowNodeInstances) { const previousFlowNodeInstanceIds = this.previousFlowNodeInstanceId.split(';'); const unaccountedFlowNodeInstances = resumedFlowNodeInstances.filter((fni) => previousFlowNodeInstanceIds.includes(fni.flowNodeInstanceId) && !this.flowNodeInstancesArrivedAtGateway.includes(fni.flowNodeInstanceId)); const newFlowNodeInstanceIds = unaccountedFlowNodeInstances.map((fni) => fni.flowNodeInstanceId); this.flowNodeInstancesArrivedAtGateway.push(...newFlowNodeInstanceIds); } else if (this.previousFlowNodeInstanceId && !this.flowNodeInstancesArrivedAtGateway.includes(this.previousFlowNodeInstanceId)) { this.flowNodeInstancesArrivedAtGateway.push(this.previousFlowNodeInstanceId); } } async startExecution() { this.logger.debug(`Executing InclusiveJoinGateway instance.`); return this.executeHandler(); } async executeHandler() { if (this.isInterrupted) { return; } const relevantFlowNodeInstances = await this.flowNodeInstanceAdapter.query({ flowNodeId: this.relevantFlowNodeIds, processInstanceId: this.processInstance.getProcessInstanceId(), state: [processcube_engine_sdk_1.FlowNodeInstanceState.suspended, processcube_engine_sdk_1.FlowNodeInstanceState.running], }); const allRelevantFlowNodesFinished = relevantFlowNodeInstances.flowNodeInstances.length == 0; if (!allRelevantFlowNodesFinished) { return; } const incomingFlowNodeInstances = (await this.flowNodeInstanceAdapter.query({ flowNodeId: this.incomingFlowNodeIds, processInstanceId: this.processInstance.getProcessInstanceId(), state: processcube_engine_sdk_1.FlowNodeInstanceState.finished, })).flowNodeInstances; const newestIncomingFlowNodeInstances = filterNewestInstanceFromFlowNode(incomingFlowNodeInstances); // Because persisting the "flowNodeInstancesArrivedAtGateway" takes some time, we need to make sure the // finished flowNodeInstances *really* arrived at the gateway, before we continue. const allFlowNodesArrived = newestIncomingFlowNodeInstances.length == this.flowNodeInstancesArrivedAtGateway.length; let missingFlowNodeInstances = []; if (!allFlowNodesArrived) { // In some cases a finished flowNodeInstance might be finished, but won't arrive at the inclusive gateway, // e.g. an exclusive gateway that lead the flow another way. // In this case we need to check if that flowNodeInstance has a successor that is/was running. missingFlowNodeInstances = newestIncomingFlowNodeInstances.map((fni) => fni.flowNodeInstanceId).filter((fniId) => !this.flowNodeInstancesArrivedAtGateway.includes(fniId)); const followingFlowNodes = await this.flowNodeInstanceAdapter.query({ previousFlowNodeInstanceId: missingFlowNodeInstances, }); const nextFlowNodesAreRunning = followingFlowNodes.flowNodeInstances.length > 0; if (!nextFlowNodesAreRunning) { return; } } this.cleanupSubscriptions(); await this.removeInstanceFromIocContainer(); const instancesToAggregate = newestIncomingFlowNodeInstances.filter((fni) => !missingFlowNodeInstances.includes(fni.flowNodeInstanceId)); this.processToken = this.aggregateResults(instancesToAggregate); await super.persistOnExit(); return this.processInstance.getProcessModelFacade().getNextFlowNodesFor(this.inclusiveGateway); } async afterExecute() { this.publishGatewayFinishedNotification(); } // This gateway can only resume, after all incoming paths have arrived. // But since a single execution thread can end after arriving at the gateway, we need to override this hook, in order to prevent an early state transition. async persistOnExit() { } aggregateResults(incomingFlowNodeInstances) { const resultToken = {}; for (const incomingFlowNodeInstance of incomingFlowNodeInstances) { resultToken[incomingFlowNodeInstance.flowNodeId] = incomingFlowNodeInstance.endToken ?? {}; } return resultToken; } subscribeToRelevantFlowNodeEvents() { const callback = this.getRelevantEventSubscriptionHandler(); const debouncedCallback = (0, lodash_1.debounce)(callback, 100, { maxWait: 200 }); this.relevantEventsSubscriptions.push(EventAggregator_1.default.subscribe(index_1.eventAggregatorSettings.messagePaths.activityCanceled, debouncedCallback)); this.relevantEventsSubscriptions.push(EventAggregator_1.default.subscribe(index_1.eventAggregatorSettings.messagePaths.boundaryEventFinished, debouncedCallback)); this.relevantEventsSubscriptions.push(EventAggregator_1.default.subscribe(index_1.eventAggregatorSettings.messagePaths.gatewayFinished, debouncedCallback)); } getRelevantEventSubscriptionHandler() { return async (payload, _eventName) => { const isSameProcess = payload.processInstanceId == this.processInstance.getProcessInstanceId(); const isRelevantFlowNode = this.relevantFlowNodeIds.includes(payload.flowNodeId); if (!isSameProcess || !isRelevantFlowNode) { return; } this.execute(); }; } subscribeToProcessKilledEvent(rejectionFunction) { return this.processInstance.onKillProcess(async () => { // This is done to prevent anybody from accessing the handler after a kill signal was received. this.isInterrupted = true; this.cleanupSubscriptions(); await this.persistOnTerminate(); await this.removeInstanceFromIocContainer(); return rejectionFunction(); }); } subscribeToProcessError(rejectionFunction) { return this.processInstance.onProcessError(async (message) => { // This is done to prevent anybody from accessing the handler after an error message was received. this.isInterrupted = true; this.cleanupSubscriptions(); const payloadIsDefined = message != undefined; this.processToken = payloadIsDefined ? message.currentToken : {}; const error = new processcube_engine_sdk_1.InternalServerError('ProcessInstance encountered an error!'); error.additionalInformation = message.currentToken; await this.removeInstanceFromIocContainer(); await this.onInterruptedCallback(this.processToken); await this.persistOnError(error); return rejectionFunction(); }); } subscribeToTerminateEndEventReached(resolveFunction) { return this.processInstance.onTerminateEndEventReached(async (message) => { // This is done to prevent anybody from accessing the handler after the process is stopped by a terminate end event. this.isInterrupted = true; this.cleanupSubscriptions(); await this.removeInstanceFromIocContainer(); this.processToken = {}; this.logger.debug('Cancelling Flow Node Execution, because a Terminate End Event has been reached.'); await this.cancel(this.processToken); return resolveFunction(); }); } cleanupSubscriptions() { this.processEventSubscriptions.forEach((subscription) => EventAggregator_1.default.unsubscribe(subscription)); for (const subscription of this.relevantEventsSubscriptions) { EventAggregator_1.default.unsubscribe(subscription); } this.relevantEventsSubscriptions = []; } async removeInstanceFromIocContainer() { const processInstanceId = this.processInstance.getProcessInstanceId(); const joinGatewayRegistration = `InclusiveJoinGatewayHandlerInstance-${processInstanceId}-${this.inclusiveGateway.id}`; if (this.processInstance.getContainer().isBound(joinGatewayRegistration)) { await this.processInstance.getContainer().unbind(joinGatewayRegistration); } } }; exports.InclusiveJoinGatewayHandler = InclusiveJoinGatewayHandler; exports.InclusiveJoinGatewayHandler = InclusiveJoinGatewayHandler = __decorate([ (0, inversify_1.injectable)(), __metadata("design:paramtypes", [EventMiddlewareHandler_1.EventMiddlewareHandler, FlowNodeHandlerFactory_1.FlowNodeHandlerFactory, index_2.FlowNodeInstanceDatabaseAdapter, Object, ProcessInstance_1.ProcessInstance]) ], InclusiveJoinGatewayHandler); //# sourceMappingURL=InclusiveJoinGatewayHandler.js.map