diff --git a/lib/util/model/ModelCloneHelper.js b/lib/util/model/ModelCloneHelper.js index 776fc0ee..4dad2120 100644 --- a/lib/util/model/ModelCloneHelper.js +++ b/lib/util/model/ModelCloneHelper.js @@ -36,8 +36,10 @@ function ModelCloneHelper(eventBus) { module.exports = ModelCloneHelper; -ModelCloneHelper.prototype.clone = function(oldElement, newElement, properties) { - this._newElement = newElement; +ModelCloneHelper.prototype.clone = function(refElement, newElement, properties) { + var context = { + newElement: newElement + }; // we want the extensionElements to be cloned last // so that they can check certain properties @@ -46,7 +48,7 @@ ModelCloneHelper.prototype.clone = function(oldElement, newElement, properties) }); forEach(properties, function(propName) { - var oldElementProp = oldElement.get(propName), + var refElementProp = refElement.get(propName), newElementProp = newElement.get(propName), propDescriptor = newElement.$model.getPropertyDescriptor(newElement, propName), name; @@ -55,22 +57,26 @@ ModelCloneHelper.prototype.clone = function(oldElement, newElement, properties) // - same values from simple types // - cloning id's // - cloning reference elements - if (newElementProp === oldElementProp || + if (newElementProp === refElementProp || (propDescriptor && (propDescriptor.isId || propDescriptor.isReference))) { return; } // 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); + if (isType(refElementProp, [ 'boolean', 'string', 'number' ]) || refElementProp === null) { + newElement.set(propName, refElementProp); return; } - if (isArray(oldElementProp)) { + if (isArray(refElementProp)) { - forEach(oldElementProp, function(extElement) { - var newProp = this._deepClone(extElement); + forEach(refElementProp, function(extElement) { + var newProp; + + context.refTopLevelProperty = extElement; + + newProp = this._deepClone(extElement, context); newProp.$parent = newElement; @@ -79,21 +85,21 @@ ModelCloneHelper.prototype.clone = function(oldElement, newElement, properties) } else { name = propName.replace(/bpmn:/, ''); - newElement[name] = this._deepClone(oldElementProp); + context.refTopLevelProperty = refElementProp; + + newElement[name] = this._deepClone(refElementProp, context); } }, this); return newElement; }; -ModelCloneHelper.prototype._deepClone = function _deepClone(element) { +ModelCloneHelper.prototype._deepClone = function _deepClone(propertyElement, context) { var eventBus = this._eventBus; - var newElement = this._newElement; + var newProp = propertyElement.$model.create(propertyElement.$type); - var newProp = element.$model.create(element.$type); - - var properties = filter(Object.keys(element), function(prop) { + var properties = filter(Object.keys(propertyElement), function(prop) { var descriptor = newProp.$model.getPropertyDescriptor(newProp, prop); if (descriptor && (descriptor.isId || descriptor.isReference)) { @@ -111,14 +117,15 @@ ModelCloneHelper.prototype._deepClone = function _deepClone(element) { }); forEach(properties, function(propName) { - // check if the element has this property defined - if (element[propName] !== undefined && (element[propName].$type || isArray(element[propName]))) { + // check if the propertyElement has this property defined + if (propertyElement[propName] !== undefined && + (propertyElement[propName].$type || isArray(propertyElement[propName]))) { - if (isArray(element[propName])) { + if (isArray(propertyElement[propName])) { newProp[propName] = []; - forEach(element[propName], function(property) { - var extProp = element.$model.getTypeDescriptor(property.$type), + forEach(propertyElement[propName], function(property) { + var extProp = propertyElement.$model.getTypeDescriptor(property.$type), newDeepProp; // we're not going to copy undefined types @@ -127,35 +134,36 @@ ModelCloneHelper.prototype._deepClone = function _deepClone(element) { } var canClone = eventBus.fire('property.clone', { - newElement: newElement, + newElement: context.newElement, + refTopLevelProperty: context.refTopLevelProperty, propertyDescriptor: extProp }); if (!canClone) { // if can clone is 'undefined' or 'false' // check for the meta information if it is allowed - if (element.$type === 'bpmn:ExtensionElements' && + if (propertyElement.$type === 'bpmn:ExtensionElements' && extProp.meta && extProp.meta.allowedIn && - !isAllowedIn(extProp, newElement.$type)) { + !isAllowedIn(extProp, context.newElement.$type)) { return false; } } - newDeepProp = this._deepClone(property); + newDeepProp = this._deepClone(property, context); newDeepProp.$parent = newProp; newProp[propName].push(newDeepProp); }, this); - } else if (element[propName].$type) { - newProp[propName] = this._deepClone(element[propName]); + } else if (propertyElement[propName].$type) { + newProp[propName] = this._deepClone(propertyElement[propName], context); newProp[propName].$parent = newProp; } } else { // just assign directly if it's a value - newProp[propName] = element[propName]; + newProp[propName] = propertyElement[propName]; } }, this); diff --git a/test/spec/util/ModelCloneHelperSpec.js b/test/spec/util/ModelCloneHelperSpec.js index 261c8113..b95f55a1 100644 --- a/test/spec/util/ModelCloneHelperSpec.js +++ b/test/spec/util/ModelCloneHelperSpec.js @@ -276,16 +276,14 @@ describe('util/ModelCloneHelper', function() { newSubProcessExtElems = newSubProcess.extensionElements.values; // then - function expectTimeCycle(extElems) { - expect(extElems[0].$type).to.equal('camunda:FailedJobRetryTimeCycle'); - expect(extElems[0].body).to.equal('foobar'); - } + expect(intCatchEvtExtElems[0].$type).to.equal('camunda:FailedJobRetryTimeCycle'); + expect(intCatchEvtExtElems[0].body).to.equal('foobar'); - expectTimeCycle(intCatchEvtExtElems); + expect(startEvtExtElems[0].$type).to.equal('camunda:FailedJobRetryTimeCycle'); + expect(startEvtExtElems[0].body).to.equal('foobar'); - expectTimeCycle(startEvtExtElems); - - expectTimeCycle(newSubProcessExtElems); + expect(newSubProcessExtElems[0].$type).to.equal('camunda:FailedJobRetryTimeCycle'); + expect(newSubProcessExtElems[0].body).to.equal('foobar'); })); @@ -298,10 +296,11 @@ describe('util/ModelCloneHelper', function() { var extensionElements = moddle.create('bpmn:ExtensionElements', { values: [ connector ] }); - var msgEvtDef = moddle.create('bpmn:MessageEventDefinition'); + var msgEvtDef = moddle.create('bpmn:MessageEventDefinition', { + extensionElements: extensionElements + }); var msgIntermThrowEvt = moddle.create('bpmn:IntermediateThrowEvent', { - extensionElements: extensionElements, eventDefinitions: [ msgEvtDef ] }); @@ -310,7 +309,7 @@ describe('util/ModelCloneHelper', function() { 'bpmn:eventDefinitions' ]); - var extElems = clonedElement.extensionElements.values; + var extElems = clonedElement.eventDefinitions[0].extensionElements.values; // then expect(extElems[0].$type).to.equal('camunda:Connector'); @@ -327,10 +326,11 @@ describe('util/ModelCloneHelper', function() { var extensionElements = moddle.create('bpmn:ExtensionElements', { values: [ field ] }); - var msgEvtDef = moddle.create('bpmn:MessageEventDefinition'); + var msgEvtDef = moddle.create('bpmn:MessageEventDefinition', { + extensionElements: extensionElements + }); var msgIntermThrowEvt = moddle.create('bpmn:IntermediateThrowEvent', { - extensionElements: extensionElements, eventDefinitions: [ msgEvtDef ] }); @@ -339,7 +339,7 @@ describe('util/ModelCloneHelper', function() { 'bpmn:eventDefinitions' ]); - var extElems = clonedElement.extensionElements.values; + var extElems = clonedElement.eventDefinitions[0].extensionElements.values; // then expect(extElems[0].$type).to.equal('camunda:Field'); @@ -356,10 +356,11 @@ describe('util/ModelCloneHelper', function() { var extensionElements = moddle.create('bpmn:ExtensionElements', { values: [ field ] }); - var msgEvtDef = moddle.create('bpmn:MessageEventDefinition'); + var msgEvtDef = moddle.create('bpmn:MessageEventDefinition', { + extensionElements: extensionElements + }); var msgIntermThrowEvt = moddle.create('bpmn:IntermediateThrowEvent', { - extensionElements: extensionElements, eventDefinitions: [ msgEvtDef ] }); @@ -367,10 +368,8 @@ describe('util/ModelCloneHelper', function() { 'bpmn:extensionElements' ]); - var extElems = clonedElement.extensionElements; - // then - expect(extElems.values).be.empty; + expect(clonedElement.eventDefinitions).to.be.empty; })); }); diff --git a/test/spec/util/camunda-moddle.js b/test/spec/util/camunda-moddle.js index 71bcbba0..65c2956f 100644 --- a/test/spec/util/camunda-moddle.js +++ b/test/spec/util/camunda-moddle.js @@ -41,27 +41,30 @@ function CamundaModdleExtension(eventBus) { eventBus.on('property.clone', function(context) { var newElement = context.newElement, + refTopLevelProperty = context.refTopLevelProperty, propDescriptor = context.propertyDescriptor; - if (isAllowed('camunda:FailedJobRetryTimeCycle', propDescriptor, newElement)) { - return includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') || - includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') || - is(newElement.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics'); - } - - if (isAllowed('camunda:Connector', propDescriptor, newElement)) { - return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition'); - } - - if (isAllowed('camunda:Field', propDescriptor, newElement)) { - return includesType(newElement.eventDefinitions, 'bpmn:MessageEventDefinition'); - } - }); + return this.canCloneProperty(newElement, refTopLevelProperty, propDescriptor); + }, this); } CamundaModdleExtension.$inject = [ 'eventBus' ]; -module.exports = { - __init__: [ 'CamundaModdleExtension' ], - CamundaModdleExtension: [ 'type', CamundaModdleExtension ] +CamundaModdleExtension.prototype.canCloneProperty = function(newElement, refTopLevelProperty, propDescriptor) { + + if (isAllowed('camunda:FailedJobRetryTimeCycle', propDescriptor, newElement)) { + return includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') || + includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') || + is(newElement.loopCharacteristics, 'bpmn:MultiInstanceLoopCharacteristics'); + } + + if (isAllowed('camunda:Connector', propDescriptor, newElement) || + isAllowed('camunda:Field', propDescriptor, newElement)) { + return is(refTopLevelProperty, 'bpmn:MessageEventDefinition'); + } +}; + +module.exports = { + __init__: [ 'camundaModdleExtension' ], + camundaModdleExtension: [ 'type', CamundaModdleExtension ] };