UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

463 lines • 27.5 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 __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 __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 __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MessageEventService = void 0; const async_lock_1 = __importDefault(require("async-lock")); const inversify_1 = require("inversify"); const uuid = __importStar(require("uuid")); const processcube_engine_sdk_1 = require("@5minds/processcube_engine_sdk"); const index_1 = require("../Contracts/index"); const Tools_1 = require("../Tools"); const EventAggregator_1 = require("../Tools/EventAggregator"); const MonitoringManager_1 = require("../Tools/MonitoringManager"); const ExecuteProcessService_1 = require("./ExecuteProcessService"); const ProcessTokenService_1 = require("./ProcessTokenService"); const asyncLocker = new async_lock_1.default({ maxPending: 1000000 }); let MessageEventService = class MessageEventService { executeProcessService; flowNodeInstanceAdapter; processDefinitionMediator; processInstanceAdapter; processTokenService; logger; subscriptions; internalSubscriptions = []; deployedMessageCatchEvents; internalIdentity; constructor(executeProcessService, processDefinitionMediator, processTokenService, identityService, flowNodeInstanceAdapter, processInstanceAdapter) { this.executeProcessService = executeProcessService; this.internalIdentity = identityService.getInternalIdentity(); this.logger = new processcube_engine_sdk_1.Logger('message_event_service'); this.processDefinitionMediator = processDefinitionMediator; this.flowNodeInstanceAdapter = flowNodeInstanceAdapter; this.processTokenService = processTokenService; this.processInstanceAdapter = processInstanceAdapter; this.subscriptions = {}; this.deployedMessageCatchEvents = {}; } async init() { this.internalSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.processDeployed, async (message) => { try { await this.refreshCatchEventDefinitions(); } finally { EventAggregator_1.EventAggregator.acknowledgeEvent(message?.eventId); } })); this.internalSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(index_1.eventAggregatorSettings.messagePaths.processUndeployed, async (message) => { try { await this.refreshCatchEventDefinitions(); } finally { EventAggregator_1.EventAggregator.acknowledgeEvent(message?.eventId); } })); await this.refreshCatchEventDefinitions(); } dispose() { this.internalSubscriptions.forEach((sub) => EventAggregator_1.EventAggregator.unsubscribe(sub)); Object.keys(this.subscriptions).forEach((key) => this.subscriptions[key].forEach((sub) => EventAggregator_1.EventAggregator.unsubscribe(sub))); this.internalSubscriptions = []; this.subscriptions = {}; } async throwMessage(data, awaitAcknowledgement) { this.logger.debug(`Posting message "${data.messageReference}" (Event ID: ${data.eventId}) to channel "${data.messageChannel}"`, { awaitAcknowledgement: awaitAcknowledgement ?? false, }); const triggerValues = await this.computeTriggerValuesForMessageReference(data.messageReference, data.currentToken); const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('MessageEventService.throwMessage', { processInstanceIdSet: data.processInstanceId !== undefined, triggerValuesSet: triggerValues !== undefined, correlationIdSet: data.correlationId !== undefined, customCorrelationIdSet: data.customCorrelationId !== undefined, processDefinitionId: data.processDefinitionId, }); const baseEvent = index_1.eventAggregatorSettings.messagePaths.messageEventTriggeredOnPath; const publishEventPromises = []; for (const triggerValue of triggerValues) { const message = { ...data, triggerValue: triggerValue, }; if (data.targetProcessInstanceId == undefined) { this.logger.trace(`Received message "${data.messageReference}".`); publishEventPromises.push(this.startProcessesWithMessageStartEvent(message.messageReference, { messageReference: message.messageReference, flowNodeInstanceId: message.flowNodeInstanceId, customCorrelationId: message.customCorrelationId, sourceCorrelationId: message.correlationId, token: message.currentToken, identity: message.identity, triggerValue: message.triggerValue, messageChannel: message.messageChannel, })); } const messageEventName = baseEvent .replace(index_1.eventAggregatorSettings.messageParams.triggerValueReference, message.triggerValue) .replace(index_1.eventAggregatorSettings.messageParams.messageReference, message.messageReference ?? '') .replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, message.targetProcessInstanceId ?? ''); publishEventPromises.push(new Promise((resolve) => EventAggregator_1.EventAggregator.publish(messageEventName, message, resolve))); } EventAggregator_1.EventAggregator.publish(index_1.eventAggregatorSettings.messagePaths.messageTriggered, data); if (awaitAcknowledgement) { await Promise.all(publishEventPromises); } this.logger.debug(`Finished posting message "${data.messageReference}" (Event ID: ${data.eventId}) to channel "${data.messageChannel}"`, { awaitAcknowledgement: awaitAcknowledgement ?? false, }); functionReporter.finish(); } async throwStartMessage(data, awaitAcknowledgement) { this.logger.debug(`Triggering Message Start Events with message "${data.messageReference}" (Event ID: ${data.eventId}) on channel "${data.messageChannel}"`, { awaitAcknowledgement: awaitAcknowledgement ?? false, }); const triggerValues = await this.computeTriggerValuesForMessageReference(data.messageReference, data.currentToken); const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('MessageEventService.throwStartMessage', { processInstanceIdSet: data.processInstanceId !== undefined, triggerValuesSet: triggerValues !== undefined, correlationIdSet: data.correlationId !== undefined, customCorrelationIdSet: data.customCorrelationId !== undefined, processDefinitionId: data.processDefinitionId, }); const publishEventPromises = []; for (const triggerValue of triggerValues) { const message = { ...data, triggerValue: triggerValue, }; this.logger.trace(`Received message "${message.messageReference}".`); publishEventPromises.push(this.startProcessesWithMessageStartEvent(message.messageReference, { messageReference: message.messageReference, flowNodeInstanceId: message.flowNodeInstanceId, customCorrelationId: message.customCorrelationId, sourceCorrelationId: message.correlationId, token: message.currentToken, identity: message.identity, triggerValue: message.triggerValue, messageChannel: message.messageChannel, })); } if (awaitAcknowledgement) { await Promise.all(publishEventPromises); } this.logger.debug(`Finished triggering Message Start Events with message "${data.messageReference}" (Event ID: ${data.eventId}) on channel "${data.messageChannel}"`, { awaitAcknowledgement: awaitAcknowledgement ?? false, }); functionReporter.finish(); } onMessage(messageReference, callback, subscribeOnce, processInstanceId, triggerValue, messageChannel) { const subscriptionId = uuid.v4(); const baseEvent = index_1.eventAggregatorSettings.messagePaths.messageEventTriggeredOnPath; const scopedSubscriptions = []; const messageEventForProcessInstance = baseEvent .replace(index_1.eventAggregatorSettings.messageParams.triggerValueReference, triggerValue ?? '') .replace(index_1.eventAggregatorSettings.messageParams.messageReference, messageReference ?? '') .replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, processInstanceId ?? ''); const messageEventForBroadcast = baseEvent .replace(index_1.eventAggregatorSettings.messageParams.triggerValueReference, triggerValue ?? '') .replace(index_1.eventAggregatorSettings.messageParams.messageReference, messageReference ?? '') .replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, ''); const onMessage = async (message) => { this.logger.debug(`Received Message "${message.messageReference}" through internal event bus (Event ID: ${message.eventId}) on channel "${message.messageChannel}"`); if (subscribeOnce) { this.unsubscribe(subscriptionId); } const oneChannelIsUndefined = messageChannel === undefined || message.messageChannel === undefined; const channelsMatch = message.messageChannel === messageChannel; if (oneChannelIsUndefined || channelsMatch) { await callback(message); } else { this.acknowledge(message.eventId); } }; scopedSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(messageEventForProcessInstance, onMessage)); scopedSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(messageEventForBroadcast, onMessage)); this.subscriptions[subscriptionId] = scopedSubscriptions; return subscriptionId; } unsubscribe(subscriptionId) { if (!subscriptionId || !this.subscriptions[subscriptionId]) { return; } for (const sub of this.subscriptions[subscriptionId]) { EventAggregator_1.EventAggregator.unsubscribe(sub); } delete this.subscriptions[subscriptionId]; } acknowledge(eventId) { EventAggregator_1.EventAggregator.acknowledgeEvent(eventId); } async startProcessesWithMessageStartEvent(eventName, args) { const identityToUse = args.identity ?? this.internalIdentity; this.logger.debug(`Starting Process Instances with matching Message Start Event: Name: ${eventName}, Channel: ${args.messageChannel}`); const eventsToStart = await this.getEventsToStart(eventName, args); this.logger.debug(`Found a grand total of ${eventsToStart.length} matching Message Start Events for message ${eventName}.`); for (const event of eventsToStart) { if (event.processModel.isSingleton) { const instancesRunning = await this.processInstanceAdapter.countRunningInstancesForModel(event.processModel.id); if (instancesRunning > 0) { this.logger.debug(`Skipping Start of new instance for process model \`${event.processModel.id}\`, since it is a Singleton and already running in instance \`${instancesRunning[0]}\`.`); continue; } } this.logger.debug(`Starting new ProcessInstance for ProcessModel ${event.processModel.id} from Message Start Event ${event.flowNode.id}`); await this.executeProcessService.start(identityToUse, { processModelId: event.processModel.id, startEventId: event.flowNode.id, initialToken: args.token, correlationId: args.customCorrelationId || args.sourceCorrelationId, triggeredByFlowNodeInstanceId: args.flowNodeInstanceId, }); this.logger.info(`Started new ProcessInstance for ProcessModel ${event.processModel.id} from Start Event ${event.flowNode.id}`); } this.logger.trace(`Finished starting ${eventsToStart.length} Process Instances for Message "${eventName}".`); } async getEventsToStart(eventName, args) { const cachedEvents = this.getCachedEvents(eventName); if (!eventName || !cachedEvents) { return []; } const startableMessageEvents = cachedEvents.filter((event) => event.flowNode.bpmnType === processcube_engine_sdk_1.BpmnType.startEvent && event.processModel.isExecutable); this.logger.debug(`Found ${startableMessageEvents.length} ProcessModels with matching Message Start Events "${eventName}". Filtering matching channels and trigger values.`); const matchingStartEvents = startableMessageEvents.filter((event) => { const startEvent = event.flowNode; const triggerValuesMatch = (!startEvent.triggerValueInToken && !args.triggerValue) || startEvent.triggerValueInToken == args.triggerValue; const channelsMatch = startEvent.messageChannel === args.messageChannel; const channelsAreUndefinedOrInternal = (startEvent.messageChannel == undefined || startEvent.messageChannel === 'internal') && (args.messageChannel == undefined || args.messageChannel === 'internal'); return triggerValuesMatch && (channelsMatch || channelsAreUndefinedOrInternal); }); this.logger.debug(`Filtering Messages that are already awaited by a matching catch event in the same process model...`); const startableEvents = await this.filterProcessesWithWaitingCatchEvents(eventName, args.token, matchingStartEvents); if (args.flowNodeInstanceId) { this.logger.trace(`Filtering already started process intances...`); return this.filterAlreadyStartedProcessInstances(startableEvents, eventName, args.flowNodeInstanceId); } return startableEvents; } async filterProcessesWithWaitingCatchEvents(eventName, eventPayload, processModels) { const processesWithoutWaitingMessageCatchEvents = []; // The Spec states that Processes with Message Start Events must not be triggered, // if a Message Catch Event with a matching Correlation is already waiting for that same message. // If a Catch Event is waiting, but the Correlation does not match, then the Event is passed on to the Start Event. for (const processModel of processModels) { const activeMessageCatchEvents = await this.flowNodeInstanceAdapter.getActiveMessageCatchEventsInProcessModel(processModel.processModel.id, eventName); if (activeMessageCatchEvents.length === 0) { processesWithoutWaitingMessageCatchEvents.push(processModel); continue; } let catchEventInstanceIsBlocking = false; for (const catchEventInstance of activeMessageCatchEvents) { const catchEventModel = processModel.processModel.flowNodes.find((flowNode) => flowNode.id === catchEventInstance.flowNodeId); if (!catchEventModel.triggerValueInEventPayload) { catchEventInstanceIsBlocking = true; break; } const requriedCatchEventPayload = await this.parseEventPayloadTriggerValue(catchEventModel.triggerValueInEventPayload, eventPayload); if (requriedCatchEventPayload === catchEventInstance.triggerValue) { catchEventInstanceIsBlocking = true; break; } } if (!catchEventInstanceIsBlocking) { processesWithoutWaitingMessageCatchEvents.push(processModel); } } return processesWithoutWaitingMessageCatchEvents; } // Only relevant for resuming Process Instances. // When resuming a still running Throw Event, we need to make sure that all receiving Events have actually been triggered. // For Start Events, this means checking if a new Process Instance was already started. async filterAlreadyStartedProcessInstances(events, eventName, sourceFlowNodeInstanceId) { const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('MessageEventService.filterAlreadyStartedProcessInstances', { events: events, eventName: eventName, sourceFlowNodeInstanceIdSet: sourceFlowNodeInstanceId !== undefined, }); const queryResults = await this.processInstanceAdapter.query({ triggeredByFlowNodeInstance: sourceFlowNodeInstanceId }); const alreadyStartedProcessInstances = queryResults.processInstances; if (alreadyStartedProcessInstances.length === 0) { functionReporter.finish(); return events; } this.logger.trace(`${alreadyStartedProcessInstances.length} ProcessInstances have already been started for event ${eventName} from Flow Node Instance ${sourceFlowNodeInstanceId}.`); const processModelsToExecute = []; for (const event of events) { const matchingProcessInstances = alreadyStartedProcessInstances.filter((processInstance) => processInstance.processModelId === event.processModel.id); if (matchingProcessInstances.some((processInstance) => processInstance.startEventId === event.flowNode.id)) { continue; } processModelsToExecute.push(event); } if (processModelsToExecute.length === 0) { this.logger.trace(`All processes for Event ${eventName} from Flow Node Instance ${sourceFlowNodeInstanceId} have already been started.`); } else { this.logger.trace(`Found ${processModelsToExecute} Process Models for event ${eventName} from Flow Node Instance ${sourceFlowNodeInstanceId} that have yet to be started.`); } functionReporter.finish(); return processModelsToExecute; } async parseEventPayloadTriggerValue(expression, payload) { return this.processTokenService.executeRuntimeExpression(`\`${expression}\``, { additionalProperties: { eventPayload: payload, }, allowNonObjectResults: true, }); } async refreshCatchEventDefinitions() { await asyncLocker.acquire('access-message-event-cache', async () => { this.logger.debug('Start Refreshing internal Message Event cache'); const catchEventDefinitions = await this.getMessageCatchEventDefinitions(); this.logger.debug(`Found a grand total of ${catchEventDefinitions.length} unique deployed Message Catch Events and Receive Tasks`); this.deployedMessageCatchEvents = {}; for (const catchEventDefinition of catchEventDefinitions) { this.logger.trace(`Adding Flow Node ${catchEventDefinition.flowNode.name} to internal message event cache.`); if (!this.deployedMessageCatchEvents[catchEventDefinition.flowNode.messageEventDefinition.name]) { this.deployedMessageCatchEvents[catchEventDefinition.flowNode.messageEventDefinition.name] = []; } this.deployedMessageCatchEvents[catchEventDefinition.flowNode.messageEventDefinition.name].push(catchEventDefinition); } this.logger.debug('Refreshing internal Message Event cache finished successfully.'); }); } async getMessageCatchEventDefinitions() { const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('MonitoringManager.MessageEventService.getProcessesWithEventDefinitions'); const isMessageCatchEvent = (flowNode) => flowNode.messageEventDefinition != null && flowNode.eventType === processcube_engine_sdk_1.EventType.messageEvent && (flowNode.bpmnType === processcube_engine_sdk_1.BpmnType.receiveTask || flowNode.bpmnType === processcube_engine_sdk_1.BpmnType.intermediateCatchEvent || flowNode.bpmnType === processcube_engine_sdk_1.BpmnType.boundaryEvent || flowNode.bpmnType === processcube_engine_sdk_1.BpmnType.startEvent); const uniqueMessageEvents = (value, index, self) => { return self.findIndex((event) => messageDefinitionsMatch(event, value)) === index; }; const messageDefinitionsMatch = (first, second) => { return (first.flowNode.id === second.flowNode.id && first.flowNode.name === second.flowNode.name && first.flowNode.bpmnType === second.flowNode.bpmnType && first.flowNode.triggerValueInEventPayload === second.flowNode.triggerValueInEventPayload && first.flowNode.messageEventDefinition.id === second.flowNode.messageEventDefinition.id && first.flowNode.messageEventDefinition.name === second.flowNode.messageEventDefinition.name); }; const runningDefinitions = await this.processDefinitionMediator.getDefinitionsWithRunningInstances(this.internalIdentity); const deployedDefinitions = await this.processDefinitionMediator.getAll(this.internalIdentity); const catchEventsFromRunningModels = runningDefinitions .flatMap((definition) => definition.processes) .filter((processModel) => processModel.flowNodes.some(isMessageCatchEvent)) .flatMap((processModel) => processModel.flowNodes .filter((flowNode) => isMessageCatchEvent(flowNode) && flowNode.bpmnType !== processcube_engine_sdk_1.BpmnType.startEvent) .map((flowNode) => { return { flowNode: flowNode, processModel: processModel, }; })); const deployedEventDefinitions = deployedDefinitions .flatMap((definition) => definition.processes) .filter((process) => process.flowNodes.some(isMessageCatchEvent)) .flatMap((processModel) => processModel.flowNodes.filter(isMessageCatchEvent).map((flowNode) => { return { flowNode: flowNode, processModel: processModel, }; })); functionReporter.finish(); const allDefinitions = [...catchEventsFromRunningModels, ...deployedEventDefinitions].filter(uniqueMessageEvents); return allDefinitions; } async computeTriggerValuesForMessageReference(messageReference, payload) { const definitions = this.getCachedEvents(messageReference); if (!definitions) { return ['']; } return this.parseTriggerValueExpressions(definitions, payload); } async parseTriggerValueExpressions(definitions, payload) { const triggerValueExpressions = definitions .map((definition) => definition.flowNode.triggerValueInEventPayload) .filter((triggerValue) => !!triggerValue) .concat(['']) .filter(this.distinctTriggerValues); const triggerValues = await Promise.all(triggerValueExpressions.map(async (expression) => { return this.processTokenService.executeRuntimeExpression(`\`${expression}\``, { additionalProperties: { eventPayload: payload, }, allowNonObjectResults: true, }); })); return triggerValues; } getCachedEvents(message) { return this.deployedMessageCatchEvents[message]; } distinctTriggerValues(value, index, self) { return self.indexOf(value) === index; } }; exports.MessageEventService = MessageEventService; exports.MessageEventService = MessageEventService = __decorate([ (0, inversify_1.injectable)(), __param(0, (0, inversify_1.inject)(index_1.IocRegistrationKeys.core.services.ExecuteProcessService)), __param(1, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.ProcessDefinitionMediator)), __param(2, (0, inversify_1.inject)(index_1.IocRegistrationKeys.core.services.ProcessTokenService)), __param(3, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.IdentityService)), __param(4, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.FlowNodeInstanceDatabaseAdapter)), __param(5, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.ProcessInstanceDatabaseAdapter)), __metadata("design:paramtypes", [ExecuteProcessService_1.ExecuteProcessService, Tools_1.ProcessDefinitionMediator, ProcessTokenService_1.ProcessTokenService, Tools_1.IdentityService, Tools_1.FlowNodeInstanceDatabaseAdapter, Tools_1.ProcessInstanceDatabaseAdapter]) ], MessageEventService); //# sourceMappingURL=MessageEventService.js.map