bpmn-elements
Version:
Executable workflow elements based on BPMN 2.0
363 lines (362 loc) • 12 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _Api = require("../Api.js");
var _messageHelper = require("../messageHelper.js");
const kCompleted = Symbol.for('completed');
const kExecuteQ = Symbol.for('executeQ');
const kExecuteMessage = Symbol.for('executeMessage');
const kMessageHandlers = Symbol.for('messageHandlers');
const kPostponed = Symbol.for('postponed');
var _default = exports.default = ActivityExecution;
function ActivityExecution(activity, context) {
this.activity = activity;
this.context = context;
this.id = activity.id;
this.broker = activity.broker;
this[kPostponed] = new Set();
this[kCompleted] = false;
this[kExecuteQ] = this.broker.assertQueue('execute-q', {
durable: true,
autoDelete: false
});
this[kMessageHandlers] = {
onParentApiMessage: this._onParentApiMessage.bind(this),
onExecuteMessage: this._onExecuteMessage.bind(this)
};
}
Object.defineProperty(ActivityExecution.prototype, 'completed', {
get() {
return this[kCompleted];
}
});
ActivityExecution.prototype.execute = function execute(executeMessage) {
if (!executeMessage) throw new Error('Execution requires message');
const executionId = executeMessage.content?.executionId;
if (!executionId) throw new Error('Execution requires execution id');
this.executionId = executionId;
const initMessage = this[kExecuteMessage] = (0, _messageHelper.cloneMessage)(executeMessage, {
executionId,
state: 'start',
isRootScope: true
});
if (executeMessage.fields.redelivered) {
this[kPostponed].clear();
this._debug('resume execution');
if (!this.source) this.source = new this.activity.Behaviour(this.activity, this.context);
this.activate();
return this.broker.publish('execution', 'execute.resume.execution', (0, _messageHelper.cloneContent)(initMessage.content), {
persistent: false
});
}
this._debug('execute');
this.activate();
this.source = new this.activity.Behaviour(this.activity, this.context);
this.broker.publish('execution', 'execute.start', (0, _messageHelper.cloneContent)(initMessage.content));
};
ActivityExecution.prototype.activate = function activate() {
if (this[kCompleted]) return;
const broker = this.broker;
const batchSize = this.activity.environment.settings.batchSize || 50;
broker.bindQueue('execute-q', 'execution', 'execute.#', {
priority: 100
});
const {
onExecuteMessage,
onParentApiMessage
} = this[kMessageHandlers];
this[kExecuteQ].assertConsumer(onExecuteMessage, {
exclusive: true,
prefetch: batchSize * 2,
priority: 100,
consumerTag: '_activity-execute'
});
if (this[kCompleted]) return this.deactivate();
broker.subscribeTmp('api', `activity.*.${this.executionId}`, onParentApiMessage, {
noAck: true,
consumerTag: '_activity-api-execution',
priority: 200
});
};
ActivityExecution.prototype.deactivate = function deactivate() {
const broker = this.broker;
broker.cancel('_activity-api-execution');
broker.cancel('_activity-execute');
broker.unbindQueue('execute-q', 'execution', 'execute.#');
};
ActivityExecution.prototype.discard = function discard() {
if (this[kCompleted]) return;
const initMessage = this[kExecuteMessage];
if (!initMessage) return this.activity.logger.warn(`<${this.id}> is not executing`);
this.getApi(initMessage).discard();
};
ActivityExecution.prototype.getApi = function getApi(apiMessage) {
const self = this;
if (!apiMessage) apiMessage = this[kExecuteMessage];
if (self.source.getApi) {
const sourceApi = self.source.getApi(apiMessage);
if (sourceApi) return sourceApi;
}
const api = (0, _Api.ActivityApi)(self.broker, apiMessage);
api.getExecuting = function getExecuting() {
const result = [];
for (const msg of self[kPostponed]) {
if (msg.content.executionId === apiMessage.content.executionId) continue;
result.push(self.getApi(msg));
}
return result;
};
return api;
};
ActivityExecution.prototype.passthrough = function passthrough(executeMessage) {
if (!this.source) return this.execute(executeMessage);
return this._sourceExecute(executeMessage);
};
ActivityExecution.prototype.getPostponed = function getPostponed() {
let apis = [];
for (const msg of this[kPostponed]) {
apis.push(this.getApi(msg));
}
if (!this.activity.isSubProcess || !this.source) return apis;
apis = apis.concat(this.source.getPostponed());
return apis;
};
ActivityExecution.prototype.getState = function getState() {
const result = {
completed: this[kCompleted]
};
const source = this.source;
if (!source || !source.getState) return result;
return {
...result,
...source.getState()
};
};
ActivityExecution.prototype.recover = function recover(state) {
this[kPostponed].clear();
if (!state) return this;
if ('completed' in state) this[kCompleted] = state.completed;
const source = this.source = new this.activity.Behaviour(this.activity, this.context);
if (source.recover) {
source.recover(state);
}
return this;
};
ActivityExecution.prototype.stop = function stop() {
const executeMessage = this[kExecuteMessage];
if (!executeMessage) return;
this.getApi(executeMessage).stop();
};
ActivityExecution.prototype._sourceExecute = function sourceExecute(executeMessage) {
try {
return this.source.execute(executeMessage);
} catch (error) {
return this.broker.publish('execution', 'execute.error', (0, _messageHelper.cloneContent)(executeMessage.content, {
error
}));
}
};
ActivityExecution.prototype._onExecuteMessage = function onExecuteMessage(routingKey, message) {
const {
fields,
content,
properties
} = message;
const isRedelivered = fields.redelivered;
if (isRedelivered && properties.persistent === false) return message.ack();
switch (routingKey) {
case 'execute.resume.execution':
{
if (!this[kPostponed].size) return this.broker.publish('execution', 'execute.start', (0, _messageHelper.cloneContent)(this[kExecuteMessage].content));
break;
}
case 'execute.cancel':
return this._onExecutionDiscarded('cancel', message);
case 'execute.error':
return this._onExecutionDiscarded('error', message);
case 'execute.discard':
return this._onExecutionDiscarded('discard', message);
case 'execute.completed':
{
if (isRedelivered) {
message.ack();
return this.broker.publish('execution', routingKey, getExecuteMessage(message).content);
}
return this._onExecutionCompleted(message);
}
case 'execute.start':
{
if (!this._onStateChangeMessage(message)) return;
return this._sourceExecute(getExecuteMessage(message));
}
case 'execute.outbound.take':
{
if (isRedelivered) {
message.ack();
break;
}
this.broker.publish('execution', 'execution.outbound.take', (0, _messageHelper.cloneContent)(content), {
type: 'outbound'
});
break;
}
default:
{
if (!this._onStateChangeMessage(message)) return;
if (isRedelivered) {
return this._sourceExecute(getExecuteMessage(message));
}
}
}
};
ActivityExecution.prototype._onStateChangeMessage = function onStateChangeMessage(message) {
const {
ignoreIfExecuting,
executionId
} = message.content;
const postponed = this[kPostponed];
let previousMsg;
for (const msg of postponed) {
if (msg.content.executionId === executionId) previousMsg = msg;
}
if (previousMsg) {
if (ignoreIfExecuting) {
message.ack();
return false;
}
postponed.delete(previousMsg);
postponed.add(message);
previousMsg.ack();
return true;
} else {
postponed.add(message);
return true;
}
};
ActivityExecution.prototype._onExecutionCompleted = function onExecutionCompleted(message) {
const postponedMsg = this._ackPostponed(message);
if (!postponedMsg) return;
const postponed = this[kPostponed];
const {
executionId,
keep,
isRootScope
} = message.content;
if (!isRootScope) {
this._debug('completed sub execution');
if (!keep) message.ack();
if (postponed.size === 1) {
const onlyMessage = postponed.values().next().value;
if (onlyMessage.content.isRootScope && !onlyMessage.content.preventComplete) {
return this.broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(onlyMessage.content));
}
}
return;
}
this._debug('completed execution', executionId);
this[kCompleted] = true;
message.ack(true);
this.deactivate();
const subApis = this.getPostponed();
postponed.clear();
for (const api of subApis) api.discard();
this._publishExecutionCompleted('completed', {
...postponedMsg.content,
...message.content
}, message.properties.correlationId);
};
ActivityExecution.prototype._onExecutionDiscarded = function onExecutionDiscarded(discardType, message) {
const postponedMsg = this._ackPostponed(message);
const {
isRootScope,
error
} = message.content;
if (!isRootScope && !postponedMsg) return;
const postponed = this[kPostponed];
const correlationId = message.properties.correlationId;
if (!error && !isRootScope) {
message.ack();
if (postponed.size === 1) {
const onlyMessage = postponed.values().next().value;
if (onlyMessage.content.isRootScope) {
return this.broker.publish('execution', 'execute.discard', onlyMessage.content, {
correlationId
});
}
}
return;
}
message.ack(true);
this.deactivate();
const subApis = this.getPostponed();
postponed.clear();
for (const api of subApis) api.discard();
this._publishExecutionCompleted(discardType, (0, _messageHelper.cloneContent)(message.content), correlationId);
};
ActivityExecution.prototype._publishExecutionCompleted = function publishExecutionCompleted(completionType, completeContent, correlationId) {
this[kCompleted] = true;
this.broker.publish('execution', `execution.${completionType}`, {
...completeContent,
state: completionType
}, {
type: completionType,
correlationId
});
};
ActivityExecution.prototype._ackPostponed = function ackPostponed(completeMessage) {
const {
executionId: eid
} = completeMessage.content;
const postponed = this[kPostponed];
for (const msg of postponed) {
if (msg.content.executionId === eid) {
postponed.delete(msg);
msg.ack();
return msg;
}
}
};
ActivityExecution.prototype._onParentApiMessage = function onParentApiMessage(routingKey, message) {
switch (message.properties.type) {
case 'error':
return this[kExecuteQ].queueMessage({
routingKey: 'execute.error'
}, {
error: message.content.error
});
case 'discard':
return this[kExecuteQ].queueMessage({
routingKey: 'execute.discard'
}, (0, _messageHelper.cloneContent)(this[kExecuteMessage].content));
case 'stop':
{
return this._onStop(message);
}
}
};
ActivityExecution.prototype._onStop = function onStop(message) {
const stoppedId = message?.content?.executionId;
const running = this.getPostponed();
for (const api of running) {
if (stoppedId !== api.content.executionId) {
api.stop();
}
}
this.broker.cancel('_activity-execute');
this.broker.cancel('_activity-api-execution');
};
ActivityExecution.prototype._debug = function debug(logMessage, executionId) {
executionId = executionId || this.executionId;
this.activity.logger.debug(`<${executionId} (${this.id})> ${logMessage}`);
};
function getExecuteMessage(message) {
const result = (0, _messageHelper.cloneMessage)(message, {
...(message.fields.redelivered && {
isRecovered: true
}),
ignoreIfExecuting: undefined
});
return result;
}