@5minds/processcube_engine
Version:
The ProcessCube Engine. Stores and executes BPMNs.
210 lines • 12.8 kB
JavaScript
"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