fix(import): forgive invalid flowElements

Instead of failing hard when we parse invalid flow elements (i.e.
TextAnnotation) we log a warning that may be handled by the diagram
import.

Related to #74
This commit is contained in:
Nico Rehwaldt 2014-06-23 14:44:03 +02:00
parent 55d9215e62
commit 8ad29d034d
5 changed files with 175 additions and 37 deletions

View File

@ -1,6 +1,6 @@
var _ = require('lodash'); var _ = require('lodash');
function BpmnTraverser(handler) { function BpmnTreeWalker(handler) {
var elementDiMap = {}; var elementDiMap = {};
var elementGfxMap = {}; var elementGfxMap = {};
@ -26,7 +26,7 @@ function BpmnTraverser(handler) {
// avoid multiple rendering of elements // avoid multiple rendering of elements
if (gfx) { if (gfx) {
return gfx; throw new Error('already rendered <' + element.id + '>');
} }
// call handler // call handler
@ -133,8 +133,8 @@ function BpmnTraverser(handler) {
// walk through all processes that have not yet been drawn and draw them // walk through all processes that have not yet been drawn and draw them
// (in case they contain lanes with DI information) // (in case they contain lanes with DI information)
var processes = _.forEach(rootElements, function(e) { var processes = _.filter(rootElements, function(e) {
return e.$type === 'bpmn:Process' && e.laneSets && handledProcesses.indexOf(e) !== -1; return e.$type === 'bpmn:Process' && e.laneSets && handledProcesses.indexOf(e) === -1;
}); });
processes.forEach(contextual(handleProcess)); processes.forEach(contextual(handleProcess));
@ -272,7 +272,9 @@ function BpmnTraverser(handler) {
if (is(e, 'bpmn:DataObjectReference')) { if (is(e, 'bpmn:DataObjectReference')) {
handleDataElement(e, context); handleDataElement(e, context);
} else { } else {
throw new Error('unrecognized element <' + e.$type + '> in context ' + (context ? context.id : null)); logError(
'unrecognized flowElement <' + e.$type + '> in context ' + (context ? context.id : null),
{ element: e, context: context });
} }
}); });
@ -326,4 +328,4 @@ function BpmnTraverser(handler) {
}; };
} }
module.exports = BpmnTraverser; module.exports = BpmnTreeWalker;

View File

@ -110,6 +110,7 @@ function importBpmnDiagram(diagram, definitions, done) {
data.label = label; data.label = label;
} }
var warnings = [];
var visitor = { var visitor = {
@ -161,16 +162,23 @@ function importBpmnDiagram(diagram, definitions, done) {
}, },
error: function(message, context) { error: function(message, context) {
console.warn('[import]', message, context); warnings.push({ message: message, context: context });
} }
}; };
var walker = new BpmnTreeWalker(visitor); var walker = new BpmnTreeWalker(visitor);
walker.handleDefinitions(definitions);
commandStack.clear(); try {
// import
walker.handleDefinitions(definitions);
done(); // clear command stack
commandStack.clear();
done(null, warnings);
} catch (e) {
done(e);
}
} }
module.exports.importBpmnDiagram = Util.failSafeAsync(importBpmnDiagram); module.exports.importBpmnDiagram = importBpmnDiagram;

View File

