Skip to content

Commit

Permalink
documented diagram converter core (#1029)
Browse files Browse the repository at this point in the history
* documented diagram converter core

* disable unstable test
  • Loading branch information
jonathanlukas authored Oct 25, 2024
1 parent 4eb6ddd commit 3a17ec5
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 19 deletions.
92 changes: 92 additions & 0 deletions backend-diagram-converter/core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Diagram Converter Core

This is the core module of the diagram converter, containing the `BpmnConverter` plus its factory, the `BpmnConverterFactory`.

## How does it work?

Works in 2 phases:

* exploring the BPMN XML
* this is done in a visitor pattern, the interface to use is `DomElementVisitor`
* each element gets visited
* convertibles are created for each process element
* they can be decorated with aspects for the conversion
* the conversion is executed
* decorated convertibles are processed
* the registered aspects are adjusted in the BPMN XML
* this is done in another visitor pattern, the interface to use is `Conversion`
* the diagram becomes a Camunda 8 diagram

## How can I use it?

You can bootstrap the `BpmnConverter` in an easy way:

```java

BpmnConverter converter = BpmnConverterFactory.getInstance().get();
```

This will return a converter that is bootstrapped using SPI for `DomElementVisitor`, `Conversion` and `NotificationService`.

If you want to build a custom converter, you can add to the SPI resources.

If this is not sufficient, you can bootstrap the converter in a custom way:

```java

import java.util.ArrayList;
import java.util.List;

List<DomElementVisitor> visitors = loadDomElementVisitors();
List<Conversion> conversions = loadConversions();
NotificationService notificationService = laodNotificationService();

BpmnConverter converter = new BpmnConverter(visitors, conversions, notificationService);
```

Next, you need `ConverterProperties`. They control the way the converter will handle several aspects of the conversion.

You can bootstrap the properties in an easy way:

```java

ConverterProperties properties = ConverterPropertiesFactory.getInstance().get();
```

You can also customize the used converter properties:

```java

DefaultConverterProperties defaultConverterProperties = new DefaultConverterProperties();
// I want to migrate to an older platform version
defaultConverterProperties.setPlatformVersion("8.5");

ConverterProperties properties = ConverterPropertiesFactory
.getInstance()
.merge(defaultConverterProperties);
```

Now, you are able to convert process models or just receive a check result:

```java

BpmnConverter converter = BpmnConverterFactory
.getInstance()
.get();
// this comes from the camunda 7 bpmn model package
BpmnModelInstance modelInstance = loadModel();
ConverterProperties properties = ConverterPropertiesFactory.getInstance().get();

// the results will be in the provided modelInstance
converter.convert(modelInstance, properties);

// the results are returned, the model instance is also modified

BpmnDiagramCheckResult result = converter.check(modelInstance, properties);
```

## How can I extend it?

Beside the way of using custom bootstrapping mechanisms, the easiest way to extend is by using the capabilities of the underlying SPI.

You can find an example in the `/example` section of the root project.
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,32 @@ protected ConverterProperties createInstance() {
return merge(new DefaultConverterProperties());
}

public ConverterProperties merge(DefaultConverterProperties properties) {
readDefaultValues(properties);
return properties;
public ConverterProperties merge(ConverterProperties properties) {
return merge(new DefaultConverterProperties(), properties);
}

private void readDefaultValues(DefaultConverterProperties properties) {
readZeebeJobType("default", properties::getDefaultJobType, properties::setDefaultJobType);
readZeebeJobType("script", properties::getScriptJobType, properties::setScriptJobType);
readZeebeHeader("script", properties::getScriptHeader, properties::setScriptHeader);
private ConverterProperties merge(
DefaultConverterProperties base, ConverterProperties properties) {
readDefaultValues(base, properties);
return base;
}

private void readDefaultValues(DefaultConverterProperties base, ConverterProperties properties) {
readZeebeJobType("default", properties::getDefaultJobType, base::setDefaultJobType);
readZeebeJobType("script", properties::getScriptJobType, base::setScriptJobType);
readZeebeHeader("script", properties::getScriptHeader, base::setScriptHeader);
readZeebeHeader(
"result-variable",
properties::getResultVariableHeader,
properties::setResultVariableHeader);
readZeebeHeader("resource", properties::getResourceHeader, properties::setResourceHeader);
"result-variable", properties::getResultVariableHeader, base::setResultVariableHeader);
readZeebeHeader("resource", properties::getResourceHeader, base::setResourceHeader);
readZeebeHeader(
"script-format", properties::getScriptFormatHeader, properties::setScriptFormatHeader);
readZeebePlatformInfo(
"version", properties::getPlatformVersion, properties::setPlatformVersion);
"script-format", properties::getScriptFormatHeader, base::setScriptFormatHeader);
readZeebePlatformInfo("version", properties::getPlatformVersion, base::setPlatformVersion);
readFlag(
"default-job-type-enabled",
properties::getDefaultJobTypeEnabled,
properties::setDefaultJobTypeEnabled);
base::setDefaultJobTypeEnabled);
readFlag(
"append-documentation",
properties::getAppendDocumentation,
properties::setAppendDocumentation);
"append-documentation", properties::getAppendDocumentation, base::setAppendDocumentation);
}

