Skip to content

Commit ee39471

Browse files
committed
Add tenant test, add some docs, activate ITests again
1 parent 3252215 commit ee39471

7 files changed

Lines changed: 149 additions & 31 deletions

File tree

.github/workflows/master.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434

3535
# Build
3636
- name: Build with Maven
37-
run: ./mvnw clean verify -U -B -T4
37+
run: ./mvnw clean verify -U -B -T4 -ntp
3838

3939
# Get GPG private key into GPG
4040
- name: Import GPG Owner Trust

docs/process-engine-worker.md

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ API and allows to build universal workers.
99
First of all add the Process Engine Worker dependency to your projects classpath. In Maven add the following to your `pom.xml`:
1010

1111
```xml
12+
1213
<dependency>
1314
<groupId>dev.bpm-crafters.process-engine-worker</groupId>
1415
<artifactId>process-engine-worker-spring-boot-starter</artifactId>
@@ -19,6 +20,7 @@ First of all add the Process Engine Worker dependency to your projects classpath
1920
Now create a simple Spring component and annotate a method with a special annotation `@ProcessEngineWorker`:
2021

2122
```java
23+
2224
@Component
2325
@RequiredArgsConstructor
2426
public class MySmartWorker {
@@ -31,39 +33,40 @@ public class MySmartWorker {
3133
) {
3234
// execute some business code
3335
var fetched = fetchGoodsInPort.fetchGoods(order);
34-
36+
3537
return Map.of("shipped", fetched);
3638
}
3739
}
3840
```
3941

4042
The `@ProcessEngineWorker` annotation supports the following properties:
4143

42-
| Property | Type | Default | Description |
43-
|----------------|------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
44-
| `topic` | `String` | `""` | Topic name to subscribe this worker for. Alias for `value`. |
45-
| `autoComplete` | `boolean` | `true` | Flag indicating if the task should be automatically completed after the worker execution. If the return type is `Map<String, Any>`, it will overrule this setting and auto-complete with the returned payload. |
46-
| `completion` | `enum` | `DEFAULT` | Configures when the worker completes a task if `autoComplete` is active. Possible values are `DEFAULT`, `BEFORE_COMMIT`, and `AFTER_COMMIT`. Has no effect if the worker is not transactional. |
47-
| `lockDuration` | `long` | `-1` | Optional lock duration in milliseconds for this worker. If set to `-1` (default), the global configuration of the process engine adapter will be used. (Available since `0.8.0`) |
44+
| Property | Type | Default | Description |
45+
|----------------|-----------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
46+
| `topic` | `String` | `"__unset"` | Topic name to subscribe this worker for. Alias for `value`. |
47+
| `autoComplete` | `boolean` | `true` | Flag indicating if the task should be automatically completed after the worker execution. If the return type is `Map<String, Any>`, it will overrule this setting and auto-complete with the returned payload. |
48+
| `completion` | `enum` | `DEFAULT` | Configures when the worker completes a task if `autoComplete` is active. Possible values are `DEFAULT`, `BEFORE_COMMIT`, and `AFTER_COMMIT`. Has no effect if the worker is not transactional. |
49+
| `lockDuration` | `long` | `-1` | Optional lock duration in milliseconds for this worker. If set to `-1` (default), the global configuration of the process engine adapter will be used. (Available since `0.8.0`) |
50+
| `tenantId` | `String` | `""` | Optional tenant id to be used for current worker during the registration. Any non-blank value will be considered. If the value is empty, the value from the property will be used. |
4851

4952
## Method parameter resolution
5053

5154
Parameter resolution of the method annotated with `ProcessEngineWorker` is based on a set of strategies
5255
registered by the `ParameterResolver` bean. Currently, the following parameters are resolved:
5356

54-
| Type | Purpose |
55-
|----------------------------------------|---------------------------------------------------------------------------|
56-
| TaskInformation | Helper abstracting all information about the external task. |
57-
| ExternTaskCompletionApi | API for completing the external task manually |
58-
| VariableConverter | Special utility to read the process variable map and deliver typed value |
59-
| Map<String, Object> | Payload object containing all variables. |
60-
| Type annotated with @Variable("name") | Marker for a process variable. |
57+
| Type | Purpose |
58+
|---------------------------------------|--------------------------------------------------------------------------|
59+
| TaskInformation | Helper abstracting all information about the external task. |
60+
| ExternTaskCompletionApi | API for completing the external task manually |
61+
| VariableConverter | Special utility to read the process variable map and deliver typed value |
62+
| Map<String, Object> | Payload object containing all variables. |
63+
| Type annotated with @Variable("name") | Marker for a process variable. |
6164

6265
Usually, the requested variable is mandatory and the parameter resolver reports an error, if the requested variable is not
6366
available in the process payload. If you want to inject the variable only if it exists in the payload you have two options.
6467
Either you set the parameter `@Variable(name = "...", mandatory = false)` or you use `Optional<T>` instead of `T` as a variable
6568
type. If you are using Kotlin and don't like `Optional`, make sure to declare variable type as nullable (`T?` instead of `T`) and
66-
set the mandatory flag to `false`.
69+
set the mandatory flag to `false`.
6770

6871
## Method return type
6972

@@ -95,6 +98,7 @@ dev:
9598
worker:
9699
registerProcessWorkers: true # Enable or disable automatic worker registration
97100
completeTasksBeforeCommit: false # Determines whether tasks are completed before transaction commit
101+
tenantId: my-tenant # Specify the tenant ID for all workers, can be overridden by the value in the `@ProcessEngineWorker` annotation
98102
```
99103
100104

