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');
function BpmnTraverser(handler) {
function BpmnTreeWalker(handler) {
var elementDiMap = {};
var elementGfxMap = {};
@ -26,7 +26,7 @@ function BpmnTraverser(handler) {
// avoid multiple rendering of elements
if (gfx) {
return gfx;
throw new Error('already rendered <' + element.id + '>');
}
// call handler
@ -133,8 +133,8 @@ function BpmnTraverser(handler) {
// walk through all processes that have not yet been drawn and draw them
// (in case they contain lanes with DI information)
var processes = _.forEach(rootElements, function(e) {
return e.$type === 'bpmn:Process' && e.laneSets && handledProcesses.indexOf(e) !== -1;
var processes = _.filter(rootElements, function(e) {
return e.$type === 'bpmn:Process' && e.laneSets && handledProcesses.indexOf(e) === -1;
});
processes.forEach(contextual(handleProcess));
@ -272,7 +272,9 @@ function BpmnTraverser(handler) {
if (is(e, 'bpmn:DataObjectReference')) {
handleDataElement(e, context);
} 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;
}
var warnings = [];
var visitor = {
@ -161,16 +162,23 @@ function importBpmnDiagram(diagram, definitions, done) {
},
error: function(message, context) {
console.warn('[import]', message, context);
warnings.push({ message: message, context: context });
}
};
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">
<dc:Bounds height="36.0" width="36.0" x="324.0" y="448.0"/>
<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:BPMNShape>
<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();
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
function runImport(diagram, xml, done) {
BpmnModel.fromXML(xml, function(err, definitions) {
if (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
Importer.importBpmnDiagram(diagram, definitions, function(err) {
runImport(diagram, xml, function(err, warnings) {
// then
expect(events).toEqual([
@ -74,31 +85,112 @@ describe('import - importer', function() {
done(err);
});
});
});
it('should clear commandStack after import', function(done) {
describe('basics', function() {
// given
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
it('should import process', function(done) {
var diagram = createDiagram();
// given
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
var commandStack = diagram.get('commandStack');
var events = [];
// when
BpmnModel.fromXML(xml, function(err, definitions) {
if (err) {
return done(err);
}
// 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
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([]);
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);
});
});
});
});