ModdleCopy.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import {
  2. find,
  3. forEach,
  4. has,
  5. isArray,
  6. isDefined,
  7. isFunction,
  8. isObject,
  9. matchPattern,
  10. reduce,
  11. sortBy
  12. } from 'min-dash';
  13. var DISALLOWED_PROPERTIES = [
  14. 'artifacts',
  15. 'dataInputAssociations',
  16. 'dataOutputAssociations',
  17. 'default',
  18. 'flowElements',
  19. 'lanes',
  20. 'incoming',
  21. 'outgoing',
  22. 'categoryValue'
  23. ];
  24. /**
  25. * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
  26. * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
  27. * @typedef {import('../../model/Types').Moddle} Moddle
  28. *
  29. * @typedef {import('../../model/Types').ModdleElement} ModdleElement
  30. */
  31. /**
  32. * Utility for copying model properties from source element to target element.
  33. *
  34. * @param {EventBus} eventBus
  35. * @param {BpmnFactory} bpmnFactory
  36. * @param {Moddle} moddle
  37. */
  38. export default function ModdleCopy(eventBus, bpmnFactory, moddle) {
  39. this._bpmnFactory = bpmnFactory;
  40. this._eventBus = eventBus;
  41. this._moddle = moddle;
  42. // copy extension elements last
  43. eventBus.on('moddleCopy.canCopyProperties', function(context) {
  44. var propertyNames = context.propertyNames;
  45. if (!propertyNames || !propertyNames.length) {
  46. return;
  47. }
  48. return sortBy(propertyNames, function(propertyName) {
  49. return propertyName === 'extensionElements';
  50. });
  51. });
  52. // default check whether property can be copied
  53. eventBus.on('moddleCopy.canCopyProperty', function(context) {
  54. var parent = context.parent,
  55. parentDescriptor = isObject(parent) && parent.$descriptor,
  56. propertyName = context.propertyName;
  57. if (propertyName && DISALLOWED_PROPERTIES.indexOf(propertyName) !== -1) {
  58. // disallow copying property
  59. return false;
  60. }
  61. if (propertyName &&
  62. parentDescriptor &&
  63. !find(parentDescriptor.properties, matchPattern({ name: propertyName }))) {
  64. // disallow copying property
  65. return false;
  66. }
  67. });
  68. // do NOT allow to copy empty extension elements
  69. eventBus.on('moddleCopy.canSetCopiedProperty', function(context) {
  70. var property = context.property;
  71. if (is(property, 'bpmn:ExtensionElements') && (!property.values || !property.values.length)) {
  72. // disallow setting copied property
  73. return false;
  74. }
  75. });
  76. }
  77. ModdleCopy.$inject = [
  78. 'eventBus',
  79. 'bpmnFactory',
  80. 'moddle'
  81. ];
  82. /**
  83. * Copy model properties of source element to target element.
  84. *
  85. * @param {ModdleElement} sourceElement
  86. * @param {ModdleElement} targetElement
  87. * @param {string[]} [propertyNames]
  88. * @param {boolean} [clone=false]
  89. *
  90. * @return {ModdleElement}
  91. */
  92. ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames, clone = false) {
  93. var self = this;
  94. if (propertyNames && !isArray(propertyNames)) {
  95. propertyNames = [ propertyNames ];
  96. }
  97. propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor);
  98. var canCopyProperties = this._eventBus.fire('moddleCopy.canCopyProperties', {
  99. propertyNames: propertyNames,
  100. sourceElement: sourceElement,
  101. targetElement: targetElement,
  102. clone: clone
  103. });
  104. if (canCopyProperties === false) {
  105. return targetElement;
  106. }
  107. if (isArray(canCopyProperties)) {
  108. propertyNames = canCopyProperties;
  109. }
  110. // copy properties
  111. forEach(propertyNames, function(propertyName) {
  112. var sourceProperty;
  113. if (has(sourceElement, propertyName)) {
  114. sourceProperty = sourceElement.get(propertyName);
  115. }
  116. var copiedProperty = self.copyProperty(sourceProperty, targetElement, propertyName, clone);
  117. if (!isDefined(copiedProperty)) {
  118. return;
  119. }
  120. var canSetProperty = self._eventBus.fire('moddleCopy.canSetCopiedProperty', {
  121. parent: targetElement,
  122. property: copiedProperty,
  123. propertyName: propertyName
  124. });
  125. if (canSetProperty === false) {
  126. return;
  127. }
  128. // TODO(nikku): unclaim old IDs if ID property is copied over
  129. // this._moddle.getPropertyDescriptor(parent, propertyName)
  130. targetElement.set(propertyName, copiedProperty);
  131. });
  132. return targetElement;
  133. };
  134. /**
  135. * Copy model property.
  136. *
  137. * @param {any} property
  138. * @param {ModdleElement} parent
  139. * @param {string} propertyName
  140. * @param {boolean} [clone=false]
  141. *
  142. * @return {any}
  143. */
  144. ModdleCopy.prototype.copyProperty = function(property, parent, propertyName, clone = false) {
  145. var self = this;
  146. // allow others to copy property
  147. var copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', {
  148. parent: parent,
  149. property: property,
  150. propertyName: propertyName,
  151. clone: clone
  152. });
  153. // return if copying is NOT allowed
  154. if (copiedProperty === false) {
  155. return;
  156. }
  157. if (copiedProperty) {
  158. if (isObject(copiedProperty) && copiedProperty.$type && !copiedProperty.$parent) {
  159. copiedProperty.$parent = parent;
  160. }
  161. return copiedProperty;
  162. }
  163. var propertyDescriptor = this._moddle.getPropertyDescriptor(parent, propertyName);
  164. // do NOT copy references
  165. if (propertyDescriptor.isReference) {
  166. return;
  167. }
  168. // copy id
  169. if (propertyDescriptor.isId) {
  170. return property && this._copyId(property, parent, clone);
  171. }
  172. // copy arrays
  173. if (isArray(property)) {
  174. return reduce(property, function(childProperties, childProperty) {
  175. // recursion
  176. copiedProperty = self.copyProperty(childProperty, parent, propertyName, clone);
  177. // copying might NOT be allowed
  178. if (copiedProperty) {
  179. return childProperties.concat(copiedProperty);
  180. }
  181. return childProperties;
  182. }, []);
  183. }
  184. // copy model elements
  185. if (isObject(property) && property.$type) {
  186. if (this._moddle.getElementDescriptor(property).isGeneric) {
  187. return;
  188. }
  189. copiedProperty = self._bpmnFactory.create(property.$type);
  190. copiedProperty.$parent = parent;
  191. // recursion
  192. copiedProperty = self.copyElement(property, copiedProperty, null, clone);
  193. return copiedProperty;
  194. }
  195. // copy primitive properties
  196. return property;
  197. };
  198. ModdleCopy.prototype._copyId = function(id, element, clone = false) {
  199. if (clone) {
  200. return id;
  201. }
  202. // disallow if already taken
  203. if (this._moddle.ids.assigned(id)) {
  204. return;
  205. } else {
  206. this._moddle.ids.claim(id, element);
  207. return id;
  208. }
  209. };
  210. // helpers //////////
  211. export function getPropertyNames(descriptor, keepDefaultProperties) {
  212. return reduce(descriptor.properties, function(properties, property) {
  213. if (keepDefaultProperties && property.default) {
  214. return properties;
  215. }
  216. return properties.concat(property.name);
  217. }, []);
  218. }
  219. function is(element, type) {
  220. return element && isFunction(element.$instanceOf) && element.$instanceOf(type);
  221. }