UNPKG

11.4 kBJavaScriptView Raw
1import {
2 filter,
3 find,
4 forEach
5} from 'min-dash';
6
7import Refs from 'object-refs';
8
9import {
10 elementToString
11} from './Util';
12
13var diRefs = new Refs(
14 { name: 'bpmnElement', enumerable: true },
15 { name: 'di', configurable: true }
16);
17
18/**
19 * Returns true if an element has the given meta-model type
20 *
21 * @param {ModdleElement} element
22 * @param {String} type
23 *
24 * @return {Boolean}
25 */
26function is(element, type) {
27 return element.$instanceOf(type);
28}
29
30
31/**
32 * Find a suitable display candidate for definitions where the DI does not
33 * correctly specify one.
34 */
35function findDisplayCandidate(definitions) {
36 return find(definitions.rootElements, function(e) {
37 return is(e, 'bpmn:Process') || is(e, 'bpmn:Collaboration');
38 });
39}
40
41
42export default function BpmnTreeWalker(handler, translate) {
43
44 // list of containers already walked
45 var handledElements = {};
46
47 // list of elements to handle deferred to ensure
48 // prerequisites are drawn
49 var deferred = [];
50
51 // Helpers //////////////////////
52
53 function contextual(fn, ctx) {
54 return function(e) {
55 fn(e, ctx);
56 };
57 }
58
59 function handled(element) {
60 handledElements[element.id] = element;
61 }
62
63 function isHandled(element) {
64 return handledElements[element.id];
65 }
66
67 function visit(element, ctx) {
68
69 var gfx = element.gfx;
70
71 // avoid multiple rendering of elements
72 if (gfx) {
73 throw new Error(
74 translate('already rendered {element}', { element: elementToString(element) })
75 );
76 }
77
78 // call handler
79 return handler.element(element, ctx);
80 }
81
82 function visitRoot(element, diagram) {
83 return handler.root(element, diagram);
84 }
85
86 function visitIfDi(element, ctx) {
87
88 try {
89 var gfx = element.di && visit(element, ctx);
90
91 handled(element);
92
93 return gfx;
94 } catch (e) {
95 logError(e.message, { element: element, error: e });
96
97 console.error(translate('failed to import {element}', { element: elementToString(element) }));
98 console.error(e);
99 }
100 }
101
102 function logError(message, context) {
103 handler.error(message, context);
104 }
105
106 // DI handling //////////////////////
107
108 function registerDi(di) {
109 var bpmnElement = di.bpmnElement;
110
111 if (bpmnElement) {
112 if (bpmnElement.di) {
113 logError(
114 translate('multiple DI elements defined for {element}', {
115 element: elementToString(bpmnElement)
116 }),
117 { element: bpmnElement }
118 );
119 } else {
120 diRefs.bind(bpmnElement, 'di');
121 bpmnElement.di = di;
122 }
123 } else {
124 logError(
125 translate('no bpmnElement referenced in {element}', {
126 element: elementToString(di)
127 }),
128 { element: di }
129 );
130 }
131 }
132
133 function handleDiagram(diagram) {
134 handlePlane(diagram.plane);
135 }
136
137 function handlePlane(plane) {
138 registerDi(plane);
139
140 forEach(plane.planeElement, handlePlaneElement);
141 }
142
143 function handlePlaneElement(planeElement) {
144 registerDi(planeElement);
145 }
146
147
148 // Semantic handling //////////////////////
149
150 /**
151 * Handle definitions and return the rendered diagram (if any)
152 *
153 * @param {ModdleElement} definitions to walk and import
154 * @param {ModdleElement} [diagram] specific diagram to import and display
155 *
156 * @throws {Error} if no diagram to display could be found
157 */
158 function handleDefinitions(definitions, diagram) {
159
160 // make sure we walk the correct bpmnElement
161
162 var diagrams = definitions.diagrams;
163
164 if (diagram && diagrams.indexOf(diagram) === -1) {
165 throw new Error(translate('diagram not part of bpmn:Definitions'));
166 }
167
168 if (!diagram && diagrams && diagrams.length) {
169 diagram = diagrams[0];
170 }
171
172 // no diagram -> nothing to import
173 if (!diagram) {
174 throw new Error(translate('no diagram to display'));
175 }
176
177 // load DI from selected diagram only
178 handleDiagram(diagram);
179
180
181 var plane = diagram.plane;
182
183 if (!plane) {
184 throw new Error(translate(
185 'no plane for {element}',
186 { element: elementToString(diagram) }
187 ));
188 }
189
190 var rootElement = plane.bpmnElement;
191
192 // ensure we default to a suitable display candidate (process or collaboration),
193 // even if non is specified in DI
194 if (!rootElement) {
195 rootElement = findDisplayCandidate(definitions);
196
197 if (!rootElement) {
198 throw new Error(translate('no process or collaboration to display'));
199 } else {
200
201 logError(
202 translate('correcting missing bpmnElement on {plane} to {rootElement}', {
203 plane: elementToString(plane),
204 rootElement: elementToString(rootElement)
205 })
206 );
207
208 // correct DI on the fly
209 plane.bpmnElement = rootElement;
210 registerDi(plane);
211 }
212 }
213
214
215 var ctx = visitRoot(rootElement, plane);
216
217 if (is(rootElement, 'bpmn:Process')) {
218 handleProcess(rootElement, ctx);
219 } else if (is(rootElement, 'bpmn:Collaboration')) {
220 handleCollaboration(rootElement, ctx);
221
222 // force drawing of everything not yet drawn that is part of the target DI
223 handleUnhandledProcesses(definitions.rootElements, ctx);
224 } else {
225 throw new Error(
226 translate('unsupported bpmnElement for {plane}: {rootElement}', {
227 plane: elementToString(plane),
228 rootElement: elementToString(rootElement)
229 })
230 );
231 }
232
233 // handle all deferred elements
234 handleDeferred(deferred);
235 }
236
237 function handleDeferred() {
238
239 var fn;
240
241 // drain deferred until empty
242 while (deferred.length) {
243 fn = deferred.shift();
244
245 fn();
246 }
247 }
248
249 function handleProcess(process, context) {
250 handleFlowElementsContainer(process, context);
251 handleIoSpecification(process.ioSpecification, context);
252
253 handleArtifacts(process.artifacts, context);
254
255 // log process handled
256 handled(process);
257 }
258
259 function handleUnhandledProcesses(rootElements, ctx) {
260
261 // walk through all processes that have not yet been drawn and draw them
262 // if they contain lanes with DI information.
263 // we do this to pass the free-floating lane test cases in the MIWG test suite
264 var processes = filter(rootElements, function(e) {
265 return !isHandled(e) && is(e, 'bpmn:Process') && e.laneSets;
266 });
267
268 processes.forEach(contextual(handleProcess, ctx));
269 }
270
271 function handleMessageFlow(messageFlow, context) {
272 visitIfDi(messageFlow, context);
273 }
274
275 function handleMessageFlows(messageFlows, context) {
276 forEach(messageFlows, contextual(handleMessageFlow, context));
277 }
278
279 function handleDataAssociation(association, context) {
280 visitIfDi(association, context);
281 }
282
283 function handleDataInput(dataInput, context) {
284 visitIfDi(dataInput, context);
285 }
286
287 function handleDataOutput(dataOutput, context) {
288 visitIfDi(dataOutput, context);
289 }
290
291 function handleArtifact(artifact, context) {
292
293 // bpmn:TextAnnotation
294 // bpmn:Group
295 // bpmn:Association
296
297 visitIfDi(artifact, context);
298 }
299
300 function handleArtifacts(artifacts, context) {
301
302 forEach(artifacts, function(e) {
303 if (is(e, 'bpmn:Association')) {
304 deferred.push(function() {
305 handleArtifact(e, context);
306 });
307 } else {
308 handleArtifact(e, context);
309 }
310 });
311 }
312
313 function handleIoSpecification(ioSpecification, context) {
314
315 if (!ioSpecification) {
316 return;
317 }
318
319 forEach(ioSpecification.dataInputs, contextual(handleDataInput, context));
320 forEach(ioSpecification.dataOutputs, contextual(handleDataOutput, context));
321 }
322
323 function handleSubProcess(subProcess, context) {
324 handleFlowElementsContainer(subProcess, context);
325 handleArtifacts(subProcess.artifacts, context);
326 }
327
328 function handleFlowNode(flowNode, context) {
329 var childCtx = visitIfDi(flowNode, context);
330
331 if (is(flowNode, 'bpmn:SubProcess')) {
332 handleSubProcess(flowNode, childCtx || context);
333 }
334
335 if (is(flowNode, 'bpmn:Activity')) {
336 handleIoSpecification(flowNode.ioSpecification, context);
337 }
338
339 // defer handling of associations
340 // affected types:
341 //
342 // * bpmn:Activity
343 // * bpmn:ThrowEvent
344 // * bpmn:CatchEvent
345 //
346 deferred.push(function() {
347 forEach(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
348 forEach(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
349 });
350 }
351
352 function handleSequenceFlow(sequenceFlow, context) {
353 visitIfDi(sequenceFlow, context);
354 }
355
356 function handleDataElement(dataObject, context) {
357 visitIfDi(dataObject, context);
358 }
359
360 function handleLane(lane, context) {
361
362 deferred.push(function() {
363
364 var newContext = visitIfDi(lane, context);
365
366 if (lane.childLaneSet) {
367 handleLaneSet(lane.childLaneSet, newContext || context);
368 }
369
370 wireFlowNodeRefs(lane);
371 });
372 }
373
374 function handleLaneSet(laneSet, context) {
375 forEach(laneSet.lanes, contextual(handleLane, context));
376 }
377
378 function handleLaneSets(laneSets, context) {
379 forEach(laneSets, contextual(handleLaneSet, context));
380 }
381
382 function handleFlowElementsContainer(container, context) {
383 handleFlowElements(container.flowElements, context);
384
385 if (container.laneSets) {
386 handleLaneSets(container.laneSets, context);
387 }
388 }
389
390 function handleFlowElements(flowElements, context) {
391 forEach(flowElements, function(e) {
392 if (is(e, 'bpmn:SequenceFlow')) {
393 deferred.push(function() {
394 handleSequenceFlow(e, context);
395 });
396 } else if (is(e, 'bpmn:BoundaryEvent')) {
397 deferred.unshift(function() {
398 handleFlowNode(e, context);
399 });
400 } else if (is(e, 'bpmn:FlowNode')) {
401 handleFlowNode(e, context);
402 } else if (is(e, 'bpmn:DataObject')) {
403
404 // SKIP (assume correct referencing via DataObjectReference)
405 } else if (is(e, 'bpmn:DataStoreReference')) {
406 handleDataElement(e, context);
407 } else if (is(e, 'bpmn:DataObjectReference')) {
408 handleDataElement(e, context);
409 } else {
410 logError(
411 translate('unrecognized flowElement {element} in context {context}', {
412 element: elementToString(e),
413 context: (context ? elementToString(context.businessObject) : 'null')
414 }),
415 { element: e, context: context }
416 );
417 }
418 });
419 }
420
421 function handleParticipant(participant, context) {
422 var newCtx = visitIfDi(participant, context);
423
424 var process = participant.processRef;
425 if (process) {
426 handleProcess(process, newCtx || context);
427 }
428 }
429
430 function handleCollaboration(collaboration) {
431
432 forEach(collaboration.participants, contextual(handleParticipant));
433
434 handleArtifacts(collaboration.artifacts);
435
436 // handle message flows latest in the process
437 deferred.push(function() {
438 handleMessageFlows(collaboration.messageFlows);
439 });
440 }
441
442
443 function wireFlowNodeRefs(lane) {
444
445 // wire the virtual flowNodeRefs <-> relationship
446 forEach(lane.flowNodeRef, function(flowNode) {
447 var lanes = flowNode.get('lanes');
448
449 if (lanes) {
450 lanes.push(lane);
451 }
452 });
453 }
454
455 // API //////////////////////
456
457 return {
458 handleDeferred: handleDeferred,
459 handleDefinitions: handleDefinitions,
460 handleSubProcess: handleSubProcess,
461 registerDi: registerDi
462 };
463}
\No newline at end of file