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