UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

417 lines • 24.7 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.SignalEventService = 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 ProcessDefinitionMediator_1 = require("../Tools/ProcessDefinitionMediator"); const ExecuteProcessService_1 = require("./ExecuteProcessService"); const ProcessTokenService_1 = require("./ProcessTokenService"); const asyncLocker = new async_lock_1.default({ maxPending: 1000000 }); let SignalEventService = class SignalEventService { executeProcessService; processDefinitionMediator; processInstanceAdapter; processTokenService; logger; subscriptions; internalSubscriptions = []; deployedSignalCatchEvents; internalIdentity; constructor(processDefinitionMediator, executeProcessService, identityService, processTokenService, processInstanceAdapter) { this.executeProcessService = executeProcessService; this.internalIdentity = identityService.getInternalIdentity(); this.logger = new processcube_engine_sdk_1.Logger('signal_event_service'); this.processDefinitionMediator = processDefinitionMediator; this.processInstanceAdapter = processInstanceAdapter; this.processTokenService = processTokenService; this.subscriptions = {}; this.deployedSignalCatchEvents = {}; } 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 throwSignal(data, awaitAcknowledgement, eventId) { this.logger.debug(`Posting signal "${data.signalReference}" (Event ID: ${data.eventId}) to channel "${data.signalChannel}"`, { awaitAcknowledgement: awaitAcknowledgement ?? false, }); const triggerValues = await this.computeTriggerValuesForSignalReference(data.signalReference, data.currentToken); const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('SignalEventService.throwSignal', { processInstanceIdSet: data.processInstanceId !== undefined, triggerValuesSet: triggerValues !== undefined, correlationIdSet: data.correlationId !== undefined, customCorrelationIdSet: data.customCorrelationId !== undefined, processDefinitionId: data.processDefinitionId, }); const baseEvent = index_1.eventAggregatorSettings.messagePaths.signalEventTriggeredOnPath; const publishEventPromises = []; for (const triggerValue of triggerValues) { const signal = { ...data, triggerValue: triggerValue, }; if (signal.targetProcessInstanceId == undefined) { this.logger.trace(`Received signal "${signal.signalReference}".`); publishEventPromises.push(this.startProcessesWithSignalStartEvent(signal.signalReference, { signalReference: signal.signalReference, flowNodeInstanceId: signal.flowNodeInstanceId, customCorrelationId: signal.customCorrelationId, sourceCorrelationId: signal.correlationId, token: signal.currentToken, identity: signal.identity, triggerValue: signal.triggerValue, signalChannel: signal.signalChannel, })); } const signalEventName = baseEvent .replace(index_1.eventAggregatorSettings.messageParams.triggerValueReference, signal.triggerValue) .replace(index_1.eventAggregatorSettings.messageParams.signalReference, signal.signalReference ?? '') .replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, signal.targetProcessInstanceId ?? ''); publishEventPromises.push(new Promise((resolve) => EventAggregator_1.EventAggregator.publish(signalEventName, signal, resolve, eventId))); } EventAggregator_1.EventAggregator.publish(index_1.eventAggregatorSettings.messagePaths.signalTriggered, data); if (awaitAcknowledgement) { await Promise.all(publishEventPromises); } this.logger.debug(`Finished posting signal "${data.signalReference}" (Event ID: ${data.eventId}) to channel "${data.signalChannel}"`, { awaitAcknowledgement: awaitAcknowledgement ?? false, }); functionReporter.finish(); } async throwStartSignal(data, awaitAcknowledgement) { this.logger.debug(`Triggering Signal Start Events with Signal "${data.signalReference}" (Event ID: ${data.eventId}) on channel "${data.signalChannel}"`, { awaitAcknowledgement: awaitAcknowledgement ?? false, }); const triggerValues = await this.computeTriggerValuesForSignalReference(data.signalReference, data.currentToken); const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('SignalEventService.throwStartSignal', { 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 signal = { ...data, triggerValue: triggerValue, }; this.logger.trace(`Received signal "${signal.signalReference}".`); publishEventPromises.push(this.startProcessesWithSignalStartEvent(signal.signalReference, { signalReference: signal.signalReference, flowNodeInstanceId: signal.flowNodeInstanceId, customCorrelationId: signal.customCorrelationId, sourceCorrelationId: signal.correlationId, token: signal.currentToken, identity: signal.identity, triggerValue: signal.triggerValue, signalChannel: signal.signalChannel, })); } if (awaitAcknowledgement) { await Promise.all(publishEventPromises); } this.logger.debug(`Finished triggering Signal Start Events with Signal "${data.signalReference}" (Event ID: ${data.eventId}) on channel "${data.signalChannel}"`, { awaitAcknowledgement: awaitAcknowledgement ?? false, }); functionReporter.finish(); } onSignal(signalReference, callback, subscribeOnce, processInstanceId, triggerValue, signalChannel) { const subscriptionId = uuid.v4(); const baseEvent = index_1.eventAggregatorSettings.messagePaths.signalEventTriggeredOnPath; const scopedSubscriptions = []; const signalEventForProcessInstance = baseEvent .replace(index_1.eventAggregatorSettings.messageParams.triggerValueReference, triggerValue ?? '') .replace(index_1.eventAggregatorSettings.messageParams.signalReference, signalReference ?? '') .replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, processInstanceId ?? ''); const signalEventForBroadcast = baseEvent .replace(index_1.eventAggregatorSettings.messageParams.triggerValueReference, triggerValue ?? '') .replace(index_1.eventAggregatorSettings.messageParams.signalReference, signalReference ?? '') .replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, ''); const onSignal = async (signal) => { this.logger.debug(`Received Signal "${signal.signalReference}" through internal event bus (Event ID: ${signal.eventId}) on channel "${signal.signalChannel}"`); if (subscribeOnce) { this.unsubscribe(subscriptionId); } const oneChannelIsUndefined = signalChannel === undefined || signal.signalChannel === undefined; const channelsMatch = signal.signalChannel === signalChannel; if (oneChannelIsUndefined || channelsMatch) { await callback(signal); } else { this.acknowledge(signal.eventId); } }; scopedSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(signalEventForProcessInstance, onSignal)); scopedSubscriptions.push(EventAggregator_1.EventAggregator.subscribe(signalEventForBroadcast, onSignal)); 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 startProcessesWithSignalStartEvent(eventName, args) { const identityToUse = args.identity ?? this.internalIdentity; this.logger.debug(`Starting Process Instances with matching Signal Start Event: Name: ${eventName}, Channel: ${args.signalChannel}`); const eventsToStart = await this.getEventsToStart(eventName, args); this.logger.debug(`Found a grand total of ${eventsToStart.length} matching Signal Start Events for Signal ${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 Signal 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 Signal "${eventName}".`); } async getEventsToStart(eventName, args) { const cachedEvents = this.getCachedEvents(eventName); if (!eventName || !cachedEvents) { return []; } const startableSignalEvents = cachedEvents.filter((event) => event.flowNode.bpmnType === processcube_engine_sdk_1.BpmnType.startEvent && event.processModel.isExecutable); this.logger.debug(`Found ${startableSignalEvents.length} ProcessModels with matching Signal Start Events "${eventName}". Filtering matching channels and trigger values.`); const matchingStartEvents = startableSignalEvents.filter((event) => { const startEvent = event.flowNode; const triggerValuesMatch = (!startEvent.triggerValueInToken && !args.triggerValue) || startEvent.triggerValueInToken == args.triggerValue; const channelsMatch = startEvent.signalChannel === args.signalChannel; const channelsAreUndefinedOrInternal = (startEvent.signalChannel == undefined || startEvent.signalChannel === 'internal') && (args.signalChannel == undefined || args.signalChannel === 'internal'); return triggerValuesMatch && (channelsMatch || channelsAreUndefinedOrInternal); }); if (!args.flowNodeInstanceId) { return matchingStartEvents; } this.logger.trace(`Filtering already started process intances...`); return await this.filterAlreadyStartedProcessInstances(matchingStartEvents, eventName, args.flowNodeInstanceId); } // 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('SignalEventService.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 refreshCatchEventDefinitions() { await asyncLocker.acquire('access-signal-event-cache', async () => { this.logger.debug('Start Refreshing internal Signal Event cache'); const catchEventDefinitions = await this.getSignalCatchEventDefinitions(); this.logger.debug(`Found a grand total of ${catchEventDefinitions.length} unique deployed Signal Catch Events`); this.deployedSignalCatchEvents = {}; for (const catchEventDefinition of catchEventDefinitions) { this.logger.trace(`Adding Flow Node ${catchEventDefinition.flowNode.name} to internal signal event cache.`); if (!this.deployedSignalCatchEvents[catchEventDefinition.flowNode.signalEventDefinition.name]) { this.deployedSignalCatchEvents[catchEventDefinition.flowNode.signalEventDefinition.name] = []; } this.deployedSignalCatchEvents[catchEventDefinition.flowNode.signalEventDefinition.name].push(catchEventDefinition); } this.logger.debug('Refreshing internal Signal Event cache finished successfully.'); }); } async getSignalCatchEventDefinitions() { const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('MonitoringManager.SignalEventService.getProcessesWithEventDefinitions'); const isSignalCatchEvent = (flowNode) => flowNode.signalEventDefinition != null && flowNode.eventType === processcube_engine_sdk_1.EventType.signalEvent && (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 uniqueSignalEvents = (value, index, self) => { return self.findIndex((event) => signalDefinitionsMatch(event, value)) === index; }; const signalDefinitionsMatch = (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.signalEventDefinition.id === second.flowNode.signalEventDefinition.id && first.flowNode.signalEventDefinition.name === second.flowNode.signalEventDefinition.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(isSignalCatchEvent)) .flatMap((processModel) => processModel.flowNodes .filter((flowNode) => isSignalCatchEvent(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(isSignalCatchEvent)) .flatMap((processModel) => processModel.flowNodes.filter(isSignalCatchEvent).map((flowNode) => { return { flowNode: flowNode, processModel: processModel, }; })); functionReporter.finish(); const allDefinitions = [...catchEventsFromRunningModels, ...deployedEventDefinitions].filter(uniqueSignalEvents); return allDefinitions; } async computeTriggerValuesForSignalReference(signalReference, payload) { const definitions = this.getCachedEvents(signalReference); 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.deployedSignalCatchEvents[message]; } distinctTriggerValues(value, index, self) { return self.indexOf(value) === index; } }; exports.SignalEventService = SignalEventService; exports.SignalEventService = SignalEventService = __decorate([ (0, inversify_1.injectable)(), __param(0, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.ProcessDefinitionMediator)), __param(1, (0, inversify_1.inject)(index_1.IocRegistrationKeys.core.services.ExecuteProcessService)), __param(2, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.IdentityService)), __param(3, (0, inversify_1.inject)(index_1.IocRegistrationKeys.core.services.ProcessTokenService)), __param(4, (0, inversify_1.inject)(index_1.IocRegistrationKeys.internal.ProcessInstanceDatabaseAdapter)), __metadata("design:paramtypes", [ProcessDefinitionMediator_1.ProcessDefinitionMediator, ExecuteProcessService_1.ExecuteProcessService, Tools_1.IdentityService, ProcessTokenService_1.ProcessTokenService, Tools_1.ProcessInstanceDatabaseAdapter]) ], SignalEventService); //# sourceMappingURL=SignalEventService.js.map