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