Repository: serverlessworkflow/sdk-java
Pull request #550 (2025-03-27 16:25:32)

[Fix_#549] Implement if for all task
Fix https://github.com/serverlessworkflow/sdk-java/issues/549


Patch:
diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java
index f51b7a0..23ca9a2 100644
--- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java
+++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java
@@ -49,12 +49,14 @@ public abstract class AbstractTaskExecutor<T extends TaskBase> implements TaskEx
   private final Optional<SchemaValidator> inputSchemaValidator;
   private final Optional<SchemaValidator> outputSchemaValidator;
   private final Optional<SchemaValidator> contextSchemaValidator;
+  private final Optional<WorkflowFilter> ifFilter;
 
   public abstract static class AbstractTaskExecutorBuilder<T extends TaskBase>
       implements TaskExecutorBuilder<T> {
     private Optional<WorkflowFilter> inputProcessor = Optional.empty();
     private Optional<WorkflowFilter> outputProcessor = Optional.empty();
     private Optional<WorkflowFilter> contextProcessor = Optional.empty();
+    private Optional<WorkflowFilter> ifFilter = Optional.empty();
     private Optional<SchemaValidator> inputSchemaValidator = Optional.empty();
     private Optional<SchemaValidator> outputSchemaValidator = Optional.empty();
     private Optional<SchemaValidator> contextSchemaValidator = Optional.empty();
@@ -100,6 +102,7 @@ public abstract class AbstractTaskExecutor<T extends TaskBase> implements TaskEx
         this.contextSchemaValidator =
             getSchemaValidator(application.validatorFactory(), resourceLoader, export.getSchema());
       }
+      this.ifFilter = optionalFilter(application.expressionFactory(), task.getIf());
     }
 
     protected final TransitionInfoBuilder next(
@@ -153,6 +156,7 @@ public abstract class AbstractTaskExecutor<T extends TaskBase> implements TaskEx
     this.inputSchemaValidator = builder.inputSchemaValidator;
     this.outputSchemaValidator = builder.outputSchemaValidator;
     this.contextSchemaValidator = builder.contextSchemaValidator;
+    this.ifFilter = builder.ifFilter;
   }
 
   protected final CompletableFuture<TaskContext> executeNext(
@@ -177,40 +181,49 @@ public abstract class AbstractTaskExecutor<T extends TaskBase> implements TaskEx
     if (!TaskExecutorHelper.isActive(workflowContext)) {
       return completable;
     }
-    return executeNext(
-        completable
-            .thenApply(
-                t -> {
-                  workflowContext
-                      .definition()
-                      .listeners()
-                      .forEach(l -> l.onTaskStarted(position, task));
-                  inputSchemaValidator.ifPresent(s -> s.validate(t.rawInput()));
-                  inputProcessor.ifPresent(
-                      p -> taskContext.input(p.apply(workflowContext, t, t.rawInput())));
-                  return t;
-                })
-            .thenCompose(t -> execute(workflowContext, t))
-            .thenApply(
-                t -> {
-                  outputProcessor.ifPresent(
-                      p -> t.output(p.apply(workflowContext, t, t.rawOutput())));
-                  outputSchemaValidator.ifPresent(s -> s.validate(t.output()));
-                  contextProcessor.ifPresent(
-                      p ->
-                          workflowContext.context(
-                              p.apply(workflowContext, t, workflowContext.context())));
-                  contextSchemaValidator.ifPresent(s -> s.validate(workflowContext.context()));
-                  t.completedAt(Instant.now());
-                  workflowContext
-                      .definition()
-                      .listeners()
-                      .forEach(l -> l.onTaskEnded(position, task));
-                  return t;
-                }),
-        workflowContext);
+    if (ifFilter
+        .map(f -> f.apply(workflowContext, taskContext, input).asBoolean(true))
+        .orElse(true)) {
+      return executeNext(
+          completable
+              .thenApply(
+                  t -> {
+                    workflowContext
+                        .definition()
+                        .listeners()
+                        .forEach(l -> l.onTaskStarted(position, task));
+                    inputSchemaValidator.ifPresent(s -> s.validate(t.rawInput()));
+                    inputProcessor.ifPresent(
+                        p -> taskContext.input(p.apply(workflowContext, t, t.rawInput())));
+                    return t;
+                  })
+              .thenCompose(t -> execute(workflowContext, t))
+              .thenApply(
+                  t -> {
+                    outputProcessor.ifPresent(
+                        p -> t.output(p.apply(workflowContext, t, t.rawOutput())));
+                    outputSchemaValidator.ifPresent(s -> s.validate(t.output()));
+                    contextProcessor.ifPresent(
+                        p ->
+                            workflowContext.context(
+                                p.apply(workflowContext, t, workflowContext.context())));
+                    contextSchemaValidator.ifPresent(s -> s.validate(workflowContext.context()));
+                    t.completedAt(Instant.now());
+                    workflowContext
+                        .definition()
+                        .listeners()
+                        .forEach(l -> l.onTaskEnded(position, task));
+                    return t;
+                  }),
+          workflowContext);
+    } else {
+      taskContext.transition(getSkipTransition());
+      return executeNext(completable, workflowContext);
+    }
   }
 
