Skip to content

Commit 28754a0

Browse files
committed
Enable any type of collection cast on *ModelCollection
Signed-off-by: Ricardo Zanini <ricardozanini@gmail.com>
1 parent 90d4367 commit 28754a0

4 files changed

Lines changed: 105 additions & 16 deletions

File tree

experimental/lambda/src/test/java/io/serverless/workflow/impl/executors/func/EventFilteringTest.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import io.serverlessworkflow.impl.events.EventPublisher;
3939
import java.net.URI;
4040
import java.nio.charset.StandardCharsets;
41-
import java.util.ArrayList;
41+
import java.util.List;
4242
import java.util.concurrent.CompletableFuture;
4343
import org.junit.jupiter.api.Test;
4444

@@ -74,7 +74,6 @@ public void testIntelligentNewsletterApprovalPath() throws Exception {
7474
try (WorkflowApplication app =
7575
WorkflowApplication.builder().withListener(new TraceExecutionListener()).build()) {
7676

77-
// 1. Build the workflow using your exact DSL
7877
Workflow workflow =
7978
FuncWorkflowBuilder.workflow("intelligent-newsletter")
8079
.tasks(
@@ -86,11 +85,9 @@ public void testIntelligentNewsletterApprovalPath() throws Exception {
8685
consumed("org.acme.newsletter.review.done")
8786
.extensionByInstanceId("instanceid")))
8887
.outputAs(
89-
(ArrayList events) -> {
88+
(List<CloudEventData> events) -> {
9089
try {
91-
return MAPPER.readValue(
92-
((CloudEventData) events.iterator().next()).toBytes(),
93-
HumanReview.class);
90+
return MAPPER.readValue(events.get(0).toBytes(), HumanReview.class);
9491
} catch (Exception e) {
9592
throw new RuntimeException("Failed to deserialize HumanReview", e);
9693
}

experimental/model/src/main/java/io/serverlessworkflow/impl/model/func/JavaModelCollection.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717

1818
import io.serverlessworkflow.impl.WorkflowModel;
1919
import io.serverlessworkflow.impl.WorkflowModelCollection;
20+
import java.lang.reflect.Array;
2021
import java.util.ArrayList;
2122
import java.util.Collection;
23+
import java.util.HashSet;
2224
import java.util.Iterator;
25+
import java.util.List;
2326
import java.util.Optional;
27+
import java.util.Set;
2428

2529
public class JavaModelCollection implements Collection<WorkflowModel>, WorkflowModelCollection {
2630

@@ -138,9 +142,29 @@ public Class<?> objectClass() {
138142
}
139143

140144
@Override
145+
@SuppressWarnings({"rawtypes", "unchecked"})
141146
public <T> Optional<T> as(Class<T> clazz) {
142-
return object.getClass().isAssignableFrom(clazz)
143-
? Optional.of(clazz.cast(object))
144-
: Optional.empty();
147+
if (object == null) return Optional.empty();
148+
149+
if (clazz.isInstance(object)) return Optional.of(clazz.cast(object));
150+
151+
if (clazz.isAssignableFrom(List.class)) return Optional.of(clazz.cast(new ArrayList<>(object)));
152+
else if (clazz.isAssignableFrom(Set.class))
153+
return Optional.of(clazz.cast(new HashSet<>(object)));
154+
155+
if (clazz.isArray()) {
156+
Class<?> componentType = clazz.getComponentType();
157+
if (!componentType.isPrimitive()) {
158+
Object[] typedArray = (Object[]) Array.newInstance(componentType, 0);
159+
return Optional.of(clazz.cast(object.toArray(typedArray)));
160+
}
161+
162+
Object primitiveArray = Array.newInstance(componentType, object.size());
163+
int i = 0;
164+
for (Object item : object) Array.set(primitiveArray, i++, item);
165+
return Optional.of(clazz.cast(primitiveArray));
166+
}
167+
168+
return Optional.empty();
145169
}
146170
}

experimental/test/src/test/java/io/serverlessworkflow/fluent/test/FuncEventFilterTest.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,28 @@
2020
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.toOne;
2121
import static org.assertj.core.api.Assertions.assertThat;
2222

23+
import com.fasterxml.jackson.databind.JsonNode;
2324
import com.fasterxml.jackson.databind.node.ArrayNode;
2425
import io.serverlessworkflow.api.types.Workflow;
2526
import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder;
2627
import io.serverlessworkflow.impl.WorkflowApplication;
2728
import io.serverlessworkflow.impl.WorkflowModel;
2829
import java.util.Collection;
30+
import java.util.List;
2931
import java.util.Map;
32+
import java.util.Set;
3033
import java.util.concurrent.CompletableFuture;
3134
import org.junit.jupiter.api.Test;
3235

3336
/**
3437
* Tests for the Event Filter DSL specification. Verifies that the fluent builder correctly wires
35-
* the payload parsing and contextual lambdas into the final Workflow definitions.
38+
* the payload parsing and contextual lambdas into the final Workflow definitions, and ensures the
39+
* ModelCollection adapters seamlessly convert between types.
3640
*/
3741
class FuncEventFilterTest {
3842

43+
public record Review(String author, String text, int rating) {}
44+
3945
@Test
4046
void testListenToOneCollection() {
4147
runIt(
@@ -47,7 +53,7 @@ void testListenToOneCollection() {
4753
}
4854

4955
@Test
50-
void testListenToOneNode() {
56+
void testListenToOneArrayNode() {
5157
runIt(
5258
FuncWorkflowBuilder.workflow("listenToOneReviewNode")
5359
.tasks(
@@ -56,6 +62,36 @@ void testListenToOneNode() {
5662
.build());
5763
}
5864

65+
@Test
66+
void testListenToOneList() {
67+
runIt(
68+
FuncWorkflowBuilder.workflow("listenToOneReviewList")
69+
.tasks(
70+
listen("waitReview", toOne("org.acme.test.review"))
71+
.outputAs((List<JsonNode> list) -> list.get(0)))
72+
.build());
73+
}
74+
75+
@Test
76+
void testListenToOneSet() {
77+
runIt(
78+
FuncWorkflowBuilder.workflow("listenToOneReviewSet")
79+
.tasks(
80+
listen("waitReview", toOne("org.acme.test.review"))
81+
.outputAs((Set<JsonNode> set) -> set.iterator().next()))
82+
.build());
83+
}
84+
85+
@Test
86+
void testListenToOneArray() {
87+
runIt(
88+
FuncWorkflowBuilder.workflow("listenToOneReviewArray")
89+
.tasks(
90+
listen("waitReview", toOne("org.acme.test.review"))
91+
.outputAs((JsonNode[] array) -> array[0]))
92+
.build());
93+
}
94+
5995
private Workflow reviewEmitter() {
6096
return FuncWorkflowBuilder.workflow("emitReview")
6197
.tasks(emitJson("draftReady", "org.acme.test.review", Review.class))
@@ -64,10 +100,14 @@ private Workflow reviewEmitter() {
64100

65101
private void runIt(Workflow listen) {
66102
Review review = new Review("Torrente", "espectacular", 5);
103+
67104
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
105+
68106
CompletableFuture<WorkflowModel> waiting =
69107
app.workflowDefinition(listen).instance(Map.of()).start();
108+
70109
app.workflowDefinition(reviewEmitter()).instance(review).start().join();
110+
71111
assertThat(waiting.join().as(Review.class).orElseThrow()).isEqualTo(review);
72112
}
73113
}

impl/model/src/main/java/io/serverlessworkflow/impl/model/jackson/JacksonModelCollection.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@
2020
import io.serverlessworkflow.impl.WorkflowModel;
2121
import io.serverlessworkflow.impl.WorkflowModelCollection;
2222
import io.serverlessworkflow.impl.jackson.JsonUtils;
23+
import java.lang.reflect.Array;
24+
import java.util.ArrayList;
2325
import java.util.Collection;
26+
import java.util.HashSet;
2427
import java.util.Iterator;
28+
import java.util.List;
2529
import java.util.Optional;
30+
import java.util.Set;
2631

2732
public class JacksonModelCollection implements WorkflowModelCollection {
2833

@@ -38,12 +43,35 @@ public class JacksonModelCollection implements WorkflowModelCollection {
3843

3944
@Override
4045
public <T> Optional<T> as(Class<T> clazz) {
41-
if (clazz.equals(Collection.class)) {
42-
return Optional.of(clazz.cast(this));
46+
if (node == null) return Optional.empty();
47+
48+
if (clazz.isInstance(node)) return Optional.of(clazz.cast(node));
49+
50+
if (clazz.isInstance(this)) return Optional.of(clazz.cast(this));
51+
52+
List<JsonNode> elements = new ArrayList<>(node.size());
53+
node.forEach(elements::add);
54+
55+
if (clazz.isAssignableFrom(List.class)) return Optional.of(clazz.cast(elements));
56+
else if (clazz.isAssignableFrom(Set.class))
57+
return Optional.of(clazz.cast(new HashSet<>(elements)));
58+
59+
if (clazz.isArray()) {
60+
Class<?> componentType = clazz.getComponentType();
61+
62+
if (!componentType.isPrimitive()) {
63+
Object[] typedArray = (Object[]) Array.newInstance(componentType, 0);
64+
return Optional.of(clazz.cast(elements.toArray(typedArray)));
65+
}
66+
67+
Object primitiveArray = Array.newInstance(componentType, elements.size());
68+
int i = 0;
69+
for (Object item : elements) Array.set(primitiveArray, i++, item);
70+
71+
return Optional.of(clazz.cast(primitiveArray));
4372
}
44-
return clazz.isAssignableFrom(ArrayNode.class)
45-
? Optional.of(clazz.cast(node))
46-
: Optional.empty();
73+
74+
return Optional.empty();
4775
}
4876

4977
@Override

0 commit comments

Comments
 (0)