@ -40,7 +40,7 @@
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_3" bpmnElement="StartEvent_1"> <bpmndi:BPMNShape id="_BPMNShape_StartEvent_3" bpmnElement="StartEvent_1">
<dc:Bounds height="36.0" width="36.0" x="324.0" y="448.0"/> <dc:Bounds height="36.0" width="36.0" x="324.0" y="448.0"/>
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds height="0.0" width="0.0" x="342.0" y="489.0"/> <dc:Bounds height="1.0" width="0.0" x="342.0" y="489.0"/>
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Task_3" bpmnElement="Task_1"> <bpmndi:BPMNShape id="_BPMNShape_Task_3" bpmnElement="Task_1">

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="_u8q6UPrKEeOYcLGs4Ul9qQ" targetNamespace="http://activiti.org/bpmn">
<bpmn2:collaboration id="_Collaboration_2">
<bpmn2:participant id="_Participant_2" name="Pool" processRef="Process_1"/>
</bpmn2:collaboration>
<bpmn2:process id="Process_1" isExecutable="false">
<bpmn2:laneSet id="LaneSet_1" name="Lane Set 1">
<bpmn2:lane id="Lane_1" name="Lane 1">
<bpmn2:flowNodeRef>Task_1</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>TextAnnotation_1</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:laneSet>
<bpmn2:task id="Task_1"/>
<bpmn2:textAnnotation id="TextAnnotation_1">
<bpmn2:text><![CDATA[FOOO
BAR
YOO!]]></bpmn2:text>
</bpmn2:textAnnotation>
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="_Collaboration_2">
<bpmndi:BPMNShape id="_BPMNShape_Participant_2" bpmnElement="_Participant_2" isHorizontal="true">
<dc:Bounds height="215.0" width="540.0" x="409.0" y="161.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Lane_2" bpmnElement="Lane_1" isHorizontal="true">
<dc:Bounds height="215.0" width="510.0" x="439.0" y="161.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Task_2" bpmnElement="Task_1">
<dc:Bounds height="80.0" width="100.0" x="490.0" y="195.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_TextAnnotation_2" bpmnElement="TextAnnotation_1">
<dc:Bounds height="106.0" width="85.0" x="744.0" y="195.0"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@ -39,29 +39,40 @@ describe('import - importer', function() {
}); });
} }
var diagram;
it('should fire <bpmn.element.add> during import', function(done) { beforeEach(function() {
diagram = createDiagram();
});
// given
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
var diagram = createDiagram(); function runImport(diagram, xml, done) {
var events = [];
// log events
diagram.get('eventBus').on('bpmn.element.add', function(e) {
events.push({ type: 'add', semantic: e.semantic.id, di: e.di.id, diagramElement: e.diagramElement.id });
});
// when
BpmnModel.fromXML(xml, function(err, definitions) { BpmnModel.fromXML(xml, function(err, definitions) {
if (err) { if (err) {
return done(err); return done(err);
} }
Importer.importBpmnDiagram(diagram, definitions, done);
});
}
describe('event emitter', function() {
it('should fire <bpmn.element.add> during import', function(done) {
// given
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
var events = [];
// log events
diagram.get('eventBus').on('bpmn.element.add', function(e) {
events.push({ type: 'add', semantic: e.semantic.id, di: e.di.id, diagramElement: e.diagramElement.id });
});
// when // when
Importer.importBpmnDiagram(diagram, definitions, function(err) { runImport(diagram, xml, function(err, warnings) {
// then // then
expect(events).toEqual([ expect(events).toEqual([
@ -74,31 +85,112 @@ describe('import - importer', function() {
done(err); done(err);
}); });
}); });
}); });
it('should clear commandStack after import', function(done) { describe('basics', function() {
// given it('should import process', function(done) {
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
var diagram = createDiagram(); // given
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
var commandStack = diagram.get('commandStack'); var events = [];
// when // log events
BpmnModel.fromXML(xml, function(err, definitions) { diagram.get('eventBus').on('bpmn.element.add', function(e) {
if (err) { events.push({ type: 'add', semantic: e.semantic.id, di: e.di.id, diagramElement: e.diagramElement.id });
return done(err); });
}
// when // when
Importer.importBpmnDiagram(diagram, definitions, function(err) { runImport(diagram, xml, function(err, warnings) {
// then
expect(events).toEqual([
{ type: 'add', semantic: 'SubProcess_1', di: '_BPMNShape_SubProcess_2', diagramElement: 'SubProcess_1' },
{ type: 'add', semantic: 'StartEvent_1', di: '_BPMNShape_StartEvent_2', diagramElement: 'StartEvent_1' },
{ type: 'add', semantic: 'Task_1', di: '_BPMNShape_Task_2', diagramElement: 'Task_1' },
{ type: 'add', semantic: 'SequenceFlow_1', di: 'BPMNEdge_SequenceFlow_1', diagramElement: 'SequenceFlow_1' }
]);
done(err);
});
});
it('should import collaboration', function(done) {
// given
var xml = fs.readFileSync('test/fixtures/bpmn/collaboration.bpmn', 'utf8');
var events = [];
// log events
diagram.get('eventBus').on('bpmn.element.add', function(e) {
events.push({ type: 'add', semantic: e.semantic.id, di: e.di.id, diagramElement: e.diagramElement.id });
});
// when
runImport(diagram, xml, function(err, warnings) {
// then
expect(events).toEqual([
{ type: 'add', semantic: 'Participant_2', di: '_BPMNShape_Participant_2', diagramElement: 'Participant_2' },
{ type: 'add', semantic: 'Lane_1', di: '_BPMNShape_Lane_2', diagramElement: 'Lane_1' },
{ type: 'add', semantic: 'Lane_2', di: '_BPMNShape_Lane_3', diagramElement: 'Lane_2' },
{ type: 'add', semantic: 'Lane_3', di: '_BPMNShape_Lane_4', diagramElement: 'Lane_3' },
{ type: 'add', semantic: 'Task_1', di: '_BPMNShape_Task_3', diagramElement: 'Task_1' },
{ type: 'add', semantic: 'Participant_1', di: '_BPMNShape_Participant_3', diagramElement: 'Participant_1' },
{ type: 'add', semantic: 'StartEvent_1', di: '_BPMNShape_StartEvent_3', diagramElement: 'StartEvent_1' }
]);
done(err);
});
});
});
describe('command stack integration', function() {
it('should clear stack after import', function(done) {
// given
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
var commandStack = diagram.get('commandStack');
// when
runImport(diagram, xml, function(err, warnings) {
// then
expect(commandStack.getStack()).toEqual([]); expect(commandStack.getStack()).toEqual([]);
done(err); done(err);
}); });
}); });
});
describe('forgiveness', function() {
it('should import invalid flowElement', function(done) {
// given
var xml = fs.readFileSync('test/fixtures/bpmn/error/invalid-flow-element.bpmn', 'utf8');
var commandStack = diagram.get('commandStack');
// when
runImport(diagram, xml, function(err, warnings) {
expect(warnings.length).toBe(1);
done(err);
});
});
}); });
}); });