diff --git a/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/BPMNTypes.java b/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/BPMNTypes.java index 027836c4..c81ab5fe 100644 --- a/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/BPMNTypes.java +++ b/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/BPMNTypes.java @@ -3,8 +3,14 @@ import java.util.Arrays; import java.util.List; +import org.openbpmn.bpmn.elements.SequenceFlow; +import org.openbpmn.bpmn.elements.core.BPMNElement; +import org.openbpmn.bpmn.elements.core.BPMNElementNode; + /** - * The BPMNTypes provides constants defining the visual elements contained in a BPMNDiagram. + * The BPMNTypes provides constants defining the visual elements contained in a + * BPMNDiagram. + * * @author rsoika * */ @@ -14,7 +20,7 @@ public class BPMNTypes { public static final String PROCESS_TYPE_PUBLIC = "Public"; public static final String PROCESS_TYPE_PRIVATE = "Private"; public static final String PROCESS_TYPE_NONE = "None"; - + // Tasks public static final String TASK = "task"; public static final String USER_TASK = "userTask"; @@ -24,15 +30,15 @@ public class BPMNTypes { public static final String MANUAL_TASK = "manualTask"; public static final String BUSINESSRULE_TASK = "businessRuleTask"; public static final String RECEIVE_TASK = "receiveTask"; - + // Events public static final String EVENT = "event"; public static final String START_EVENT = "startEvent"; - public static final String END_EVENT= "endEvent"; + public static final String END_EVENT = "endEvent"; public static final String CATCH_EVENT = "intermediateCatchEvent"; public static final String THROW_EVENT = "intermediateThrowEvent"; public static final String BOUNDARY_EVENT = "boundaryEvent"; - + // Event Definitions public static final String EVENT_DEFINITION_CONDITIONAL = "conditionalEventDefinition"; public static final String EVENT_DEFINITION_COMPENSATION = "compensationEventDefinition"; @@ -44,19 +50,19 @@ public class BPMNTypes { public static final String EVENT_DEFINITION_ERROR = "errorEventDefinition"; public static final String EVENT_DEFINITION_TERMINATE = "terminateEventDefinition"; public static final String EVENT_DEFINITION_CANCEL = "cancelEventDefinition"; - + // Multiple Event Definitions public static final String MULTIPLE_EVENT_DEFINITIONS = "multipleEventDefinition"; - + // Gateways - public static final String GATEWAY = "gateway"; + public static final String GATEWAY = "gateway"; public static final String EXCLUSIVE_GATEWAY = "exclusiveGateway"; public static final String INCLUSIVE_GATEWAY = "inclusiveGateway"; public static final String EVENTBASED_GATEWAY = "eventBasedGateway"; public static final String PARALLEL_GATEWAY = "parallelGateway"; public static final String COMPLEX_GATEWAY = "complexGateway"; - - // Others + + // Others public static final String DATAOBJECT = "dataObject"; public static final String TEXTANNOTATION = "textAnnotation"; public static final String POOL = "pool"; @@ -127,6 +133,20 @@ public class BPMNTypes { BPMNTypes.SEQUENCE_FLOW); + /** + * Returns true if the given element is a FlowElement, + * which are Events, Gateways , Sequence Flows or Activities + */ + public static boolean isFlowElement(BPMNElement element) { + if (element instanceof SequenceFlow) { + return true; + } + if (element instanceof BPMNElementNode) { + return BPMN_FLOWELEMENTS.contains(((BPMNElementNode) element).getType()); + } + return false; + } + public final static List BPMN_NODE_ELEMENTS = Arrays.asList(// BPMNTypes.TASK, // BPMNTypes.MANUAL_TASK, // @@ -180,4 +200,3 @@ public class BPMNTypes { }); } - diff --git a/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/elements/Lane.java b/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/elements/Lane.java index 55bfcda6..ed79bf0c 100644 --- a/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/elements/Lane.java +++ b/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/elements/Lane.java @@ -105,7 +105,7 @@ public Element getLaneSet() { } /** - * This method insets a given BPMNFlowElment to this lane + * This method inserts a given BPMNFlowElement to this lane *

* {@code * @@ -117,12 +117,14 @@ public Element getLaneSet() { * @param laneId * @throws BPMNInvalidReferenceException */ - public void insert(BPMNElementNode bpmnEementNode) throws BPMNInvalidReferenceException { - // append flowNodeRef - Element flowNodeRef = model.createElement(BPMNNS.BPMN2, "flowNodeRef"); - Text textNode = this.getDoc().createTextNode(bpmnEementNode.getId()); - flowNodeRef.appendChild(textNode); - this.getElementNode().appendChild(flowNodeRef); + public void insert(BPMNElementNode bpmnElementNode) { + // append flowNodeRef if not yet listed + if (!contains(bpmnElementNode)) { + Element flowNodeRef = model.createElement(BPMNNS.BPMN2, "flowNodeRef"); + Text textNode = this.getDoc().createTextNode(bpmnElementNode.getId()); + flowNodeRef.appendChild(textNode); + this.getElementNode().appendChild(flowNodeRef); + } } /** @@ -143,6 +145,32 @@ public boolean contains(BPMNElementNode bpmnElement) { return false; } + /** + * This method removes a given BPMNFlowElement from this lane + *

+ * {@code + * + + StartEvent_4 + } + * + * @param element + * @param laneId + * @throws BPMNInvalidReferenceException + */ + public void remove(BPMNElementNode bpmnElement) { + // remove flowNodeRef if listed + if (contains(bpmnElement)) { + Set refs = model.findChildNodesByName(this.getElementNode(), BPMNNS.BPMN2, "flowNodeRef"); + for (Element element : refs) { + if (bpmnElement.getId().equals(element.getTextContent())) { + this.elementNode.removeChild(element); + break; + } + } + } + } + /** * Returns a list of a all BPMNFlowElement IDs contained by this lane * @@ -157,6 +185,25 @@ public Set getFlowElementIDs() { return result; } + /** + * This method adds a flowElementID to the lane + * + * @param id + */ + /* + * public void addFlowElementID(String id) { + * // test if the id is already listed... + * Set refList = getFlowElementIDs(); + * if (refList.contains(id)) { + * // already listed - no op + * return; + * } + * // add id.... + * model.createElement(null, id) + * + * } + */ + @Override public double getDefaultWidth() { return DEFAULT_WIDTH; diff --git a/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/elements/core/BPMNElementNode.java b/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/elements/core/BPMNElementNode.java index fdb5d439..53506905 100644 --- a/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/elements/core/BPMNElementNode.java +++ b/open-bpmn.metamodel/src/main/java/org/openbpmn/bpmn/elements/core/BPMNElementNode.java @@ -7,6 +7,7 @@ import org.openbpmn.bpmn.BPMNTypes; import org.openbpmn.bpmn.elements.Association; import org.openbpmn.bpmn.elements.BPMNProcess; +import org.openbpmn.bpmn.elements.Lane; import org.openbpmn.bpmn.elements.Participant; import org.openbpmn.bpmn.elements.SequenceFlow; import org.openbpmn.bpmn.exceptions.BPMNInvalidReferenceException; @@ -104,9 +105,36 @@ public BPMNBounds setBounds(double x, double y, double width, double height) thr return bounds; } + /** + * Update the absolute position of a BPMNElementNode. + * + * When BPMNElementNode is part of a participant with lanes, this method + * automatically updates the bpmn2:flowNodeRef of the containing lane. + * + * @param x + * @param y + */ public void setPosition(double x, double y) { try { this.getBounds().setPosition(x, y); + + // update lane flowNodeRef if the element is part of a pool with lanes.... + if (BPMNTypes.isFlowElement(this) + && this.getBpmnProcess().getProcessType() == BPMNTypes.PROCESS_TYPE_PRIVATE) { + // get all lanes and test if a lane is the containing lane according to the + // position. + Set lanes = this.getBpmnProcess().getLanes(); + for (Lane lane : lanes) { + BPMNBounds laneBounds = lane.getBounds(); + if (laneBounds.containsPoint(new BPMNPoint(x, y))) { + // found containing lane! + lane.insert(this); + } else { + // remove if listed in this lane.... + lane.remove(this); + } + } + } } catch (BPMNMissingElementException e) { BPMNModel.error("Failed to update bounds position for element '" + this.getId() + "'"); } diff --git a/open-bpmn.metamodel/src/test/java/org/openbpmn/metamodel/test/elements/TestFlowNodeRef.java b/open-bpmn.metamodel/src/test/java/org/openbpmn/metamodel/test/elements/TestFlowNodeRef.java new file mode 100644 index 00000000..38ebeb90 --- /dev/null +++ b/open-bpmn.metamodel/src/test/java/org/openbpmn/metamodel/test/elements/TestFlowNodeRef.java @@ -0,0 +1,112 @@ +package org.openbpmn.metamodel.test.elements; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Set; +import java.util.logging.Logger; + +import org.junit.jupiter.api.Test; +import org.openbpmn.bpmn.BPMNModel; +import org.openbpmn.bpmn.BPMNTypes; +import org.openbpmn.bpmn.elements.Activity; +import org.openbpmn.bpmn.elements.BPMNProcess; +import org.openbpmn.bpmn.elements.Lane; +import org.openbpmn.bpmn.elements.Participant; +import org.openbpmn.bpmn.exceptions.BPMNModelException; +import org.openbpmn.bpmn.util.BPMNModelFactory; + +/** + * This test class is testing the bpmn2:flowNodeRef element of a Lane within a + * participant. + * + * The flowNodeRef depends on the position of a flow element. If setPosition is + * called on a BPMNElementNode the flowNodeRef of a containing lane is + * automatically updated. + * + * @author rsoika + * + */ +public class TestFlowNodeRef { + + private static Logger logger = Logger.getLogger(TestBPMNLanes.class.getName()); + + static BPMNModel model = null; + + /** + * This test class creates a Collaboration model with a 2 BPMNLane and a flow + * element in one lane. + * The test verifies if the flowNodeRef of the lane is updated correctly. + * + * For this reason first a task is crated without a position. So it is not a + * nodeRef of any lane. + * Then the position is changed to become part of lane1 and next the position is + * changed to lane2 + */ + @Test + public void testCreateCollaborationModelWithLane() { + String out = "src/test/resources/output/flownode-ref_1.bpmn"; + + logger.info("...create collaboration model with 2 lanes"); + + String exporter = "demo"; + String version = "1.0.0"; + String targetNameSpace = "http://org.openbpmn"; + BPMNModel model = BPMNModelFactory.createInstance(exporter, version, targetNameSpace); + + try { + assertEquals(1, model.getProcesses().size()); + + // create participant + Participant participantSales = model.addParticipant("Sales Team"); + assertTrue(model.isCollaborationDiagram()); + assertEquals(2, model.getProcesses().size()); + assertEquals(2, model.getParticipants().size()); + + BPMNProcess process = participantSales.openProcess(); + // add a new Lane + Lane laneA = process.addLane("Team-A"); + assertNotNull(laneA); + Lane laneB = process.addLane("Team-B"); + assertNotNull(laneB); + + // add a task + Activity task = process.addTask("task_1", "Task", BPMNTypes.TASK); + + // if the task has not yet a position, than we expect that the lane has no + // nodeRefs + Set flowElementList = laneA.getFlowElementIDs(); + assertNotNull(flowElementList); + + assertEquals(0, flowElementList.size()); + + // now we set the position of the task .... + task.setPosition(160, 50); + // now the task should be part of laneA + flowElementList = laneA.getFlowElementIDs(); + assertEquals(1, flowElementList.size()); + assertEquals("task_1", flowElementList.iterator().next()); + + // next move the element to the second lane.... + task.setPosition(160, 220); + flowElementList = laneA.getFlowElementIDs(); + // in laneA no more nodeRefs are expected.... + assertEquals(0, flowElementList.size()); + // ...but in laneB.... + flowElementList = laneB.getFlowElementIDs(); + assertEquals(1, flowElementList.size()); + assertEquals("task_1", flowElementList.iterator().next()); + + } catch (BPMNModelException e) { + e.printStackTrace(); + fail(); + } + assertNotNull(model); + + model.save(out); + logger.info("...model created successful: " + out); + } + +}