feat(util/model): make property cloning pluggable
This introduces a 'property.clone' event that allows plugging into the cloning mechanism when cloning nested extension elements. Related to camunda/camunda-bpmn-moddle#35
This commit is contained in:
parent
2ecb9aeae4
commit
b37182b53b
|
@ -32,7 +32,7 @@ function removeProperties(element, properties) {
|
|||
|
||||
function BpmnCopyPaste(bpmnFactory, eventBus, copyPaste, clipboard, moddle, canvas, bpmnRules) {
|
||||
|
||||
var helper = new ModelCloneHelper();
|
||||
var helper = new ModelCloneHelper(eventBus);
|
||||
|
||||
copyPaste.registerDescriptor(function(element, descriptor) {
|
||||
var businessObject = getBusinessObject(element),
|
||||
|
|
|
@ -54,9 +54,9 @@ function toggeling(element, target) {
|
|||
/**
|
||||
* This module takes care of replacing BPMN elements
|
||||
*/
|
||||
function BpmnReplace(bpmnFactory, replace, selection, modeling, moddle) {
|
||||
function BpmnReplace(bpmnFactory, replace, selection, modeling, eventBus) {
|
||||
|
||||
var helper = new ModelCloneHelper(moddle);
|
||||
var helper = new ModelCloneHelper(eventBus);
|
||||
|
||||
/**
|
||||
* Prepares a new business object for the replacement element
|
||||
|
@ -188,6 +188,6 @@ function BpmnReplace(bpmnFactory, replace, selection, modeling, moddle) {
|
|||
this.replaceElement = replaceElement;
|
||||
}
|
||||
|
||||
BpmnReplace.$inject = [ 'bpmnFactory', 'replace', 'selection', 'modeling', 'moddle' ];
|
||||
BpmnReplace.$inject = [ 'bpmnFactory', 'replace', 'selection', 'modeling', 'eventBus' ];
|
||||
|
||||
module.exports = BpmnReplace;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
var forEach = require('lodash/collection/forEach'),
|
||||
filter = require('lodash/collection/filter'),
|
||||
any = require('lodash/collection/any'),
|
||||
sort = require('lodash/collection/sortBy'),
|
||||
isArray = require('lodash/lang/isArray');
|
||||
|
||||
var IGNORED_PROPERTIES = require('./ModelCloneUtils').IGNORED_PROPERTIES;
|
||||
|
@ -28,7 +29,9 @@ function isType(element, types) {
|
|||
* A bpmn properties cloning interface
|
||||
*
|
||||
*/
|
||||
function ModelCloneHelper() {}
|
||||
function ModelCloneHelper(eventBus) {
|
||||
this._eventBus = eventBus;
|
||||
}
|
||||
|
||||
module.exports = ModelCloneHelper;
|
||||
|
||||
|
@ -36,6 +39,12 @@ module.exports = ModelCloneHelper;
|
|||
ModelCloneHelper.prototype.clone = function(oldElement, newElement, properties) {
|
||||
this._newElement = newElement;
|
||||
|
||||
// we want the extensionElements to be cloned last
|
||||
// so that they can check certain properties
|
||||
properties = sort(properties, function(prop) {
|
||||
return prop === 'bpmn:extensionElements';
|
||||
});
|
||||
|
||||
forEach(properties, function(propName) {
|
||||
var oldElementProp = oldElement.get(propName),
|
||||
newElementProp = newElement.get(propName),
|
||||
|
@ -78,6 +87,8 @@ ModelCloneHelper.prototype.clone = function(oldElement, newElement, properties)
|
|||
};
|
||||
|
||||
ModelCloneHelper.prototype._deepClone = function _deepClone(element) {
|
||||
var eventBus = this._eventBus;
|
||||
|
||||
var newElement = this._newElement;
|
||||
|
||||
var newProp = element.$model.create(element.$type);
|
||||
|
@ -115,11 +126,19 @@ ModelCloneHelper.prototype._deepClone = function _deepClone(element) {
|
|||
return;
|
||||
}
|
||||
|
||||
// make sure only allowed extensionElements are copied
|
||||
if (element.$type === 'bpmn:ExtensionElements' &&
|
||||
extProp.meta && extProp.meta.allowedIn &&
|
||||
!isAllowedIn(extProp, newElement.$type)) {
|
||||
return;
|
||||
var canClone = eventBus.fire('property.clone', {
|
||||
newElement: newElement,
|
||||
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' &&
|
||||
extProp.meta && extProp.meta.allowedIn &&
|
||||
!isAllowedIn(extProp, newElement.$type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
newDeepProp = this._deepClone(property);
|
||||
|
|
|
@ -744,7 +744,10 @@
|
|||
"bpmn:BusinessRuleTask",
|
||||
"bpmn:ScriptTask",
|
||||
"bpmn:ReceiveTask",
|
||||
"bpmn:CallActivity"
|
||||
"bpmn:CallActivity",
|
||||
"bpmn:TimerEventDefinition",
|
||||
"bpmn:SignalEventDefinition",
|
||||
"bpmn:MultiInstanceLoopCharacteristics"
|
||||
]
|
||||
},
|
||||
"properties": [
|
||||
|
@ -777,7 +780,8 @@
|
|||
"bpmn:IntermediateThrowEvent",
|
||||
"bpmn:EndEvent",
|
||||
"bpmn:BoundaryEvent",
|
||||
"bpmn:CallActivity"
|
||||
"bpmn:CallActivity",
|
||||
"bpmn:SubProcess"
|
||||
]
|
||||
},
|
||||
"properties": [
|
||||
|
|
|
@ -10,13 +10,15 @@ var ModelCloneHelper = require('../../../lib/util/model/ModelCloneHelper');
|
|||
|
||||
var camundaPackage = require('../../fixtures/json/model/camunda');
|
||||
|
||||
var camundaModdleModule = require('./camunda-moddle');
|
||||
|
||||
function getProp(element, property) {
|
||||
return element && element.$model.properties.get(element, property);
|
||||
}
|
||||
|
||||
describe('util/ModelCloneHelper', function() {
|
||||
|
||||
var testModules = [ coreModule ];
|
||||
var testModules = [ camundaModdleModule, coreModule ];
|
||||
|
||||
var basicXML = require('../../fixtures/bpmn/basic.bpmn');
|
||||
|
||||
|
@ -29,8 +31,8 @@ describe('util/ModelCloneHelper', function() {
|
|||
|
||||
var helper;
|
||||
|
||||
beforeEach(inject(function(moddle) {
|
||||
helper = new ModelCloneHelper(moddle);
|
||||
beforeEach(inject(function(eventBus) {
|
||||
helper = new ModelCloneHelper(eventBus);
|
||||
}));
|
||||
|
||||
describe('simple', function() {
|
||||
|
@ -211,4 +213,160 @@ describe('util/ModelCloneHelper', function() {
|
|||
|
||||
});
|
||||
|
||||
|
||||
describe('special cases', function() {
|
||||
|
||||
it('failed job retry time cycle', inject(function(moddle) {
|
||||
|
||||
function createExtElems() {
|
||||
var retryTimeCycle = moddle.create('camunda:FailedJobRetryTimeCycle', { body: 'foobar' });
|
||||
|
||||
return moddle.create('bpmn:ExtensionElements', { values: [ retryTimeCycle ] });
|
||||
}
|
||||
|
||||
// given
|
||||
var timerEvtDef = moddle.create('bpmn:TimerEventDefinition', {
|
||||
timeDuration: 'foobar'
|
||||
});
|
||||
|
||||
var signalEvtDef = moddle.create('bpmn:SignalEventDefinition', {
|
||||
timeDuration: 'foobar'
|
||||
});
|
||||
|
||||
var multiInst = moddle.create('bpmn:MultiInstanceLoopCharacteristics');
|
||||
|
||||
var timerStartEvent = moddle.create('bpmn:StartEvent', {
|
||||
extensionElements: createExtElems(),
|
||||
eventDefinitions: [ timerEvtDef ]
|
||||
});
|
||||
|
||||
var signalStartEvt = moddle.create('bpmn:StartEvent', {
|
||||
extensionElements: createExtElems(),
|
||||
eventDefinitions: [ signalEvtDef ]
|
||||
});
|
||||
|
||||
var subProcess = moddle.create('bpmn:SubProcess', {
|
||||
extensionElements: createExtElems(),
|
||||
loopCharacteristics: multiInst
|
||||
});
|
||||
|
||||
var intCatchEvt = helper.clone(timerStartEvent, moddle.create('bpmn:IntermediateCatchEvent'), [
|
||||
'bpmn:extensionElements',
|
||||
'bpmn:eventDefinitions'
|
||||
]);
|
||||
|
||||
var startEvt = helper.clone(signalStartEvt, moddle.create('bpmn:StartEvent'), [
|
||||
'bpmn:extensionElements',
|
||||
'bpmn:eventDefinitions'
|
||||
]);
|
||||
|
||||
var newSubProcess = helper.clone(subProcess, moddle.create('bpmn:SubProcess'), [
|
||||
'bpmn:extensionElements',
|
||||
'bpmn:loopCharacteristics'
|
||||
]);
|
||||
|
||||
var intCatchEvtExtElems = intCatchEvt.extensionElements.values,
|
||||
startEvtExtElems = startEvt.extensionElements.values,
|
||||
newSubProcessExtElems = newSubProcess.extensionElements.values;
|
||||
|
||||
// then
|
||||
function expectTimeCycle(extElems) {
|
||||
expect(extElems[0].$type).to.equal('camunda:FailedJobRetryTimeCycle');
|
||||
expect(extElems[0].body).to.equal('foobar');
|
||||
}
|
||||
|
||||
expectTimeCycle(intCatchEvtExtElems);
|
||||
|
||||
expectTimeCycle(startEvtExtElems);
|
||||
|
||||
expectTimeCycle(newSubProcessExtElems);
|
||||
}));
|
||||
|
||||
|
||||
it('connector', inject(function(moddle) {
|
||||
|
||||
// given
|
||||
var connector = moddle.create('camunda:Connector', {
|
||||
connectorId: 'hello_connector'
|
||||
});
|
||||
|
||||
var extensionElements = moddle.create('bpmn:ExtensionElements', { values: [ connector ] });
|
||||
|
||||
var msgEvtDef = moddle.create('bpmn:MessageEventDefinition');
|
||||
|
||||
var msgIntermThrowEvt = moddle.create('bpmn:IntermediateThrowEvent', {
|
||||
extensionElements: extensionElements,
|
||||
eventDefinitions: [ msgEvtDef ]
|
||||
});
|
||||
|
||||
var clonedElement = helper.clone(msgIntermThrowEvt, moddle.create('bpmn:EndEvent'), [
|
||||
'bpmn:extensionElements',
|
||||
'bpmn:eventDefinitions'
|
||||
]);
|
||||
|
||||
var extElems = clonedElement.extensionElements.values;
|
||||
|
||||
// then
|
||||
expect(extElems[0].$type).to.equal('camunda:Connector');
|
||||
expect(extElems[0].connectorId).to.equal('hello_connector');
|
||||
}));
|
||||
|
||||
|
||||
it('field', inject(function(moddle) {
|
||||
|
||||
// given
|
||||
var field = moddle.create('camunda:Field', {
|
||||
name: 'hello_field'
|
||||
});
|
||||
|
||||
var extensionElements = moddle.create('bpmn:ExtensionElements', { values: [ field ] });
|
||||
|
||||
var msgEvtDef = moddle.create('bpmn:MessageEventDefinition');
|
||||
|
||||
var msgIntermThrowEvt = moddle.create('bpmn:IntermediateThrowEvent', {
|
||||
extensionElements: extensionElements,
|
||||
eventDefinitions: [ msgEvtDef ]
|
||||
});
|
||||
|
||||
var clonedElement = helper.clone(msgIntermThrowEvt, moddle.create('bpmn:EndEvent'), [
|
||||
'bpmn:extensionElements',
|
||||
'bpmn:eventDefinitions'
|
||||
]);
|
||||
|
||||
var extElems = clonedElement.extensionElements.values;
|
||||
|
||||
// then
|
||||
expect(extElems[0].$type).to.equal('camunda:Field');
|
||||
expect(extElems[0].name).to.equal('hello_field');
|
||||
}));
|
||||
|
||||
|
||||
it('not clone field', inject(function(moddle) {
|
||||
|
||||
// given
|
||||
var field = moddle.create('camunda:Field', {
|
||||
name: 'hello_field'
|
||||
});
|
||||
|
||||
var extensionElements = moddle.create('bpmn:ExtensionElements', { values: [ field ] });
|
||||
|
||||
var msgEvtDef = moddle.create('bpmn:MessageEventDefinition');
|
||||
|
||||
var msgIntermThrowEvt = moddle.create('bpmn:IntermediateThrowEvent', {
|
||||
extensionElements: extensionElements,
|
||||
eventDefinitions: [ msgEvtDef ]
|
||||
});
|
||||
|
||||
var clonedElement = helper.clone(msgIntermThrowEvt, moddle.create('bpmn:IntermediateThrowEvent'), [
|
||||
'bpmn:extensionElements'
|
||||
]);
|
||||
|
||||
var extElems = clonedElement.extensionElements;
|
||||
|
||||
// then
|
||||
expect(extElems.values).be.empty;
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
'use strict';
|
||||
|
||||
var any = require('lodash/collection/any');
|
||||
|
||||
var ALLOWED_TYPES = {
|
||||
FailedJobRetryTimeCycle: [ 'bpmn:StartEvent', 'bpmn:BoundaryEvent', 'bpmn:IntermediateCatchEvent', 'bpmn:Activity' ],
|
||||
Connector: [ 'bpmn:EndEvent', 'bpmn:IntermediateThrowEvent' ],
|
||||
Field: [ 'bpmn:EndEvent', 'bpmn:IntermediateThrowEvent' ]
|
||||
};
|
||||
|
||||
|
||||
function is(element, type) {
|
||||
return element && (typeof element.$instanceOf === 'function') && element.$instanceOf(type);
|
||||
}
|
||||
|
||||
function exists(element) {
|
||||
return element && element.length;
|
||||
}
|
||||
|
||||
function includesType(collection, type) {
|
||||
return exists(collection) && any(collection, function(element) {
|
||||
return is(element, type);
|
||||
});
|
||||
}
|
||||
|
||||
function anyType(element, types) {
|
||||
return any(types, function(type) {
|
||||
return is(element, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isAllowed(propName, propDescriptor, newElement) {
|
||||
var name = propDescriptor.name,
|
||||
types = ALLOWED_TYPES[ name.replace(/camunda:/, '') ];
|
||||
|
||||
return name === propName && anyType(newElement, types);
|
||||
}
|
||||
|
||||
|
||||
function CamundaModdleExtension(eventBus) {
|
||||
|
||||
eventBus.on('property.clone', function(context) {
|
||||
var newElement = context.newElement,
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CamundaModdleExtension.$inject = [ 'eventBus' ];
|
||||
|
||||
module.exports = {
|
||||
__init__: [ 'CamundaModdleExtension' ],
|
||||
CamundaModdleExtension: [ 'type', CamundaModdleExtension ]
|
||||
};
|
Loading…
Reference in New Issue