diff --git a/lib/features/modeling/behavior/RootElementReferenceBehavior.js b/lib/features/modeling/behavior/RootElementReferenceBehavior.js
new file mode 100644
index 00000000..8d6aef0a
--- /dev/null
+++ b/lib/features/modeling/behavior/RootElementReferenceBehavior.js
@@ -0,0 +1,165 @@
+import inherits from 'inherits';
+
+import {
+ find,
+ isArray,
+ matchPattern,
+ some
+} from 'min-dash';
+
+import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
+
+import {
+ add as collectionAdd,
+ remove as collectionRemove
+} from 'diagram-js/lib/util/Collections';
+
+import {
+ getBusinessObject,
+ is
+} from '../../../util/ModelUtil';
+
+import { hasEventDefinition } from '../../../util/DiUtil';
+
+var LOW_PRIORITY = 500;
+
+
+/**
+ * Add referenced root elements (error, escalation, message, signal) if they don't exist.
+ * Copy referenced root elements on copy & paste.
+ */
+export default function RootElementReferenceBehavior(bpmnjs, eventBus, injector) {
+ injector.invoke(CommandInterceptor, this);
+
+ function hasRootElement(rootElement) {
+ var definitions = bpmnjs.getDefinitions(),
+ rootElements = definitions.get('rootElements');
+
+ return !!find(rootElements, matchPattern({ id: rootElement.id }));
+ }
+
+ function getRootElementReferencePropertyName(eventDefinition) {
+ if (is(eventDefinition, 'bpmn:ErrorEventDefinition')) {
+ return 'errorRef';
+ } else if (is(eventDefinition, 'bpmn:EscalationEventDefinition')) {
+ return 'escalationRef';
+ } else if (is(eventDefinition, 'bpmn:MessageEventDefinition')) {
+ return 'messageRef';
+ } else if (is(eventDefinition, 'bpmn:SignalEventDefinition')) {
+ return 'signalRef';
+ }
+ }
+
+ function getRootElementReferenced(eventDefinition) {
+ return eventDefinition.get(getRootElementReferencePropertyName(eventDefinition));
+ }
+
+ // create shape
+ this.executed('shape.create', function(context) {
+ var shape = context.shape;
+
+ if (!hasAnyEventDefinition(shape, [
+ 'bpmn:ErrorEventDefinition',
+ 'bpmn:EscalationEventDefinition',
+ 'bpmn:MessageEventDefinition',
+ 'bpmn:SignalEventDefinition'
+ ])) {
+ return;
+ }
+
+ var businessObject = getBusinessObject(shape),
+ eventDefinitions = businessObject.get('eventDefinitions'),
+ eventDefinition = eventDefinitions[ 0 ],
+ rootElement = getRootElementReferenced(eventDefinition),
+ rootElements;
+
+ if (rootElement && !hasRootElement(rootElement)) {
+ rootElements = bpmnjs.getDefinitions().get('rootElements');
+
+ // add root element
+ collectionAdd(rootElements, rootElement);
+
+ context.addedRootElement = rootElement;
+ }
+ }, true);
+
+ this.reverted('shape.create', function(context) {
+ var addedRootElement = context.addedRootElement;
+
+ if (!addedRootElement) {
+ return;
+ }
+
+ var rootElements = bpmnjs.getDefinitions().get('rootElements');
+
+ // remove root element
+ collectionRemove(rootElements, addedRootElement);
+ }, true);
+
+ eventBus.on('copyPaste.copyElement', function(context) {
+ var descriptor = context.descriptor,
+ element = context.element;
+
+ if (!hasAnyEventDefinition(element, [
+ 'bpmn:ErrorEventDefinition',
+ 'bpmn:EscalationEventDefinition',
+ 'bpmn:MessageEventDefinition',
+ 'bpmn:SignalEventDefinition'
+ ])) {
+ return;
+ }
+
+ var businessObject = getBusinessObject(element),
+ eventDefinitions = businessObject.get('eventDefinitions'),
+ eventDefinition = eventDefinitions[ 0 ],
+ rootElement = getRootElementReferenced(eventDefinition);
+
+ if (rootElement) {
+ descriptor.referencedRootElement = rootElement;
+ }
+ });
+
+ eventBus.on('copyPaste.pasteElement', LOW_PRIORITY, function(context) {
+ var descriptor = context.descriptor,
+ businessObject = descriptor.businessObject;
+
+ if (!hasAnyEventDefinition(businessObject, [
+ 'bpmn:ErrorEventDefinition',
+ 'bpmn:EscalationEventDefinition',
+ 'bpmn:MessageEventDefinition',
+ 'bpmn:SignalEventDefinition'
+ ])) {
+ return;
+ }
+
+ var eventDefinitions = businessObject.get('eventDefinitions'),
+ eventDefinition = eventDefinitions[ 0 ],
+ referencedRootElement = descriptor.referencedRootElement;
+
+ if (!referencedRootElement) {
+ return;
+ }
+
+ eventDefinition.set(getRootElementReferencePropertyName(eventDefinition), referencedRootElement);
+ });
+}
+
+RootElementReferenceBehavior.$inject = [
+ 'bpmnjs',
+ 'eventBus',
+ 'injector'
+];
+
+inherits(RootElementReferenceBehavior, CommandInterceptor);
+
+// helpers //////////
+
+function hasAnyEventDefinition(element, types) {
+ if (!isArray(types)) {
+ types = [ types ];
+ }
+
+ return some(types, function(type) {
+ return hasEventDefinition(element, type);
+ });
+}
\ No newline at end of file
diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js
index 2979ef83..d26c4e19 100644
--- a/lib/features/modeling/behavior/index.js
+++ b/lib/features/modeling/behavior/index.js
@@ -3,6 +3,7 @@ import AppendBehavior from './AppendBehavior';
import AssociationBehavior from './AssociationBehavior';
import AttachEventBehavior from './AttachEventBehavior';
import BoundaryEventBehavior from './BoundaryEventBehavior';
+import RootElementReferenceBehavior from './RootElementReferenceBehavior';
import CreateBehavior from './CreateBehavior';
import FixHoverBehavior from './FixHoverBehavior';
import CreateDataObjectBehavior from './CreateDataObjectBehavior';
@@ -37,6 +38,7 @@ export default {
'associationBehavior',
'attachEventBehavior',
'boundaryEventBehavior',
+ 'rootElementReferenceBehavior',
'createBehavior',
'fixHoverBehavior',
'createDataObjectBehavior',
@@ -69,6 +71,7 @@ export default {
associationBehavior: [ 'type', AssociationBehavior ],
attachEventBehavior: [ 'type', AttachEventBehavior ],
boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
+ rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ],
createBehavior: [ 'type', CreateBehavior ],
fixHoverBehavior: [ 'type', FixHoverBehavior ],
createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ],
diff --git a/test/spec/features/modeling/behavior/RootElementReferenceBehavior.bpmn b/test/spec/features/modeling/behavior/RootElementReferenceBehavior.bpmn
new file mode 100644
index 00000000..f4f8083c
--- /dev/null
+++ b/test/spec/features/modeling/behavior/RootElementReferenceBehavior.bpmn
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/spec/features/modeling/behavior/RootElementReferenceBehaviorSpec.js b/test/spec/features/modeling/behavior/RootElementReferenceBehaviorSpec.js
new file mode 100644
index 00000000..6b4e6a3b
--- /dev/null
+++ b/test/spec/features/modeling/behavior/RootElementReferenceBehaviorSpec.js
@@ -0,0 +1,265 @@
+import {
+ bootstrapModeler,
+ getBpmnJS,
+ inject
+} from 'test/TestHelper';
+
+import coreModule from 'lib/core';
+import modelingModule from 'lib/features/modeling';
+
+import {
+ getBusinessObject,
+ is
+} from 'lib/util/ModelUtil';
+
+import {
+ remove as collectionRemove
+} from 'diagram-js/lib/util/Collections';
+
+import {
+ filter,
+ find,
+ forEach,
+ matchPattern
+} from 'min-dash';
+
+var testModules = [
+ coreModule,
+ modelingModule
+];
+
+
+describe('features/modeling - root element reference behavior', function() {
+
+ var diagramXML = require('./RootElementReferenceBehavior.bpmn');
+
+ beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
+
+
+ describe('add root element', function() {
+
+ forEach([
+ 'error',
+ 'escalation',
+ 'message',
+ 'signal'
+ ], function(type) {
+
+ describe(type, function() {
+
+ var id = capitalizeFirstChar(type) + 'BoundaryEvent_1';
+
+ var boundaryEvent,
+ host,
+ rootElement;
+
+ describe('should add', function() {
+
+ beforeEach(inject(function(bpmnjs, copyPaste, elementRegistry, modeling) {
+
+ // given
+ boundaryEvent = elementRegistry.get(id);
+
+ host = elementRegistry.get('Task_2');
+
+ var businessObject = getBusinessObject(boundaryEvent),
+ eventDefinitions = businessObject.get('eventDefinitions'),
+ eventDefinition = eventDefinitions[ 0 ];
+
+ rootElement = getRootElementReferenced(eventDefinition);
+
+ // when
+ copyPaste.copy(boundaryEvent);
+
+ modeling.removeShape(boundaryEvent);
+
+ collectionRemove(bpmnjs.getDefinitions().get('rootElements'), rootElement);
+
+ expect(hasRootElement(rootElement)).to.be.false;
+
+ boundaryEvent = copyPaste.paste({
+ element: host,
+ point: {
+ x: host.x,
+ y: host.y
+ },
+ hints: {
+ attach: 'attach'
+ }
+ })[0];
+ }));
+
+
+ it('', function() {
+
+ // then
+ expect(hasRootElement(rootElement)).to.be.true;
+ });
+
+
+ it('', inject(function(commandStack) {
+
+ // when
+ commandStack.undo();
+
+ // then
+ expect(hasRootElement(rootElement)).to.be.false;
+ }));
+
+
+ it('', inject(function(commandStack) {
+
+ // given
+ commandStack.undo();
+
+ // when
+ commandStack.redo();
+
+ // then
+ expect(hasRootElement(rootElement)).to.be.true;
+ }));
+
+ });
+
+
+ it('should NOT add', inject(function(bpmnFactory, bpmnjs, copyPaste, elementRegistry, moddleCopy, modeling) {
+
+ // given
+ boundaryEvent = elementRegistry.get(id);
+
+ host = elementRegistry.get('Task_2');
+
+ var businessObject = getBusinessObject(boundaryEvent),
+ eventDefinitions = businessObject.get('eventDefinitions'),
+ eventDefinition = eventDefinitions[ 0 ],
+ rootElements = bpmnjs.getDefinitions().get('rootElements');
+
+ rootElement = getRootElementReferenced(eventDefinition);
+
+ copyPaste.copy(boundaryEvent);
+
+ modeling.removeShape(boundaryEvent);
+
+ collectionRemove(rootElements, rootElement);
+
+ expect(hasRootElement(rootElement)).to.be.false;
+
+ var rootElementWithSameId = bpmnFactory.create(rootElement.$type);
+
+ moddleCopy.copyElement(rootElement, rootElementWithSameId);
+
+ collectionRemove(rootElements, rootElementWithSameId);
+
+ // when
+ boundaryEvent = copyPaste.paste({
+ element: host,
+ point: {
+ x: host.x,
+ y: host.y
+ },
+ hints: {
+ attach: 'attach'
+ }
+ })[0];
+
+ // then
+ var rootElementsOfType = filter(rootElements, matchPattern({ $type: rootElement.$type }));
+
+ expect(rootElementsOfType).to.have.lengthOf(1);
+ }));
+
+ });
+
+ });
+
+ });
+
+
+ describe('copy root element reference', function() {
+
+ forEach([
+ 'error',
+ 'escalation',
+ 'message',
+ 'signal'
+ ], function(type) {
+
+ describe(type, function() {
+
+ var id = capitalizeFirstChar(type) + 'BoundaryEvent_1';
+
+ var boundaryEvent,
+ host,
+ rootElement;
+
+ beforeEach(inject(function(copyPaste, elementRegistry) {
+
+ // given
+ boundaryEvent = elementRegistry.get(id);
+
+ host = elementRegistry.get('Task_2');
+
+ var businessObject = getBusinessObject(boundaryEvent),
+ eventDefinitions = businessObject.get('eventDefinitions'),
+ eventDefinition = eventDefinitions[ 0 ];
+
+ rootElement = getRootElementReferenced(eventDefinition);
+
+ copyPaste.copy(boundaryEvent);
+
+ // when
+ boundaryEvent = copyPaste.paste({
+ element: host,
+ point: {
+ x: host.x,
+ y: host.y
+ },
+ hints: {
+ attach: 'attach'
+ }
+ })[0];
+ }));
+
+
+ it('should copy root element reference', function() {
+
+ // then
+ var businessObject = getBusinessObject(boundaryEvent),
+ eventDefinitions = businessObject.get('eventDefinitions'),
+ eventDefinition = eventDefinitions[ 0 ];
+
+ expect(getRootElementReferenced(eventDefinition)).to.equal(rootElement);
+ });
+
+ });
+
+ });
+
+ });
+
+});
+
+// helpers //////////
+
+function getRootElementReferenced(eventDefinition) {
+ if (is(eventDefinition, 'bpmn:ErrorEventDefinition')) {
+ return eventDefinition.get('errorRef');
+ } else if (is(eventDefinition, 'bpmn:EscalationEventDefinition')) {
+ return eventDefinition.get('escalationRef');
+ } else if (is(eventDefinition, 'bpmn:MessageEventDefinition')) {
+ return eventDefinition.get('messageRef');
+ } else if (is(eventDefinition, 'bpmn:SignalEventDefinition')) {
+ return eventDefinition.get('signalRef');
+ }
+}
+
+function hasRootElement(rootElement) {
+ var definitions = getBpmnJS().getDefinitions(),
+ rootElements = definitions.get('rootElements');
+
+ return !!find(rootElements, matchPattern({ id: rootElement.id }));
+}
+
+function capitalizeFirstChar(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
\ No newline at end of file