import { find, forEach, isArray, isDefined, isObject, matchPattern, reduce, has, sortBy } from 'min-dash'; var DISALLOWED_PROPERTIES = [ 'artifacts', 'dataInputAssociations', 'dataOutputAssociations', 'default', 'flowElements', 'lanes', 'incoming', 'outgoing' ]; /** * @typedef {Function} listener * * @param {Object} context * @param {Array} context.propertyNames * @param {ModdleElement} context.sourceElement * @param {ModdleElement} context.targetElement * * @returns {Array|boolean} - Return properties to be copied or false to disallow * copying. */ /** * @typedef {Function} listener * * @param {Object} context * @param {ModdleElement} context.parent * @param {*} context.property * @param {string} context.propertyName * * @returns {*|boolean} - Return copied property or false to disallow * copying. */ /** * @typedef {Function} listener * * @param {Object} context * @param {ModdleElement} context.parent * @param {*} context.property * @param {string} context.propertyName * * @returns {boolean} - Return false to disallow * setting copied property. */ /** * Utility for copying model properties from source element to target element. * * @param {EventBus} eventBus * @param {BpmnFactory} bpmnFactory * @param {BpmnModdle} 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; 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 {Array} [propertyNames] * * @param {ModdleElement} */ ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames) { 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 }); 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); var canSetProperty = self._eventBus.fire('moddleCopy.canSetCopiedProperty', { parent: targetElement, property: copiedProperty, propertyName: propertyName }); if (canSetProperty === false) { return; } if (isDefined(copiedProperty)) { targetElement.set(propertyName, copiedProperty); } }); return targetElement; }; /** * Copy model property. * * @param {*} property * @param {ModdleElement} parent * @param {string} propertyName * * @returns {*} */ ModdleCopy.prototype.copyProperty = function(property, parent, propertyName) { var self = this; // allow others to copy property var copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', { parent: parent, property: property, propertyName: propertyName }); // 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 Ids and references if (propertyDescriptor.isId || propertyDescriptor.isReference) { return; } // copy arrays if (isArray(property)) { return reduce(property, function(childProperties, childProperty) { // recursion copiedProperty = self.copyProperty(childProperty, parent, propertyName); // copying might NOT be allowed if (copiedProperty) { copiedProperty.$parent = parent; 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); return copiedProperty; } // copy primitive properties return property; }; // 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 is(element, type) { return element && (typeof element.$instanceOf === 'function') && element.$instanceOf(type); }