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