BpmnRules.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  1. import {
  2. find,
  3. some,
  4. every,
  5. forEach
  6. } from 'min-dash';
  7. import inherits from 'inherits';
  8. import {
  9. is,
  10. getBusinessObject
  11. } from '../../util/ModelUtil';
  12. import {
  13. isAny
  14. } from '../modeling/util/ModelingUtil';
  15. import {
  16. isLabel
  17. } from '../../util/LabelUtil';
  18. import {
  19. isExpanded,
  20. isEventSubProcess,
  21. isInterrupting,
  22. hasErrorEventDefinition,
  23. hasEscalationEventDefinition,
  24. hasCompensateEventDefinition
  25. } from '../../util/DiUtil';
  26. import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
  27. import {
  28. getBoundaryAttachment as isBoundaryAttachment
  29. } from '../snapping/BpmnSnappingUtil';
  30. /**
  31. * BPMN specific modeling rule
  32. */
  33. export default function BpmnRules(eventBus) {
  34. RuleProvider.call(this, eventBus);
  35. }
  36. inherits(BpmnRules, RuleProvider);
  37. BpmnRules.$inject = [ 'eventBus' ];
  38. BpmnRules.prototype.init = function() {
  39. this.addRule('connection.start', function(context) {
  40. var source = context.source;
  41. return canStartConnection(source);
  42. });
  43. this.addRule('connection.create', function(context) {
  44. var source = context.source,
  45. target = context.target,
  46. hints = context.hints || {},
  47. targetParent = hints.targetParent,
  48. targetAttach = hints.targetAttach;
  49. // don't allow incoming connections on
  50. // newly created boundary events
  51. // to boundary events
  52. if (targetAttach) {
  53. return false;
  54. }
  55. // temporarily set target parent for scoping
  56. // checks to work
  57. if (targetParent) {
  58. target.parent = targetParent;
  59. }
  60. try {
  61. return canConnect(source, target);
  62. } finally {
  63. // unset temporary target parent
  64. if (targetParent) {
  65. target.parent = null;
  66. }
  67. }
  68. });
  69. this.addRule('connection.reconnectStart', function(context) {
  70. var connection = context.connection,
  71. source = context.hover || context.source,
  72. target = connection.target;
  73. return canConnect(source, target, connection);
  74. });
  75. this.addRule('connection.reconnectEnd', function(context) {
  76. var connection = context.connection,
  77. source = connection.source,
  78. target = context.hover || context.target;
  79. return canConnect(source, target, connection);
  80. });
  81. this.addRule('connection.updateWaypoints', function(context) {
  82. // OK! but visually ignore
  83. return null;
  84. });
  85. this.addRule('shape.resize', function(context) {
  86. var shape = context.shape,
  87. newBounds = context.newBounds;
  88. return canResize(shape, newBounds);
  89. });
  90. this.addRule('elements.move', function(context) {
  91. var target = context.target,
  92. shapes = context.shapes,
  93. position = context.position;
  94. return canAttach(shapes, target, null, position) ||
  95. canReplace(shapes, target, position) ||
  96. canMove(shapes, target, position) ||
  97. canInsert(shapes, target, position);
  98. });
  99. this.addRule('shape.create', function(context) {
  100. return canCreate(
  101. context.shape,
  102. context.target,
  103. context.source,
  104. context.position
  105. );
  106. });
  107. this.addRule('shape.attach', function(context) {
  108. return canAttach(
  109. context.shape,
  110. context.target,
  111. null,
  112. context.position
  113. );
  114. });
  115. this.addRule('element.copy', function(context) {
  116. var collection = context.collection,
  117. element = context.element;
  118. return canCopy(collection, element);
  119. });
  120. this.addRule('element.paste', function(context) {
  121. var parent = context.parent,
  122. element = context.element,
  123. position = context.position,
  124. source = context.source,
  125. target = context.target;
  126. if (source || target) {
  127. return canConnect(source, target);
  128. }
  129. return canAttach([ element ], parent, null, position) || canCreate(element, parent, null, position);
  130. });
  131. this.addRule('elements.paste', function(context) {
  132. var tree = context.tree,
  133. target = context.target;
  134. return canPaste(tree, target);
  135. });
  136. };
  137. BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow;
  138. BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;
  139. BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation;
  140. BpmnRules.prototype.canConnectAssociation = canConnectAssociation;
  141. BpmnRules.prototype.canMove = canMove;
  142. BpmnRules.prototype.canAttach = canAttach;
  143. BpmnRules.prototype.canReplace = canReplace;
  144. BpmnRules.prototype.canDrop = canDrop;
  145. BpmnRules.prototype.canInsert = canInsert;
  146. BpmnRules.prototype.canCreate = canCreate;
  147. BpmnRules.prototype.canConnect = canConnect;
  148. BpmnRules.prototype.canResize = canResize;
  149. BpmnRules.prototype.canCopy = canCopy;
  150. /**
  151. * Utility functions for rule checking
  152. */
  153. /**
  154. * Checks if given element can be used for starting connection.
  155. *
  156. * @param {Element} source
  157. * @return {Boolean}
  158. */
  159. function canStartConnection(element) {
  160. if (nonExistingOrLabel(element)) {
  161. return null;
  162. }
  163. return isAny(element, [
  164. 'bpmn:FlowNode',
  165. 'bpmn:InteractionNode',
  166. 'bpmn:DataObjectReference',
  167. 'bpmn:DataStoreReference'
  168. ]);
  169. }
  170. function nonExistingOrLabel(element) {
  171. return !element || isLabel(element);
  172. }
  173. function isSame(a, b) {
  174. return a === b;
  175. }
  176. function getOrganizationalParent(element) {
  177. do {
  178. if (is(element, 'bpmn:Process')) {
  179. return getBusinessObject(element);
  180. }
  181. if (is(element, 'bpmn:Participant')) {
  182. return (
  183. getBusinessObject(element).processRef ||
  184. getBusinessObject(element)
  185. );
  186. }
  187. } while ((element = element.parent));
  188. }
  189. function isTextAnnotation(element) {
  190. return is(element, 'bpmn:TextAnnotation');
  191. }
  192. function isCompensationBoundary(element) {
  193. return is(element, 'bpmn:BoundaryEvent') &&
  194. hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
  195. }
  196. function isForCompensation(e) {
  197. return getBusinessObject(e).isForCompensation;
  198. }
  199. function isSameOrganization(a, b) {
  200. var parentA = getOrganizationalParent(a),
  201. parentB = getOrganizationalParent(b);
  202. return parentA === parentB;
  203. }
  204. function isMessageFlowSource(element) {
  205. return (
  206. is(element, 'bpmn:InteractionNode') && (
  207. !is(element, 'bpmn:Event') || (
  208. is(element, 'bpmn:ThrowEvent') &&
  209. hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
  210. )
  211. )
  212. );
  213. }
  214. function isMessageFlowTarget(element) {
  215. return (
  216. is(element, 'bpmn:InteractionNode') &&
  217. !isForCompensation(element) && (
  218. !is(element, 'bpmn:Event') || (
  219. is(element, 'bpmn:CatchEvent') &&
  220. hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
  221. )
  222. )
  223. );
  224. }
  225. function getScopeParent(element) {
  226. var parent = element;
  227. while ((parent = parent.parent)) {
  228. if (is(parent, 'bpmn:FlowElementsContainer')) {
  229. return getBusinessObject(parent);
  230. }
  231. if (is(parent, 'bpmn:Participant')) {
  232. return getBusinessObject(parent).processRef;
  233. }
  234. }
  235. return null;
  236. }
  237. function isSameScope(a, b) {
  238. var scopeParentA = getScopeParent(a),
  239. scopeParentB = getScopeParent(b);
  240. return scopeParentA && (scopeParentA === scopeParentB);
  241. }
  242. function hasEventDefinition(element, eventDefinition) {
  243. var bo = getBusinessObject(element);
  244. return !!find(bo.eventDefinitions || [], function(definition) {
  245. return is(definition, eventDefinition);
  246. });
  247. }
  248. function hasEventDefinitionOrNone(element, eventDefinition) {
  249. var bo = getBusinessObject(element);
  250. return (bo.eventDefinitions || []).every(function(definition) {
  251. return is(definition, eventDefinition);
  252. });
  253. }
  254. function isSequenceFlowSource(element) {
  255. return (
  256. is(element, 'bpmn:FlowNode') &&
  257. !is(element, 'bpmn:EndEvent') &&
  258. !isEventSubProcess(element) &&
  259. !(is(element, 'bpmn:IntermediateThrowEvent') &&
  260. hasEventDefinition(element, 'bpmn:LinkEventDefinition')
  261. ) &&
  262. !isCompensationBoundary(element) &&
  263. !isForCompensation(element)
  264. );
  265. }
  266. function isSequenceFlowTarget(element) {
  267. return (
  268. is(element, 'bpmn:FlowNode') &&
  269. !is(element, 'bpmn:StartEvent') &&
  270. !is(element, 'bpmn:BoundaryEvent') &&
  271. !isEventSubProcess(element) &&
  272. !(is(element, 'bpmn:IntermediateCatchEvent') &&
  273. hasEventDefinition(element, 'bpmn:LinkEventDefinition')
  274. ) &&
  275. !isForCompensation(element)
  276. );
  277. }
  278. function isEventBasedTarget(element) {
  279. return (
  280. is(element, 'bpmn:ReceiveTask') || (
  281. is(element, 'bpmn:IntermediateCatchEvent') && (
  282. hasEventDefinition(element, 'bpmn:MessageEventDefinition') ||
  283. hasEventDefinition(element, 'bpmn:TimerEventDefinition') ||
  284. hasEventDefinition(element, 'bpmn:ConditionalEventDefinition') ||
  285. hasEventDefinition(element, 'bpmn:SignalEventDefinition')
  286. )
  287. )
  288. );
  289. }
  290. function isConnection(element) {
  291. return element.waypoints;
  292. }
  293. function getParents(element) {
  294. var parents = [];
  295. while (element) {
  296. element = element.parent;
  297. if (element) {
  298. parents.push(element);
  299. }
  300. }
  301. return parents;
  302. }
  303. function isParent(possibleParent, element) {
  304. var allParents = getParents(element);
  305. return allParents.indexOf(possibleParent) !== -1;
  306. }
  307. function canConnect(source, target, connection) {
  308. if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
  309. return null;
  310. }
  311. if (!is(connection, 'bpmn:DataAssociation')) {
  312. if (canConnectMessageFlow(source, target)) {
  313. return { type: 'bpmn:MessageFlow' };
  314. }
  315. if (canConnectSequenceFlow(source, target)) {
  316. return { type: 'bpmn:SequenceFlow' };
  317. }
  318. }
  319. var connectDataAssociation = canConnectDataAssociation(source, target);
  320. if (connectDataAssociation) {
  321. return connectDataAssociation;
  322. }
  323. if (isCompensationBoundary(source) && isForCompensation(target)) {
  324. return {
  325. type: 'bpmn:Association',
  326. associationDirection: 'One'
  327. };
  328. }
  329. if (canConnectAssociation(source, target)) {
  330. return {
  331. type: 'bpmn:Association'
  332. };
  333. }
  334. return false;
  335. }
  336. /**
  337. * Can an element be dropped into the target element
  338. *
  339. * @return {Boolean}
  340. */
  341. function canDrop(element, target, position) {
  342. // can move labels everywhere
  343. if (isLabel(element)) {
  344. return true;
  345. }
  346. // disallow to create elements on collapsed pools
  347. if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
  348. return false;
  349. }
  350. // allow to create new participants on
  351. // on existing collaboration and process diagrams
  352. if (is(element, 'bpmn:Participant')) {
  353. return is(target, 'bpmn:Process') || is(target, 'bpmn:Collaboration');
  354. }
  355. // allow moving DataInput / DataOutput within its original container only
  356. if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) {
  357. if (element.parent) {
  358. return target === element.parent;
  359. }
  360. }
  361. // allow creating lanes on participants and other lanes only
  362. if (is(element, 'bpmn:Lane')) {
  363. return is(target, 'bpmn:Participant') || is(target, 'bpmn:Lane');
  364. }
  365. if (is(element, 'bpmn:BoundaryEvent')) {
  366. return false;
  367. }
  368. // drop flow elements onto flow element containers
  369. // and participants
  370. if (is(element, 'bpmn:FlowElement') && !is(element, 'bpmn:DataStoreReference')) {
  371. if (is(target, 'bpmn:FlowElementsContainer')) {
  372. return isExpanded(target);
  373. }
  374. return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]);
  375. }
  376. // account for the fact that data associations are always
  377. // rendered and moved to top (Process or Collaboration level)
  378. //
  379. // artifacts may be placed wherever, too
  380. if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) {
  381. return isAny(target, [
  382. 'bpmn:Collaboration',
  383. 'bpmn:Lane',
  384. 'bpmn:Participant',
  385. 'bpmn:Process',
  386. 'bpmn:SubProcess' ]);
  387. }
  388. if (is(element, 'bpmn:MessageFlow')) {
  389. return is(target, 'bpmn:Collaboration')
  390. || element.source.parent == target
  391. || element.target.parent == target;
  392. }
  393. return false;
  394. }
  395. function canPaste(tree, target) {
  396. var topLevel = tree[0],
  397. participants;
  398. if (is(target, 'bpmn:Collaboration')) {
  399. return every(topLevel, function(e) {
  400. return e.type === 'bpmn:Participant';
  401. });
  402. }
  403. if (is(target, 'bpmn:Process')) {
  404. participants = some(topLevel, function(e) {
  405. return e.type === 'bpmn:Participant';
  406. });
  407. return !(participants && target.children.length > 0);
  408. }
  409. // disallow to create elements on collapsed pools
  410. if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
  411. return false;
  412. }
  413. if (is(target, 'bpmn:FlowElementsContainer')) {
  414. return isExpanded(target);
  415. }
  416. return isAny(target, [
  417. 'bpmn:Collaboration',
  418. 'bpmn:Lane',
  419. 'bpmn:Participant',
  420. 'bpmn:Process',
  421. 'bpmn:SubProcess' ]);
  422. }
  423. function isBoundaryEvent(element) {
  424. return !isLabel(element) && is(element, 'bpmn:BoundaryEvent');
  425. }
  426. function isLane(element) {
  427. return is(element, 'bpmn:Lane');
  428. }
  429. /**
  430. * We treat IntermediateThrowEvents as boundary events during create,
  431. * this must be reflected in the rules.
  432. */
  433. function isBoundaryCandidate(element) {
  434. return isBoundaryEvent(element) ||
  435. (is(element, 'bpmn:IntermediateThrowEvent') && !element.parent);
  436. }
  437. function isReceiveTaskAfterEventBasedGateway(element) {
  438. return (
  439. is(element, 'bpmn:ReceiveTask') &&
  440. find(element.incoming, function(incoming) {
  441. return is(incoming.source, 'bpmn:EventBasedGateway');
  442. })
  443. );
  444. }
  445. function canAttach(elements, target, source, position) {
  446. if (!Array.isArray(elements)) {
  447. elements = [ elements ];
  448. }
  449. // disallow appending as boundary event
  450. if (source) {
  451. return false;
  452. }
  453. // only (re-)attach one element at a time
  454. if (elements.length !== 1) {
  455. return false;
  456. }
  457. var element = elements[0];
  458. // do not attach labels
  459. if (isLabel(element)) {
  460. return false;
  461. }
  462. // only handle boundary events
  463. if (!isBoundaryCandidate(element)) {
  464. return false;
  465. }
  466. // allow default move operation
  467. if (!target) {
  468. return true;
  469. }
  470. // disallow drop on event sub processes
  471. if (isEventSubProcess(target)) {
  472. return false;
  473. }
  474. // only allow drop on non compensation activities
  475. if (!is(target, 'bpmn:Activity') || isForCompensation(target)) {
  476. return false;
  477. }
  478. // only attach to subprocess border
  479. if (position && !isBoundaryAttachment(position, target)) {
  480. return false;
  481. }
  482. // do not attach on receive tasks after event based gateways
  483. if (isReceiveTaskAfterEventBasedGateway(target)) {
  484. return false;
  485. }
  486. return 'attach';
  487. }
  488. /**
  489. * Defines how to replace elements for a given target.
  490. *
  491. * Returns an array containing all elements which will be replaced.
  492. *
  493. * @example
  494. *
  495. * [{ id: 'IntermediateEvent_2',
  496. * type: 'bpmn:StartEvent'
  497. * },
  498. * { id: 'IntermediateEvent_5',
  499. * type: 'bpmn:EndEvent'
  500. * }]
  501. *
  502. * @param {Array} elements
  503. * @param {Object} target
  504. *
  505. * @return {Object} an object containing all elements which have to be replaced
  506. */
  507. function canReplace(elements, target, position) {
  508. if (!target) {
  509. return false;
  510. }
  511. var canExecute = {
  512. replacements: []
  513. };
  514. forEach(elements, function(element) {
  515. if (!isEventSubProcess(target)) {
  516. if (is(element, 'bpmn:StartEvent') &&
  517. element.type !== 'label' &&
  518. canDrop(element, target)) {
  519. // replace a non-interrupting start event by a blank interrupting start event
  520. // when the target is not an event sub process
  521. if (!isInterrupting(element)) {
  522. canExecute.replacements.push({
  523. oldElementId: element.id,
  524. newElementType: 'bpmn:StartEvent'
  525. });
  526. }
  527. // replace an error/escalation/compansate start event by a blank interrupting start event
  528. // when the target is not an event sub process
  529. if (hasErrorEventDefinition(element) ||
  530. hasEscalationEventDefinition(element) ||
  531. hasCompensateEventDefinition(element)) {
  532. canExecute.replacements.push({
  533. oldElementId: element.id,
  534. newElementType: 'bpmn:StartEvent'
  535. });
  536. }
  537. }
  538. }
  539. if (!is(target, 'bpmn:Transaction')) {
  540. if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') &&
  541. element.type !== 'label') {
  542. if (is(element, 'bpmn:EndEvent') && canDrop(element, target)) {
  543. canExecute.replacements.push({
  544. oldElementId: element.id,
  545. newElementType: 'bpmn:EndEvent'
  546. });
  547. }
  548. if (is(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
  549. canExecute.replacements.push({
  550. oldElementId: element.id,
  551. newElementType: 'bpmn:BoundaryEvent'
  552. });
  553. }
  554. }
  555. }
  556. });
  557. return canExecute.replacements.length ? canExecute : false;
  558. }
  559. function canMove(elements, target) {
  560. // do not move selection containing boundary events
  561. if (some(elements, isBoundaryEvent)) {
  562. return false;
  563. }
  564. // do not move selection containing lanes
  565. if (some(elements, isLane)) {
  566. return false;
  567. }
  568. // allow default move check to start move operation
  569. if (!target) {
  570. return true;
  571. }
  572. return elements.every(function(element) {
  573. return canDrop(element, target);
  574. });
  575. }
  576. function canCreate(shape, target, source, position) {
  577. if (!target) {
  578. return false;
  579. }
  580. if (isLabel(target)) {
  581. return null;
  582. }
  583. if (isSame(source, target)) {
  584. return false;
  585. }
  586. // ensure we do not drop the element
  587. // into source
  588. if (source && isParent(source, target)) {
  589. return false;
  590. }
  591. return canDrop(shape, target, position) || canInsert(shape, target, position);
  592. }
  593. function canResize(shape, newBounds) {
  594. if (is(shape, 'bpmn:SubProcess')) {
  595. return (
  596. isExpanded(shape) && (
  597. !newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
  598. )
  599. );
  600. }
  601. if (is(shape, 'bpmn:Lane')) {
  602. return !newBounds || (newBounds.width >= 130 && newBounds.height >= 60);
  603. }
  604. if (is(shape, 'bpmn:Participant')) {
  605. return !newBounds || (newBounds.width >= 250 && newBounds.height >= 50);
  606. }
  607. if (isTextAnnotation(shape)) {
  608. return true;
  609. }
  610. return false;
  611. }
  612. /**
  613. * Check, whether one side of the relationship
  614. * is a text annotation.
  615. */
  616. function isOneTextAnnotation(source, target) {
  617. var sourceTextAnnotation = isTextAnnotation(source),
  618. targetTextAnnotation = isTextAnnotation(target);
  619. return (
  620. (sourceTextAnnotation || targetTextAnnotation) &&
  621. (sourceTextAnnotation !== targetTextAnnotation)
  622. );
  623. }
  624. function canConnectAssociation(source, target) {
  625. // do not connect connections
  626. if (isConnection(source) || isConnection(target)) {
  627. return false;
  628. }
  629. // compensation boundary events are exception
  630. if (isCompensationBoundary(source) && isForCompensation(target)) {
  631. return true;
  632. }
  633. // don't connect parent <-> child
  634. if (isParent(target, source) || isParent(source, target)) {
  635. return false;
  636. }
  637. // allow connection of associations between <!TextAnnotation> and <TextAnnotation>
  638. return isOneTextAnnotation(source, target);
  639. }
  640. function canConnectMessageFlow(source, target) {
  641. return isMessageFlowSource(source) &&
  642. isMessageFlowTarget(target) &&
  643. !isSameOrganization(source, target);
  644. }
  645. function canConnectSequenceFlow(source, target) {
  646. return isSequenceFlowSource(source) &&
  647. isSequenceFlowTarget(target) &&
  648. isSameScope(source, target) &&
  649. !(is(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
  650. }
  651. function canConnectDataAssociation(source, target) {
  652. if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
  653. isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) {
  654. return { type: 'bpmn:DataInputAssociation' };
  655. }
  656. if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
  657. isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) {
  658. return { type: 'bpmn:DataOutputAssociation' };
  659. }
  660. return false;
  661. }
  662. function canInsert(shape, flow, position) {
  663. if (!flow) {
  664. return false;
  665. }
  666. if (Array.isArray(shape)) {
  667. if (shape.length !== 1) {
  668. return false;
  669. }
  670. shape = shape[0];
  671. }
  672. if (flow.source === shape ||
  673. flow.target === shape) {
  674. return false;
  675. }
  676. // return true if we can drop on the
  677. // underlying flow parent
  678. //
  679. // at this point we are not really able to talk
  680. // about connection rules (yet)
  681. return (
  682. isAny(flow, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
  683. !isLabel(flow) &&
  684. is(shape, 'bpmn:FlowNode') &&
  685. !is(shape, 'bpmn:BoundaryEvent') &&
  686. canDrop(shape, flow.parent, position));
  687. }
  688. function contains(collection, element) {
  689. return (collection && element) && collection.indexOf(element) !== -1;
  690. }
  691. function canCopy(collection, element) {
  692. if (is(element, 'bpmn:Lane') && !contains(collection, element.parent)) {
  693. return false;
  694. }
  695. if (is(element, 'bpmn:BoundaryEvent') && !contains(collection, element.host)) {
  696. return false;
  697. }
  698. return true;
  699. }