UNPKG

11.5 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 // make sure we walk the correct bpmnElement
160
161 var diagrams = definitions.diagrams;
162
163 if (diagram && diagrams.indexOf(diagram) === -1) {
164 throw new Error(translate('diagram not part of bpmn:Definitions'));
165 }
166
167 if (!diagram && diagrams && diagrams.length) {
168 diagram = diagrams[0];
169 }
170
171 // no diagram -> nothing to import
172 if (!diagram) {
173 throw new Error(translate('no diagram to display'));
174 }
175
176 // load DI from selected diagram only
177 handleDiagram(diagram);
178
179
180 var plane = diagram.plane;
181
182 if (!plane) {
183 throw new Error(translate(
184 'no plane for {element}',
185 { element: elementToString(diagram) }
186 ));
187 }
188
189 var rootElement = plane.bpmnElement;
190
191 // ensure we default to a suitable display candidate (process or collaboration),
192 // even if non is specified in DI
193 if (!rootElement) {
194 rootElement = findDisplayCandidate(definitions);
195
196 if (!rootElement) {
197 throw new Error(translate('no process or collaboration to display'));
198 } else {
199
200 logError(
201 translate('correcting missing bpmnElement on {plane} to {rootElement}', {
202 plane: elementToString(plane),
203 rootElement: elementToString(rootElement)
204 })
205 );
206
207 // correct DI on the fly
208 plane.bpmnElement = rootElement;
209 registerDi(plane);
210 }
211 }
212
213
214 var ctx = visitRoot(rootElement, plane);
215
216 if (is(rootElement, 'bpmn:Process')) {
217 handleProcess(rootElement, ctx);
218 } else if (is(rootElement, 'bpmn:Collaboration')) {
219 handleCollaboration(rootElement, ctx);
220
221 // force drawing of everything not yet drawn that is part of the target DI
222 handleUnhandledProcesses(definitions.rootElements, ctx);
223 } else {
224 throw new Error(
225 translate('unsupported bpmnElement for {plane}: {rootElement}', {
226 plane: elementToString(plane),
227 rootElement: elementToString(rootElement)
228 })
229 );
230 }
231
232 // handle all deferred elements
233 handleDeferred(deferred);
234 }
235
236 function handleDeferred() {
237
238 var fn;
239
240 // drain deferred until empty
241 while (deferred.length) {
242 fn = deferred.shift();
243
244 fn();
245 }
246 }
247
248 function handleProcess(process, context) {
249 handleFlowElementsContainer(process, context);
250 handleIoSpecification(process.ioSpecification, context);
251
252 handleArtifacts(process.artifacts, context);
253
254 // log process handled
255 handled(process);
256 }
257
258 function handleUnhandledProcesses(rootElements) {
259
260 // walk through all processes that have not yet been drawn and draw them
261 // if they contain lanes with DI information.
262 // we do this to pass the free-floating lane test cases in the MIWG test suite
263 var processes = filter(rootElements, function(e) {
264 return !isHandled(e) && is(e, 'bpmn:Process') && e.laneSets;
265 });
266
267 processes.forEach(contextual(handleProcess));
268 }
269
270 function handleMessageFlow(messageFlow, context) {
271 visitIfDi(messageFlow, context);
272 }
273
274 function handleMessageFlows(messageFlows, context) {
275 forEach(messageFlows, contextual(handleMessageFlow, context));
276 }
277
278 function handleDataAssociation(association, context) {
279 visitIfDi(association, context);
280 }
281
282 function handleDataInput(dataInput, context) {
283 visitIfDi(dataInput, context);
284 }
285
286 function handleDataOutput(dataOutput, context) {
287 visitIfDi(dataOutput, context);
288 }
289
290 function handleArtifact(artifact, context) {
291
292 // bpmn:TextAnnotation
293 // bpmn:Group
294 // bpmn:Association
295
296 visitIfDi(artifact, context);
297 }
298
299 function handleArtifacts(artifacts, context) {
300
301 forEach(artifacts, function(e) {
302 if (is(e, 'bpmn:Association')) {
303 deferred.push(function() {
304 handleArtifact(e, context);
305 });
306 } else {
307 handleArtifact(e, context);
308 }
309 });
310 }
311
312 function handleIoSpecification(ioSpecification, context) {
313
314 if (!ioSpecification) {
315 return;
316 }
317
318 forEach(ioSpecification.dataInputs, contextual(handleDataInput, context));
319 forEach(ioSpecification.dataOutputs, contextual(handleDataOutput, context));
320 }
321
322 function handleSubProcess(subProcess, context) {
323 handleFlowElementsContainer(subProcess, context);
324 handleArtifacts(subProcess.artifacts, context);
325 }
326
327 function handleFlowNode(flowNode, context) {
328 var childCtx = visitIfDi(flowNode, context);
329
330 if (is(flowNode, 'bpmn:SubProcess')) {
331 handleSubProcess(flowNode, childCtx || context);
332 }
333
334 if (is(flowNode, 'bpmn:Activity')) {
335 handleIoSpecification(flowNode.ioSpecification, context);
336 }
337
338 // defer handling of associations
339 // affected types:
340 //
341 // * bpmn:Activity
342 // * bpmn:ThrowEvent
343 // * bpmn:CatchEvent
344 //
345 deferred.push(function() {
346 forEach(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
347 forEach(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
348 });
349 }
350
351 function handleSequenceFlow(sequenceFlow, context) {
352 visitIfDi(sequenceFlow, context);
353 }
354
355 function handleDataElement(dataObject, context) {
356 visitIfDi(dataObject, context);
357 }
358
359 function handleBoundaryEvent(dataObject, context) {
360 visitIfDi(dataObject, context);
361 }
362
363 function handleLane(lane, context) {
364
365 deferred.push(function() {
366
367 var newContext = visitIfDi(lane, context);
368
369 if (lane.childLaneSet) {
370 handleLaneSet(lane.childLaneSet, newContext || context);
371 }
372
373 wireFlowNodeRefs(lane);
374 });
375 }
376
377 function handleLaneSet(laneSet, context) {
378 forEach(laneSet.lanes, contextual(handleLane, context));
379 }
380
381 function handleLaneSets(laneSets, context) {
382 forEach(laneSets, contextual(handleLaneSet, context));
383 }
384
385 function handleFlowElementsContainer(container, context) {
386 handleFlowElements(container.flowElements, context);
387
388 if (container.laneSets) {
389 handleLaneSets(container.laneSets, context);
390 }
391 }
392
393 function handleFlowElements(flowElements, context) {
394 forEach(flowElements, function(e) {
395 if (is(e, 'bpmn:SequenceFlow')) {
396 deferred.push(function() {
397 handleSequenceFlow(e, context);
398 });
399 } else if (is(e, 'bpmn:BoundaryEvent')) {
400 deferred.unshift(function() {
401 handleBoundaryEvent(e, context);
402 });
403 } else if (is(e, 'bpmn:FlowNode')) {
404 handleFlowNode(e, context);
405 } else if (is(e, 'bpmn:DataObject')) {
406 // SKIP (assume correct referencing via DataObjectReference)
407 } else if (is(e, 'bpmn:DataStoreReference')) {
408 handleDataElement(e, context);
409 } else if (is(e, 'bpmn:DataObjectReference')) {
410 handleDataElement(e, context);
411 } else {
412 logError(
413 translate('unrecognized flowElement {element} in context {context}', {
414 element: elementToString(e),
415 context: (context ? elementToString(context.businessObject) : 'null')
416 }),
417 { element: e, context: context }
418 );
419 }
420 });
421 }
422
423 function handleParticipant(participant, context) {
424 var newCtx = visitIfDi(participant, context);
425
426 var process = participant.processRef;
427 if (process) {
428 handleProcess(process, newCtx || context);
429 }
430 }
431
432 function handleCollaboration(collaboration) {
433
434 forEach(collaboration.participants, contextual(handleParticipant));
435
436 handleArtifacts(collaboration.artifacts);
437
438 // handle message flows latest in the process
439 deferred.push(function() {
440 handleMessageFlows(collaboration.messageFlows);
441 });
442 }
443
444
445 function wireFlowNodeRefs(lane) {
446 // wire the virtual flowNodeRefs <-> relationship
447 forEach(lane.flowNodeRef, function(flowNode) {
448 var lanes = flowNode.get('lanes');
449
450 if (lanes) {
451 lanes.push(lane);
452 }
453 });
454 }
455
456 // API //////////////////////
457
458 return {
459 handleDeferred: handleDeferred,
460 handleDefinitions: handleDefinitions,
461 handleSubProcess: handleSubProcess,
462 registerDi: registerDi
463 };
464}
\No newline at end of file