@5minds/processcube_engine
Version:
The ProcessCube Engine. Stores and executes BPMNs.
417 lines • 24.7 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.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