itest/spring-boot-starter-integration-test/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<phase>integration-test</phase>
5151
<goals>
5252
<goal>integration-test</goal>
53+
<goal>verify</goal>
5354
</goals>
5455
</execution>
5556
</executions>

itest/spring-boot-starter-integration-test/src/test/kotlin/dev/bpmcrafters/processengine/worker/fixture/TestApplication.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ import com.fasterxml.jackson.databind.ObjectMapper
44
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
55
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
66
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
7+
import dev.bpmcrafters.processengine.worker.idempotency.TaskLogEntry
8+
import dev.bpmcrafters.processengine.worker.idempotency.TaskLogEntryRepository
79
import org.springframework.boot.autoconfigure.SpringBootApplication
810
import org.springframework.boot.autoconfigure.domain.EntityScan
911
import org.springframework.context.annotation.Bean
1012
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
1113
import java.text.SimpleDateFormat
1214

1315
@SpringBootApplication
14-
@EntityScan(basePackageClasses = [MyEntity::class])
15-
@EnableJpaRepositories(basePackageClasses = [MyEntityRepository::class])
16+
@EntityScan(basePackageClasses = [MyEntity::class, TaskLogEntry::class])
17+
@EnableJpaRepositories(basePackageClasses = [MyEntityRepository::class, TaskLogEntryRepository::class])
1618
class TestApplication {
1719

1820
@Bean

spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/ProcessEngineWorker.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,19 @@ annotation class ProcessEngineWorker(
4242
/**
4343
* Tenant ID to subscribe this worker for. Defaults to "__unset" representing no tenant.
4444
*/
45-
val tenantId: String = DEFAULT_UNSET_TENANT_ID,
45+
val tenantId: String = "",
4646
) {
4747
companion object {
4848
/**
4949
* Null value for the topic.
5050
*/
5151
const val DEFAULT_UNSET_TOPIC = "__unset"
5252

53-
/**
54-
* Null value for the tenant.
55-
*/
56-
const val DEFAULT_UNSET_TENANT_ID = "__unset"
57-
5853
/**
5954
* Sentinel value indicating lock duration is not specified.
6055
*/
6156
const val DEFAULT_UNSET_LOCK_DURATION = -1L
57+
6258
}
6359

6460
/**

spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/registrar/ReflectionUtils.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,14 @@ fun Method.getTopic(): String {
152152
* Returns the auto-completion flag from annotation.
153153
*/
154154
fun Method.getAutoComplete(): Boolean {
155-
return this.getAnnotation(ProcessEngineWorker::class.java).autoComplete
155+
return AnnotationUtils.findAnnotation(this, ProcessEngineWorker::class.java)!!.autoComplete
156156
}
157157

158158
/**
159159
* Returns the auto-completion flag from annotation.
160160
*/
161161
fun Method.getCompletion(): Completion {
162-
return this.getAnnotation(ProcessEngineWorker::class.java).completion
162+
return AnnotationUtils.findAnnotation(this, ProcessEngineWorker::class.java)!!.completion
163163
}
164164

165165
/**
@@ -168,7 +168,7 @@ fun Method.getCompletion(): Completion {
168168
* @since 0.8.0
169169
*/
170170
fun Method.getLockDuration(): Long? {
171-
val lockDuration = this.getAnnotation(ProcessEngineWorker::class.java).lockDuration
171+
val lockDuration = AnnotationUtils.findAnnotation(this, ProcessEngineWorker::class.java)!!.lockDuration
172172
return if (lockDuration == ProcessEngineWorker.DEFAULT_UNSET_LOCK_DURATION) {
173173
null
174174
} else {
@@ -182,11 +182,9 @@ fun Method.getLockDuration(): Long? {
182182
* @since 0.8.4
183183
*/
184184
fun Method.getTenantId(): String? {
185-
val tenantId = this.getAnnotation(ProcessEngineWorker::class.java).tenantId
186-
return if (tenantId == ProcessEngineWorker.DEFAULT_UNSET_TENANT_ID || tenantId.isBlank()) {
185+
val tenantId = AnnotationUtils.findAnnotation(this, ProcessEngineWorker::class.java)!!.tenantId
186+
return tenantId.ifBlank {
187187
null
188-
} else {
189-
tenantId
190188
}
191189
}
192190

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package dev.bpmcrafters.processengine.worker.registrar
2+
3+
import dev.bpmcrafters.processengine.worker.ProcessEngineWorker
4+
import dev.bpmcrafters.processengine.worker.configuration.ProcessEngineWorkerProperties
5+
import dev.bpmcrafters.processengine.worker.idempotency.IdempotencyRegistry
6+
import dev.bpmcrafters.processengineapi.CommonRestrictions
7+
import dev.bpmcrafters.processengineapi.task.ServiceTaskCompletionApi
8+
import dev.bpmcrafters.processengineapi.task.SubscribeForTaskCmd
9+
import dev.bpmcrafters.processengineapi.task.TaskSubscriptionApi
10+
import org.assertj.core.api.Assertions.assertThat
11+
import org.junit.jupiter.api.Test
12+
import org.mockito.kotlin.*
13+
import org.springframework.transaction.support.TransactionTemplate
14+
import java.util.concurrent.CompletableFuture
15+
16+
internal class ProcessEngineStarterRegistrarTenantTest {
17+
18+
private val properties = ProcessEngineWorkerProperties()
19+
private val taskSubscriptionApi = mock<TaskSubscriptionApi>()
20+
private val taskCompletionApi = mock<ServiceTaskCompletionApi>()
21+
private val variableConverter = mock<VariableConverter>()
22+
private val parameterResolver = mock<ParameterResolver>()
23+
private val resultResolver = mock<ResultResolver>()
24+
private val transactionalTemplate = mock<TransactionTemplate>()
25+
private val metrics = mock<ProcessEngineWorkerMetrics>()
26+
private val idempotencyRegistry = mock<IdempotencyRegistry>()
27+
28+
private val testSubject = ProcessEngineStarterRegistrar(
29+
properties,
30+
taskSubscriptionApi,
31+
taskCompletionApi,
32+
variableConverter,
33+
parameterResolver,
34+
resultResolver,
35+
transactionalTemplate,
36+
metrics,
37+
idempotencyRegistry
38+
)
39+
40+
@Test
41+
fun `should use tenantId from annotation`() {
42+
class TenantWorker {
43+
@ProcessEngineWorker(topic = "test", tenantId = "tenant-from-annotation")
44+
fun work() {}
45+
}
46+
47+
val bean = TenantWorker()
48+
whenever(taskSubscriptionApi.subscribeForTask(any())).thenReturn(CompletableFuture.completedFuture(mock()))
49+
50+
testSubject.postProcessAfterInitialization(bean, "tenantWorker")
51+
52+
val captor = argumentCaptor<SubscribeForTaskCmd>()
53+
verify(taskSubscriptionApi).subscribeForTask(captor.capture())
54+
55+
assertThat(captor.firstValue.restrictions[CommonRestrictions.TENANT_ID]).isEqualTo("tenant-from-annotation")
56+
}
57+
58+
@Test
59+
fun `should use tenantId from properties if not specified on annotation`() {
60+
properties.tenantId = "tenant-from-properties"
61+
62+
class NoTenantWorker {
63+
@ProcessEngineWorker(topic = "test")
64+
fun work() {}
65+
}
66+
67+
val bean = NoTenantWorker()
68+
whenever(taskSubscriptionApi.subscribeForTask(any())).thenReturn(CompletableFuture.completedFuture(mock()))
69+
70+
testSubject.postProcessAfterInitialization(bean, "noTenantWorker")
71+
72+
val captor = argumentCaptor<SubscribeForTaskCmd>()
73+
verify(taskSubscriptionApi).subscribeForTask(captor.capture())
74+
75+
assertThat(captor.firstValue.restrictions[CommonRestrictions.TENANT_ID]).isEqualTo("tenant-from-properties")
76+
}
77+
78+
@Test
79+
fun `annotation tenantId should overrule property tenantId`() {
80+
properties.tenantId = "tenant-from-properties"
81+
82+
class OverruleWorker {
83+
@ProcessEngineWorker(topic = "test", tenantId = "tenant-from-annotation")
84+
fun work() {}
85+
}
86+
87+
val bean = OverruleWorker()
88+
whenever(taskSubscriptionApi.subscribeForTask(any())).thenReturn(CompletableFuture.completedFuture(mock()))
89+
90+
testSubject.postProcessAfterInitialization(bean, "overruleWorker")
91+
92+
val captor = argumentCaptor<SubscribeForTaskCmd>()
93+
verify(taskSubscriptionApi).subscribeForTask(captor.capture())
94+
95+
assertThat(captor.firstValue.restrictions[CommonRestrictions.TENANT_ID]).isEqualTo("tenant-from-annotation")
96+
}
97+
98+
@Test
99+
fun `should not have tenantId restriction if neither is specified`() {
100+
properties.tenantId = null
101+
102+
class NoTenantAtAllWorker {
103+
@ProcessEngineWorker(topic = "test")
104+
fun work() {}
105+
}
106+
107+
val bean = NoTenantAtAllWorker()
108+
whenever(taskSubscriptionApi.subscribeForTask(any())).thenReturn(CompletableFuture.completedFuture(mock()))
109+
110+
testSubject.postProcessAfterInitialization(bean, "noTenantAtAllWorker")
111+
112+
val captor = argumentCaptor<SubscribeForTaskCmd>()
113+
verify(taskSubscriptionApi).subscribeForTask(captor.capture())
114+
115+
assertThat(captor.firstValue.restrictions).doesNotContainKey(CommonRestrictions.TENANT_ID)
116+
}
117+
}

0 commit comments

Comments
 (0)