Skip to content

Commit

Permalink
Added new TimerEventDefinitionExtension
Browse files Browse the repository at this point in the history
Issue imixs#177
  • Loading branch information
rsoika committed Feb 14, 2023
1 parent cc67937 commit 89d5420
Show file tree
Hide file tree
Showing 7 changed files with 524 additions and 2 deletions.
28 changes: 28 additions & 0 deletions open-bpmn.glsp-client/workspace/test-timer.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" exporter="org.openbpmn" exporterVersion="1.0.0" targetNamespace="http://org.openbpmn">
<bpmn2:process id="process_1" name="Default Process" processType="Public">
<bpmn2:documentation id="documentation_irBnnQ"/>
<bpmn2:startEvent id="event_n8bj0g" name="Event-1">
<bpmn2:documentation id="documentation_4XhEKA"/>
<bpmn2:timerEventDefinition id="timerEventDefinition_hElKhw">
<bpmn2:timeDuration id="FormalExpression_0" xsi:type="bpmn2:tFormalExpression">3cc</bpmn2:timeDuration>
</bpmn2:timerEventDefinition>
<bpmn2:timerEventDefinition id="timerEventDefinition_dFC6Sg">
<bpmn2:timeCycle id="FormalExpression_1" xsi:type="bpmn2:tFormalExpression">2bx</bpmn2:timeCycle>
</bpmn2:timerEventDefinition>
<bpmn2:timerEventDefinition id="timerEventDefinition_peWH9A">
<bpmn2:timeDate id="FormalExpression_2" xsi:type="bpmn2:tFormalExpression">1ay</bpmn2:timeDate>
</bpmn2:timerEventDefinition>
</bpmn2:startEvent>
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1" name="OpenBPMN Diagram">
<bpmndi:BPMNPlane bpmnElement="process_1" id="BPMNPlane_1">
<bpmndi:BPMNShape bpmnElement="event_n8bj0g" id="BPMNShape_KvjEBA">
<dc:Bounds height="36.0" width="36.0" x="90.0" y="54.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_HFenEA">
<dc:Bounds height="16.0" width="100.0" x="58.0" y="90.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@ private void updateLinkEventDefinitions(final List<Element> eventDefinitions, fi
}

/**
* Adds the ConditionalEvent definitions
* Adds the ConditionalEvent definitions. Here we use a detail-control layout to
* have more flexibility designing the widgets.
*
* @param eventDefinitions
* @param dataBuilder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
/********************************************************************************
* Copyright (c) 2022 Imixs Software Solutions GmbH and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
package org.openbpmn.extension;

import java.util.Iterator;
import java.util.Set;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.glsp.graph.GModelElement;
import org.openbpmn.bpmn.BPMNModel;
import org.openbpmn.bpmn.BPMNNS;
import org.openbpmn.bpmn.BPMNTypes;
import org.openbpmn.bpmn.elements.Event;
import org.openbpmn.bpmn.elements.core.BPMNElement;
import org.openbpmn.bpmn.exceptions.BPMNModelException;
import org.openbpmn.glsp.jsonforms.DataBuilder;
import org.openbpmn.glsp.jsonforms.SchemaBuilder;
import org.openbpmn.glsp.jsonforms.UISchemaBuilder;
import org.openbpmn.glsp.jsonforms.UISchemaBuilder.Layout;
import org.openbpmn.glsp.model.BPMNGModelState;
import org.w3c.dom.Element;

import com.google.inject.Inject;

/**
* The TimerEventDefinitionExtension is responsible to read and update optional
* TimerEventDefinitions from the BPMN model. The Extension builds a custom
* property section named 'Timers' shown a list of all TimerEventDefinitions
* define in a Event.
*
* @author rsoika
*/
public class TimerEventDefinitionExtension extends AbstractBPMNElementExtension {

@SuppressWarnings("unused")
private static Logger logger = LogManager.getLogger(DefaultBPMNSequenceFlowExtension.class);

@Inject
protected BPMNGModelState modelState;

public TimerEventDefinitionExtension() {
super();
}

@Override
public int getPriority() {
return 100; // below default settings from Edge element
}

/**
* Returns if this Extension can be applied to the given elementTypeID
*/
@Override
public boolean handlesElementTypeId(final String elementTypeId) {
return BPMNTypes.BPMN_EVENTS.contains(elementTypeId);
}

/**
* This Extension is for BPMNEvents only
*/
@Override
public boolean handlesBPMNElement(final BPMNElement bpmnElement) {
return (bpmnElement instanceof Event);
}

/**
* This Helper Method generates a JSON Object with the BPMNElement properties.
* <p>
* This json object is used on the GLSP Client to generate the EMF JsonForms
*/
@Override
public void buildPropertiesForm(final BPMNElement bpmnElement, final DataBuilder dataBuilder,
final SchemaBuilder schemaBuilder, final UISchemaBuilder uiSchemaBuilder) {

Event event = (Event) bpmnElement;

// Link
Set<Element> linkEventDefinitions = event.getEventDefinitionsByType("timerEventDefinition");

if (linkEventDefinitions.size() > 0) {
/******************************
* Create UISchema
******************************/
final JsonObject radioOption = Json.createObjectBuilder() //
.add("format", "radio").build();
uiSchemaBuilder. //
addCategory("Timer Definitions"). //
addLayout(Layout.VERTICAL);
// create a detail control Layout....
JsonArrayBuilder controlsArrayBuilder = Json.createArrayBuilder();
controlsArrayBuilder //
.add(Json.createObjectBuilder() //
.add("type", "Control") //
.add("scope", "#/properties/type") //
.add("options", radioOption) //
) //
.add(Json.createObjectBuilder() //
.add("type", "Control") //
.add("scope", "#/properties/value") //
.add("label", "Value") //
);
JsonObjectBuilder detailLayoutBuilder = Json.createObjectBuilder(). //
add("type", "HorizontalLayout"). ///
add("elements", controlsArrayBuilder);
JsonObjectBuilder detailBuilder = Json.createObjectBuilder(). //
add("detail", detailLayoutBuilder.build());
uiSchemaBuilder.addDetailLayout("timers", "Timer Definition", detailBuilder.build());

/***************************
* Create schemaBuilder
***************************/
String[] timerTypes = { "Time/Date", "Interval", "Duration" };
schemaBuilder.addArray("timers");
schemaBuilder.addProperty("type", "string", null, timerTypes);
schemaBuilder.addProperty("value", "string", null, null);

/**************************
* Create DataBuilder
*
* Each timerDefinition is represented as a separate object
**************************/
dataBuilder.addArray("timers");

for (Element timerDefinition : linkEventDefinitions) {
dataBuilder.addObject();
// test the type of the timer object....
Element timeDuration = BPMNModel.findChildNodeByName(timerDefinition,
event.getBpmnProcess().getModel().getPrefix(BPMNNS.BPMN2) + ":timeDuration");

Element timeCycle = BPMNModel.findChildNodeByName(timerDefinition,
event.getBpmnProcess().getModel().getPrefix(BPMNNS.BPMN2) + ":timeCycle");

Element timeDate = BPMNModel.findChildNodeByName(timerDefinition,
event.getBpmnProcess().getModel().getPrefix(BPMNNS.BPMN2) + ":timeDate");

String timerValue = "";
String timerType = "Time/Date"; // default
if (timeDate != null) {
timerType = "Time/Date";
timerValue = timeDate.getTextContent();
}
if (timeDuration != null) {
timerType = "Duration";
timerValue = timeDuration.getTextContent();
}
if (timeCycle != null) {
timerType = "Interval";
timerValue = timeCycle.getTextContent();
}
dataBuilder.addData("type", timerType);
dataBuilder.addData("value", timerValue);
}
}
}

/**
* Update the timers definitions
*/
@Override
public void updatePropertiesData(final JsonObject json, final BPMNElement bpmnElement,
final GModelElement gNodeElement) {

Event bpmnEvent = (Event) bpmnElement;
Set<String> features = json.keySet();
for (String feature : features) {

// Update eventDefinitions for each definition type...
Set<Element> eventDefinitions = bpmnEvent.getEventDefinitions();
if ("timers".equals(feature)) {
JsonArray dataList = json.getJsonArray("timers");
updateTimerEventDefinitions(bpmnEvent, dataList);
}

}
}

/**
* This method updates all timerEventDefinitions. The method expects a
* dataList containing all timer definitions with its values.
* The method simply overwrites all timerEventDefinitions.
* <p>
* Example:
*
* <pre>
* {@code
* <bpmn2:startEvent id="event_n8bj0g" name="Event-1">
* <bpmn2:documentation id="documentation_4XhEKA"/>
* <bpmn2:timerEventDefinition id="timerEventDefinition_hElKhw">
* <bpmn2:timeDuration id="FormalExpression_0" xsi:type=
"bpmn2:tFormalExpression">3cc</bpmn2:timeDuration>
* </bpmn2:timerEventDefinition>
* </bpmn2:startEvent>
* }
* </pre>
*
* @param bpmnEvent
* @param dataList
*/
private void updateTimerEventDefinitions(final Event bpmnEvent, final JsonArray dataList) {
// find all conditionalEventDefinitions for this event
Set<Element> timerEventDefinitions = bpmnEvent.getEventDefinitionsByType("timerEventDefinition");
// If the size of the DataList is not equals the size of the
// DefinitionList we add or remove definitions...
while (timerEventDefinitions.size() != dataList.size()) {
try {
if (timerEventDefinitions.size() < dataList.size()) {
// add a new empty condition placeholder...
bpmnEvent.addEventDefinition("timerEventDefinition");
}
if (timerEventDefinitions.size() > dataList.size()) {
// delete first condition from the list
Element definition = timerEventDefinitions.iterator().next();
String id = definition.getAttribute("id");
bpmnEvent.deleteEventDefinition(id);
}
} catch (BPMNModelException e) {
logger.error("Failed to update BPMN Event Definition list: " + e.getMessage());
e.printStackTrace();
}
// Update event definition list
timerEventDefinitions = bpmnEvent.getEventDefinitionsByType("timerEventDefinition");
}

// now we can update the values one by one
// NOTE: the id can change within the definitionList if an element was deleted
// or moved! but we do not care about this issue.
Iterator<Element> iter = timerEventDefinitions.iterator();
int i = 0;
while (iter.hasNext()) {
Element eventDefinitionElement = iter.next();
JsonObject jsonData = dataList.getJsonObject(i); // .get(i);
if (jsonData != null) {
// remove the old child elements
while (eventDefinitionElement.hasChildNodes()) {
eventDefinitionElement.removeChild(eventDefinitionElement.getFirstChild());
}
// depending on the type we need to create a new child element
Element timerObject = null;
// { "Time/Date", "Interval", "Duration" };
String timerType = jsonData.getString("type", "Time/Date");
if ("Time/Date".equals(timerType)) {
timerObject = bpmnEvent.getModel().createElement(BPMNNS.BPMN2, "timeDate");
}
if ("Duration".equals(timerType)) {
timerObject = bpmnEvent.getModel().createElement(BPMNNS.BPMN2, "timeDuration");
}
if ("Interval".equals(timerType)) {
timerObject = bpmnEvent.getModel().createElement(BPMNNS.BPMN2, "timeCycle");

}

// xsi:type="bpmn2:tFormalExpression" id="FormalExpression_1"
timerObject.setAttribute("xsi:type", "bpmn2:tFormalExpression");
timerObject.setAttribute("id", "FormalExpression_" + i);
timerObject.setTextContent(jsonData.getString("value", ""));

// finally add the new updated timerObject
eventDefinitionElement.appendChild(timerObject);

}
i++;
// update completed
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.openbpmn.extension.DefaultBPMNSequenceFlowExtension;
import org.openbpmn.extension.DefaultBPMNTaskExtension;
import org.openbpmn.extension.DefaultBPMNTextAnnotationExtension;
import org.openbpmn.extension.TimerEventDefinitionExtension;
import org.openbpmn.glsp.elements.data.BPMNApplyEditLabelOperationHandler;
import org.openbpmn.glsp.elements.data.BPMNCreateDataObjectHandler;
import org.openbpmn.glsp.elements.data.BPMNCreateMessageHandler;
Expand Down Expand Up @@ -230,6 +231,7 @@ public void configureBPMNExtensions(final Multibinder<BPMNExtension> binding) {
binding.addBinding().to(DefaultBPMNTextAnnotationExtension.class);
binding.addBinding().to(DefaultBPMNEdgeExtension.class);
binding.addBinding().to(DefaultBPMNSequenceFlowExtension.class);
binding.addBinding().to(TimerEventDefinitionExtension.class);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,10 @@ public Participant findParticipantByProcessId(String processId) {

/**
* This helper method returns a set of child nodes by name from a given parent
* node.
* node. If no nodes were found, the method returns an empty list.
*
* See also {@link #findChildNodeByName(Element parent, String nodeName)
* findChildNodeByName}
*
* @param parent
* @param nodeName
Expand All @@ -1200,6 +1203,29 @@ public static Set<Element> findChildNodesByName(Element parent, String nodeName)
return result;
}

/**
* This helper method returns the first child node by name from a given parent
* node. If no nodes were found the method returns null.
*
* See also {@link #findChildNodesByName(Element parent, String nodeName)
* findChildNodesByName}
*
* @param parent
* @param nodeName
* @return - Child Element matching the given node name. If no nodes were found,
* the method returns null
*/
public static Element findChildNodeByName(Element parent, String nodeName) {
Set<Element> elementList = findChildNodesByName(parent, nodeName);
if (elementList.iterator().hasNext()) {
// return first element
return elementList.iterator().next();
} else {
// no child elements with the given name found!
return null;
}
}

/**
* Writes the current instance to the file system.
*
Expand Down
Loading

0 comments on commit 89d5420

Please sign in to comment.