UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

376 lines • 20.4 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.CronjobService = void 0; const cron_parser_1 = __importDefault(require("cron-parser")); const dayjs_1 = __importDefault(require("dayjs")); 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/InternalMessages/index"); const index_2 = require("../Contracts/index"); const Tools_1 = require("../Tools"); const index_3 = require("../Tools/DatabaseAdaptersSequelize/index"); const EventAggregator_1 = __importDefault(require("../Tools/EventAggregator")); const index_4 = require("../Tools/Iam/index"); const ExecuteProcessService_1 = require("./ExecuteProcessService"); const index_5 = require("./Facades/index"); let CronjobService = class CronjobService { executeProcessService; logger; cronjobAdapter; processDefinitionMediator; processInstanceAdapter; timerFacade; cronjobDictionary = {}; // This identity is used to enable the `ExecuteProcessService` to always get full ProcessModels. // It needs those in order to be able to correctly start a ProcessModel. internalIdentity; serviceIsRunning = false; notificationSubscriptions = []; constructor(cronjobAdapter, executeProcessService, identityService, processDefinitionMediator, processInstanceAdapter, timerFacade) { this.cronjobAdapter = cronjobAdapter; this.logger = new processcube_engine_sdk_1.Logger('cronjob_service'); this.executeProcessService = executeProcessService; this.processDefinitionMediator = processDefinitionMediator; this.processInstanceAdapter = processInstanceAdapter; this.timerFacade = timerFacade; this.internalIdentity = identityService.getInternalIdentity(); } get isRunning() { return this.serviceIsRunning; } async start() { if (this.isRunning) { return; } this.logger.trace('Subscribing to process deployment notifications'); const onProcessDeployedNotification = await this.onProcessDeployed(async (message) => { this.logger.trace('Received ProcessDeployed notification. Updating cronjob list...'); try { const deployedProcessDefinitions = Array.isArray(message.processDefinitionId) ? message.processDefinitionId : [message.processDefinitionId]; await this.updateCronjobs(deployedProcessDefinitions, message.cronjobs); } finally { EventAggregator_1.default.acknowledgeEvent(message.eventId); } }); const onProcessUndeployedNotification = await this.onProcessUndeployed(async (message) => { this.logger.trace('Received ProcessUndeployed notification. Updating cronjob list...'); try { await this.removeCronjobsByProcessDefinition(message.processDefinitionId); } finally { EventAggregator_1.default.acknowledgeEvent(message.eventId); } }); this.notificationSubscriptions.push(onProcessDeployedNotification); this.notificationSubscriptions.push(onProcessUndeployedNotification); this.logger.trace('Creating Cronjobs from deployed process models'); const cronjobs = await this.cronjobAdapter.getActiveCronjobs(); this.logger.trace(`Found ${cronjobs.length} active cycle start events.`); for (const cronjob of cronjobs) { try { await this.initializeCronjob(cronjob); } catch (error) { this.logger.error(`Could not schedule cronjob for StartEvent '${cronjob.startEventId}' of ProcessModel '${cronjob.processModelId}':`, { err: error }); } } const processDefinitionIds = cronjobs.map((cronjob) => cronjob.processDefinitionId).filter((value, index, self) => self.indexOf(value) === index); for (const processDefinitionId of processDefinitionIds) { const eventMessage = this.getEventMessage(processDefinitionId); this.logger.trace('Publishing CronjobCreated Message with payload', { eventMessage: eventMessage, }); EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.cronjobCreated, eventMessage); } this.serviceIsRunning = true; this.logger.trace('Done.'); } dispose() { this.stop(); } stop() { if (!this.isRunning) { return; } this.logger.trace('Removing ProcessDeployment notifications'); for (const subscription of this.notificationSubscriptions) { this.removeSubscription(subscription); } this.notificationSubscriptions = []; this.logger.trace('Stopping all currently running cronjobs...'); const processDefinitionIds = Object.keys(this.cronjobDictionary); for (const processDefinitionId of processDefinitionIds) { const eventMessage = this.getEventMessage(processDefinitionId); this.stopCronjobs(processDefinitionId); this.logger.trace('Publishing CronjobStopped Message with payload', { eventMessage: eventMessage, }); EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.cronjobStopped, eventMessage); } this.serviceIsRunning = false; this.logger.trace('Done.'); } async updateCronjobs(deployedProcessDefinitionIds, cronjobMap) { const cronjobs = Object.values(cronjobMap).flat(); await index_3.CronjobDatabaseAdapter.deleteByProcessDefinitionId(deployedProcessDefinitionIds); for (const cronjob of cronjobs) { const nextTriggerDate = cronjob.enabled ? cron_parser_1.default.parse(cronjob.crontab).next().toDate() : null; await this.cronjobAdapter.create(cronjob.processDefinitionId, cronjob.processModelId, cronjob.startEventId, cronjob.enabled, cronjob.crontab, nextTriggerDate); } if (!this.isRunning) { return; } for (const processDefinitionId of deployedProcessDefinitionIds) { const previousCronjobs = this.cronjobDictionary[processDefinitionId]; const newCronjobs = cronjobMap[processDefinitionId].filter((cronjob) => cronjob.enabled); const noNewCronjobs = newCronjobs.length === 0; if (noNewCronjobs) { if (!previousCronjobs) { continue; } this.logger.trace(`ProcessDefinition ${processDefinitionId} no longer contains any active cronjobs. Removing all active jobs for that ProcessModel...`); const eventMessage = this.getEventMessage(processDefinitionId); this.stopCronjobs(processDefinitionId); this.cronjobDictionary[processDefinitionId] = []; this.logger.trace('Publishing CronjobStopped Message with payload', { eventMessage: eventMessage, }); EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.cronjobStopped, eventMessage); this.logger.trace('Done.'); continue; } if (previousCronjobs) { this.stopCronjobs(processDefinitionId); this.cronjobDictionary[processDefinitionId] = []; } this.logger.trace(`Creating or updating cronjobs for ProcessDefinition ${processDefinitionId}...`); for (const cronjob of newCronjobs) { await this.initializeCronjob(cronjob); } const eventToPublish = previousCronjobs ? index_1.eventAggregatorSettings.messagePaths.cronjobUpdated : index_1.eventAggregatorSettings.messagePaths.cronjobCreated; const eventMessage = this.getEventMessage(processDefinitionId); this.logger.trace(`Publishing ${eventToPublish} Message with payload`, { eventMessage: eventMessage, }); EventAggregator_1.default.publish(eventToPublish, eventMessage); this.logger.trace('Done. New Cronjobs for ProcessDefinition: ', { cronjobs: this.cronjobDictionary[processDefinitionId].map((entry) => { return { cronjob: entry.cronjob, processModelId: entry.processModelId, startEventId: entry.startEventId, }; }), }); } } async updateCronjob(options) { const cronjob = await this.cronjobAdapter.getCronjob(options.processModelId, options.flowNodeId); const cronjobConfigForProcessDefinition = this.cronjobDictionary[cronjob.processDefinitionId]; if (!cronjobConfigForProcessDefinition && !options.enabled) { return; } const nextTriggerDate = options.enabled ? cron_parser_1.default.parse(cronjob.crontab).next().toDate() : null; if (cronjobConfigForProcessDefinition) { this.stopCronjob(cronjob.processDefinitionId, options.flowNodeId); } await this.cronjobAdapter.update(options.processModelId, options.flowNodeId, options.enabled, cronjob.crontab, nextTriggerDate); if (!options.enabled) { return; } await this.initializeCronjob(cronjob); } async removeCronjobsByProcessDefinition(processDefinitionId) { if (!this.isRunning || !this.cronjobDictionary[processDefinitionId]) { return; } this.logger.trace(`Removing cronjobs for ProcessDefinition ${processDefinitionId}...`); const eventMessage = this.getEventMessage(processDefinitionId); this.stopCronjobs(processDefinitionId); this.logger.trace('Publishing CronjobRemoved Message with payload', { eventMessage: eventMessage, }); EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.cronjobStopped, eventMessage); EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.cronjobRemoved, eventMessage); this.logger.trace('Done.'); } async initializeCronjob(cronjob) { this.logger.debug(`Creating Cronjob Subscription for StartEvent ${cronjob.startEventId} in Process Definition ${cronjob.processDefinitionId}`); if (!this.cronjobDictionary[cronjob.processDefinitionId]) { this.cronjobDictionary[cronjob.processDefinitionId] = []; } this.logger.debug(`Initializing cronjob on Start Event ${cronjob.startEventId} in ProcessDefinition '${cronjob.processDefinitionId}'`); const crontab = cronjob.crontab; const crontabIsInvalid = !this.isValidCrontab(crontab); if (crontabIsInvalid) { throw new processcube_engine_sdk_1.InternalServerError(`Crontab '${crontab}' on TimerStartEvent '${cronjob.startEventId}' in ProcessDefinition '${cronjob.processDefinitionId}' is invalid!`, 'process'); } const onCronjobExpired = async (expiredCronjob, processDefinitionId, startEventId) => { this.logger.debug(`A Cronjob for ProcessDefinition ${processDefinitionId} has expired: `, { expiredCronjob: expiredCronjob, processDefinitionId: processDefinitionId, startEventId: startEventId, }); await this.executeProcessModelWithCronjob(expiredCronjob, processDefinitionId); }; const model = (await this.processDefinitionMediator.getByProcessModelId(this.internalIdentity, cronjob.processModelId)).processes.find((process) => process.id === cronjob.processModelId); this.logger.trace(`Subscribing to Cronjob Expiration for Start Event ${cronjob.startEventId} in ProcessDefinition '${cronjob.processDefinitionId}'`); const timerSubscription = this.timerFacade.initializeTimer(cronjob.startEventId, processcube_engine_sdk_1.Model.Events.TimerType.timeCycle, cronjob.crontab, onCronjobExpired.bind(this, crontab, cronjob.processDefinitionId, cronjob.startEventId)); const newCronJobConfig = { subscription: timerSubscription, processModelId: cronjob.processModelId, startEventId: cronjob.startEventId, isSingleton: model?.isSingleton ?? false, cronjob: crontab, }; this.cronjobDictionary[cronjob.processDefinitionId].push(newCronJobConfig); } isValidCrontab(crontab) { try { cron_parser_1.default.parse(crontab); return true; } catch (error) { return false; } } async executeProcessModelWithCronjob(crontab, processDefinitionId) { const matchingConfig = this.cronjobDictionary[processDefinitionId]?.find((config) => config.cronjob === crontab); const nextTriggerDate = cron_parser_1.default.parse(matchingConfig.cronjob).next().toDate(); await this.cronjobAdapter.updateTriggerDates(matchingConfig.processModelId, matchingConfig.startEventId, (0, dayjs_1.default)().toDate(), nextTriggerDate); if (matchingConfig.isSingleton) { const instancesActive = await this.processInstanceAdapter.countRunningInstancesForModel(matchingConfig.processModelId); if (instancesActive > 0) { this.logger.debug(`Skipping Start of new instance for process model \`${matchingConfig.processModelId}\`, since it is a Singleton and already running in instance \`${instancesActive[0]}\`.`); return; } } const startResult = await this.executeProcessService.start(this.internalIdentity, { processModelId: matchingConfig.processModelId, startEventId: matchingConfig.startEventId, initialToken: {}, correlationId: `cronjob_${uuid.v4()}`, }); const executedCronjobMessage = { ...this.getEventMessage(processDefinitionId, matchingConfig.startEventId), correlationId: startResult.correlationId, processInstanceId: startResult.processInstanceId, }; this.logger.trace('Publishing CronjobExecuted Message with payload', { executedCronjobMessage: executedCronjobMessage, }); EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.cronjobExecuted, executedCronjobMessage); } stopCronjobs(processDefinitionId) { const configForProcessDefinition = this.cronjobDictionary[processDefinitionId]; for (const config of configForProcessDefinition) { this.timerFacade.cancelTimerSubscription(config.subscription); } delete this.cronjobDictionary[processDefinitionId]; } stopCronjob(processDefinitionId, flowNodeId) { const configForProcessDefinition = this.cronjobDictionary[processDefinitionId]; const matchingConfig = configForProcessDefinition.find((config) => config.startEventId === flowNodeId); if (matchingConfig) { this.timerFacade.cancelTimerSubscription(matchingConfig.subscription); } this.cronjobDictionary[processDefinitionId] = configForProcessDefinition.filter((config) => config.startEventId !== flowNodeId); } getEventMessage(processDefinitionId, startEventId) { const cronjobsForProcessDefinition = this.cronjobDictionary[processDefinitionId]; const matches = startEventId ? cronjobsForProcessDefinition?.filter((entry) => startEventId === entry.startEventId) : cronjobsForProcessDefinition; const eventMessage = { processDefinitionId: processDefinitionId, cronjobs: matches.map((entry) => { return { cronjob: entry.cronjob, processModelId: entry.processModelId, startEventId: entry.startEventId, }; }), }; return eventMessage; } removeSubscription(subscription) { EventAggregator_1.default.unsubscribe(subscription); } async onProcessDeployed(callback, subscribeOnce = false) { return this.createSubscription(index_1.eventAggregatorSettings.messagePaths.processDeployed, callback, subscribeOnce); } async onProcessUndeployed(callback, subscribeOnce = false) { return this.createSubscription(index_1.eventAggregatorSettings.messagePaths.processUndeployed, callback, subscribeOnce); } createSubscription(eventName, callback, subscribeOnce) { if (subscribeOnce) { return EventAggregator_1.default.subscribeOnce(eventName, callback); } return EventAggregator_1.default.subscribe(eventName, callback); } }; exports.CronjobService = CronjobService; exports.CronjobService = CronjobService = __decorate([ (0, inversify_1.injectable)(), __param(0, (0, inversify_1.inject)(index_2.IocRegistrationKeys.internal.CronjobDatabaseAdapter)), __param(1, (0, inversify_1.inject)(index_2.IocRegistrationKeys.core.services.ExecuteProcessService)), __param(2, (0, inversify_1.inject)(index_2.IocRegistrationKeys.internal.IdentityService)), __param(3, (0, inversify_1.inject)(index_2.IocRegistrationKeys.internal.ProcessDefinitionMediator)), __param(4, (0, inversify_1.inject)(index_2.IocRegistrationKeys.internal.ProcessInstanceDatabaseAdapter)), __param(5, (0, inversify_1.inject)(index_2.IocRegistrationKeys.core.services.TimerFacade)), __metadata("design:paramtypes", [index_3.CronjobDatabaseAdapter, ExecuteProcessService_1.ExecuteProcessService, index_4.IdentityService, Tools_1.ProcessDefinitionMediator, Tools_1.ProcessInstanceDatabaseAdapter, index_5.TimerFacade]) ], CronjobService); //# sourceMappingURL=CronjobService.js.map