private void readZeebeJobType(String jobType, Supplier<String> getter, Consumer<String> setter) {
Expand Down Expand Up @@ -89,6 +89,7 @@ private <T> void readDefaultValue(
T currentValue = getter.get();
if (currentValue != null) {
LOG.debug("Converter property {} already set", key);
setter.accept(currentValue);
return;
}
LOG.debug("Reading converter property {}", key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ void shouldMergeProperties() {
ConverterProperties converterProperties =
ConverterPropertiesFactory.getInstance().merge(properties);
assertEquals("adapter", converterProperties.getDefaultJobType());
assertNotNull(properties.getResourceHeader());
assertNotNull(converterProperties.getResourceHeader());
}
}
44 changes: 44 additions & 0 deletions example/extended-converter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>extended-converter</artifactId>
<packaging>jar</packaging>

<parent>
<groupId>org.camunda.community.migration</groupId>
<artifactId>migration-examples-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<properties>
<encoding>UTF-8</encoding>
<project.build.sourceEncoding>${encoding}</project.build.sourceEncoding>
<project.build.resourceEncoding>${encoding}</project.build.resourceEncoding>
<version.java>17</version.java>
<maven.compiler.source>${version.java}</maven.compiler.source>
<maven.compiler.target>${version.java}</maven.compiler.target>
<version.camunda-7-to-8-migration>0.10.0</version.camunda-7-to-8-migration>
</properties>

<dependencies>
<dependency>
<groupId>org.camunda.community.migration</groupId>
<artifactId>backend-diagram-converter-core</artifactId>
<version>${version.camunda-7-to-8-migration}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
<version>3.26.3</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.camunda.community.migration.example.extendedConverter;

import org.camunda.bpm.model.xml.instance.DomElement;
import org.camunda.community.migration.converter.BpmnDiagramCheckResult.Severity;
import org.camunda.community.migration.converter.DomElementVisitorContext;
import org.camunda.community.migration.converter.convertible.ExclusiveGatewayConvertible;
import org.camunda.community.migration.converter.message.ComposedMessage;
import org.camunda.community.migration.converter.visitor.DomElementVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.camunda.community.migration.converter.NamespaceUri.*;

public class CustomDomElementVisitor implements DomElementVisitor {
private static final Logger LOG = LoggerFactory.getLogger(CustomDomElementVisitor.class);

@Override
public void visit(DomElementVisitorContext context) {
DomElement element = context.getElement();
if (element
.getNamespaceURI()
.equals(BPMN) && element
.getLocalName()
.equals("exclusiveGateway")) {
// this is only applied to exclusive gateways
List<String> outgoingSequenceFlowIds = findOutgoingSequenceFlows(element);
if (outgoingSequenceFlowIds.size() > 1) {
// only when they are forking
List<ConditionExpression> expressions = outgoingSequenceFlowIds
.stream()
.map(id -> element
.getDocument()
.getElementById(id))
.filter(Objects::nonNull)
.map(this::extractConditionExpression)
.filter(Objects::nonNull)
.toList();
String property = expressions
.stream()
.map(e -> e.id() + ": " + e.language() + ": " + e.expression())
.collect(Collectors.joining(", "));
context.addConversion(ExclusiveGatewayConvertible.class,
gateway -> gateway.addZeebeProperty("originalExpressions", property)
);
ComposedMessage composedMessage = new ComposedMessage();
composedMessage.setMessage("Original expressions are: " + property);
composedMessage.setSeverity(Severity.INFO);
context.addMessage(composedMessage);
}
}
}

private List<String> findOutgoingSequenceFlows(DomElement element) {
return element
.getChildElementsByNameNs(BPMN, "outgoing")
.stream()
.map(DomElement::getTextContent)
.toList();
}

private ConditionExpression extractConditionExpression(DomElement sequenceFlow) {
return sequenceFlow
.getChildElementsByNameNs(BPMN, "conditionExpression")
.stream()
.map(dom -> {
String language = dom.getAttribute("language");
if (language == null) {
language = "juel";
}
return new ConditionExpression(sequenceFlow.getAttribute("id"),language, dom.getTextContent());
})
.findFirst()
.orElse(null);
}

private record ConditionExpression(String id, String language, String expression) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.camunda.community.migration.example.extendedConverter.CustomDomElementVisitor
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.camunda.community.migration.example.extendedConverter;

import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.community.migration.converter.BpmnConverter;
import org.camunda.community.migration.converter.BpmnConverterFactory;
import org.camunda.community.migration.converter.ConverterPropertiesFactory;
import org.camunda.community.migration.converter.DomElementVisitorFactory;
import org.camunda.community.migration.converter.visitor.DomElementVisitor;
import org.junit.jupiter.api.Test;

import java.io.StringWriter;
import java.util.List;

import static org.assertj.core.api.Assertions.*;

public class ExtendedConverterTest {
private static BpmnModelInstance loadModelInstance(String bpmnFile) {
return Bpmn.readModelFromStream(ExtendedConverterTest.class
.getClassLoader()
.getResourceAsStream(bpmnFile));
}

@Test
void shouldLoadCustomDomElementVisitor() {
List<DomElementVisitor> domElementVisitors = DomElementVisitorFactory
.getInstance()
.get();
assertThat(domElementVisitors).hasAtLeastOneElementOfType(CustomDomElementVisitor.class);
}

@Test
void shouldAddPropertiesToGateway() {
BpmnConverter converter = BpmnConverterFactory
.getInstance()
.get();
BpmnModelInstance modelInstance = loadModelInstance("example-model.bpmn");
converter.convert(modelInstance,
ConverterPropertiesFactory
.getInstance()
.get()
);
StringWriter writer = new StringWriter();
converter.printXml(modelInstance.getDocument(),true,writer);
System.out.println(writer);
}
}
Loading

0 comments on commit 3a17ec5

Please sign in to comment.