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 :
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)
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
P.S. suggestions in Java are fine too! I wrote this in Java before porting it to Scala