feat(util/model): improve the way we clone properties
Now we make sure we don't copy id or reference properties/elements.
This commit is contained in:
parent
af9a36b9b6
commit
2dcfb1b3c9
|
@ -2,9 +2,10 @@
|
|||
|
||||
var forEach = require('lodash/collection/forEach'),
|
||||
filter = require('lodash/collection/filter'),
|
||||
isArray = require('lodash/lang/isArray'),
|
||||
contains = require('lodash/collection/contains');
|
||||
any = require('lodash/collection/any'),
|
||||
isArray = require('lodash/lang/isArray');
|
||||
|
||||
var IGNORED_PROPERTIES = require('./ModelCloneUtils').IGNORED_PROPERTIES;
|
||||
|
||||
function isAllowedIn(extProp, type) {
|
||||
var allowedIn = extProp.meta.allowedIn;
|
||||
|
@ -17,107 +18,125 @@ function isAllowedIn(extProp, type) {
|
|||
return allowedIn.indexOf(type) !== -1;
|
||||
}
|
||||
|
||||
|
||||
function isAllowedIn(extProp, type) {
|
||||
var allowedIn = extProp.meta.allowedIn;
|
||||
|
||||
// '*' is a wildcard, which means any element is allowed to use this property
|
||||
if (allowedIn.length === 1 && allowedIn[0] === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return allowedIn.indexOf(type) !== -1;
|
||||
function isType(element, types) {
|
||||
return any(types, function(type) {
|
||||
return typeof element === type;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A bpmn properties cloning interface
|
||||
*
|
||||
* @param {Moddle} moddle
|
||||
*/
|
||||
function ModelCloneHelper(moddle) {
|
||||
this._moddle = moddle;
|
||||
}
|
||||
function ModelCloneHelper() {}
|
||||
|
||||
module.exports = ModelCloneHelper;
|
||||
|
||||
|
||||
ModelCloneHelper.prototype.clone = function(oldElement, newElement, properties) {
|
||||
var moddle = this._moddle;
|
||||
this._newElement = newElement;
|
||||
|
||||
forEach(properties, function(propName) {
|
||||
var oldElementProp = oldElement.$model.properties.get(oldElement, propName),
|
||||
newElementProp = newElement.$model.properties.get(newElement, propName);
|
||||
var oldElementProp = oldElement.get(propName),
|
||||
newElementProp = newElement.get(propName),
|
||||
propDescriptor = newElement.$model.getPropertyDescriptor(newElement, propName),
|
||||
name;
|
||||
|
||||
if (newElementProp === oldElementProp) {
|
||||
// we're not interested in cloning:
|
||||
// - same values from simple types
|
||||
// - cloning id's
|
||||
// - cloning reference elements
|
||||
if (newElementProp === oldElementProp ||
|
||||
(propDescriptor && (propDescriptor.isId || propDescriptor.isReference))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if it's not an 'extensionElement' or 'documentation' just set the property
|
||||
if (!(contains([ 'bpmn:extensionElements', 'bpmn:documentation' ], propName))) {
|
||||
newElement.$model.properties.set(newElement, propName, oldElementProp);
|
||||
// if the property is of type 'boolean', 'string', 'number' or 'null', just set it
|
||||
if (isType(oldElementProp, [ 'boolean', 'string', 'number' ]) || oldElementProp === null) {
|
||||
newElement.set(propName, oldElementProp);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (propName === 'bpmn:extensionElements') {
|
||||
newElement.$model.properties.set(newElement, propName, moddle.create('bpmn:ExtensionElements', { values: [] }));
|
||||
|
||||
forEach(oldElementProp.values, function(extElement) {
|
||||
|
||||
var extProp = moddle.registry.typeMap[extElement.$type];
|
||||
|
||||
if (extProp.meta.allowedIn && isAllowedIn(extProp, newElement.$type)) {
|
||||
|
||||
var newProp = this._deepClone(extElement);
|
||||
|
||||
newProp.$parent = newElement.extensionElements;
|
||||
|
||||
newElement.extensionElements.values.push(newProp);
|
||||
}
|
||||
}, this);
|
||||
} else if (propName === 'bpmn:documentation') {
|
||||
newElement.documentation = [];
|
||||
if (isArray(oldElementProp)) {
|
||||
|
||||
forEach(oldElementProp, function(extElement) {
|
||||
var newProp = this._deepClone(extElement);
|
||||
|
||||
newProp.$parent = newElement;
|
||||
|
||||
newElement.documentation.push(newProp);
|
||||
newElementProp.push(newProp);
|
||||
}, this);
|
||||
} else {
|
||||
name = propName.replace(/bpmn:/, '');
|
||||
|
||||
newElement[name] = this._deepClone(oldElementProp);
|
||||
}
|
||||
}, this);
|
||||
|
||||
return newElement;
|
||||
};
|
||||
|
||||
ModelCloneHelper.prototype._deepClone = function _deepClone(extElement) {
|
||||
var newProp = extElement.$model.create(extElement.$type),
|
||||
properties = filter(Object.keys(extElement), function(prop) { return prop !== '$type'; });
|
||||
ModelCloneHelper.prototype._deepClone = function _deepClone(element) {
|
||||
var newElement = this._newElement;
|
||||
|
||||
var newProp = element.$model.create(element.$type);
|
||||
|
||||
var properties = filter(Object.keys(element), function(prop) {
|
||||
var descriptor = newProp.$model.getPropertyDescriptor(newProp, prop);
|
||||
|
||||
if (descriptor && (descriptor.isId || descriptor.isReference)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we need to make sure we don't clone certain properties
|
||||
// which we cannot easily know if they hold references or not
|
||||
if (IGNORED_PROPERTIES.indexOf(prop) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure we don't copy the type
|
||||
return prop !== '$type';
|
||||
});
|
||||
|
||||
forEach(properties, function(propName) {
|
||||
// check if the extElement has this property defined
|
||||
if (extElement[propName] !== undefined && (extElement[propName].$type || isArray(extElement[propName]))) {
|
||||
// check if the element has this property defined
|
||||
if (element[propName] !== undefined && (element[propName].$type || isArray(element[propName]))) {
|
||||
|
||||
if (isArray(extElement[propName])) {
|
||||
if (isArray(element[propName])) {
|
||||
newProp[propName] = [];
|
||||
|
||||
forEach(extElement[propName], function(property) {
|
||||
var newDeepProp = this._deepClone(property);
|
||||
forEach(element[propName], function(property) {
|
||||
var extProp = element.$model.getTypeDescriptor(property.$type),
|
||||
newDeepProp;
|
||||
|
||||
// we're not going to copy undefined types
|
||||
if (!extProp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure only allowed extensionElements are copied
|
||||
if (element.$type === 'bpmn:ExtensionElements' &&
|
||||
extProp.meta && extProp.meta.allowedIn &&
|
||||
!isAllowedIn(extProp, newElement.$type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
newDeepProp = this._deepClone(property);
|
||||
|
||||
newDeepProp.$parent = newProp;
|
||||
|
||||
newProp[propName].push(newDeepProp);
|
||||
}, this);
|
||||
|
||||
} else if (extElement[propName].$type) {
|
||||
newProp[propName] = this._deepClone(extElement[propName]);
|
||||
} else if (element[propName].$type) {
|
||||
newProp[propName] = this._deepClone(element[propName]);
|
||||
|
||||
newProp[propName].$parent = newProp;
|
||||
}
|
||||
} else {
|
||||
// just assign directly if it's a value
|
||||
newProp[propName] = extElement[propName];
|
||||
newProp[propName] = element[propName];
|
||||
}
|
||||
}, this);
|
||||
|
||||
|
|
|
@ -117,6 +117,9 @@ describe('util/ModelCloneHelper', function() {
|
|||
var executionListener = serviceTask.extensionElements.values[0];
|
||||
|
||||
// then
|
||||
expect(executionListener).to.not.equal(userTask.extensionElements.values[0]);
|
||||
expect(executionListener.$type).to.equal('camunda:ExecutionListener');
|
||||
|
||||
expect(executionListener.$type).to.equal('camunda:ExecutionListener');
|
||||
expect(executionListener.$parent).to.equal(serviceTask.extensionElements);
|
||||
|
||||
|
@ -182,6 +185,30 @@ describe('util/ModelCloneHelper', function() {
|
|||
expect(newOutParam.definition.items[0].value).to.equal('${1+1}');
|
||||
}));
|
||||
|
||||
|
||||
it('should not pass disallowed deeply nested property - connector', inject(function(moddle) {
|
||||
|
||||
// given
|
||||
var connector = moddle.create('camunda:Connector', {
|
||||
connectorId: 'hello_connector'
|
||||
});
|
||||
|
||||
var extensionElements = moddle.create('bpmn:ExtensionElements', { values: [ connector ] });
|
||||
|
||||
var serviceTask = moddle.create('bpmn:UserTask', {
|
||||
extensionElements: extensionElements
|
||||
});
|
||||
|
||||
var userTask = helper.clone(serviceTask, moddle.create('bpmn:UserTask'), [
|
||||
'bpmn:extensionElements'
|
||||
]);
|
||||
|
||||
var extElem = userTask.extensionElements;
|
||||
|
||||
// then
|
||||
expect(extElem.values).to.be.empty;
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue