Skip to content
Advertisement

Java – Camunda BPMN model API: how to save valid xml?

I’m building some test BPMN 2.0 models and saving them to xml files, in a Java project, by following the examples provided by the official doc, in this case the example 2.

I imported the lib in my pom like below:

<!-- https://mvnrepository.com/artifact/org.camunda.bpm.model/camunda-bpmn-model -->
<dependency>
    <groupId>org.camunda.bpm.model</groupId>
    <artifactId>camunda-bpmn-model</artifactId>
    <version>7.10.0</version>
</dependency>

and this is my test class, following the example 2:

import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.*;
import org.camunda.bpm.model.bpmn.instance.Process;

import java.io.File;
import java.io.IOException;

public class Test {
    protected static <T extends BpmnModelElementInstance> T createElement(BpmnModelInstance modelInstance, BpmnModelElementInstance parentElement, String id, Class<T> elementClass) {
        T element = modelInstance.newInstance(elementClass);
        element.setAttributeValue("id", id, true);
        parentElement.addChildElement(element);
        return element;
    }

    public static SequenceFlow createSequenceFlow(BpmnModelInstance modelInstance, Process process, FlowNode from, FlowNode to) {
        String identifier = from.getId() + "-" + to.getId();
        SequenceFlow sequenceFlow = createElement(modelInstance, process, identifier, SequenceFlow.class);
        process.addChildElement(sequenceFlow);
        sequenceFlow.setSource(from);
        from.getOutgoing().add(sequenceFlow);
        sequenceFlow.setTarget(to);
        to.getIncoming().add(sequenceFlow);
        return sequenceFlow;
    }

    public static void main(String[] args) throws IOException {
        // create an empty model
        BpmnModelInstance modelInstance = Bpmn.createEmptyModel();
        Definitions definitions = modelInstance.newInstance(Definitions.class);
        definitions.setTargetNamespace("http://camunda.org/examples");
        modelInstance.setDefinitions(definitions);

        // create a process
        Process process = modelInstance.newInstance(Process.class);
        process.setId("process");
        definitions.addChildElement(process);

        // create elements
        StartEvent startEvent = createElement(modelInstance, process, "start", StartEvent.class);
        ParallelGateway fork = createElement(modelInstance, process, "fork", ParallelGateway.class);
        ServiceTask task1 = createElement(modelInstance, process, "task1", ServiceTask.class);
        task1.setName("Service Task");
        UserTask task2 = createElement(modelInstance, process, "task2", UserTask.class);
        task2.setName("User Task");
        ParallelGateway join = createElement(modelInstance, process, "join", ParallelGateway.class);
        EndEvent endEvent = createElement(modelInstance, process, "end", EndEvent.class);

        // create flows
        createSequenceFlow(modelInstance, process, startEvent, fork);
        createSequenceFlow(modelInstance, process, fork, task1);
        createSequenceFlow(modelInstance, process, fork, task2);
        createSequenceFlow(modelInstance, process, task1, join);
        createSequenceFlow(modelInstance, process, task2, join);
        createSequenceFlow(modelInstance, process, join, endEvent);

        // validate and write model to file
        Bpmn.validateModel(modelInstance);
        File file = new File("bpmn-model.bpmn.xml");
        file.createNewFile();
        Bpmn.writeModelToFile(file, modelInstance);
    }
}

Here the generated BPMN 2.0 xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions id="definitions_7b2df680-1d9c-4897-ba8b-a83548ab937f" targetNamespace="http://camunda.org/examples" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="process">
    <startEvent id="start">
      <outgoing>start-fork</outgoing>
    </startEvent>
    <parallelGateway id="fork">
      <incoming>start-fork</incoming>
      <outgoing>fork-task1</outgoing>
      <outgoing>fork-task2</outgoing>
    </parallelGateway>
    <serviceTask id="task1" name="Service Task">
      <incoming>fork-task1</incoming>
      <outgoing>task1-join</outgoing>
    </serviceTask>
    <userTask id="task2" name="User Task">
      <incoming>fork-task2</incoming>
      <outgoing>task2-join</outgoing>
    </userTask>
    <parallelGateway id="join">
      <incoming>task1-join</incoming>
      <incoming>task2-join</incoming>
      <outgoing>join-end</outgoing>
    </parallelGateway>
    <endEvent id="end">
      <incoming>join-end</incoming>
    </endEvent>
    <sequenceFlow id="start-fork" sourceRef="start" targetRef="fork"/>
    <sequenceFlow id="fork-task1" sourceRef="fork" targetRef="task1"/>
    <sequenceFlow id="fork-task2" sourceRef="fork" targetRef="task2"/>
    <sequenceFlow id="task1-join" sourceRef="task1" targetRef="join"/>
    <sequenceFlow id="task2-join" sourceRef="task2" targetRef="join"/>
    <sequenceFlow id="join-end" sourceRef="join" targetRef="end"/>
  </process>
</definitions>

Altough the validation method, the generated xml appears to be invalid when trying to display the BPMN using both the Camunda Modeler app and bpmn.io.

What is wrong with this example, hence with my code? How can I make the generated xml to be valid? Since bpmn 2.0 is a standard, I’m also a bit surprised of this problem.

Advertisement

Answer

I finally got it working by switching to the fluent model builder API, following this https://blog.camunda.com/post/2014/02/the-new-camunda-bpmn-model-api/

This is my updated test calss:

import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.GatewayDirection;

import java.io.File;
import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        BpmnModelInstance modelInstance = Bpmn.createProcess()
            .name("BPMN API Invoice Process")
            .executable()
            .startEvent()
            .name("Invoice received")
            .camundaFormKey("embedded:app:forms/start-form.html")
            .userTask()
            .name("Assign Approver")
            .camundaFormKey("embedded:app:forms/assign-approver.html")
            .camundaAssignee("demo")
            .userTask()
            .id("approveInvoice")
            .name("Approve Invoice")
            .camundaFormKey("embedded:app:forms/approve-invoice.html")
            .camundaAssignee("${approver}")
            .exclusiveGateway()
            .name("Invoice approved?")
            .gatewayDirection(GatewayDirection.Diverging)
            .condition("yes", "${approved}")
            .userTask()
            .name("Prepare Bank Transfer")
            .camundaFormKey("embedded:app:forms/prepare-bank-transfer.html")
            .camundaCandidateGroups("accounting")
            .serviceTask()
            .name("Archive Invoice")
            .endEvent()
            .name("Invoice processed")
            .moveToLastGateway()
            .condition("no", "${!approved}")
            .userTask()
            .name("Review Invoice")
            .camundaFormKey("embedded:app:forms/review-invoice.html")
            .camundaAssignee("demo")
            .exclusiveGateway()
            .name("Review successful?")
            .gatewayDirection(GatewayDirection.Diverging)
            .condition("no", "${!clarified}")
            .endEvent()
            .name("Invoice not processed")
            .moveToLastGateway()
            .condition("yes", "${clarified}")
            .connectTo("approveInvoice")
            .done();

        // validate and write model to file
        Bpmn.validateModel(modelInstance);
        File file = new File("bpmn-model.bpmn.xml");
        file.createNewFile();

        String bpmnString = Bpmn.convertToString(modelInstance);
        System.out.println("bpmnString");
        System.out.println(bpmnString);

        Bpmn.writeModelToFile(file, modelInstance);
    }
}

