UNPKG

bpmn-js

Version:

A bpmn 2.0 toolkit and web modeler

223 lines (178 loc) 6.96 kB
import inherits from 'inherits-browser'; import { getBusinessObject, is } from '../../../util/ModelUtil'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; import { hasEventDefinition, isEventSubProcess } from '../../../util/DiUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('../lib/features/modeling/Modeling').default} Modeling */ /** * Behavior ensuring that only a single compensation activity is connected to a * compensation boundary event when connecting, reconnecting or replacing shapes. * * @param {import('diagram-js/lib/core/EventBus').default} eventBus * @param {import('../Modeling').default} modeling * @param {import('../../rules/BpmnRules').default} bpmnRules */ export default function CompensateBoundaryEventBehavior(eventBus, modeling, bpmnRules) { CommandInterceptor.call(this, eventBus); this.preExecute('shape.replace', handleReplacement, true); this.postExecuted('shape.replace', handleReplacementPostExecuted, true); this.preExecute('connection.create', handleNewConnection, true); this.postExecuted('connection.delete', handleConnectionRemoval, true); this.postExecuted('connection.reconnect', handleReconnection, true); this.postExecuted('element.updateProperties', handlePropertiesUpdate, true); /** * Given a connection from boundary event is removed, remove the `isForCompensation` property. */ function handleConnectionRemoval(context) { const source = context.source, target = context.target; if (isCompensationBoundaryEvent(source) && isForCompensation(target)) { removeIsForCompensationProperty(target); } } /** * Add `isForCompensation` property and make sure only a single compensation activity is connected. */ function handleNewConnection(context) { const connection = context.connection, source = context.source, target = context.target; if (isCompensationBoundaryEvent(source) && isForCompensationAllowed(target)) { addIsForCompensationProperty(target); removeExistingAssociations(source, [ connection ]); } } function handleReconnection(context) { const newTarget = context.newTarget, oldSource = context.oldSource, oldTarget = context.oldTarget; // target changes if (oldTarget !== newTarget) { const source = oldSource; // oldTarget perspective if (isForCompensation(oldTarget)) { removeIsForCompensationProperty(oldTarget); } // newTarget perspective if (isCompensationBoundaryEvent(source) && isForCompensationAllowed(newTarget)) { addIsForCompensationProperty(newTarget); } } } function handlePropertiesUpdate(context) { const { element } = context; if (isForCompensation(element)) { removeDisallowedConnections(element); removeAttachments(element); } else if (isForCompensationAllowed(element)) { removeIncomingCompensationAssociations(element); } } /** * When replacing a boundary event, make sure the compensation activity is connected, * and remove the potential candidates for connection replacement to have a single compensation activity. */ function handleReplacement(context) { const { newData, oldShape } = context; // from compensate boundary event if (isCompensationBoundaryEvent(context.oldShape) && newData.eventDefinitionType !== 'bpmn:CompensateEventDefinition' || newData.type !== 'bpmn:BoundaryEvent' ) { const targetConnection = oldShape.outgoing.find( ({ target }) => isForCompensation(target) ); if (targetConnection && targetConnection.target) { context._connectionTarget = targetConnection.target; } } // to compensate boundary event else if ( !isCompensationBoundaryEvent(context.oldShape) && newData.eventDefinitionType === 'bpmn:CompensateEventDefinition' && newData.type === 'bpmn:BoundaryEvent' ) { const targetConnection = oldShape.outgoing.find( ({ target }) => isForCompensationAllowed(target) ); if (targetConnection && targetConnection.target) { context._connectionTarget = targetConnection.target; } removeOutgoingSequenceFlows(oldShape); } } function handleReplacementPostExecuted(context) { const { _connectionTarget: target, newShape } = context; if (target) { modeling.connect(newShape, target); } } function addIsForCompensationProperty(target) { modeling.updateProperties(target, { isForCompensation: true }); } function removeIsForCompensationProperty(target) { modeling.updateProperties(target, { isForCompensation: undefined }); } function removeDisallowedConnections(element) { for (const connection of element.incoming) { if (!bpmnRules.canConnect(connection.source, element)) { modeling.removeConnection(connection); } } for (const connection of element.outgoing) { if (!bpmnRules.canConnect(element, connection.target)) { modeling.removeConnection(connection); } } } function removeExistingAssociations(boundaryEvent, ignoredAssociations) { const associations = boundaryEvent.outgoing.filter(connection => is(connection, 'bpmn:Association')); const associationsToRemove = associations.filter(association => { return isForCompensation(association.target) && !ignoredAssociations.includes(association); }); // remove existing associations associationsToRemove.forEach(association => modeling.removeConnection(association)); } function removeAttachments(element) { const attachments = element.attachers.slice(); if (!attachments.length) { return; } modeling.removeElements(attachments); } function removeIncomingCompensationAssociations(element) { const compensationAssociations = element.incoming.filter( connection => isCompensationBoundaryEvent(connection.source) ); modeling.removeElements(compensationAssociations); } function removeOutgoingSequenceFlows(element) { const sequenceFlows = element.outgoing.filter( connection => is(connection, 'bpmn:SequenceFlow') ); modeling.removeElements(sequenceFlows); } } inherits(CompensateBoundaryEventBehavior, CommandInterceptor); CompensateBoundaryEventBehavior.$inject = [ 'eventBus', 'modeling', 'bpmnRules' ]; // helpers ////////// function isForCompensation(element) { const bo = getBusinessObject(element); return bo && bo.get('isForCompensation'); } function isCompensationBoundaryEvent(element) { return element && is(element, 'bpmn:BoundaryEvent') && hasEventDefinition(element, 'bpmn:CompensateEventDefinition'); } function isForCompensationAllowed(element) { return element && is(element, 'bpmn:Activity') && !isEventSubProcess(element); }