Skip to content

Commit

Permalink
implemented new FlowNavigator, refactoring, docu
Browse files Browse the repository at this point in the history
Issue #237
  • Loading branch information
rsoika committed Aug 9, 2024
1 parent 9580811 commit 1904749
Show file tree
Hide file tree
Showing 10 changed files with 931 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1499,7 +1499,7 @@ public void save(File file) {
}

// finally write the xml to disk
writeXml(doc, output);
writeToOutputStream(doc, output);
} catch (TransformerException | IOException e) {
logger.warning("Failed to save BPMN file: " + e.getMessage());
e.printStackTrace();
Expand Down Expand Up @@ -1539,7 +1539,7 @@ public void save(String filePath) {
}

// finally write the xml to disk
writeXml(doc, output);
writeToOutputStream(doc, output);
} catch (TransformerException | IOException e) {
logger.warning("Failed to save BPMN file: " + e.getMessage());
e.printStackTrace();
Expand Down Expand Up @@ -2004,7 +2004,7 @@ private void loadMessageList() throws BPMNModelException {
* @param output
* @throws TransformerException
*/
private void writeXml(Document doc, OutputStream output) throws TransformerException {
public void writeToOutputStream(Document doc, OutputStream output) throws TransformerException {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@
package org.openbpmn.bpmn.exceptions;

/**
* BPMNModelException is the abstract super class for all BPMN
* Exception classes. A BPMNModelException signals an error in the Model
* BPMNModelException is the abstract super class for all BPMN
* Exception classes. A BPMNModelException signals an error in the Model
* logic. BPMNModelException need to be caught.
*
* @author rsoika
*/
public abstract class BPMNModelException extends Exception {

public static final String INVALID_MODEL = "INVALID_MODEL";

private static final long serialVersionUID = 1L;

protected String errorContext = "UNDEFINED";
protected String errorCode = "UNDEFINED";

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,69 +1,167 @@
package org.openbpmn.bpmn.navigation;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.logging.Logger;

import org.openbpmn.bpmn.elements.Gateway;
import org.openbpmn.bpmn.elements.SequenceFlow;
import org.openbpmn.bpmn.elements.core.BPMNElementNode;
import org.openbpmn.bpmn.exceptions.BPMNValidationException;

/**
* The BPMNFlowNavigator can be used to navigate through a BPMN model. Based on
* a Source BPMNElement the navigator routes to the next Flow Element fulfilling
* a
* given criteria. For example you can find all Event Nodes followed by an
* Activity Node. Or you can navigate to the next Activity from an Event.
* <p>
* The BPMNFlowNavigator expects a filter argument (Functional Interface
* Predicate) with one BPMNElementNode argument that return a boolean value.
* <p>
* In case the filter does not specify a Gateway, Gateway Nodes are ignored.
*
*/
public class BPMNFlowNavigator<T> implements Iterator<BPMNElementNode> {

private Predicate<BPMNElementNode> filter;
protected static Logger logger = Logger.getLogger(BPMNElementNode.class.getName());

private Predicate<BPMNElementNode> filter = null;
private Predicate<String> conditionEvaluator = null;
private int index;
BPMNElementNode bpmnElementNode = null;
List<SequenceFlow> outgoingFlows;
Iterator<SequenceFlow> flowIterator;
BPMNElementNode targetNode = null;
private List<BPMNElementNode> targetNodes;

/**
* Creates a new BPMNFlowNavigator with a given filter criteria.
* The method collects all BPMNElements following the given start element and
* matching the given filter
*
* @param bpmnElementNode
* @param filter
* @throws BPMNValidationException
*/
public BPMNFlowNavigator(BPMNElementNode bpmnElementNode, Predicate<BPMNElementNode> filter)
throws BPMNValidationException {
this.bpmnElementNode = bpmnElementNode;
this.filter = filter;
this.targetNodes = new ArrayList<>();
this.index = 0;

// find outgoing flows
outgoingFlows = new ArrayList<>(bpmnElementNode.getOutgoingSequenceFlows());

// if we have more than one outgoing flow, we thrown an exception
if (outgoingFlows.size() > 1) {
throw new BPMNValidationException(
"Element '" + bpmnElementNode.getId() + "' should not have more than one outgoing SequenceFlow!");
}

// find all elements
findValidNodes(bpmnElementNode);
}

public BPMNElementNode findTarget(Predicate<T> filter) {
BPMNElementNode result = null;

return result;
/**
* Creates a new BPMNFlowNavigator with a given filter criteria.
* The method collects all BPMNElements following the given start element and
* matching the given filter
*
* @param bpmnElementNode
* @param filter
* @param conditionEvaluator optional conditional evaluator
* @throws BPMNValidationException
*/
public BPMNFlowNavigator(BPMNElementNode bpmnElementNode, Predicate<BPMNElementNode> filter,
Predicate<String> conditionEvaluator)
throws BPMNValidationException {
this.filter = filter;
this.conditionEvaluator = conditionEvaluator;
this.targetNodes = new ArrayList<>();
this.index = 0;
// find all elements
findValidNodes(bpmnElementNode);
}

@Override
public boolean hasNext() {
while (index < outgoingFlows.size()) {
return index < targetNodes.size();
}

SequenceFlow flow = outgoingFlows.get(index);
@Override
public BPMNElementNode next() {
if (!hasNext()) {
throw new IllegalStateException("No more elements");
}
return targetNodes.get(index++);
}

targetNode = flow.getTargetElement();
/**
* Iterates tough all outgoing sequence flows and tests if the target element
* matches the filter criteria.
* <p>
* If a element does not match the filter criteria and is an instance of a
* Gateway, the method will recursive call all following elements of the gateway
* node.
*
* @param currentNode
*/
private void findValidNodes(BPMNElementNode currentNode) {
Set<SequenceFlow> flowSet = currentNode.getOutgoingSequenceFlows();
// Check if the sequence flow has a condition and evaluate it if a
// conditionEvaluator is defined
if (currentNode instanceof Gateway && conditionEvaluator != null) {
flowSet = filterConditionalFlows((Gateway) currentNode, flowSet);
}

if (filter.test(targetNode)) {
return true;
for (SequenceFlow flow : flowSet) {
BPMNElementNode node = flow.getTargetElement();
if (filter.test(node)) {
targetNodes.add(node);
} else {
// if the node is a Gateway, recursively search its outgoing flows
if (node instanceof Gateway) {
findValidNodes(node);
}
}
index++;

}
return false;
}

@Override
public BPMNElementNode next() {
if (!hasNext()) {
throw new IllegalStateException("No more elements");
/**
* This method filters all conditional flows outgoing from a gateway that did
* not eval to true by the given conditionEvaluator. The result is a Set with
* exactly one flow !
*
*
* @return
*/
private Set<SequenceFlow> filterConditionalFlows(Gateway gateway, Set<SequenceFlow> flowSet) {
LinkedHashSet<SequenceFlow> result = new LinkedHashSet<SequenceFlow>();

List<SequenceFlow> sortedList = new ArrayList<>(flowSet);
// Sort the list using a comparator that places empty conditions at the end
sortedList.sort(Comparator.comparing(flow -> {
String condition = flow.getConditionExpression();
return (condition == null || condition.trim().isEmpty()) ? 1 : 0;
}));

// now we eval each condition. As soon as we have a match or the condition is
// empty we stop.
for (SequenceFlow flow : sortedList) {
String condition = flow.getConditionExpression();
if (condition != null && (conditionEvaluator.test(condition) == true)) {
// the condition matches - return this as the result;
result.add(flow);
return result;

}
// In case the condition is empty this is the default flow...
if (condition == null || condition.trim().isEmpty()) {
// default condition
result.add(flow);
return result;
}
}
// return (T) outgoingFlows.get(index++);
index++;
return targetNode;
// no match - should not happen
logger.warning("Gateway " + gateway.getName() + " (" + gateway.getId()
+ ") has conditional flows but non of the outgoing sequence flows is matching the condition!");
return result;

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import javax.xml.parsers.ParserConfigurationException;

import org.openbpmn.bpmn.BPMNModel;
import org.openbpmn.bpmn.exceptions.BPMNInvalidIDException;
import org.openbpmn.bpmn.exceptions.BPMNModelException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
Expand Down Expand Up @@ -139,8 +140,14 @@ public static BPMNModel read(String modelFilePath) throws BPMNModelException {
*/
public static BPMNModel read(InputStream is) throws BPMNModelException {
logger.fine("read from inputStream...");
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setIgnoringElementContentWhitespace(true); // because of a bug this does not have any effect!
if (is == null) {
throw new BPMNInvalidIDException(BPMNModelException.INVALID_MODEL,
"Model can not be parsed: InputStream is null");
}
DocumentBuilderFactory docFactory = DocumentBuilderFactory
.newInstance();
docFactory.setIgnoringElementContentWhitespace(true); // because of a bug this does not have
// any effect!
docFactory.setNamespaceAware(true);

try {
Expand Down
Loading

0 comments on commit 1904749

Please sign in to comment.