From a7f0d131ca20f2b92d4c45512907b7b7c2cfaca3 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Tue, 11 Aug 2026 17:16:38 +0200 Subject: [PATCH 1/3] [Fix #1611] Improving service loaders performance Signed-off-by: Francisco Javier Tirado Sarti --- .../executors/RunScriptExecutorBuilder.java | 11 ++++++-- .../impl/executors/RunTaskExecutor.java | 8 +++--- .../executors/http/HttpExecutorBuilder.java | 28 +++++++++++++------ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutorBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutorBuilder.java index 87198d540..e2952edb2 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutorBuilder.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutorBuilder.java @@ -25,12 +25,19 @@ import io.serverlessworkflow.impl.resources.ResourceLoaderUtils; import io.serverlessworkflow.impl.scripts.ScriptLanguageId; import io.serverlessworkflow.impl.scripts.ScriptRunner; +import java.util.Collection; import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; public class RunScriptExecutorBuilder implements RunnableTaskBuilder { + private static Collection scriptRunners = + ServiceLoader.load(ScriptRunner.class).stream() + .map(ServiceLoader.Provider::get) + .sorted() + .toList(); + @Override public CallableTask build(RunScript taskConfiguration, WorkflowDefinition definition) { ScriptUnion scriptUnion = taskConfiguration.getScript(); @@ -65,10 +72,8 @@ public CallableTask build(RunScript taskConfiguration, WorkflowDefinition defini m), taskConfiguration.isAwait(), taskConfiguration.getReturn(), - ServiceLoader.load(ScriptRunner.class).stream() - .map(ServiceLoader.Provider::get) + scriptRunners.stream() .filter(s -> s.identifier().equals(language)) - .sorted() .findFirst() .orElseThrow( () -> diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunTaskExecutor.java index c398a36d7..fe2eab357 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunTaskExecutor.java @@ -22,6 +22,7 @@ import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowMutablePosition; +import java.util.Collection; import java.util.ServiceLoader; import java.util.ServiceLoader.Provider; import java.util.concurrent.CompletableFuture; @@ -30,8 +31,9 @@ public class RunTaskExecutor extends RegularTaskExecutor { private final CallableTask runnable; - private static final ServiceLoader runnables = - ServiceLoader.load(RunnableTaskBuilder.class); + private static final Collection runnables = + ServiceLoader.load(RunnableTaskBuilder.class).stream().map(Provider::get).sorted().toList(); + ; public static class RunTaskExecutorBuilder extends RegularTaskExecutorBuilder { @@ -43,9 +45,7 @@ protected RunTaskExecutorBuilder( RunTaskConfiguration config = task.getRun().get(); this.runnable = runnables.stream() - .map(Provider::get) .filter(r -> r.accept(config.getClass())) - .sorted() .findFirst() .map(r -> r.build(config, definition)) .orElseThrow( 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 5c9f025bb..fd259606d 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 @@ -31,12 +31,15 @@ import java.util.Map; import java.util.Optional; import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; public class HttpExecutorBuilder { public static final String HTTP_REQUEST_DECORATOR_KEY = "HttpRequestDecorators"; private final WorkflowDefinition definition; - private final List requestDecorators; + private static final Map> appRequestDecorators = + new ConcurrentHashMap<>(); + private List requestDecorators; private WorkflowValueResolver pathSupplier; private Object body; private String method = HttpMethod.GET; @@ -47,14 +50,21 @@ 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); + this.requestDecorators = + appRequestDecorators.computeIfAbsent( + definition.application().id(), + __ -> { + List result = new ArrayList<>(); + result.addAll( + definition + .application() + .>additionalObject( + HTTP_REQUEST_DECORATOR_KEY) + .orElse(List.of())); + ServiceLoader.load(HttpRequestDecorator.class).forEach(result::add); + Collections.sort(result); + return result; + }); } public HttpExecutorBuilder withAuth(ReferenceableAuthenticationPolicy policy) { From 15e8e871194358155cd0bac2fdedd323a5ae5a60 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Tue, 11 Aug 2026 17:47:27 +0200 Subject: [PATCH 2/3] [Fix #1611] Allow reloading by checking when application is created Signed-off-by: Francisco Javier Tirado Sarti --- .../impl/WorkflowApplication.java | 13 +++++++ .../impl/auth/CommonOAuthProvider.java | 39 ++++++++++--------- .../impl/auth/OAuth2AuthProvider.java | 1 + .../impl/auth/OpenIdAuthProvider.java | 1 + .../impl/executors/EmitExecutor.java | 12 +++--- .../executors/RunScriptExecutorBuilder.java | 10 +---- .../impl/executors/RunTaskExecutor.java | 9 +---- .../executors/http/HttpExecutorBuilder.java | 25 ++++-------- 8 files changed, 49 insertions(+), 61 deletions(-) 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 d125572cd..21be00280 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -114,6 +114,7 @@ public class WorkflowApplication implements AutoCloseable { private final WorkflowLifeCycleCloudEventFactory lifeCycleCloudEventFactory; private final ScheduledExecutorService schedulerExecutorService; private final Set allowedCommands; + private final Map, List> serviceLoadedClasses = new ConcurrentHashMap<>(); private WorkflowApplication(Builder builder) { this.taskFactory = builder.taskFactory; @@ -708,4 +709,16 @@ public WorkflowLifeCycleCloudEventFactory lifeCycleCloudEventFactory() { public Set allowedCommands() { return allowedCommands; } + + @SuppressWarnings("unchecked") + public List serviceLoadedClasses(Class clazz) { + return (List) + serviceLoadedClasses.computeIfAbsent( + clazz, + c -> + ServiceLoader.load(clazz).stream() + .map(ServiceLoader.Provider::get) + .sorted() + .toList()); + } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java index e1e8fb6e2..ebfbda92c 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java @@ -16,13 +16,13 @@ package io.serverlessworkflow.impl.auth; import static io.serverlessworkflow.impl.WorkflowUtils.checkSecret; -import static io.serverlessworkflow.impl.WorkflowUtils.loadFirst; import static io.serverlessworkflow.impl.WorkflowUtils.secret; import io.serverlessworkflow.api.types.OAuth2AuthenticationData; import io.serverlessworkflow.api.types.SecretBasedAuthenticationPolicy; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowValueResolver; @@ -35,14 +35,6 @@ public abstract class CommonOAuthProvider implements AuthProvider { private final WorkflowValueResolver tokenProvider; - private static JWTConverter jwtConverter = - loadFirst(JWTConverter.class) - .orElseThrow(() -> new IllegalStateException("No JWTConverter implementation found")); - - private static AccessTokenProviderFactory accessTokenProviderFactory = - loadFirst(AccessTokenProviderFactory.class) - .orElseThrow(() -> new IllegalStateException("No JWTConverter implementation found")); - protected CommonOAuthProvider(WorkflowValueResolver tokenProvider) { this.tokenProvider = tokenProvider; } @@ -67,35 +59,44 @@ protected static OAuth2AuthenticationData fillFromMap( } protected static WorkflowValueResolver accessToken( + WorkflowApplication app, Workflow workflow, OAuth2AuthenticationData authenticationData, SecretBasedAuthenticationPolicy secret, AuthRequestBuilder builder) { if (authenticationData != null) { - return build(authenticationData, builder); + return build(authenticationData, builder, app); } else if (secret != null) { - return build(checkSecret(workflow, secret), builder); + return build(checkSecret(workflow, secret), builder, app); } throw new IllegalStateException("Both policy and secret are null"); } private static WorkflowValueResolver build( - OAuth2AuthenticationData authenticationData, AuthRequestBuilder authBuilder) { + OAuth2AuthenticationData authenticationData, + AuthRequestBuilder authBuilder, + WorkflowApplication app) { AccessTokenProvider tokenProvider = - accessTokenProviderFactory.build( - authBuilder.apply(authenticationData), authenticationData.getIssuers(), jwtConverter); + app.serviceLoadedClasses(AccessTokenProviderFactory.class) + .get(0) + .build( + authBuilder.apply(authenticationData), + authenticationData.getIssuers(), + app.serviceLoadedClasses(JWTConverter.class).get(0)); return (w, t, m) -> tokenProvider; } private static WorkflowValueResolver build( - String secretName, AuthRequestBuilder authBuilder) { + String secretName, AuthRequestBuilder authBuilder, WorkflowApplication app) { return (w, t, m) -> { Map secret = secret(w, secretName); String issuers = (String) secret.get("issuers"); - return accessTokenProviderFactory.build( - authBuilder.apply(secret), - issuers != null ? Arrays.asList(issuers.split(",")) : null, - jwtConverter); + return app.serviceLoadedClasses(AccessTokenProviderFactory.class) + .get(0) + .build( + authBuilder.apply(secret), + issuers != null ? Arrays.asList(issuers.split(",")) : null, + app.serviceLoadedClasses(JWTConverter.class).get(0)); }; } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuth2AuthProvider.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuth2AuthProvider.java index 6bce3d814..16e0e1ea7 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuth2AuthProvider.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuth2AuthProvider.java @@ -24,6 +24,7 @@ public OAuth2AuthProvider( WorkflowApplication application, Workflow workflow, OAuthPolicyData policyData) { super( accessToken( + application, workflow, policyData.data(), policyData.secret(), diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OpenIdAuthProvider.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OpenIdAuthProvider.java index 80dd4138b..425e3c082 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OpenIdAuthProvider.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OpenIdAuthProvider.java @@ -24,6 +24,7 @@ public OpenIdAuthProvider( WorkflowApplication application, Workflow workflow, OAuthPolicyData policyData) { super( accessToken( + application, workflow, policyData.data(), policyData.secret(), diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java index f34253471..1d42b9b5d 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java @@ -41,16 +41,10 @@ import java.util.Collection; import java.util.Map; import java.util.Optional; -import java.util.ServiceLoader; import java.util.concurrent.CompletableFuture; public class EmitExecutor extends RegularTaskExecutor { - private static final Collection emittedDecorators = - ServiceLoader.load(EmittedEventDecorator.class).stream() - .map(ServiceLoader.Provider::get) - .sorted() - .toList(); private final EventPropertiesBuilder props; public static class EmitExecutorBuilder @@ -139,7 +133,11 @@ private CloudEvent buildCloudEvent(WorkflowContext workflow, TaskContext taskCon .additionalFilter() .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) .ifPresent(value -> value.forEach((k, v) -> addExtension(ceBuilder, k, v))); - emittedDecorators.forEach(d -> d.decorate(ceBuilder, workflow, taskContext)); + workflow + .definition() + .application() + .serviceLoadedClasses(EmittedEventDecorator.class) + .forEach(d -> d.decorate(ceBuilder, workflow, taskContext)); return ceBuilder.build(); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutorBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutorBuilder.java index e2952edb2..02d2b813b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutorBuilder.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutorBuilder.java @@ -25,19 +25,11 @@ import io.serverlessworkflow.impl.resources.ResourceLoaderUtils; import io.serverlessworkflow.impl.scripts.ScriptLanguageId; import io.serverlessworkflow.impl.scripts.ScriptRunner; -import java.util.Collection; import java.util.Objects; import java.util.Optional; -import java.util.ServiceLoader; public class RunScriptExecutorBuilder implements RunnableTaskBuilder { - private static Collection scriptRunners = - ServiceLoader.load(ScriptRunner.class).stream() - .map(ServiceLoader.Provider::get) - .sorted() - .toList(); - @Override public CallableTask build(RunScript taskConfiguration, WorkflowDefinition definition) { ScriptUnion scriptUnion = taskConfiguration.getScript(); @@ -72,7 +64,7 @@ public CallableTask build(RunScript taskConfiguration, WorkflowDefinition defini m), taskConfiguration.isAwait(), taskConfiguration.getReturn(), - scriptRunners.stream() + application.serviceLoadedClasses(ScriptRunner.class).stream() .filter(s -> s.identifier().equals(language)) .findFirst() .orElseThrow( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunTaskExecutor.java index fe2eab357..f1fe7861e 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunTaskExecutor.java @@ -22,19 +22,12 @@ import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowMutablePosition; -import java.util.Collection; -import java.util.ServiceLoader; -import java.util.ServiceLoader.Provider; import java.util.concurrent.CompletableFuture; public class RunTaskExecutor extends RegularTaskExecutor { private final CallableTask runnable; - private static final Collection runnables = - ServiceLoader.load(RunnableTaskBuilder.class).stream().map(Provider::get).sorted().toList(); - ; - public static class RunTaskExecutorBuilder extends RegularTaskExecutorBuilder { private CallableTask runnable; @@ -44,7 +37,7 @@ protected RunTaskExecutorBuilder( super(position, task, definition); RunTaskConfiguration config = task.getRun().get(); this.runnable = - runnables.stream() + definition.application().serviceLoadedClasses(RunnableTaskBuilder.class).stream() .filter(r -> r.accept(config.getClass())) .findFirst() .map(r -> r.build(config, definition)) 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 fd259606d..50693a5ee 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 @@ -30,15 +30,11 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.ServiceLoader; -import java.util.concurrent.ConcurrentHashMap; public class HttpExecutorBuilder { public static final String HTTP_REQUEST_DECORATOR_KEY = "HttpRequestDecorators"; private final WorkflowDefinition definition; - private static final Map> appRequestDecorators = - new ConcurrentHashMap<>(); private List requestDecorators; private WorkflowValueResolver pathSupplier; private Object body; @@ -51,20 +47,13 @@ public class HttpExecutorBuilder { private HttpExecutorBuilder(WorkflowDefinition definition) { this.definition = definition; this.requestDecorators = - appRequestDecorators.computeIfAbsent( - definition.application().id(), - __ -> { - List result = new ArrayList<>(); - result.addAll( - definition - .application() - .>additionalObject( - HTTP_REQUEST_DECORATOR_KEY) - .orElse(List.of())); - ServiceLoader.load(HttpRequestDecorator.class).forEach(result::add); - Collections.sort(result); - return result; - }); + new ArrayList<>(definition.application().serviceLoadedClasses(HttpRequestDecorator.class)); + requestDecorators.addAll( + definition + .application() + .>additionalObject(HTTP_REQUEST_DECORATOR_KEY) + .orElse(List.of())); + Collections.sort(requestDecorators); } public HttpExecutorBuilder withAuth(ReferenceableAuthenticationPolicy policy) { From 31e2bb2156023f24952f8400289f2659a6707582 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Tue, 11 Aug 2026 20:32:27 +0200 Subject: [PATCH 3/3] [Fix #1611] Addressing AI comments Signed-off-by: Francisco Javier Tirado Sarti --- .../serverlessworkflow/impl/WorkflowApplication.java | 10 +++++++++- .../impl/auth/CommonOAuthProvider.java | 10 ++++------ 2 files changed, 13 insertions(+), 7 deletions(-) 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 21be00280..6675902cb 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -711,7 +711,7 @@ public Set allowedCommands() { } @SuppressWarnings("unchecked") - public List serviceLoadedClasses(Class clazz) { + public > List serviceLoadedClasses(Class clazz) { return (List) serviceLoadedClasses.computeIfAbsent( clazz, @@ -721,4 +721,12 @@ public List serviceLoadedClasses(Class clazz) { .sorted() .toList()); } + + public > T serviceLoadedClass(Class serviceClass) { + List list = serviceLoadedClasses(serviceClass); + if (list.isEmpty()) { + throw new IllegalStateException("No " + serviceClass + " implementation found"); + } + return list.get(0); + } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java index ebfbda92c..3404ac1d2 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java @@ -77,12 +77,11 @@ private static WorkflowValueResolver build( AuthRequestBuilder authBuilder, WorkflowApplication app) { AccessTokenProvider tokenProvider = - app.serviceLoadedClasses(AccessTokenProviderFactory.class) - .get(0) + app.serviceLoadedClass(AccessTokenProviderFactory.class) .build( authBuilder.apply(authenticationData), authenticationData.getIssuers(), - app.serviceLoadedClasses(JWTConverter.class).get(0)); + app.serviceLoadedClass(JWTConverter.class)); return (w, t, m) -> tokenProvider; } @@ -91,12 +90,11 @@ private static WorkflowValueResolver build( return (w, t, m) -> { Map secret = secret(w, secretName); String issuers = (String) secret.get("issuers"); - return app.serviceLoadedClasses(AccessTokenProviderFactory.class) - .get(0) + return app.serviceLoadedClass(AccessTokenProviderFactory.class) .build( authBuilder.apply(secret), issuers != null ? Arrays.asList(issuers.split(",")) : null, - app.serviceLoadedClasses(JWTConverter.class).get(0)); + app.serviceLoadedClass(JWTConverter.class)); }; } }