From b4293416e2cbb50edbd217f2b04d5f6544561722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Andr=C3=A9?= Date: Thu, 18 Jun 2026 18:38:30 -0300 Subject: [PATCH] Add gaps ListenToConfigurerBuilder and enhance DSL for event filtering and data expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matheus André --- .../spec/AbstractListenTaskBuilder.java | 24 ++++++ .../spec/ListenToConfigurerBuilder.java | 74 +++++++++++++++++++ .../spec/dsl/ExprEventEmitPropertiesSpec.java | 11 +++ .../fluent/spec/dsl/ExprEventFilterSpec.java | 11 +++ .../fluent/spec/WorkflowBuilderTest.java | 50 +++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ListenToConfigurerBuilder.java diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AbstractListenTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AbstractListenTaskBuilder.java index c94d78453..db3a6ba0a 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AbstractListenTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AbstractListenTaskBuilder.java @@ -19,6 +19,9 @@ import io.serverlessworkflow.api.types.ListenTask; import io.serverlessworkflow.api.types.ListenTaskConfiguration; import io.serverlessworkflow.api.types.ListenTo; +import io.serverlessworkflow.api.types.TaskTimeout; +import io.serverlessworkflow.api.types.Timeout; +import io.serverlessworkflow.api.types.TimeoutAfter; import java.util.function.Consumer; public abstract class AbstractListenTaskBuilder< @@ -63,6 +66,27 @@ public AbstractListenTaskBuilder to(Consumer c) { return this; } + public ListenToConfigurerBuilder to() { + return new ListenToConfigurerBuilder<>(this, taskItemListBuilder); + } + + void applyTo(ListenTo listenTo) { + this.config.setTo(listenTo); + } + + public AbstractListenTaskBuilder timeout(String durationExpression) { + this.listenTask.setTimeout( + new TaskTimeout() + .withTaskTimeoutDefinition( + new Timeout() + .withAfter(new TimeoutAfter().withDurationExpression(durationExpression)))); + return this; + } + + protected ListenTask getListenTask() { + return listenTask; + } + public ListenTask build() { return listenTask; } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ListenToConfigurerBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ListenToConfigurerBuilder.java new file mode 100644 index 000000000..3b69603d3 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ListenToConfigurerBuilder.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.spec; + +import java.util.function.Consumer; + +public class ListenToConfigurerBuilder> { + + private final AbstractListenTaskBuilder listenTaskBuilder; + private final T taskItemListBuilder; + private final ListenToBuilder listenToBuilder; + private boolean strategySet; + + public ListenToConfigurerBuilder( + AbstractListenTaskBuilder listenTaskBuilder, T taskItemListBuilder) { + this.listenTaskBuilder = listenTaskBuilder; + this.taskItemListBuilder = taskItemListBuilder; + this.listenToBuilder = new ListenToBuilder(); + } + + public ListenToConfigurerBuilder one(Consumer filter) { + listenToBuilder.one(filter); + strategySet = true; + return this; + } + + public ListenToConfigurerBuilder all(Consumer... filters) { + listenToBuilder.all(filters); + strategySet = true; + return this; + } + + public ListenToConfigurerBuilder any(Consumer... filters) { + listenToBuilder.any(filters); + strategySet = true; + return this; + } + + public ListenToConfigurerBuilder until(String expression) { + listenToBuilder.until(expression); + return this; + } + + public ListenToConfigurerBuilder forEach( + String item, Consumer> tasksConsumer) { + final SubscriptionIteratorBuilder iteratorBuilder = + new SubscriptionIteratorBuilder<>(this.taskItemListBuilder); + tasksConsumer.accept(iteratorBuilder); + listenTaskBuilder.getListenTask().setForeach(iteratorBuilder.build()); + return this; + } + + public AbstractListenTaskBuilder apply() { + listenTaskBuilder.applyTo(listenToBuilder.build()); + return listenTaskBuilder; + } + + public boolean isStrategySet() { + return strategySet; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ExprEventEmitPropertiesSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ExprEventEmitPropertiesSpec.java index 2a45c7163..2fae54b39 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ExprEventEmitPropertiesSpec.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ExprEventEmitPropertiesSpec.java @@ -33,4 +33,15 @@ public SELF jsonData(Map data) { addPropertyStep(e -> e.data(data)); return JSON(); } + + /** + * Sets the event data as an expression without setting content type. + * + * @param expr the data expression (e.g., "${.temperature}") + * @return self + */ + public SELF data(String expr) { + addPropertyStep(e -> e.data(expr)); + return self(); + } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ExprEventFilterSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ExprEventFilterSpec.java index 2db621b6d..dbe0c4933 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ExprEventFilterSpec.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ExprEventFilterSpec.java @@ -26,4 +26,15 @@ public SELF jsonData(String expr) { addPropertyStep(e -> e.data(expr)); return JSON(); } + + /** + * Sets the event data filter expression without setting content type. + * + * @param expr the data filter expression (e.g., "${ .temperature > 38 }") + * @return self + */ + public SELF data(String expr) { + addPropertyStep(e -> e.data(expr)); + return self(); + } } diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java index c350392da..b316de7fe 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java @@ -20,6 +20,7 @@ import static io.serverlessworkflow.fluent.spec.dsl.DSL.cases; import static io.serverlessworkflow.fluent.spec.dsl.DSL.doTasks; import static io.serverlessworkflow.fluent.spec.dsl.DSL.emit; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.event; import static io.serverlessworkflow.fluent.spec.dsl.DSL.forEach; import static io.serverlessworkflow.fluent.spec.dsl.DSL.fork; import static io.serverlessworkflow.fluent.spec.dsl.DSL.http; @@ -703,4 +704,53 @@ void testDoTaskRunWorkflow() { RunTaskConfiguration.ProcessReturnType.NONE, runTask.getRun().getRunWorkflow().getReturn()); assertEquals(false, runTask.getRun().getRunWorkflow().isAwait()); } + + @Test + void testListenWithConfigurerBuilder() { + Workflow wf = + WorkflowBuilder.workflow("listen-with-configurer", "test", "0.1.0") + .tasks( + doTasks( + listen( + "waitForEvent", + l -> + l.to() + .any( + event().type("com.example.event.A"), + event().type("com.example.event.B")) + .until("$.count > 0") + .forEach( + "item", + f -> + f.tasks( + set("processed", s -> s.put("eventType", "processed")))) + .apply()))) + .build(); + + List items = wf.getDo(); + assertNotNull(items, "Do list must not be null"); + assertEquals(1, items.size(), "There should be one listen task"); + + TaskItem item = items.get(0); + assertEquals("waitForEvent", item.getName(), "TaskItem name should match"); + ListenTask lt = item.getTask().getListenTask(); + assertNotNull(lt, "ListenTask should be present"); + + // Verify strategy + var strategy = lt.getListen().getTo().getAnyEventConsumptionStrategy(); + assertNotNull(strategy, "Any strategy should be set"); + assertEquals(2, strategy.getAny().size(), "Should have 2 event filters"); + + // Verify until condition + var until = strategy.getUntil(); + assertNotNull(until, "Until should be set"); + assertEquals("$.count > 0", until.getAnyEventUntilCondition(), "Until expression should match"); + + // Verify foreach + var foreach = lt.getForeach(); + assertNotNull(foreach, "Foreach should be set"); + assertNotNull(foreach.getDo(), "Foreach do tasks should be set"); + assertEquals(1, foreach.getDo().size(), "Foreach do should have 1 task"); + assertEquals("processed", foreach.getDo().get(0).getName(), "Foreach task name should match"); + } }