UNPKG

bpmn-elements

Version:

Executable workflow elements based on BPMN 2.0

187 lines (157 loc) 5.79 kB
import { cloneContent, shiftParent } from '../messageHelper.js'; import { ActivityError } from '../error/Errors.js'; import { ScriptCondition, ExpressionCondition } from '../condition.js'; const kExecuteMessage = Symbol.for('executeMessage'); export default function ConditionalEventDefinition(activity, eventDefinition, _context, index) { const { id, broker, environment } = activity; const { type = 'ConditionalEventDefinition', behaviour = {} } = eventDefinition; this.id = id; this.type = type; this.behaviour = behaviour; this.activity = activity; this.environment = environment; this.broker = broker; this.logger = environment.Logger(type.toLowerCase()); this.condition = this.getCondition(index); } Object.defineProperty(ConditionalEventDefinition.prototype, 'executionId', { get() { return this[kExecuteMessage]?.content.executionId; }, }); ConditionalEventDefinition.prototype.execute = function execute(executeMessage) { this[kExecuteMessage] = executeMessage; if (!this.condition) return this._setup(executeMessage); this.evaluate(executeMessage, (err, result) => { this.evaluateCallback(err, result); if (!err && !result) { this._setup(executeMessage); } }); }; ConditionalEventDefinition.prototype._setup = function setup(executeMessage) { const broker = this.broker; const executeContent = executeMessage.content; const { executionId, parent } = executeContent; const parentExecutionId = parent.executionId; const onApiMessage = this._onApiMessage.bind(this); broker.subscribeTmp('api', `activity.#.${executionId}`, onApiMessage, { noAck: true, consumerTag: `_api-${executionId}`, }); broker.subscribeTmp('api', `activity.#.${parentExecutionId}`, onApiMessage, { noAck: true, consumerTag: `_parent-signal-${executionId}`, }); broker.subscribeTmp('api', '#.signal.*', this._onDelegateApiMessage.bind(this), { noAck: true, consumerTag: `_api-delegated-${executionId}`, }); const waitContent = cloneContent(executeContent, { executionId: parentExecutionId, ...(this.condition && { condition: this.condition.type }), }); waitContent.parent = shiftParent(parent); broker.publish('event', 'activity.wait', waitContent); }; /** * Evaluate condition * @param {import('types').ElementBrokerMessage} message * @param {CallableFunction} callback */ ConditionalEventDefinition.prototype.evaluate = function evaluate(message, callback) { const condition = this.condition; if (!condition) { this._debug(`condition is empty <${condition}>`); return callback(); } condition.execute(message, callback); }; /** * Handle evaluate result or error * @param {Error|null} err Condition evaluation error * @param {any} result Result from evaluated condition, completes execution if truthy */ ConditionalEventDefinition.prototype.evaluateCallback = function evaluateCallback(err, result) { const broker = this.broker; const executeMessage = this[kExecuteMessage]; const executeContent = executeMessage.content; if (err) { return broker.publish( 'execution', 'execute.error', cloneContent(executeContent, { error: new ActivityError(err.message, executeMessage, err) }, { mandatory: true }) ); } this._debug(`condition evaluated to ${!!result}`); this.broker.publish( 'event', 'activity.condition', cloneContent(this[kExecuteMessage].content, { conditionResult: result, }) ); if (!result) return; this._stop(); return broker.publish('execution', 'execute.completed', cloneContent(executeContent, { output: result })); }; /** * Get condition * @param {number} index Eventdefinition sequence number, used to name registered script * @returns {ExpressionCondition|ScriptCondition|null} */ ConditionalEventDefinition.prototype.getCondition = function getCondition(index) { const behaviour = this.behaviour; if (behaviour.script) { const { language, body, resource } = behaviour.script; const scriptId = `${this.id}/${index}`; const script = this.environment.scripts.register({ id: scriptId, type: this.type, environment: this.environment, behaviour: { scriptFormat: language, ...(body && { script: body }), ...(resource && { resource }), }, }); if (script) { return new ScriptCondition(this, script, language); } } else if (behaviour.expression) { return new ExpressionCondition(this, behaviour.expression); } }; ConditionalEventDefinition.prototype._onDelegateApiMessage = function onDelegateApiMessage(routingKey, message) { if (message.content.message?.id === this.id) { this._onApiMessage(routingKey, message); } }; ConditionalEventDefinition.prototype._onApiMessage = function onApiMessage(routingKey, message) { const messageType = message.properties.type; switch (messageType) { case 'signal': { if (!this.condition) break; return this.evaluate(message, (err, result) => this.evaluateCallback(err, result)); } case 'discard': { this._stop(); this._debug('discarded'); return this.broker.publish('execution', 'execute.discard', cloneContent(this[kExecuteMessage].content, { state: 'discard' })); } case 'stop': { this._stop(); return this._debug('stopped'); } } }; ConditionalEventDefinition.prototype._stop = function stop() { const executionId = this.executionId; const broker = this.broker; broker.cancel(`_api-${executionId}`); broker.cancel(`_parent-signal-${executionId}`); broker.cancel(`_api-delegated-${executionId}`); }; ConditionalEventDefinition.prototype._debug = function debug(msg) { this.logger.debug(`<${this.executionId} (${this.activity.id})> ${msg}`); };