Skip to content

Commit

Permalink
feat(import): import BPMN Semantic from CSV file (#48)
Browse files Browse the repository at this point in the history
Create BPMN Semantic using 2 CSV
* one contains the 'nodes'
* one contains the 'edges'

Once the BPMN semantic is created, the full diagram can be generated
  • Loading branch information
oanesini authored Jul 24, 2020
1 parent d24f8b3 commit 7af2352
Show file tree
Hide file tree
Showing 16 changed files with 371 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/
package io.process.analytics.tools.bpmn.generator;

import static io.process.analytics.tools.bpmn.generator.export.BPMNExporter.defaultBpmnExporter;
import static io.process.analytics.tools.bpmn.generator.internal.BpmnInOut.defaultBpmnInOut;
import static java.lang.String.format;
import static java.util.Arrays.asList;
Expand All @@ -26,10 +27,8 @@

import io.process.analytics.tools.bpmn.generator.algo.ShapeLayouter;
import io.process.analytics.tools.bpmn.generator.algo.ShapeSorter;
import io.process.analytics.tools.bpmn.generator.converter.AlgoToDisplayModelConverter;
import io.process.analytics.tools.bpmn.generator.converter.BpmnToAlgoModelConverter;
import io.process.analytics.tools.bpmn.generator.export.ASCIIExporter;
import io.process.analytics.tools.bpmn.generator.export.BPMNExporter;
import io.process.analytics.tools.bpmn.generator.export.SVGExporter;
import io.process.analytics.tools.bpmn.generator.internal.BpmnInOut;
import io.process.analytics.tools.bpmn.generator.internal.FileUtils;
Expand Down Expand Up @@ -80,11 +79,11 @@ private static void log(String message) {
// TODO implementation to be extracted in a a dedicated class
// =================================================================================================================

private final BpmnInOut bpmnInOut;
protected final BpmnInOut bpmnInOut;

@RequiredArgsConstructor
@Getter
private static class LayoutSortedDiagram {
public static class LayoutSortedDiagram {

private final TDefinitions originalDefinitions;
private final Grid grid;
Expand Down Expand Up @@ -116,22 +115,25 @@ public LayoutSortedDiagram loadAndLayout(File bpmnInputFile) {
TDefinitions originalDefinitions = bpmnInOut.readFromBpmn(bpmnInputFile);
log("BPMN file Loaded");

return computeLayout(originalDefinitions);
}

protected LayoutSortedDiagram computeLayout(TDefinitions definitions) {
log("Converting BPMN into internal model");
Diagram diagram = new BpmnToAlgoModelConverter().toAlgoModel(originalDefinitions);
Diagram diagram = new BpmnToAlgoModelConverter().toAlgoModel(definitions);
log("Conversion done");

log("Sorting and generating Layout");
Diagram sortedDiagram = new ShapeSorter().sort(diagram);
Grid grid = new ShapeLayouter().layout(sortedDiagram);
log("Sort and Layout done");

return new LayoutSortedDiagram(originalDefinitions, grid, sortedDiagram);
return new LayoutSortedDiagram(definitions, grid, sortedDiagram);
}

private void exportToBpmn(LayoutSortedDiagram diagram, File outputFile) {
protected void exportToBpmn(LayoutSortedDiagram diagram, File outputFile) {
log("Exporting to BPMN");
BPMNExporter bpmnExporter = new BPMNExporter(new AlgoToDisplayModelConverter());
TDefinitions newDefinitions = bpmnExporter.export(diagram.originalDefinitions, diagram.grid, diagram.diagram);
TDefinitions newDefinitions = defaultBpmnExporter().export(diagram.originalDefinitions, diagram.grid, diagram.diagram);
this.bpmnInOut.writeToBpmnFile(newDefinitions, outputFile);
log("BPMN exported to " + outputFile);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.process.analytics.tools.bpmn.generator;

import static io.process.analytics.tools.bpmn.generator.internal.BpmnInOut.defaultBpmnInOut;
import static io.process.analytics.tools.bpmn.generator.internal.FileUtils.fileContent;

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

import io.process.analytics.tools.bpmn.generator.input.CSVtoBPMN;
import io.process.analytics.tools.bpmn.generator.internal.BpmnInOut;
import io.process.analytics.tools.bpmn.generator.internal.generated.model.TDefinitions;
import lombok.extern.log4j.Log4j2;

@Log4j2
public class App2 extends App {

public App2(BpmnInOut bpmnInOut) {
super(bpmnInOut);
}

public static void main(String[] args) throws IOException {
File nodeDiscovery = new File(args[0]);
File edgeDiscovery = new File(args[1]);
File outputFile = new File(args[2]);

new App2(defaultBpmnInOut()).process(nodeDiscovery, edgeDiscovery, outputFile);
}

private void process(File nodeDiscovery, File edgeDiscovery, File outputFile) throws IOException {
log.info("Converting CSV into internal model");
TDefinitions definitions = new CSVtoBPMN().readFromCSV(fileContent(nodeDiscovery), fileContent(edgeDiscovery));
log.info("Conversion done");

LayoutSortedDiagram layoutSortedDiagram = computeLayout(definitions);
exportToBpmn(layoutSortedDiagram, outputFile);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private Join getAJoinThatWasProcessed(List<Join> joins) {
}

private Set<Edge> removeEdgesStartingWithNode(Set<Edge> edges, Shape startShape) {
return edges.stream().filter(e -> e.getFrom() != startShape.getId()).collect(Collectors.toSet());
return edges.stream().filter(e -> !e.getFrom().equals(startShape.getId())).collect(Collectors.toSet());
}

private Set<Shape> getStartNodes(Set<Shape> nodesToSort, Set<Edge> edges) {
Expand All @@ -137,7 +137,7 @@ private Set<Shape> getStartNodes(Set<Shape> nodesToSort, Set<Edge> edges) {
private List<Join> findAllJoins(Set<Shape> shapes, Set<Edge> edges) {
//get the nodes that are "join"
return shapes.stream().map(n -> Join.builder().to(n))
.map(j -> j.incomings(edges.stream().filter(e -> e.getTo() == j.to.getId()).map(Edge::getFrom).collect(Collectors.toList())))
.map(j -> j.incomings(edges.stream().filter(e -> e.getTo().equals(j.to.getId())).map(Edge::getFrom).collect(Collectors.toList())))
.map(Join.JoinBuilder::build)
//keep only join if there is more than 1 edge incoming
.filter(j -> j.incomings.size() > 1)
Expand All @@ -147,7 +147,7 @@ private List<Join> findAllJoins(Set<Shape> shapes, Set<Edge> edges) {


private boolean hasNoIncomingLink(Shape m, Set<Edge> edges) {
return edges.stream().noneMatch(e -> e.getTo() == m.getId());
return edges.stream().noneMatch(e -> e.getTo().equals(m.getId()));
}

@Data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.process.analytics.tools.bpmn.generator.converter;

import static io.process.analytics.tools.bpmn.generator.internal.Semantic.getId;
import static io.process.analytics.tools.bpmn.generator.model.ShapeType.*;

import java.util.List;
Expand Down Expand Up @@ -59,9 +60,4 @@ static Shape toShape(TFlowElement flowNode) {
return new Shape(flowNode.getId(), flowNode.getName(), shapeType);
}

// assuming this is a TBaseElement
private static String getId(Object object) {
return ((TBaseElement) object).getId();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@
import io.process.analytics.tools.bpmn.generator.converter.AlgoToDisplayModelConverter.DisplayModel;
import io.process.analytics.tools.bpmn.generator.internal.BPMNDiagramRichBuilder;
import io.process.analytics.tools.bpmn.generator.internal.generated.model.TDefinitions;
import io.process.analytics.tools.bpmn.generator.model.Grid;
import io.process.analytics.tools.bpmn.generator.model.Diagram;
import io.process.analytics.tools.bpmn.generator.model.Grid;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class BPMNExporter {

private final AlgoToDisplayModelConverter converter;

public static BPMNExporter defaultBpmnExporter() {
return new BPMNExporter(new AlgoToDisplayModelConverter());
}

public TDefinitions export(TDefinitions originalBpmnDefinitions, Grid grid, Diagram diagram) {
BPMNDiagramRichBuilder builder = new BPMNDiagramRichBuilder(originalBpmnDefinitions);
DisplayModel displayModel = converter.convert(grid, diagram);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package io.process.analytics.tools.bpmn.generator.input;

import static io.process.analytics.tools.bpmn.generator.internal.Semantic.addFlowNodes;
import static io.process.analytics.tools.bpmn.generator.internal.Semantic.addSequenceFlows;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.XMLConstants;

import io.process.analytics.tools.bpmn.generator.internal.Semantic;
import io.process.analytics.tools.bpmn.generator.internal.generated.model.*;

public class CSVtoBPMN {

// map original flow element id with the values we are using
// id cannot be numeric, in that case we map the id with a generated one, letting edge reference element ids with the values we are using for BPMN
private final Map<String, String> mappingShapeId = new HashMap<>();

public TDefinitions readFromCSV(String nodes, String edges) {
TProcess process = new TProcess();
process.setId("process_1");
TDefinitions definitions = new TDefinitions();
definitions.setId("definitions_1");
definitions.setTargetNamespace(XMLConstants.NULL_NS_URI);
Semantic semantic = new Semantic(definitions);
semantic.add(process);

addFlowNodes(process, getFlowNodeElements(nodes));
addSequenceFlows(process, getEdgeElements(edges));

return definitions;
}

private List<TFlowNode> getFlowNodeElements(String nodes) {
String[] lines = toLinesWithoutHeader(nodes);
List<TFlowNode> flowElements = new ArrayList<>();
for (String line : lines) {
if(line == null){
continue;
}
String[] node = line.split(",");
TFlowNode userTask = new TUserTask();
userTask.setName(removeEnclosingDoubleQuote(node[2]));

String originalId = node[1];
String bpmnId = originalId;
if (isNumeric(originalId)) {
bpmnId = "bpmnElement_" + originalId;
}
this.mappingShapeId.put(originalId, bpmnId);

userTask.setId(bpmnId);
flowElements.add(userTask);
}
return flowElements;
}

private static String removeEnclosingDoubleQuote(String s) {
return s.replaceAll("^\"|\"$", "");
}

private String[] toLinesWithoutHeader(String fileContent) {
String[] lines = fileContent.split("\n");
lines[0] = null;
return lines;
}

private List<TSequenceFlow> getEdgeElements(String edges) {
String[] lines = toLinesWithoutHeader(edges);
List<TSequenceFlow> flowElements = new ArrayList<>();
for (String line : lines) {
if(line == null){
continue;
}
String[] edge = line.split(",");
TSequenceFlow tSequenceFlow = new TSequenceFlow();

TUserTask sourceRef = new TUserTask();
tSequenceFlow.setSourceRef(sourceRef);
sourceRef.setId(this.mappingShapeId.getOrDefault(edge[2], edge[2]));

TUserTask targetRef = new TUserTask();
tSequenceFlow.setTargetRef(targetRef);
targetRef.setId(this.mappingShapeId.getOrDefault(edge[3], edge[3]));

String id = edge[1];
if (isNumeric(id)) {
id = "sequenceFlow_" + id;
}
tSequenceFlow.setId(id);
flowElements.add(tSequenceFlow);
}
return flowElements;
}

// TODO not optimal for performance, see https://www.baeldung.com/java-check-string-number
// bpmn element id cannot be numeric
private static boolean isNumeric(String s) {
if (s == null) {
return false;
}
try {
Double.parseDouble(s);
} catch (NumberFormatException nfe) {
return false;
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public TDefinitions build() {
}

private static QName bpmnElementQName(String bpmnElement) {
return new QName("http://www.omg.org/spec/BPMN/20100524/DI", bpmnElement, "");
return new QName("http://www.omg.org/spec/BPMN/20100524/DI", bpmnElement, XMLConstants.DEFAULT_NS_PREFIX);
}

// TODO we need to know if this is a process or a collaboration to set the actual QName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package io.process.analytics.tools.bpmn.generator.internal;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;

import io.process.analytics.tools.bpmn.generator.internal.generated.model.*;
import lombok.Getter;
Expand All @@ -37,6 +40,11 @@ public class Semantic {
@Getter
private final TDefinitions definitions;

// assuming this is a TBaseElement
public static String getId(Object object) {
return ((TBaseElement) object).getId();
}

public List<TParticipant> getParticipants() {
return getCollaboration()
.map(TCollaboration::getParticipant)
Expand Down Expand Up @@ -94,6 +102,28 @@ public BpmnElements getBpmnElements(TProcess process) {
return new BpmnElements(flowNodes, sequenceFlows);
}

public void add(TProcess process) {
definitions.getRootElement()
.add(new JAXBElement<>(bpmnElementQName("process"), TProcess.class, null, process));
}

public static void addFlowNodes(TProcess process, Collection<TFlowNode> flowElements) {
flowElements.stream()
// TODO name should be set accordingly to the type of the flow element
.map(f -> new JAXBElement<>(bpmnElementQName("userTask"), TFlowNode.class, null, f))
.forEach(f -> process.getFlowElement().add(f));
}

public static void addSequenceFlows(TProcess process, Collection<TSequenceFlow> sequenceFlowElements) {
sequenceFlowElements.stream()
.map(e -> new JAXBElement<>(bpmnElementQName("sequenceFlow"), TSequenceFlow.class, null, e))
.forEach(e -> process.getFlowElement().add(e));
}

private static QName bpmnElementQName(String bpmnElement) {
return new QName("http://www.omg.org/spec/BPMN/20100524/MODEL", bpmnElement, XMLConstants.DEFAULT_NS_PREFIX);
}

@RequiredArgsConstructor
@Getter
public static class BpmnElements {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ public static Edge edge(String id, String from, String to) {
return new Edge(id, from, to, false);
}
public static Edge edge(Shape from, Shape to) {
return new Edge(generateRandomId(), from.getId(), to.getId(), false);
return new Edge(generateRandomId(), copy(from.getId()), copy(to.getId()), false);
}
public static Edge revertedEdge(Edge original) {
return original.toBuilder().from(original.getTo()).to(original.getFrom()).reverted(true).build();
}

private static String copy(String s) {
return s + ""; // hack to ensure the returned string is equal to the provided one but doesn't have the same reference
}

}
Loading

0 comments on commit 7af2352

Please sign in to comment.