123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926 |
- import {
- every,
- find,
- forEach,
- some
- } from 'min-dash';
- import inherits from 'inherits';
- import {
- is,
- getBusinessObject
- } from '../../util/ModelUtil';
- import {
- getParent,
- isAny
- } from '../modeling/util/ModelingUtil';
- import {
- isLabel
- } from '../../util/LabelUtil';
- import {
- isExpanded,
- isEventSubProcess,
- isInterrupting,
- hasErrorEventDefinition,
- hasEscalationEventDefinition,
- hasCompensateEventDefinition
- } from '../../util/DiUtil';
- import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
- import {
- getBoundaryAttachment as isBoundaryAttachment
- } from '../snapping/BpmnSnappingUtil';
- /**
- * BPMN specific modeling rule
- */
- export default function BpmnRules(eventBus) {
- RuleProvider.call(this, eventBus);
- }
- inherits(BpmnRules, RuleProvider);
- BpmnRules.$inject = [ 'eventBus' ];
- BpmnRules.prototype.init = function() {
- this.addRule('connection.start', function(context) {
- var source = context.source;
- return canStartConnection(source);
- });
- this.addRule('connection.create', function(context) {
- var source = context.source,
- target = context.target,
- hints = context.hints || {},
- targetParent = hints.targetParent,
- targetAttach = hints.targetAttach;
- // don't allow incoming connections on
- // newly created boundary events
- // to boundary events
- if (targetAttach) {
- return false;
- }
- // temporarily set target parent for scoping
- // checks to work
- if (targetParent) {
- target.parent = targetParent;
- }
- try {
- return canConnect(source, target);
- } finally {
- // unset temporary target parent
- if (targetParent) {
- target.parent = null;
- }
- }
- });
- this.addRule('connection.reconnect', function(context) {
- var connection = context.connection,
- source = context.source,
- target = context.target;
- return canConnect(source, target, connection);
- });
- this.addRule('connection.updateWaypoints', function(context) {
- return {
- type: context.connection.type
- };
- });
- this.addRule('shape.resize', function(context) {
- var shape = context.shape,
- newBounds = context.newBounds;
- return canResize(shape, newBounds);
- });
- this.addRule('elements.create', function(context) {
- var elements = context.elements,
- position = context.position,
- target = context.target;
- return every(elements, function(element) {
- if (isConnection(element)) {
- return canConnect(element.source, element.target, element);
- }
- if (element.host) {
- return canAttach(element, element.host, null, position);
- }
- return canCreate(element, target, null, position);
- });
- });
- this.addRule('elements.move', function(context) {
- var target = context.target,
- shapes = context.shapes,
- position = context.position;
- return canAttach(shapes, target, null, position) ||
- canReplace(shapes, target, position) ||
- canMove(shapes, target, position) ||
- canInsert(shapes, target, position);
- });
- this.addRule('shape.create', function(context) {
- return canCreate(
- context.shape,
- context.target,
- context.source,
- context.position
- );
- });
- this.addRule('shape.attach', function(context) {
- return canAttach(
- context.shape,
- context.target,
- null,
- context.position
- );
- });
- this.addRule('element.copy', function(context) {
- var element = context.element,
- elements = context.elements;
- return canCopy(elements, element);
- });
- };
- BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow;
- BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;
- BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation;
- BpmnRules.prototype.canConnectAssociation = canConnectAssociation;
- BpmnRules.prototype.canMove = canMove;
- BpmnRules.prototype.canAttach = canAttach;
- BpmnRules.prototype.canReplace = canReplace;
- BpmnRules.prototype.canDrop = canDrop;
- BpmnRules.prototype.canInsert = canInsert;
- BpmnRules.prototype.canCreate = canCreate;
- BpmnRules.prototype.canConnect = canConnect;
- BpmnRules.prototype.canResize = canResize;
- BpmnRules.prototype.canCopy = canCopy;
- /**
- * Utility functions for rule checking
- */
- /**
- * Checks if given element can be used for starting connection.
- *
- * @param {Element} source
- * @return {boolean}
- */
- function canStartConnection(element) {
- if (nonExistingOrLabel(element)) {
- return null;
- }
- return isAny(element, [
- 'bpmn:FlowNode',
- 'bpmn:InteractionNode',
- 'bpmn:DataObjectReference',
- 'bpmn:DataStoreReference'
- ]);
- }
- function nonExistingOrLabel(element) {
- return !element || isLabel(element);
- }
- function isSame(a, b) {
- return a === b;
- }
- function getOrganizationalParent(element) {
- do {
- if (is(element, 'bpmn:Process')) {
- return getBusinessObject(element);
- }
- if (is(element, 'bpmn:Participant')) {
- return (
- getBusinessObject(element).processRef ||
- getBusinessObject(element)
- );
- }
- } while ((element = element.parent));
- }
- function isTextAnnotation(element) {
- return is(element, 'bpmn:TextAnnotation');
- }
- function isGroup(element) {
- return is(element, 'bpmn:Group') && !element.labelTarget;
- }
- function isCompensationBoundary(element) {
- return is(element, 'bpmn:BoundaryEvent') &&
- hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
- }
- function isForCompensation(e) {
- return getBusinessObject(e).isForCompensation;
- }
- function isSameOrganization(a, b) {
- var parentA = getOrganizationalParent(a),
- parentB = getOrganizationalParent(b);
- return parentA === parentB;
- }
- function isMessageFlowSource(element) {
- return (
- is(element, 'bpmn:InteractionNode') && (
- !is(element, 'bpmn:Event') || (
- is(element, 'bpmn:ThrowEvent') &&
- hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
- )
- )
- );
- }
- function isMessageFlowTarget(element) {
- return (
- is(element, 'bpmn:InteractionNode') &&
- !isForCompensation(element) && (
- !is(element, 'bpmn:Event') || (
- is(element, 'bpmn:CatchEvent') &&
- hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
- )
- )
- );
- }
- function getScopeParent(element) {
- var parent = element;
- while ((parent = parent.parent)) {
- if (is(parent, 'bpmn:FlowElementsContainer')) {
- return getBusinessObject(parent);
- }
- if (is(parent, 'bpmn:Participant')) {
- return getBusinessObject(parent).processRef;
- }
- }
- return null;
- }
- function isSameScope(a, b) {
- var scopeParentA = getScopeParent(a),
- scopeParentB = getScopeParent(b);
- return scopeParentA === scopeParentB;
- }
- function hasEventDefinition(element, eventDefinition) {
- var bo = getBusinessObject(element);
- return !!find(bo.eventDefinitions || [], function(definition) {
- return is(definition, eventDefinition);
- });
- }
- function hasEventDefinitionOrNone(element, eventDefinition) {
- var bo = getBusinessObject(element);
- return (bo.eventDefinitions || []).every(function(definition) {
- return is(definition, eventDefinition);
- });
- }
- function isSequenceFlowSource(element) {
- return (
- is(element, 'bpmn:FlowNode') &&
- !is(element, 'bpmn:EndEvent') &&
- !isEventSubProcess(element) &&
- !(is(element, 'bpmn:IntermediateThrowEvent') &&
- hasEventDefinition(element, 'bpmn:LinkEventDefinition')
- ) &&
- !isCompensationBoundary(element) &&
- !isForCompensation(element)
- );
- }
- function isSequenceFlowTarget(element) {
- return (
- is(element, 'bpmn:FlowNode') &&
- !is(element, 'bpmn:StartEvent') &&
- !is(element, 'bpmn:BoundaryEvent') &&
- !isEventSubProcess(element) &&
- !(is(element, 'bpmn:IntermediateCatchEvent') &&
- hasEventDefinition(element, 'bpmn:LinkEventDefinition')
- ) &&
- !isForCompensation(element)
- );
- }
- function isEventBasedTarget(element) {
- return (
- is(element, 'bpmn:ReceiveTask') || (
- is(element, 'bpmn:IntermediateCatchEvent') && (
- hasEventDefinition(element, 'bpmn:MessageEventDefinition') ||
- hasEventDefinition(element, 'bpmn:TimerEventDefinition') ||
- hasEventDefinition(element, 'bpmn:ConditionalEventDefinition') ||
- hasEventDefinition(element, 'bpmn:SignalEventDefinition')
- )
- )
- );
- }
- function isConnection(element) {
- return element.waypoints;
- }
- function getParents(element) {
- var parents = [];
- while (element) {
- element = element.parent;
- if (element) {
- parents.push(element);
- }
- }
- return parents;
- }
- function isParent(possibleParent, element) {
- var allParents = getParents(element);
- return allParents.indexOf(possibleParent) !== -1;
- }
- function canConnect(source, target, connection) {
- if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
- return null;
- }
- if (!is(connection, 'bpmn:DataAssociation')) {
- if (canConnectMessageFlow(source, target)) {
- return { type: 'bpmn:MessageFlow' };
- }
- if (canConnectSequenceFlow(source, target)) {
- return { type: 'bpmn:SequenceFlow' };
- }
- }
- var connectDataAssociation = canConnectDataAssociation(source, target);
- if (connectDataAssociation) {
- return connectDataAssociation;
- }
- if (isCompensationBoundary(source) && isForCompensation(target)) {
- return {
- type: 'bpmn:Association',
- associationDirection: 'One'
- };
- }
- if (canConnectAssociation(source, target)) {
- return {
- type: 'bpmn:Association'
- };
- }
- return false;
- }
- /**
- * Can an element be dropped into the target element
- *
- * @return {boolean}
- */
- function canDrop(element, target, position) {
- // can move labels and groups everywhere
- if (isLabel(element) || isGroup(element)) {
- return true;
- }
- // disallow to create elements on collapsed pools
- if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
- return false;
- }
- // allow to create new participants on
- // existing collaboration and process diagrams
- if (is(element, 'bpmn:Participant')) {
- return is(target, 'bpmn:Process') || is(target, 'bpmn:Collaboration');
- }
- // allow moving DataInput / DataOutput within its original container only
- if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) {
- if (element.parent) {
- return target === element.parent;
- }
- }
- // allow creating lanes on participants and other lanes only
- if (is(element, 'bpmn:Lane')) {
- return is(target, 'bpmn:Participant') || is(target, 'bpmn:Lane');
- }
- // disallow dropping boundary events which cannot replace with intermediate event
- if (is(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) {
- return false;
- }
- // drop flow elements onto flow element containers
- // and participants
- if (is(element, 'bpmn:FlowElement') && !is(element, 'bpmn:DataStoreReference')) {
- if (is(target, 'bpmn:FlowElementsContainer')) {
- return isExpanded(target);
- }
- return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]);
- }
- // account for the fact that data associations are always
- // rendered and moved to top (Process or Collaboration level)
- //
- // artifacts may be placed wherever, too
- if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) {
- return isAny(target, [
- 'bpmn:Collaboration',
- 'bpmn:Lane',
- 'bpmn:Participant',
- 'bpmn:Process',
- 'bpmn:SubProcess' ]);
- }
- if (is(element, 'bpmn:MessageFlow')) {
- return is(target, 'bpmn:Collaboration')
- || element.source.parent == target
- || element.target.parent == target;
- }
- return false;
- }
- function isDroppableBoundaryEvent(event) {
- return getBusinessObject(event).cancelActivity && (
- hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event)
- );
- }
- function isBoundaryEvent(element) {
- return !isLabel(element) && is(element, 'bpmn:BoundaryEvent');
- }
- function isLane(element) {
- return is(element, 'bpmn:Lane');
- }
- /**
- * We treat IntermediateThrowEvents as boundary events during create,
- * this must be reflected in the rules.
- */
- function isBoundaryCandidate(element) {
- if (isBoundaryEvent(element)) {
- return true;
- }
- if (is(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) {
- return true;
- }
- return (
- is(element, 'bpmn:IntermediateCatchEvent') &&
- hasCommonBoundaryIntermediateEventDefinition(element)
- );
- }
- function hasNoEventDefinition(element) {
- var bo = getBusinessObject(element);
- return bo && !(bo.eventDefinitions && bo.eventDefinitions.length);
- }
- function hasCommonBoundaryIntermediateEventDefinition(element) {
- return hasOneOfEventDefinitions(element, [
- 'bpmn:MessageEventDefinition',
- 'bpmn:TimerEventDefinition',
- 'bpmn:SignalEventDefinition',
- 'bpmn:ConditionalEventDefinition'
- ]);
- }
- function hasOneOfEventDefinitions(element, eventDefinitions) {
- return eventDefinitions.some(function(definition) {
- return hasEventDefinition(element, definition);
- });
- }
- function isReceiveTaskAfterEventBasedGateway(element) {
- return (
- is(element, 'bpmn:ReceiveTask') &&
- find(element.incoming, function(incoming) {
- return is(incoming.source, 'bpmn:EventBasedGateway');
- })
- );
- }
- function canAttach(elements, target, source, position) {
- if (!Array.isArray(elements)) {
- elements = [ elements ];
- }
- // only (re-)attach one element at a time
- if (elements.length !== 1) {
- return false;
- }
- var element = elements[0];
- // do not attach labels
- if (isLabel(element)) {
- return false;
- }
- // only handle boundary events
- if (!isBoundaryCandidate(element)) {
- return false;
- }
- // disallow drop on event sub processes
- if (isEventSubProcess(target)) {
- return false;
- }
- // only allow drop on non compensation activities
- if (!is(target, 'bpmn:Activity') || isForCompensation(target)) {
- return false;
- }
- // only attach to subprocess border
- if (position && !isBoundaryAttachment(position, target)) {
- return false;
- }
- // do not attach on receive tasks after event based gateways
- if (isReceiveTaskAfterEventBasedGateway(target)) {
- return false;
- }
- return 'attach';
- }
- /**
- * Defines how to replace elements for a given target.
- *
- * Returns an array containing all elements which will be replaced.
- *
- * @example
- *
- * [{ id: 'IntermediateEvent_2',
- * type: 'bpmn:StartEvent'
- * },
- * { id: 'IntermediateEvent_5',
- * type: 'bpmn:EndEvent'
- * }]
- *
- * @param {Array} elements
- * @param {Object} target
- *
- * @return {Object} an object containing all elements which have to be replaced
- */
- function canReplace(elements, target, position) {
- if (!target) {
- return false;
- }
- var canExecute = {
- replacements: []
- };
- forEach(elements, function(element) {
- if (!isEventSubProcess(target)) {
- if (is(element, 'bpmn:StartEvent') &&
- element.type !== 'label' &&
- canDrop(element, target)) {
- // replace a non-interrupting start event by a blank interrupting start event
- // when the target is not an event sub process
- if (!isInterrupting(element)) {
- canExecute.replacements.push({
- oldElementId: element.id,
- newElementType: 'bpmn:StartEvent'
- });
- }
- // replace an error/escalation/compensate start event by a blank interrupting start event
- // when the target is not an event sub process
- if (hasErrorEventDefinition(element) ||
- hasEscalationEventDefinition(element) ||
- hasCompensateEventDefinition(element)) {
- canExecute.replacements.push({
- oldElementId: element.id,
- newElementType: 'bpmn:StartEvent'
- });
- }
- }
- }
- if (!is(target, 'bpmn:Transaction')) {
- if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') &&
- element.type !== 'label') {
- if (is(element, 'bpmn:EndEvent') && canDrop(element, target)) {
- canExecute.replacements.push({
- oldElementId: element.id,
- newElementType: 'bpmn:EndEvent'
- });
- }
- if (is(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
- canExecute.replacements.push({
- oldElementId: element.id,
- newElementType: 'bpmn:BoundaryEvent'
- });
- }
- }
- }
- });
- return canExecute.replacements.length ? canExecute : false;
- }
- function canMove(elements, target) {
- // do not move selection containing lanes
- if (some(elements, isLane)) {
- return false;
- }
- // allow default move check to start move operation
- if (!target) {
- return true;
- }
- return elements.every(function(element) {
- return canDrop(element, target);
- });
- }
- function canCreate(shape, target, source, position) {
- if (!target) {
- return false;
- }
- if (isLabel(shape) || isGroup(shape)) {
- return true;
- }
- if (isSame(source, target)) {
- return false;
- }
- // ensure we do not drop the element
- // into source
- if (source && isParent(source, target)) {
- return false;
- }
- return canDrop(shape, target, position) || canInsert(shape, target, position);
- }
- function canResize(shape, newBounds) {
- if (is(shape, 'bpmn:SubProcess')) {
- return (
- isExpanded(shape) && (
- !newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
- )
- );
- }
- if (is(shape, 'bpmn:Lane')) {
- return !newBounds || (newBounds.width >= 130 && newBounds.height >= 60);
- }
- if (is(shape, 'bpmn:Participant')) {
- return !newBounds || (newBounds.width >= 250 && newBounds.height >= 50);
- }
- if (isTextAnnotation(shape)) {
- return true;
- }
- if (isGroup(shape)) {
- return true;
- }
- return false;
- }
- /**
- * Check, whether one side of the relationship
- * is a text annotation.
- */
- function isOneTextAnnotation(source, target) {
- var sourceTextAnnotation = isTextAnnotation(source),
- targetTextAnnotation = isTextAnnotation(target);
- return (
- (sourceTextAnnotation || targetTextAnnotation) &&
- (sourceTextAnnotation !== targetTextAnnotation)
- );
- }
- function canConnectAssociation(source, target) {
- // do not connect connections
- if (isConnection(source) || isConnection(target)) {
- return false;
- }
- // compensation boundary events are exception
- if (isCompensationBoundary(source) && isForCompensation(target)) {
- return true;
- }
- // don't connect parent <-> child
- if (isParent(target, source) || isParent(source, target)) {
- return false;
- }
- // allow connection of associations between <!TextAnnotation> and <TextAnnotation>
- if (isOneTextAnnotation(source, target)) {
- return true;
- }
- // can connect associations where we can connect
- // data associations, too (!)
- return !!canConnectDataAssociation(source, target);
- }
- function canConnectMessageFlow(source, target) {
- // during connect user might move mouse out of canvas
- // https://github.com/bpmn-io/bpmn-js/issues/1033
- if (getRootElement(source) && !getRootElement(target)) {
- return false;
- }
- return (
- isMessageFlowSource(source) &&
- isMessageFlowTarget(target) &&
- !isSameOrganization(source, target)
- );
- }
- function canConnectSequenceFlow(source, target) {
- if (
- isEventBasedTarget(target) &&
- target.incoming.length > 0 &&
- areOutgoingEventBasedGatewayConnections(target.incoming) &&
- !is(source, 'bpmn:EventBasedGateway')
- ) {
- return false;
- }
- return isSequenceFlowSource(source) &&
- isSequenceFlowTarget(target) &&
- isSameScope(source, target) &&
- !(is(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
- }
- function canConnectDataAssociation(source, target) {
- if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
- isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) {
- return { type: 'bpmn:DataInputAssociation' };
- }
- if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
- isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) {
- return { type: 'bpmn:DataOutputAssociation' };
- }
- return false;
- }
- function canInsert(shape, flow, position) {
- if (!flow) {
- return false;
- }
- if (Array.isArray(shape)) {
- if (shape.length !== 1) {
- return false;
- }
- shape = shape[0];
- }
- if (flow.source === shape ||
- flow.target === shape) {
- return false;
- }
- // return true if we can drop on the
- // underlying flow parent
- //
- // at this point we are not really able to talk
- // about connection rules (yet)
- return (
- isAny(flow, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
- !isLabel(flow) &&
- is(shape, 'bpmn:FlowNode') &&
- !is(shape, 'bpmn:BoundaryEvent') &&
- canDrop(shape, flow.parent, position));
- }
- function includes(elements, element) {
- return (elements && element) && elements.indexOf(element) !== -1;
- }
- function canCopy(elements, element) {
- if (isLabel(element)) {
- return true;
- }
- if (is(element, 'bpmn:Lane') && !includes(elements, element.parent)) {
- return false;
- }
- return true;
- }
- function isOutgoingEventBasedGatewayConnection(connection) {
- if (connection && connection.source) {
- return is(connection.source, 'bpmn:EventBasedGateway');
- }
- }
- function areOutgoingEventBasedGatewayConnections(connections) {
- connections = connections || [];
- return connections.some(isOutgoingEventBasedGatewayConnection);
- }
- function getRootElement(element) {
- return getParent(element, 'bpmn:Process') || getParent(element, 'bpmn:Collaboration');
- }
|