CompensateBoundaryEventBehavior.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import inherits from 'inherits-browser';
  2. import { getBusinessObject, is } from '../../../util/ModelUtil';
  3. import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
  4. import { hasEventDefinition, isEventSubProcess } from '../../../util/DiUtil';
  5. /**
  6. * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
  7. * @typedef {import('../lib/features/modeling/Modeling').default} Modeling
  8. */
  9. /**
  10. * Behavior ensuring that only a single compensation activity is connected to a
  11. * compensation boundary event when connecting, reconnecting or replacing shapes.
  12. *
  13. * @param {import('diagram-js/lib/core/EventBus').default} eventBus
  14. * @param {import('../Modeling').default} modeling
  15. * @param {import('../../rules/BpmnRules').default} bpmnRules
  16. */
  17. export default function CompensateBoundaryEventBehavior(eventBus, modeling, bpmnRules) {
  18. CommandInterceptor.call(this, eventBus);
  19. this.preExecute('shape.replace', handleReplacement, true);
  20. this.postExecuted('shape.replace', handleReplacementPostExecuted, true);
  21. this.preExecute('connection.create', handleNewConnection, true);
  22. this.postExecuted('connection.delete', handleConnectionRemoval, true);
  23. this.postExecuted('connection.reconnect', handleReconnection, true);
  24. this.postExecuted('element.updateProperties', handlePropertiesUpdate, true);
  25. /**
  26. * Given a connection from boundary event is removed, remove the `isForCompensation` property.
  27. */
  28. function handleConnectionRemoval(context) {
  29. const source = context.source,
  30. target = context.target;
  31. if (isCompensationBoundaryEvent(source) && isForCompensation(target)) {
  32. removeIsForCompensationProperty(target);
  33. }
  34. }
  35. /**
  36. * Add `isForCompensation` property and make sure only a single compensation activity is connected.
  37. */
  38. function handleNewConnection(context) {
  39. const connection = context.connection,
  40. source = context.source,
  41. target = context.target;
  42. if (isCompensationBoundaryEvent(source) && isForCompensationAllowed(target)) {
  43. addIsForCompensationProperty(target);
  44. removeExistingAssociations(source, [ connection ]);
  45. }
  46. }
  47. function handleReconnection(context) {
  48. const newTarget = context.newTarget,
  49. oldSource = context.oldSource,
  50. oldTarget = context.oldTarget;
  51. // target changes
  52. if (oldTarget !== newTarget) {
  53. const source = oldSource;
  54. // oldTarget perspective
  55. if (isForCompensation(oldTarget)) {
  56. removeIsForCompensationProperty(oldTarget);
  57. }
  58. // newTarget perspective
  59. if (isCompensationBoundaryEvent(source) && isForCompensationAllowed(newTarget)) {
  60. addIsForCompensationProperty(newTarget);
  61. }
  62. }
  63. }
  64. function handlePropertiesUpdate(context) {
  65. const { element } = context;
  66. if (isForCompensation(element)) {
  67. removeDisallowedConnections(element);
  68. removeAttachments(element);
  69. } else if (isForCompensationAllowed(element)) {
  70. removeIncomingCompensationAssociations(element);
  71. }
  72. }
  73. /**
  74. * When replacing a boundary event, make sure the compensation activity is connected,
  75. * and remove the potential candidates for connection replacement to have a single compensation activity.
  76. */
  77. function handleReplacement(context) {
  78. const {
  79. newData,
  80. oldShape
  81. } = context;
  82. // from compensate boundary event
  83. if (isCompensationBoundaryEvent(context.oldShape) &&
  84. newData.eventDefinitionType !== 'bpmn:CompensateEventDefinition' ||
  85. newData.type !== 'bpmn:BoundaryEvent'
  86. ) {
  87. const targetConnection = oldShape.outgoing.find(
  88. ({ target }) => isForCompensation(target)
  89. );
  90. if (targetConnection && targetConnection.target) {
  91. context._connectionTarget = targetConnection.target;
  92. }
  93. }
  94. // to compensate boundary event
  95. else if (
  96. !isCompensationBoundaryEvent(context.oldShape) &&
  97. newData.eventDefinitionType === 'bpmn:CompensateEventDefinition' &&
  98. newData.type === 'bpmn:BoundaryEvent'
  99. ) {
  100. const targetConnection = oldShape.outgoing.find(
  101. ({ target }) => isForCompensationAllowed(target)
  102. );
  103. if (targetConnection && targetConnection.target) {
  104. context._connectionTarget = targetConnection.target;
  105. }
  106. removeOutgoingSequenceFlows(oldShape);
  107. }
  108. }
  109. function handleReplacementPostExecuted(context) {
  110. const { _connectionTarget: target, newShape } = context;
  111. if (target) {
  112. modeling.connect(newShape, target);
  113. }
  114. }
  115. function addIsForCompensationProperty(target) {
  116. modeling.updateProperties(target, { isForCompensation: true });
  117. }
  118. function removeIsForCompensationProperty(target) {
  119. modeling.updateProperties(target, { isForCompensation: undefined });
  120. }
  121. function removeDisallowedConnections(element) {
  122. for (const connection of element.incoming) {
  123. if (!bpmnRules.canConnect(connection.source, element)) {
  124. modeling.removeConnection(connection);
  125. }
  126. }
  127. for (const connection of element.outgoing) {
  128. if (!bpmnRules.canConnect(element, connection.target)) {
  129. modeling.removeConnection(connection);
  130. }
  131. }
  132. }
  133. function removeExistingAssociations(boundaryEvent, ignoredAssociations) {
  134. const associations = boundaryEvent.outgoing.filter(connection => is(connection, 'bpmn:Association'));
  135. const associationsToRemove = associations.filter(association => {
  136. return isForCompensation(association.target) && !ignoredAssociations.includes(association);
  137. });
  138. // remove existing associations
  139. associationsToRemove.forEach(association => modeling.removeConnection(association));
  140. }
  141. function removeAttachments(element) {
  142. const attachments = element.attachers.slice();
  143. if (!attachments.length) {
  144. return;
  145. }
  146. modeling.removeElements(attachments);
  147. }
  148. function removeIncomingCompensationAssociations(element) {
  149. const compensationAssociations = element.incoming.filter(
  150. connection => isCompensationBoundaryEvent(connection.source)
  151. );
  152. modeling.removeElements(compensationAssociations);
  153. }
  154. function removeOutgoingSequenceFlows(element) {
  155. const sequenceFlows = element.outgoing.filter(
  156. connection => is(connection, 'bpmn:SequenceFlow')
  157. );
  158. modeling.removeElements(sequenceFlows);
  159. }
  160. }
  161. inherits(CompensateBoundaryEventBehavior, CommandInterceptor);
  162. CompensateBoundaryEventBehavior.$inject = [
  163. 'eventBus',
  164. 'modeling',
  165. 'bpmnRules'
  166. ];
  167. // helpers //////////
  168. function isForCompensation(element) {
  169. const bo = getBusinessObject(element);
  170. return bo && bo.get('isForCompensation');
  171. }
  172. function isCompensationBoundaryEvent(element) {
  173. return element && is(element, 'bpmn:BoundaryEvent') &&
  174. hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
  175. }
  176. function isForCompensationAllowed(element) {
  177. return element && is(element, 'bpmn:Activity') && !isEventSubProcess(element);
  178. }