and this is the generated xml:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="definitions_3bd3e094-3e53-4644-af0c-1a048653920c" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="process_e14ca74e-6095-4a2b-bced-debcb3b37954" isExecutable="true" name="BPMN API Invoice Process">
    <startEvent camunda:formKey="embedded:app:forms/start-form.html" id="startEvent_e8daa33a-af99-4b7a-87ee-200b0e7bedcd" name="Invoice received">
      <outgoing>sequenceFlow_cb28e7e5-ebc0-4f27-b8b2-24403ab87ce3</outgoing>
    </startEvent>
    <userTask camunda:assignee="demo" camunda:formKey="embedded:app:forms/assign-approver.html" id="userTask_029f48b9-de24-4292-af49-845e81fdcc4b" name="Assign Approver">
      <incoming>sequenceFlow_cb28e7e5-ebc0-4f27-b8b2-24403ab87ce3</incoming>
      <outgoing>sequenceFlow_57a81fcd-fb35-42cd-afca-7c4c7914936d</outgoing>
    </userTask>
    <sequenceFlow id="sequenceFlow_cb28e7e5-ebc0-4f27-b8b2-24403ab87ce3" sourceRef="startEvent_e8daa33a-af99-4b7a-87ee-200b0e7bedcd" targetRef="userTask_029f48b9-de24-4292-af49-845e81fdcc4b"/>
    <userTask camunda:assignee="${approver}" camunda:formKey="embedded:app:forms/approve-invoice.html" id="approveInvoice" name="Approve Invoice">
      <incoming>sequenceFlow_57a81fcd-fb35-42cd-afca-7c4c7914936d</incoming>
      <incoming>sequenceFlow_6987bcb8-5f6a-467b-9b25-e21b90dd0d4d</incoming>
      <outgoing>sequenceFlow_1fe15a36-427e-4000-8fc7-50b8925ea1e5</outgoing>
    </userTask>
    <sequenceFlow id="sequenceFlow_57a81fcd-fb35-42cd-afca-7c4c7914936d" sourceRef="userTask_029f48b9-de24-4292-af49-845e81fdcc4b" targetRef="approveInvoice"/>
    <exclusiveGateway gatewayDirection="Diverging" id="exclusiveGateway_b66a0444-2889-4cae-ac78-c1f65405ba0b" name="Invoice approved?">
      <incoming>sequenceFlow_1fe15a36-427e-4000-8fc7-50b8925ea1e5</incoming>
      <outgoing>sequenceFlow_9156c1d4-f756-447b-a384-e3bf17c43ff4</outgoing>
      <outgoing>sequenceFlow_29bab2ba-6108-4731-94c3-93a6955d1946</outgoing>
    </exclusiveGateway>
    <sequenceFlow id="sequenceFlow_1fe15a36-427e-4000-8fc7-50b8925ea1e5" sourceRef="approveInvoice" targetRef="exclusiveGateway_b66a0444-2889-4cae-ac78-c1f65405ba0b"/>
    <sequenceFlow id="sequenceFlow_9156c1d4-f756-447b-a384-e3bf17c43ff4" name="yes" sourceRef="exclusiveGateway_b66a0444-2889-4cae-ac78-c1f65405ba0b" targetRef="userTask_79a63b4f-96a7-43d7-aab3-e8e0e83769e5">
      <conditionExpression id="conditionExpression_13d7607b-d7e7-4fa4-a364-aeabff28a0f1">${approved}</conditionExpression>
    </sequenceFlow>
    <userTask camunda:candidateGroups="accounting" camunda:formKey="embedded:app:forms/prepare-bank-transfer.html" id="userTask_79a63b4f-96a7-43d7-aab3-e8e0e83769e5" name="Prepare Bank Transfer">
      <incoming>sequenceFlow_9156c1d4-f756-447b-a384-e3bf17c43ff4</incoming>
      <outgoing>sequenceFlow_b82e04ab-a0cc-4969-989a-1ec64d74d990</outgoing>
    </userTask>
    <serviceTask id="serviceTask_9d976f99-f8fb-4705-ae6d-a606758111f4" name="Archive Invoice">
      <incoming>sequenceFlow_b82e04ab-a0cc-4969-989a-1ec64d74d990</incoming>
      <outgoing>sequenceFlow_f0f99688-25b1-417e-9a5d-0d17503cb3ba</outgoing>
    </serviceTask>
    <sequenceFlow id="sequenceFlow_b82e04ab-a0cc-4969-989a-1ec64d74d990" sourceRef="userTask_79a63b4f-96a7-43d7-aab3-e8e0e83769e5" targetRef="serviceTask_9d976f99-f8fb-4705-ae6d-a606758111f4"/>
    <endEvent id="endEvent_affc9949-819a-4b79-98bd-0f2fd844d014" name="Invoice processed">
      <incoming>sequenceFlow_f0f99688-25b1-417e-9a5d-0d17503cb3ba</incoming>
    </endEvent>
    <sequenceFlow id="sequenceFlow_f0f99688-25b1-417e-9a5d-0d17503cb3ba" sourceRef="serviceTask_9d976f99-f8fb-4705-ae6d-a606758111f4" targetRef="endEvent_affc9949-819a-4b79-98bd-0f2fd844d014"/>
    <sequenceFlow id="sequenceFlow_29bab2ba-6108-4731-94c3-93a6955d1946" name="no" sourceRef="exclusiveGateway_b66a0444-2889-4cae-ac78-c1f65405ba0b" targetRef="userTask_7b955191-5741-44df-b7ba-d154ddfbe616">
      <conditionExpression id="conditionExpression_ce06d35f-8638-4699-802b-7f1b94732f46">${!approved}</conditionExpression>
    </sequenceFlow>
    <userTask camunda:assignee="demo" camunda:formKey="embedded:app:forms/review-invoice.html" id="userTask_7b955191-5741-44df-b7ba-d154ddfbe616" name="Review Invoice">
      <incoming>sequenceFlow_29bab2ba-6108-4731-94c3-93a6955d1946</incoming>
      <outgoing>sequenceFlow_74d95e16-7907-4000-9ffb-cdba8c3ddde4</outgoing>
    </userTask>
    <exclusiveGateway gatewayDirection="Diverging" id="exclusiveGateway_5aa645c5-a5e7-4453-8498-8462092b85b1" name="Review successful?">
      <incoming>sequenceFlow_74d95e16-7907-4000-9ffb-cdba8c3ddde4</incoming>
      <outgoing>sequenceFlow_3bf21b37-c534-4541-b84f-03d3fe05c989</outgoing>
      <outgoing>sequenceFlow_6987bcb8-5f6a-467b-9b25-e21b90dd0d4d</outgoing>
    </exclusiveGateway>
    <sequenceFlow id="sequenceFlow_74d95e16-7907-4000-9ffb-cdba8c3ddde4" sourceRef="userTask_7b955191-5741-44df-b7ba-d154ddfbe616" targetRef="exclusiveGateway_5aa645c5-a5e7-4453-8498-8462092b85b1"/>
    <sequenceFlow id="sequenceFlow_3bf21b37-c534-4541-b84f-03d3fe05c989" name="no" sourceRef="exclusiveGateway_5aa645c5-a5e7-4453-8498-8462092b85b1" targetRef="endEvent_68057c88-fa46-43cc-a93c-774b26be7b50">
      <conditionExpression id="conditionExpression_8bb43e50-dd2f-40ba-9cc9-91a69813a835">${!clarified}</conditionExpression>
    </sequenceFlow>
    <endEvent id="endEvent_68057c88-fa46-43cc-a93c-774b26be7b50" name="Invoice not processed">
      <incoming>sequenceFlow_3bf21b37-c534-4541-b84f-03d3fe05c989</incoming>
    </endEvent>
    <sequenceFlow id="sequenceFlow_6987bcb8-5f6a-467b-9b25-e21b90dd0d4d" name="yes" sourceRef="exclusiveGateway_5aa645c5-a5e7-4453-8498-8462092b85b1" targetRef="approveInvoice">
      <conditionExpression id="conditionExpression_c3fb4bcf-905a-47dd-84cf-6cfe1980ca2e">${clarified}</conditionExpression>
    </sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_946186ab-976d-40e2-9ed2-4672566340a2">
    <bpmndi:BPMNPlane bpmnElement="process_e14ca74e-6095-4a2b-bced-debcb3b37954" id="BPMNPlane_78d2c2b7-e5e7-414f-abae-0dcddaefbdfd">
      <bpmndi:BPMNShape bpmnElement="startEvent_e8daa33a-af99-4b7a-87ee-200b0e7bedcd" id="BPMNShape_2da46013-859f-418a-8612-25bd57aabb52">
        <dc:Bounds height="36.0" width="36.0" x="100.0" y="100.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="userTask_029f48b9-de24-4292-af49-845e81fdcc4b" id="BPMNShape_79fd4553-cfb0-4740-b4fe-093d1c96474b">
        <dc:Bounds height="80.0" width="100.0" x="186.0" y="78.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_cb28e7e5-ebc0-4f27-b8b2-24403ab87ce3" id="BPMNEdge_77315869-b425-4daf-85b6-6121867d91bc">
        <di:waypoint x="136.0" y="118.0"/>
        <di:waypoint x="186.0" y="118.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="approveInvoice" id="BPMNShape_b54e1244-b4fb-4e70-8c0d-4b76eb2798b0">
        <dc:Bounds height="80.0" width="100.0" x="336.0" y="78.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_57a81fcd-fb35-42cd-afca-7c4c7914936d" id="BPMNEdge_dfa41f0e-273b-4d62-b8b8-ef9286b24f7d">
        <di:waypoint x="286.0" y="118.0"/>
        <di:waypoint x="336.0" y="118.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="exclusiveGateway_b66a0444-2889-4cae-ac78-c1f65405ba0b" id="BPMNShape_bc1af4c3-ef1f-4c26-a42c-b1c2982bf35c" isMarkerVisible="true">
        <dc:Bounds height="50.0" width="50.0" x="486.0" y="93.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_1fe15a36-427e-4000-8fc7-50b8925ea1e5" id="BPMNEdge_9e81a730-2822-4c70-8061-9bbecf05022d">
        <di:waypoint x="436.0" y="118.0"/>
        <di:waypoint x="486.0" y="118.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="userTask_79a63b4f-96a7-43d7-aab3-e8e0e83769e5" id="BPMNShape_8c455164-1097-43ca-814f-6d601735b27a">
        <dc:Bounds height="80.0" width="100.0" x="586.0" y="78.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_9156c1d4-f756-447b-a384-e3bf17c43ff4" id="BPMNEdge_47ba6c2b-fd10-4faf-8ee9-cbe019282080">
        <di:waypoint x="536.0" y="118.0"/>
        <di:waypoint x="586.0" y="118.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="serviceTask_9d976f99-f8fb-4705-ae6d-a606758111f4" id="BPMNShape_d2e257f4-4c63-44e6-a8ab-d3179a83682c">
        <dc:Bounds height="80.0" width="100.0" x="736.0" y="78.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_b82e04ab-a0cc-4969-989a-1ec64d74d990" id="BPMNEdge_e5727f31-71a0-4f03-8a9d-7aca2cd9999a">
        <di:waypoint x="686.0" y="118.0"/>
        <di:waypoint x="736.0" y="118.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="endEvent_affc9949-819a-4b79-98bd-0f2fd844d014" id="BPMNShape_4a1b2f6f-6929-4fd7-b6cf-9f49410242d7">
        <dc:Bounds height="36.0" width="36.0" x="886.0" y="100.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_f0f99688-25b1-417e-9a5d-0d17503cb3ba" id="BPMNEdge_28346c07-6da9-41d2-bcf2-76a9583ea933">
        <di:waypoint x="836.0" y="118.0"/>
        <di:waypoint x="886.0" y="118.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="userTask_7b955191-5741-44df-b7ba-d154ddfbe616" id="BPMNShape_1c62b32d-2c17-48b0-bbc7-ac0074c97e62">
        <dc:Bounds height="80.0" width="100.0" x="586.0" y="208.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_29bab2ba-6108-4731-94c3-93a6955d1946" id="BPMNEdge_40c826c8-638d-4cd2-b9a1-ccdfa9a5ee6e">
        <di:waypoint x="511.0" y="143.0"/>
        <di:waypoint x="511.0" y="248.0"/>
        <di:waypoint x="586.0" y="248.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="exclusiveGateway_5aa645c5-a5e7-4453-8498-8462092b85b1" id="BPMNShape_36d66f18-3b46-4fde-9342-3ccd8d0f838d" isMarkerVisible="true">
        <dc:Bounds height="50.0" width="50.0" x="736.0" y="223.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_74d95e16-7907-4000-9ffb-cdba8c3ddde4" id="BPMNEdge_257a94d2-a5f6-4a88-9503-f5b9fd65cc7e">
        <di:waypoint x="686.0" y="248.0"/>
        <di:waypoint x="736.0" y="248.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="endEvent_68057c88-fa46-43cc-a93c-774b26be7b50" id="BPMNShape_2dff264a-5f15-44d9-ba38-b962be76ea74">
        <dc:Bounds height="36.0" width="36.0" x="836.0" y="230.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_3bf21b37-c534-4541-b84f-03d3fe05c989" id="BPMNEdge_0a149725-d17c-4b6f-b7db-7854d0a27b00">
        <di:waypoint x="786.0" y="248.0"/>
        <di:waypoint x="836.0" y="248.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_6987bcb8-5f6a-467b-9b25-e21b90dd0d4d" id="BPMNEdge_8b25f14c-9653-42ab-a57f-48fc07bd7ee8">
        <di:waypoint x="761.0" y="273.0"/>
        <di:waypoint x="761.0" y="118.0"/>
        <di:waypoint x="336.0" y="118.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

By comparing it to the above xml, I notice a few new <bpmndi ...> elements not present before.

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement