123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- import {
- filter,
- find,
- forEach
- } from 'min-dash';
- import Refs from 'object-refs';
- import {
- elementToString
- } from './Util';
- var diRefs = new Refs(
- { name: 'bpmnElement', enumerable: true },
- { name: 'di', configurable: true }
- );
- /**
- * Returns true if an element has the given meta-model type
- *
- * @param {ModdleElement} element
- * @param {string} type
- *
- * @return {boolean}
- */
- function is(element, type) {
- return element.$instanceOf(type);
- }
- /**
- * Find a suitable display candidate for definitions where the DI does not
- * correctly specify one.
- */
- function findDisplayCandidate(definitions) {
- return find(definitions.rootElements, function(e) {
- return is(e, 'bpmn:Process') || is(e, 'bpmn:Collaboration');
- });
- }
- export default function BpmnTreeWalker(handler, translate) {
- // list of containers already walked
- var handledElements = {};
- // list of elements to handle deferred to ensure
- // prerequisites are drawn
- var deferred = [];
- // Helpers //////////////////////
- function contextual(fn, ctx) {
- return function(e) {
- fn(e, ctx);
- };
- }
- function handled(element) {
- handledElements[element.id] = element;
- }
- function isHandled(element) {
- return handledElements[element.id];
- }
- function visit(element, ctx) {
- var gfx = element.gfx;
- // avoid multiple rendering of elements
- if (gfx) {
- throw new Error(
- translate('already rendered {element}', { element: elementToString(element) })
- );
- }
- // call handler
- return handler.element(element, ctx);
- }
- function visitRoot(element, diagram) {
- return handler.root(element, diagram);
- }
- function visitIfDi(element, ctx) {
- try {
- var gfx = element.di && visit(element, ctx);
- handled(element);
- return gfx;
- } catch (e) {
- logError(e.message, { element: element, error: e });
- console.error(translate('failed to import {element}', { element: elementToString(element) }));
- console.error(e);
- }
- }
- function logError(message, context) {
- handler.error(message, context);
- }
- // DI handling //////////////////////
- function registerDi(di) {
- var bpmnElement = di.bpmnElement;
- if (bpmnElement) {
- if (bpmnElement.di) {
- logError(
- translate('multiple DI elements defined for {element}', {
- element: elementToString(bpmnElement)
- }),
- { element: bpmnElement }
- );
- } else {
- diRefs.bind(bpmnElement, 'di');
- bpmnElement.di = di;
- }
- } else {
- logError(
- translate('no bpmnElement referenced in {element}', {
- element: elementToString(di)
- }),
- { element: di }
- );
- }
- }
- function handleDiagram(diagram) {
- handlePlane(diagram.plane);
- }
- function handlePlane(plane) {
- registerDi(plane);
- forEach(plane.planeElement, handlePlaneElement);
- }
- function handlePlaneElement(planeElement) {
- registerDi(planeElement);
- }
- // Semantic handling //////////////////////
- /**
- * Handle definitions and return the rendered diagram (if any)
- *
- * @param {ModdleElement} definitions to walk and import
- * @param {ModdleElement} [diagram] specific diagram to import and display
- *
- * @throws {Error} if no diagram to display could be found
- */
- function handleDefinitions(definitions, diagram) {
- // make sure we walk the correct bpmnElement
- var diagrams = definitions.diagrams;
- if (diagram && diagrams.indexOf(diagram) === -1) {
- throw new Error(translate('diagram not part of bpmn:Definitions'));
- }
- if (!diagram && diagrams && diagrams.length) {
- diagram = diagrams[0];
- }
- // no diagram -> nothing to import
- if (!diagram) {
- throw new Error(translate('no diagram to display'));
- }
- // load DI from selected diagram only
- handleDiagram(diagram);
- var plane = diagram.plane;
- if (!plane) {
- throw new Error(translate(
- 'no plane for {element}',
- { element: elementToString(diagram) }
- ));
- }
- var rootElement = plane.bpmnElement;
- // ensure we default to a suitable display candidate (process or collaboration),
- // even if non is specified in DI
- if (!rootElement) {
- rootElement = findDisplayCandidate(definitions);
- if (!rootElement) {
- throw new Error(translate('no process or collaboration to display'));
- } else {
- logError(
- translate('correcting missing bpmnElement on {plane} to {rootElement}', {
- plane: elementToString(plane),
- rootElement: elementToString(rootElement)
- })
- );
- // correct DI on the fly
- plane.bpmnElement = rootElement;
- registerDi(plane);
- }
- }
- var ctx = visitRoot(rootElement, plane);
- if (is(rootElement, 'bpmn:Process')) {
- handleProcess(rootElement, ctx);
- } else if (is(rootElement, 'bpmn:Collaboration')) {
- handleCollaboration(rootElement, ctx);
- // force drawing of everything not yet drawn that is part of the target DI
- handleUnhandledProcesses(definitions.rootElements, ctx);
- } else {
- throw new Error(
- translate('unsupported bpmnElement for {plane}: {rootElement}', {
- plane: elementToString(plane),
- rootElement: elementToString(rootElement)
- })
- );
- }
- // handle all deferred elements
- handleDeferred(deferred);
- }
- function handleDeferred() {
- var fn;
- // drain deferred until empty
- while (deferred.length) {
- fn = deferred.shift();
- fn();
- }
- }
- function handleProcess(process, context) {
- handleFlowElementsContainer(process, context);
- handleIoSpecification(process.ioSpecification, context);
- handleArtifacts(process.artifacts, context);
- // log process handled
- handled(process);
- }
- function handleUnhandledProcesses(rootElements, ctx) {
- // walk through all processes that have not yet been drawn and draw them
- // if they contain lanes with DI information.
- // we do this to pass the free-floating lane test cases in the MIWG test suite
- var processes = filter(rootElements, function(e) {
- return !isHandled(e) && is(e, 'bpmn:Process') && e.laneSets;
- });
- processes.forEach(contextual(handleProcess, ctx));
- }
- function handleMessageFlow(messageFlow, context) {
- visitIfDi(messageFlow, context);
- }
- function handleMessageFlows(messageFlows, context) {
- forEach(messageFlows, contextual(handleMessageFlow, context));
- }
- function handleDataAssociation(association, context) {
- visitIfDi(association, context);
- }
- function handleDataInput(dataInput, context) {
- visitIfDi(dataInput, context);
- }
- function handleDataOutput(dataOutput, context) {
- visitIfDi(dataOutput, context);
- }
- function handleArtifact(artifact, context) {
- // bpmn:TextAnnotation
- // bpmn:Group
- // bpmn:Association
- visitIfDi(artifact, context);
- }
- function handleArtifacts(artifacts, context) {
- forEach(artifacts, function(e) {
- if (is(e, 'bpmn:Association')) {
- deferred.push(function() {
- handleArtifact(e, context);
- });
- } else {
- handleArtifact(e, context);
- }
- });
- }
- function handleIoSpecification(ioSpecification, context) {
- if (!ioSpecification) {
- return;
- }
- forEach(ioSpecification.dataInputs, contextual(handleDataInput, context));
- forEach(ioSpecification.dataOutputs, contextual(handleDataOutput, context));
- }
- function handleSubProcess(subProcess, context) {
- handleFlowElementsContainer(subProcess, context);
- handleArtifacts(subProcess.artifacts, context);
- }
- function handleFlowNode(flowNode, context) {
- var childCtx = visitIfDi(flowNode, context);
- if (is(flowNode, 'bpmn:SubProcess')) {
- handleSubProcess(flowNode, childCtx || context);
- }
- if (is(flowNode, 'bpmn:Activity')) {
- handleIoSpecification(flowNode.ioSpecification, context);
- }
- // defer handling of associations
- // affected types:
- //
- // * bpmn:Activity
- // * bpmn:ThrowEvent
- // * bpmn:CatchEvent
- //
- deferred.push(function() {
- forEach(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
- forEach(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
- });
- }
- function handleSequenceFlow(sequenceFlow, context) {
- visitIfDi(sequenceFlow, context);
- }
- function handleDataElement(dataObject, context) {
- visitIfDi(dataObject, context);
- }
- function handleLane(lane, context) {
- deferred.push(function() {
- var newContext = visitIfDi(lane, context);
- if (lane.childLaneSet) {
- handleLaneSet(lane.childLaneSet, newContext || context);
- }
- wireFlowNodeRefs(lane);
- });
- }
- function handleLaneSet(laneSet, context) {
- forEach(laneSet.lanes, contextual(handleLane, context));
- }
- function handleLaneSets(laneSets, context) {
- forEach(laneSets, contextual(handleLaneSet, context));
- }
- function handleFlowElementsContainer(container, context) {
- handleFlowElements(container.flowElements, context);
- if (container.laneSets) {
- handleLaneSets(container.laneSets, context);
- }
- }
- function handleFlowElements(flowElements, context) {
- forEach(flowElements, function(e) {
- if (is(e, 'bpmn:SequenceFlow')) {
- deferred.push(function() {
- handleSequenceFlow(e, context);
- });
- } else if (is(e, 'bpmn:BoundaryEvent')) {
- deferred.unshift(function() {
- handleFlowNode(e, context);
- });
- } else if (is(e, 'bpmn:FlowNode')) {
- handleFlowNode(e, context);
- } else if (is(e, 'bpmn:DataObject')) {
- // SKIP (assume correct referencing via DataObjectReference)
- } else if (is(e, 'bpmn:DataStoreReference')) {
- handleDataElement(e, context);
- } else if (is(e, 'bpmn:DataObjectReference')) {
- handleDataElement(e, context);
- } else {
- logError(
- translate('unrecognized flowElement {element} in context {context}', {
- element: elementToString(e),
- context: (context ? elementToString(context.businessObject) : 'null')
- }),
- { element: e, context: context }
- );
- }
- });
- }
- function handleParticipant(participant, context) {
- var newCtx = visitIfDi(participant, context);
- var process = participant.processRef;
- if (process) {
- handleProcess(process, newCtx || context);
- }
- }
- function handleCollaboration(collaboration) {
- forEach(collaboration.participants, contextual(handleParticipant));
- handleArtifacts(collaboration.artifacts);
- // handle message flows latest in the process
- deferred.push(function() {
- handleMessageFlows(collaboration.messageFlows);
- });
- }
- function wireFlowNodeRefs(lane) {
- // wire the virtual flowNodeRefs <-> relationship
- forEach(lane.flowNodeRef, function(flowNode) {
- var lanes = flowNode.get('lanes');
- if (lanes) {
- lanes.push(lane);
- }
- });
- }
- // API //////////////////////
- return {
- handleDeferred: handleDeferred,
- handleDefinitions: handleDefinitions,
- handleSubProcess: handleSubProcess,
- registerDi: registerDi
- };
- }
|