Generate diagrams dynamically (with BPMN modeler fluent API or other)

Hi all, I have been using BPMN 2.0 model Fluent builder API’s ability to automatically generate BPMN diagrams in order to visualise a tree structure.
For now I am using a method for each type of diagram element, but I would like to group them in a single generic method.

Here is the tree loop (will later refactor to tail-recursive function):

Example function
val rootNode: Node =
      Utils.createNode("0", "Log in", "start_1",
        List(
          Utils.createNode("start_1", "Create Membership", "service_1",
            List(
              Utils.createNode("service_1", "Show Main Page", "end_1")
            )
          ),
          Utils.createNode("start_1", "Find Membership", "service_2",
            List(
             Utils.createNode("service_2", "Get user updates", "service_3",
              List( Utils.createNode("service_3", "Show main page with updates", "end_2_3"))
            ),
              Utils.createNode("service_2", "Error 404", "id404"),
                Utils.createNode("service_2", "Error 405|", "id405")
            )
          )
        )
      )

def parse(rootNode: BpmnParsable, processId: String): BpmnModelInstance = {

    val modelInstance: BpmnModelInstance = Bpmn.createExecutableProcess(processId)
      .startEvent(rootNode.getProcessId)
      .name(rootNode.getOperationName)
      .done()
    var branchCount = 1

    def getForkedChildren(n: BpmnParsable): List[BpmnParsable] = {
      val branchId = n.getProcessId + "_fork_" + branchCount
      branchCount += 1
      appendGateway(modelInstance, n.getProcessId, branchId)
      val forkedChildren = n.getChildren.mapConserve(child => child.setParentId(branchId))
      forkedChildren
    }

    val nodeStack: Stack[BpmnParsable] = new Stack()
    var rootChildren = rootNode.getChildren match {
      case Nil => return modelInstance
      case _ :: Nil => rootNode.getChildren
      case _ :: _ => getForkedChildren(rootNode)
    }

    nodeStack.pushAll(rootChildren)

    while (nodeStack.nonEmpty) {
      val currentNode = nodeStack.pop.get
      val children = currentNode.getChildren

      children match {
        // no children -> node is a leaf, i.e. an end event
        case Nil =>
          appendEndEvent(modelInstance, currentNode.getParentId, currentNode.getProcessId, currentNode.getOperationName)
          //appendElement(modelInstance, currentNode)
        case x =>
          appendServiceTask(modelInstance, currentNode.getParentId, currentNode.getProcessId, currentNode.getOperationName)
          x match {
            case head :: Nil =>
              // one child -> node is a task leading to next node
              nodeStack.push(head)
            case _ :: _ =>
              // anything else (multiple children) -> the node is a task leading to a fork containing all children
              val forkedChildren = getForkedChildren(currentNode)
              nodeStack.pushAll(forkedChildren)
              // Handled on the upper level, adding this empty case for the warnings to stop...
            case Nil =>()
          }
      }
    }
    modelInstance
  }

  parse(rootNode, "example")

And here’s code duplication bonanza :grimacing::

Builder functions
def appendServiceTask[T <: FlowNode](mi: BpmnModelInstance,
                                       parent_id: String,
                                       nodeId: String,
                                       nodeName: String) = {
    val parentelem: T = mi.getModelElementById(parent_id)
    parentelem.builder
      .serviceTask(nodeId) // only element that differs
      .name(nodeName).done()
  }

  def appendEndEvent[T <: FlowNode](mi: BpmnModelInstance,
                                    parent_id: String,
                                    nodeId: String,
                                    nodeName: String) = {
    val parentelem: T = mi.getModelElementById(parent_id)
    parentelem.builder
      .endEvent(nodeId) // only element that differs
      .name(nodeName).done()
  }

  def appendGateway[T <: FlowNode](mi: BpmnModelInstance,
                                   parent_id: String,
                                   nodeId: String) = {
    val parentelem: T = mi.getModelElementById(parent_id)
    parentelem.builder
      .parallelGateway(nodeId) // only element that differs
      .done()
  }

And the result here [UPDATED]:
loop-based.bpmn (8.0 KB)
updated

To generify the building process I need to pass the function the type of the incoming builder element (which should be alright), but then I need to be able to dynamically append an element.
E.g:

`builder.endEvent(node_id)` 

would be replaced by something like

`builder.addElement(EndEvent.class).setId(node_id)`

I’m not sure if it’s my lack of knowledge in Java / Scala or in the Camunda API that to be blamed, but I need your help in any case :slight_smile:

P.S. suggestions in Java are fine too! I wrote this in Java before porting it to Scala

Something like this may work:

BpmnModelInstance instance = ..;
Class<? extends ModelElementInstance> elementType = EndEvent.class;
ModelElementInstance parentElement = instance.getModelElementById("foo");
ModelElementInstance newElement = parentElement.getModelInstance().newInstance(elementType);
newElement.setAttributeValue("id", "bar");
parentElement.addChildElement(newElement);

If that is not what you want, you could still have a single function that takes the class and node id and internally does the big case distinction.

@thorben thanks for your reply!
I tried it before (along with the suggestions in the documentation: BPMN Model API), but that did not work. It gives the following error:

Exception in thread “main” org.camunda.bpm.model.xml.ModelException: New child is not a valid child element type: endEvent; valid types are: [documentation, extensionElements, auditing, monitoring, categoryValueRef, incoming, outgoing, ioSpecification, property, dataInputAssociation, dataOutputAssociation, resourceRole, loopCharacteristics]

It’s a shame because that way is the most straightforward.

I need the diagram element to be generated, and I think that for now it’s only possible by using the fluent builder API. The issue with this API is that I cannot find a way to dynamically get the builder of one type, as they need to be explicitly called with, for instance, .endEvent() returning an EndEventBuilder, and the builder type undergoes metamorphosis with each element added subsequently.

It really seems odd that automatic diagram generation comes only with the fluent API. Surely the way the diagram gets generated and adjusted from Builder call to Builder call is similar to iterating over a tree of ready-made elements … e.g. my clunky solution :smile: ?

If there’s a way to create the model beforehand and then feed it to a diagram generator, then I’d love to know what that generator is!