+  protected abstract TransitionInfo getSkipTransition();
+
   protected abstract CompletableFuture<TaskContext> execute(
       WorkflowContext workflow, TaskContext taskContext);
 }
diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java
index 24c1e84..7cac9a8 100644
--- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java
+++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java
@@ -54,6 +54,11 @@ public abstract class RegularTaskExecutor<T extends TaskBase> extends AbstractTa
     }
   }
 
+  @Override
+  protected TransitionInfo getSkipTransition() {
+    return transition;
+  }
+
   protected CompletableFuture<TaskContext> execute(
       WorkflowContext workflow, TaskContext taskContext) {
     CompletableFuture<TaskContext> future =
diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java
index 70b127c..9bd3a74 100644
--- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java
+++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java
@@ -79,6 +79,11 @@ public class SwitchExecutor extends AbstractTaskExecutor<SwitchTask> {
     }
   }
 
+  @Override
+  protected TransitionInfo getSkipTransition() {
+    return defaultTask;
+  }
+
   private SwitchExecutor(SwitchExecutorBuilder builder) {
     super(builder);
     this.defaultTask = TransitionInfo.build(builder.defaultTask);


Test patch:
diff --git a/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java b/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java
index 4ea8728..4a0a073 100644
--- a/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java
+++ b/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java
@@ -81,6 +81,14 @@ public class WorkflowDefinitionTest {
             "simple-expression.yaml",
             Map.of("input", Arrays.asList(1, 2, 3)),
             WorkflowDefinitionTest::checkSpecialKeywords),
+        args(
+            "conditional-set.yaml",
+            Map.of("enabled", true),
+            WorkflowDefinitionTest::checkEnableCondition),
+        args(
+            "conditional-set.yaml",
+            Map.of("enabled", false),
+            WorkflowDefinitionTest::checkDisableCondition),
         args(
             "raise-inline copy.yaml",
             WorkflowDefinitionTest::checkWorkflowException,
@@ -166,4 +174,14 @@ public class WorkflowDefinitionTest {
     assertThat(result.get("id").toString()).hasSize(26);
     assertThat(result.get("version").toString()).contains("alpha");
   }
+
+  private static void checkEnableCondition(Object obj) {
+    Map<String, Object> result = (Map<String, Object>) obj;
+    assertThat(result.get("name")).isEqualTo("javierito");
+  }
+
+  private static void checkDisableCondition(Object obj) {
+    Map<String, Object> result = (Map<String, Object>) obj;
+    assertThat(result.get("enabled")).isEqualTo(false);
+  }
 }
diff --git a/impl/core/src/test/resources/conditional-set.yaml b/impl/core/src/test/resources/conditional-set.yaml
new file mode 100644
index 0000000..5e06622
--- /dev/null
+++ b/impl/core/src/test/resources/conditional-set.yaml
@@ -0,0 +1,10 @@
+document:
+  dsl: '1.0.0-alpha5'
+  namespace: test
+  name: conditional-set
+  version: '0.1.0'
+do: 
+  - conditionalExpression:
+      if: .enabled
+      set:
+        name: javierito
\ No newline at end of file