Files
bpmn-js/lib/features/copy-paste/ModdleCopy.js

302 lines
7.1 KiB
JavaScript

import {
find,
forEach,
has,
isArray,
isDefined,
isObject,
matchPattern,
reduce,
sortBy
} from 'min-dash';
import { is, isAny } from '../../util/ModelUtil';
import { hasAnyEventDefinition } from '../../util/ModelUtil';
var DISALLOWED_PROPERTIES = [
'artifacts',
'dataInputAssociations',
'dataOutputAssociations',
'default',
'flowElements',
'lanes',
'incoming',
'outgoing',
'categoryValue'
];
/**
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
* @typedef {import('../../model/Types').Moddle} Moddle
*
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
*/
/**
* Utility for copying model properties from source element to target element.
*
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
* @param {Moddle} moddle
*/
export default function ModdleCopy(eventBus, bpmnFactory, moddle) {
this._bpmnFactory = bpmnFactory;
this._eventBus = eventBus;
this._moddle = moddle;
// copy extension elements last
eventBus.on('moddleCopy.canCopyProperties', function(context) {
var propertyNames = context.propertyNames;
if (!propertyNames || !propertyNames.length) {
return;
}
return sortBy(propertyNames, function(propertyName) {
return propertyName === 'extensionElements';
});
});
// default check whether property can be copied
eventBus.on('moddleCopy.canCopyProperty', function(context) {
var parent = context.parent,
parentDescriptor = isObject(parent) && parent.$descriptor,
propertyName = context.propertyName,
property = context.property;
// allow specific referenced elements to be copied
// check whether the property is a reference and belongs to event definition
if (isRefPropertyAllowed(property, parent)) {
return property;
}
if (propertyName && DISALLOWED_PROPERTIES.indexOf(propertyName) !== -1) {
// disallow copying property
return false;
}
if (propertyName &&
parentDescriptor &&
!find(parentDescriptor.properties, matchPattern({ name: propertyName }))) {
// disallow copying property
return false;
}
});
// do NOT allow to copy empty extension elements
eventBus.on('moddleCopy.canSetCopiedProperty', function(context) {
var property = context.property;
if (is(property, 'bpmn:ExtensionElements') && (!property.values || !property.values.length)) {
// disallow setting copied property
return false;
}
});
}
ModdleCopy.$inject = [
'eventBus',
'bpmnFactory',
'moddle'
];
/**
* Copy model properties of source element to target element.
*
* @param {ModdleElement} sourceElement
* @param {ModdleElement} targetElement
* @param {string[]} [propertyNames]
* @param {boolean} [clone=false]
*
* @return {ModdleElement}
*/
ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames, clone = false) {
var self = this;
if (propertyNames && !isArray(propertyNames)) {
propertyNames = [ propertyNames ];
}
propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor);
var canCopyProperties = this._eventBus.fire('moddleCopy.canCopyProperties', {
propertyNames: propertyNames,
sourceElement: sourceElement,
targetElement: targetElement,
clone: clone
});
if (canCopyProperties === false) {
return targetElement;
}
if (isArray(canCopyProperties)) {
propertyNames = canCopyProperties;
}
// copy properties
forEach(propertyNames, function(propertyName) {
var sourceProperty;
if (has(sourceElement, propertyName)) {
sourceProperty = sourceElement.get(propertyName);
}
var copiedProperty = self.copyProperty(sourceProperty, targetElement, propertyName, clone);
if (!isDefined(copiedProperty)) {
return;
}
var canSetProperty = self._eventBus.fire('moddleCopy.canSetCopiedProperty', {
parent: targetElement,
property: copiedProperty,
propertyName: propertyName
});
if (canSetProperty === false) {
return;
}
// TODO(nikku): unclaim old IDs if ID property is copied over
// this._moddle.getPropertyDescriptor(parent, propertyName)
targetElement.set(propertyName, copiedProperty);
});
return targetElement;
};
/**
* Copy model property.
*
* @param {any} property
* @param {ModdleElement} parent
* @param {string} propertyName
* @param {boolean} [clone=false]
*
* @return {any}
*/
ModdleCopy.prototype.copyProperty = function(property, parent, propertyName, clone = false) {
var self = this;
// allow others to copy property
var copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', {
parent: parent,
property: property,
propertyName: propertyName,
clone: clone
});
// return if copying is NOT allowed
if (copiedProperty === false) {
return;
}
if (copiedProperty) {
if (isObject(copiedProperty) && copiedProperty.$type && !copiedProperty.$parent) {
copiedProperty.$parent = parent;
}
return copiedProperty;
}
var propertyDescriptor = this._moddle.getPropertyDescriptor(parent, propertyName);
// do NOT copy references
if (propertyDescriptor.isReference) {
return;
}
// copy id
if (propertyDescriptor.isId) {
return property && this._copyId(property, parent, clone);
}
// copy arrays
if (isArray(property)) {
return reduce(property, function(childProperties, childProperty) {
// recursion
copiedProperty = self.copyProperty(childProperty, parent, propertyName, clone);
// copying might NOT be allowed
if (copiedProperty) {
return childProperties.concat(copiedProperty);
}
return childProperties;
}, []);
}
// copy model elements
if (isObject(property) && property.$type) {
if (this._moddle.getElementDescriptor(property).isGeneric) {
return;
}
copiedProperty = self._bpmnFactory.create(property.$type);
copiedProperty.$parent = parent;
// recursion
copiedProperty = self.copyElement(property, copiedProperty, null, clone);
return copiedProperty;
}
// copy primitive properties
return property;
};
ModdleCopy.prototype._copyId = function(id, element, clone = false) {
if (clone) {
return id;
}
// disallow if already taken
if (this._moddle.ids.assigned(id)) {
return;
} else {
this._moddle.ids.claim(id, element);
return id;
}
};
// helpers //////////
export function getPropertyNames(descriptor, keepDefaultProperties) {
return reduce(descriptor.properties, function(properties, property) {
if (keepDefaultProperties && property.default) {
return properties;
}
return properties.concat(property.name);
}, []);
}
function isRefPropertyAllowed(property, parent) {
const allowedEventDefTypes = [
'bpmn:ErrorEventDefinition',
'bpmn:EscalationEventDefinition',
'bpmn:MessageEventDefinition',
'bpmn:SignalEventDefinition'
];
const allowedPropertyTypes = [
'bpmn:Error',
'bpmn:Message',
'bpmn:Signal',
'bpmn:Escalation'
];
return hasAnyEventDefinition(parent, allowedEventDefTypes) &&
isAny(property, allowedPropertyTypes);
}