From b68e679b053c734072a7b66e42d4aa13b28fcf58 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Wed, 1 Jul 2026 12:53:06 +0200 Subject: [PATCH] [Fix #1507] Loading HttpRequestDecorator from WorkflowAdditionaObject Signed-off-by: fjtirado --- .../impl/WorkflowApplication.java | 13 +++ .../impl/WorkflowAdditionalObjectTest.java | 103 ++++++++++++++++++ .../impl/executors/http/HttpExecutor.java | 10 +- .../executors/http/HttpExecutorBuilder.java | 18 ++- 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowAdditionalObjectTest.java diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index cf6f83b75..c5da66600 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -68,6 +68,7 @@ import java.util.ServiceLoader.Provider; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; import java.util.stream.Collectors; public class WorkflowApplication implements AutoCloseable { @@ -91,6 +92,7 @@ public class WorkflowApplication implements AutoCloseable { private final WorkflowModelFactory contextFactory; private final WorkflowScheduler scheduler; private final Map> additionalObjects; + private final Map> additionalObjectSuppliers; private final AuthProviderFactory authProviderFactory; private final ConfigManager configManager; private final SecretManager secretManager; @@ -123,6 +125,7 @@ private WorkflowApplication(Builder builder) { this.scheduler = builder.scheduler; this.schedulerListener = builder.schedulerListener; this.additionalObjects = builder.additionalObjects; + this.additionalObjectSuppliers = builder.additionalObjectSuppliers; this.authProviderFactory = builder.authProviderFactory; this.configManager = builder.configManager; this.secretManager = builder.secretManager; @@ -245,6 +248,7 @@ public SchemaValidator getValidator(SchemaInline inline) { private WorkflowModelFactory modelFactory; private WorkflowModelFactory contextFactory; private Map> additionalObjects = new HashMap<>(); + private Map> additionalObjectSuppliers = new HashMap<>(); private AuthProviderFactory authProviderFactory; private SecretManager secretManager; private ConfigManager configManager; @@ -362,6 +366,11 @@ public Builder withAdditionalObject( return this; } + public Builder withAdditionalObject(String name, Supplier additionalObject) { + additionalObjectSuppliers.put(name, additionalObject); + return this; + } + public Builder withAuthProviderFactory(AuthProviderFactory authProviderFactory) { this.authProviderFactory = authProviderFactory; return this; @@ -594,6 +603,10 @@ public String id() { return id; } + public Optional additionalObject(String name) { + return Optional.ofNullable(additionalObjectSuppliers.get(name)).map(v -> (T) v.get()); + } + public Optional additionalObject( String name, WorkflowContext workflowContext, TaskContext taskContext) { return Optional.ofNullable(additionalObjects.get(name)) diff --git a/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowAdditionalObjectTest.java b/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowAdditionalObjectTest.java new file mode 100644 index 000000000..0cdc8e3f2 --- /dev/null +++ b/impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowAdditionalObjectTest.java @@ -0,0 +1,103 @@ +/* + * 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.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.serverlessworkflow.impl.additional.WorkflowAdditionalObject; +import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionCompletableListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class WorkflowAdditionalObjectTest { + + private WorkflowExecutionCompletableListener mediumPrio = new MediumPriorityListener("javi"); + private WorkflowExecutionCompletableListener lowestPrio = new LowestPriorityListener(); + private WorkflowExecutionCompletableListener topPrio = new TopPriorityListener(); + private WorkflowModelFactory modelFactory; + + @BeforeEach + void setup() { + modelFactory = mock(WorkflowModelFactory.class); + } + + private static class DummyAdditionalObjectBiFunction + implements WorkflowAdditionalObject { + @Override + public Integer apply(WorkflowContextData workflowContext, TaskContextData taskContext) { + return Integer.valueOf((int) taskContext.retryAttempt()); + } + } + + private class DummyAdditionalObjectSupplier implements Supplier> { + @Override + public List get() { + return List.of(mediumPrio, lowestPrio, topPrio); + } + } + + @Test + void testAdditionalObjectBiFunction() { + WorkflowContext workflowContext = mock(WorkflowContext.class); + TaskContext taskContext = mock(TaskContext.class); + when(taskContext.retryAttempt()).thenReturn((short) 2); + final String key = "Dummy_Bifactory"; + try (WorkflowApplication appl = + WorkflowApplication.builder() + .withAdditionalObject(key, new DummyAdditionalObjectBiFunction()) + .withModelFactory(modelFactory) + .build()) { + assertThat(appl.additionalObject(key, workflowContext, taskContext).orElse(0)) + .isEqualTo(2); + } + } + + @Test + void testAdditionalObjectSupplier() { + final String key = "Dummy_supplier"; + try (WorkflowApplication appl = + WorkflowApplication.builder() + .withModelFactory(modelFactory) + .withAdditionalObject(key, new DummyAdditionalObjectSupplier()) + .build()) { + List priorities = new ArrayList<>(); + WorkflowExecutionCompletableListener anotherMediumPrio = + new MediumPriorityListener("javierito"); + priorities.add(anotherMediumPrio); + priorities.addAll(appl.>additionalObject(key).orElse(List.of())); + Collections.sort(priorities); + assertThat(priorities).isEqualTo(List.of(topPrio, anotherMediumPrio, mediumPrio, lowestPrio)); + } + } + + @Test + void testAdditionalObjectSupplierNull() { + final String key = "Null_supplier"; + try (WorkflowApplication appl = + WorkflowApplication.builder() + .withModelFactory(modelFactory) + .withAdditionalObject(key, () -> null) + .build()) { + assertThat(appl.additionalObject(key)).isEmpty(); + } + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java index c787cd2bd..39f9e0911 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java @@ -28,7 +28,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.ServiceLoader; import java.util.concurrent.CompletableFuture; public class HttpExecutor implements CallableTask { @@ -45,17 +44,14 @@ public class HttpExecutor implements CallableTask { Optional>> headersMap, Optional>> queryMap, RequestExecutor requestFunction, - Optional> pathSupplier) { + Optional> pathSupplier, + Collection requestDecorators) { this.uriSupplier = uriSupplier; this.headersMap = headersMap; this.queryMap = queryMap; this.requestFunction = requestFunction; this.pathSupplier = pathSupplier; - this.requestDecorators = - ServiceLoader.load(HttpRequestDecorator.class).stream() - .map(ServiceLoader.Provider::get) - .sorted() - .toList(); + this.requestDecorators = requestDecorators; } public CompletableFuture apply( diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutorBuilder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutorBuilder.java index dcbf50575..5c9f025bb 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutorBuilder.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutorBuilder.java @@ -24,12 +24,19 @@ import io.serverlessworkflow.impl.auth.AuthProvider; import jakarta.ws.rs.HttpMethod; import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.ServiceLoader; public class HttpExecutorBuilder { + public static final String HTTP_REQUEST_DECORATOR_KEY = "HttpRequestDecorators"; private final WorkflowDefinition definition; + private final List requestDecorators; private WorkflowValueResolver pathSupplier; private Object body; private String method = HttpMethod.GET; @@ -40,6 +47,14 @@ public class HttpExecutorBuilder { private HttpExecutorBuilder(WorkflowDefinition definition) { this.definition = definition; + this.requestDecorators = new ArrayList<>(); + requestDecorators.addAll( + definition + .application() + .>additionalObject(HTTP_REQUEST_DECORATOR_KEY) + .orElse(List.of())); + ServiceLoader.load(HttpRequestDecorator.class).forEach(requestDecorators::add); + Collections.sort(requestDecorators); } public HttpExecutorBuilder withAuth(ReferenceableAuthenticationPolicy policy) { @@ -110,7 +125,8 @@ public HttpExecutor build(WorkflowValueResolver uriSupplier) { Optional.ofNullable(headersMap), Optional.ofNullable(queryMap), buildRequestExecutor(), - Optional.ofNullable(pathSupplier)); + Optional.ofNullable(pathSupplier), + requestDecorators); } public static HttpExecutorBuilder builder(WorkflowDefinition definition) {