Skip to content

Commit fc7252c

Browse files
committed
feat: #146 make remote adapter spring boot 4 compatible
1 parent 6bac05e commit fc7252c

File tree

15 files changed

+556
-5
lines changed

15 files changed

+556
-5
lines changed

engine-adapter/c7-remote-spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengineapi/adapter/c7/remote/springboot/C7RemotePullServicesAutoConfiguration.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ import org.camunda.community.rest.variables.ValueMapper
2323
import org.springframework.beans.factory.annotation.Qualifier
2424
import org.springframework.boot.autoconfigure.AutoConfiguration
2525
import org.springframework.boot.autoconfigure.AutoConfigureAfter
26-
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading
27-
import org.springframework.boot.autoconfigure.thread.Threading
2826
import org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder
2927
import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder
3028
import org.springframework.context.annotation.Bean
@@ -68,14 +66,14 @@ class C7RemotePullServicesAutoConfiguration {
6866

6967
@Bean("taskScheduler")
7068
@Order(100)
71-
@ConditionalOnThreading(Threading.VIRTUAL)
69+
@Conditional(VirtualThreadingCondition::class)
7270
fun taskSchedulerVirtualThreads(builder: SimpleAsyncTaskSchedulerBuilder): SimpleAsyncTaskScheduler {
7371
return builder.build()
7472
}
7573

7674
@Bean("taskScheduler")
7775
@Order(100)
78-
@ConditionalOnThreading(Threading.PLATFORM)
76+
@Conditional(PlatformThreadingCondition::class)
7977
fun taskSchedulerPlatformThreads(threadPoolTaskSchedulerBuilder: ThreadPoolTaskSchedulerBuilder): ThreadPoolTaskScheduler {
8078
return threadPoolTaskSchedulerBuilder.build()
8179
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dev.bpmcrafters.processengineapi.adapter.c7.remote.springboot
2+
3+
import org.springframework.context.annotation.Condition
4+
import org.springframework.context.annotation.ConditionContext
5+
import org.springframework.core.type.AnnotatedTypeMetadata
6+
7+
class PlatformThreadingCondition : Condition {
8+
9+
override fun matches(
10+
context: ConditionContext,
11+
metadata: AnnotatedTypeMetadata
12+
): Boolean {
13+
return !VirtualThreadingCondition().matches(context, metadata)
14+
}
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dev.bpmcrafters.processengineapi.adapter.c7.remote.springboot
2+
3+
import org.springframework.boot.system.JavaVersion
4+
import org.springframework.context.annotation.Condition
5+
import org.springframework.context.annotation.ConditionContext
6+
import org.springframework.core.type.AnnotatedTypeMetadata
7+
8+
class VirtualThreadingCondition : Condition {
9+
10+
override fun matches(
11+
context: ConditionContext,
12+
metadata: AnnotatedTypeMetadata
13+
): Boolean {
14+
return context.environment.getProperty("spring.threads.virtual.enabled", java.lang.Boolean.TYPE, false)
15+
&& JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.TWENTY_ONE)
16+
}
17+
18+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Java Example to demonstrate usage of process API using Spring Boot 4
2+
3+
This example is a test that we can invoke API defined in Kotlin from Java. It utilizes the API directly.
4+
5+
## Features in the example
6+
7+
There are some features in the C7 remote adapter already. In addition, there are some features in the example:
8+
9+
- AbstractSynchronousTaskHandler to complete external tasks in a synchronous way
10+
- In-Memory user task pool for retrieving infos about open user tasks
11+
12+
## Process
13+
14+
![Service Task Process](src/main/resources/simple-process.png)
15+
16+
17+
## How to run
18+
19+
- Start docker-compose
20+
- Build with Maven
21+
- Start `JavaCamunda7RemoteExampleApplication`
22+
- Open http://localhost:8081/swagger-ui/index.html
23+
- Start process
24+
- Wait, wait, wait, check the logs, wait...
25+
- Copy the resulting retrieved user task id
26+
- Complete the user task with id
27+
- Wait, wait, wait, check the logs, wait...
28+
- Correlate message by providing the process instance id
29+
- Hint: don't hurry, the error of correlation is not implemented yet (if you try it before both tasks are executed)
30+
31+
## How to run using IntelliJ test script
32+
- Build with Maven
33+
- Start `JavaCamunda7RemoteExampleApplication`
34+
- Run `simple-process-demo.http` script
35+
- Analyze the results
36+
- Run `simple-process-demo-failed-user.http` script
37+
- Analyze the results
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
3+
camunda-bpm-platform-7:
4+
image: camunda/camunda-bpm-platform:run-7.24.0
5+
# image: registry.camunda.cloud/cambpm-ee/camunda-bpm-platform-ee:7.24.1
6+
pull_policy: always
7+
ports:
8+
- '9090:8080'
9+
environment:
10+
CAMUNDA_BPM_DEFAULT-SERIALIZATION-FORMAT: "application/json"
11+
command:
12+
- './camunda.sh'
13+
- '--rest'
14+
- '--webapps'
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>dev.bpm-crafters.process-engine-examples</groupId>
7+
<artifactId>process-engine-api-example-root-c7</artifactId>
8+
<version>2025.11.2-SNAPSHOT</version>
9+
<relativePath>../pom.xml</relativePath>
10+
</parent>
11+
12+
<properties>
13+
<spring-boot.version>4.0.2</spring-boot.version>
14+
</properties>
15+
16+
<artifactId>process-engine-api-example-java-c7-remote-sb4</artifactId>
17+
<name>Example: Java C7 Remote Spring Boot 4</name>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>dev.bpm-crafters.process-engine-examples</groupId>
22+
<artifactId>process-engine-api-example-java-c7-common-fixture</artifactId>
23+
</dependency>
24+
25+
<dependency>
26+
<groupId>dev.bpm-crafters.process-engine-adapters</groupId>
27+
<artifactId>process-engine-adapter-camunda-platform-c7-remote-spring-boot-starter</artifactId>
28+
</dependency>
29+
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-web</artifactId>
33+
</dependency>
34+
35+
<!-- Feign REST Client -->
36+
<dependency>
37+
<groupId>io.holunda.c7</groupId>
38+
<artifactId>c7-rest-client-spring-boot-starter-feign-4</artifactId>
39+
<version>${c7-rest-client-spring-boot.version}</version>
40+
</dependency>
41+
42+
<dependency>
43+
<groupId>com.fasterxml.jackson.module</groupId>
44+
<artifactId>jackson-module-kotlin</artifactId>
45+
</dependency>
46+
</dependencies>
47+
48+
</project>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
### Start process
2+
< {%
3+
client.global.clearAll()
4+
%}
5+
// @no-log
6+
POST http://localhost:8081/simple-service-tasks/start-process?value=string&intValue=1
7+
8+
> {%
9+
client.test("Request executed successfully", function () {
10+
client.assert(response.status === 201, "Response status is not 201");
11+
});
12+
13+
client.global.set("instanceId", response.headers.valueOf("Location"));
14+
%}
15+
16+
### Get user tasks
17+
< {%
18+
import {wait} from "wait";
19+
console.log("waiting 5 secs");
20+
wait(5);
21+
%}
22+
23+
// @no-log
24+
GET http://localhost:8081/simple-service-tasks/tasks
25+
Accept: application/json
26+
27+
> {%
28+
client.test("Request executed successfully", function () {
29+
client.assert(response.status === 200, "Response status is not 201");
30+
});
31+
client.test("Content-Type is application/json", () => {
32+
const contentType = response.headers.valueOf("content-type");
33+
client.assert(contentType == "application/json",
34+
`Expected Content-Type is application/json, but actual is ${contentType}`)
35+
})
36+
37+
const tasks = response.body;
38+
const taskId = jsonPath(tasks, "$[0].taskId");
39+
console.log("Created user task: ", taskId);
40+
client.global.set("taskId", taskId);
41+
%}
42+
43+
### Complete user task
44+
// @no-log
45+
POST http://localhost:8081/simple-service-tasks/tasks/{{ taskId }}/error?value=value-of-user-task-error
46+
47+
> {%
48+
client.test("Request executed successfully", function () {
49+
client.assert(response.status === 204, "Response status is not 204");
50+
});
51+
%}
52+
53+
### Correlate signal
54+
< {%
55+
import {wait} from "wait";
56+
console.log("waiting 5 secs");
57+
wait(5);
58+
%}
59+
60+
// @no-log
61+
POST http://localhost:8081/simple-service-tasks/signal?value=value-delivered-by-signal
62+
63+
> {%
64+
client.test("Request executed successfully", function () {
65+
client.assert(response.status === 204, "Response status is not 204");
66+
});
67+
%}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
### Generate correlationKey
2+
< {%
3+
client.global.clearAll();
4+
%}
5+
// @no-log
6+
GET https://www.uuidtools.com/api/generate/v4
7+
Accept: application/json
8+
9+
> {%
10+
const correlationKey = jsonPath(response.body, "$[0]");
11+
console.log("Correlation key", correlationKey);
12+
client.global.set("correlationKey", correlationKey);
13+
%}
14+
15+
### Deploy process
16+
// @no-log
17+
POST http://localhost:8081/simple-service-tasks/deploy
18+
19+
> {%
20+
client.test("Request executed successfully", function () {
21+
client.assert(response.status === 201, "Response status is not 201");
22+
});
23+
24+
client.global.set("deploymentKey", response.headers.valueOf("Location"));
25+
%}
26+
27+
28+
### Start process
29+
// @no-log
30+
POST http://localhost:8081/simple-service-tasks/start-process?value={{ correlationKey }}&intValue=1
31+
32+
> {%
33+
client.test("Request executed successfully", function () {
34+
client.assert(response.status === 201, "Response status is not 201");
35+
});
36+
37+
client.global.set("instanceId", response.headers.valueOf("Location"));
38+
%}
39+
40+
### Get user tasks
41+
< {%
42+
import {wait} from "wait";
43+
console.log("waiting 5 secs");
44+
wait(5);
45+
%}
46+
// @no-log
47+
GET http://localhost:8081/simple-service-tasks/tasks
48+
Accept: application/json
49+
50+
> {%
51+
client.test("Request executed successfully", function () {
52+
client.assert(response.status === 200, "Response status is not 201");
53+
});
54+
client.test("Content-Type is application/json", () => {
55+
const contentType = response.headers.valueOf("content-type");
56+
client.assert(contentType == "application/json",
57+
`Expected Content-Type is application/json, but actual is ${contentType}`);
58+
})
59+
60+
const tasks = response.body;
61+
const taskId = jsonPath(tasks, "$[0].taskId");
62+
console.log("Created user task: ", taskId);
63+
client.global.set("taskId", taskId);
64+
%}
65+
66+
### Complete user task
67+
// @no-log
68+
POST http://localhost:8081/simple-service-tasks/tasks/{{ taskId }}/complete?value=value-of-user-task-completion
69+
70+
> {%
71+
client.test("Request executed successfully", function () {
72+
client.assert(response.status === 204, "Response status is not 204");
73+
});
74+
%}
75+
76+
### Correlate message
77+
< {%
78+
import {wait} from "wait";
79+
console.log("waiting 5 secs");
80+
wait(5);
81+
%}
82+
// @no-log
83+
POST http://localhost:8081/simple-service-tasks/correlate/{{ correlationKey }}?value=value-delivered-by-correlation
84+
85+
> {%
86+
client.test("Request executed successfully", function () {
87+
client.assert(response.status === 204, "Response status is not 204");
88+
});
89+
%}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dev.bpmcrafters.example.javac7;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import io.swagger.v3.core.util.ObjectMapperFactory;
5+
import org.springframework.boot.SpringApplication;
6+
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.context.annotation.Bean;
8+
9+
@SpringBootApplication
10+
public class JavaCamunda7RemoteExampleApplication {
11+
12+
@Bean
13+
public ObjectMapper objectMapper() {
14+
return new ObjectMapper().findAndRegisterModules();
15+
}
16+
17+
public static void main(String[] args) {
18+
SpringApplication.run(JavaCamunda7RemoteExampleApplication.class, args);
19+
}
20+
21+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
server:
2+
port: 8081
3+
4+
spring:
5+
application:
6+
name: Java Camunda Platform 7 Remote
7+
8+
management:
9+
endpoints:
10+
web:
11+
exposure:
12+
include: "*"
13+
14+
springdoc:
15+
swagger-ui:
16+
try-it-out-enabled: true
17+
18+
dev:
19+
bpm-crafters:
20+
process-api:
21+
adapter:
22+
c7remote:
23+
enabled: true
24+
service-tasks:
25+
delivery-strategy: remote_scheduled
26+
schedule-delivery-fixed-rate-in-seconds: 5
27+
worker-id: embedded-worker
28+
lock-time-in-seconds: 10
29+
deserialize-on-server: false
30+
worker-thread-pool-queue-capacity: 5
31+
worker-thread-pool-size: 2
32+
max-task-count: 2
33+
user-tasks:
34+
schedule-delivery-fixed-rate-in-seconds: 5
35+
delivery-strategy: remote_scheduled
36+
execute-initial-pull-on-startup: false
37+
deserialize-on-server: false
38+
39+
feign:
40+
client:
41+
config:
42+
default:
43+
url: "http://localhost:9090/engine-rest/"
44+
45+
logging:
46+
level:
47+
dev.bpmcrafters.processengineapi: INFO
48+
dev.bpmcrafters.processengineapi.adapter.c7.remote.springboot: TRACE

0 commit comments

Comments
 (0)