From a4f8af93b654b98dc8e90d4b72611aee90150173 Mon Sep 17 00:00:00 2001
From: Miguel Prieto <miguel.prieto@orkes.io>
Date: Mon, 14 Oct 2024 16:08:08 -0300
Subject: [PATCH] Improve Spring modules with proper auto-configuration and
 added more properties

---
 .../client/spring/ClientProperties.java       | 60 +++++++++++
 .../ConductorClientAutoConfiguration.java     | 45 ++++++---
 .../ConductorWorkerAutoConfiguration.java     | 14 ++-
 ...ot.autoconfigure.AutoConfiguration.imports |  2 +
 .../java/conductor-java-sdk/gradle.properties |  2 +-
 .../io/orkes/conductor/client/ApiClient.java  |  5 +-
 .../client/spring/OrkesClientProperties.java  | 44 +++++++++
 ...OrkesConductorClientAutoConfiguration.java | 99 +++++++++----------
 ...ot.autoconfigure.AutoConfiguration.imports |  1 +
 9 files changed, 201 insertions(+), 71 deletions(-)
 create mode 100644 conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
 create mode 100644 conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/java/io/orkes/conductor/client/spring/OrkesClientProperties.java
 create mode 100644 conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

diff --git a/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ClientProperties.java b/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ClientProperties.java
index f459c1cd7..5e1b5f554 100644
--- a/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ClientProperties.java
+++ b/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ClientProperties.java
@@ -21,8 +21,40 @@
 @ConfigurationProperties("conductor.client")
 public class ClientProperties {
 
+    public static class Timeout {
+        private int connect = -1;
+        private int read = -1;
+        private int write = -1;
+
+        public int getConnect() {
+            return connect;
+        }
+
+        public void setConnect(int connect) {
+            this.connect = connect;
+        }
+
+        public int getRead() {
+            return read;
+        }
+
+        public void setRead(int read) {
+            this.read = read;
+        }
+
+        public int getWrite() {
+            return write;
+        }
+
+        public void setWrite(int write) {
+            this.write = write;
+        }
+    }
+
     private String rootUri;
 
+    private String basePath;
+
     private String workerNamePrefix = "workflow-worker-%d";
 
     private int threadCount = 1;
@@ -39,6 +71,26 @@ public class ClientProperties {
 
     private int taskPollTimeout = 100;
 
+    private Timeout timeout = new Timeout();
+
+    private boolean verifyingSsl = true;
+
+    public void setVerifyingSsl(boolean verifyingSsl) {
+        this.verifyingSsl = verifyingSsl;
+    }
+
+    public boolean isVerifyingSsl() {
+        return verifyingSsl;
+    }
+
+    public Timeout getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(Timeout timeout) {
+        this.timeout = timeout;
+    }
+
     public String getRootUri() {
         return rootUri;
     }
@@ -110,4 +162,12 @@ public int getTaskPollTimeout() {
     public void setTaskPollTimeout(int taskPollTimeout) {
         this.taskPollTimeout = taskPollTimeout;
     }
+
+    public String getBasePath() {
+        return basePath;
+    }
+
+    public void setBasePath(String basePath) {
+        this.basePath = basePath;
+    }
 }
diff --git a/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorClientAutoConfiguration.java b/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorClientAutoConfiguration.java
index 194ad6cf6..8190f8390 100644
--- a/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorClientAutoConfiguration.java
+++ b/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorClientAutoConfiguration.java
@@ -16,10 +16,14 @@
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureOrder;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
 import org.springframework.core.env.Environment;
 
 import com.netflix.conductor.client.automator.TaskRunnerConfigurer;
@@ -32,34 +36,46 @@
 
 import lombok.extern.slf4j.Slf4j;
 
-@Configuration(proxyBeanMethods = false)
-@EnableConfigurationProperties(ClientProperties.class)
+@AutoConfiguration
 @Slf4j
+@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
+@EnableConfigurationProperties(ClientProperties.class)
 public class ConductorClientAutoConfiguration {
 
-    @ConditionalOnMissingBean
     @Bean
-    public ConductorClient conductorClient(ClientProperties clientProperties) {
-        // TODO allow configuration of other properties via application.properties
-        return ConductorClient.builder()
-                .basePath(clientProperties.getRootUri())
-                .build();
+    @ConditionalOnMissingBean
+    public ConductorClient conductorClient(ClientProperties properties) {
+        var basePath = StringUtils.isBlank(properties.getRootUri()) ? properties.getBasePath() : properties.getRootUri();
+        if (basePath == null) {
+            return null;
+        }
+
+        var builder = ConductorClient.builder()
+                .basePath(basePath)
+                .connectTimeout(properties.getTimeout().getConnect())
+                .readTimeout(properties.getTimeout().getRead())
+                .writeTimeout(properties.getTimeout().getWrite())
+                .verifyingSsl(properties.isVerifyingSsl());
+        return builder.build();
     }
 
-    @ConditionalOnMissingBean
     @Bean
+    @ConditionalOnBean(ConductorClient.class)
+    @ConditionalOnMissingBean
     public TaskClient taskClient(ConductorClient client) {
         return new TaskClient(client);
     }
 
-    @ConditionalOnMissingBean
     @Bean
+    @ConditionalOnBean(ConductorClient.class)
+    @ConditionalOnMissingBean
     public AnnotatedWorkerExecutor annotatedWorkerExecutor(TaskClient taskClient) {
         return new AnnotatedWorkerExecutor(taskClient);
     }
 
-    @ConditionalOnMissingBean
     @Bean(initMethod = "init", destroyMethod = "shutdown")
+    @ConditionalOnBean(ConductorClient.class)
+    @ConditionalOnMissingBean
     public TaskRunnerConfigurer taskRunnerConfigurer(Environment env,
                                                      TaskClient taskClient,
                                                      ClientProperties clientProperties,
@@ -90,12 +106,15 @@ public TaskRunnerConfigurer taskRunnerConfigurer(Environment env,
     }
 
     @Bean
+    @ConditionalOnBean(ConductorClient.class)
+    @ConditionalOnMissingBean
     public WorkflowExecutor workflowExecutor(ConductorClient client, AnnotatedWorkerExecutor annotatedWorkerExecutor) {
         return new WorkflowExecutor(client, annotatedWorkerExecutor);
     }
 
-    @ConditionalOnMissingBean
     @Bean
+    @ConditionalOnBean(ConductorClient.class)
+    @ConditionalOnMissingBean
     public WorkflowClient workflowClient(ConductorClient client) {
         return new WorkflowClient(client);
     }
diff --git a/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorWorkerAutoConfiguration.java b/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorWorkerAutoConfiguration.java
index cdc212c5e..f40b1017d 100644
--- a/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorWorkerAutoConfiguration.java
+++ b/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorWorkerAutoConfiguration.java
@@ -14,9 +14,13 @@
 
 import java.util.Map;
 
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureOrder;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.Ordered;
 import org.springframework.core.env.Environment;
 import org.springframework.stereotype.Component;
 
@@ -24,8 +28,10 @@
 import com.netflix.conductor.sdk.workflow.executor.task.AnnotatedWorkerExecutor;
 import com.netflix.conductor.sdk.workflow.executor.task.WorkerConfiguration;
 
-@Component
-public class ConductorWorkerAutoConfiguration implements ApplicationListener<ContextRefreshedEvent> {
+@AutoConfiguration
+@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
+@ConditionalOnBean(TaskClient.class)
+public class ConductorWorkerAutoConfiguration {
 
     private final TaskClient taskClient;
 
@@ -33,7 +39,7 @@ public ConductorWorkerAutoConfiguration(TaskClient taskClient) {
         this.taskClient = taskClient;
     }
 
-    @Override
+    @EventListener(ContextRefreshedEvent.class)
     public void onApplicationEvent(ContextRefreshedEvent refreshedEvent) {
         ApplicationContext applicationContext = refreshedEvent.getApplicationContext();
         Environment environment = applicationContext.getEnvironment();
diff --git a/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..2e489d842
--- /dev/null
+++ b/conductor-clients/java/conductor-java-sdk/conductor-client-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+com.netflix.conductor.client.spring.ConductorClientAutoConfiguration
+com.netflix.conductor.client.spring.ConductorWorkerAutoConfiguration
diff --git a/conductor-clients/java/conductor-java-sdk/gradle.properties b/conductor-clients/java/conductor-java-sdk/gradle.properties
index 19a01881d..c984afef6 100644
--- a/conductor-clients/java/conductor-java-sdk/gradle.properties
+++ b/conductor-clients/java/conductor-java-sdk/gradle.properties
@@ -1 +1 @@
-version=4.0.0
+version=4.0.1
diff --git a/conductor-clients/java/conductor-java-sdk/orkes-client/src/main/java/io/orkes/conductor/client/ApiClient.java b/conductor-clients/java/conductor-java-sdk/orkes-client/src/main/java/io/orkes/conductor/client/ApiClient.java
index 3d1ef30c5..94f5a736a 100644
--- a/conductor-clients/java/conductor-java-sdk/orkes-client/src/main/java/io/orkes/conductor/client/ApiClient.java
+++ b/conductor-clients/java/conductor-java-sdk/orkes-client/src/main/java/io/orkes/conductor/client/ApiClient.java
@@ -19,6 +19,7 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.NotNull;
 
 import com.netflix.conductor.client.exception.ConductorClientException;
@@ -150,8 +151,8 @@ public static ApiClientBuilder builder() {
     public static class ApiClientBuilder extends Builder<ApiClientBuilder> {
 
         public ApiClientBuilder credentials(String key, String secret) {
-            if (key == null || secret == null) {
-                throw new IllegalArgumentException("Key and secret must not be null");
+            if (StringUtils.isBlank(key) || StringUtils.isBlank(secret)) {
+                throw new IllegalArgumentException("Key and secret must not be blank (null or empty)");
             }
 
             this.addHeaderSupplier(new OrkesAuthentication(key, secret));
diff --git a/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/java/io/orkes/conductor/client/spring/OrkesClientProperties.java b/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/java/io/orkes/conductor/client/spring/OrkesClientProperties.java
new file mode 100644
index 000000000..0f0a3a46b
--- /dev/null
+++ b/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/java/io/orkes/conductor/client/spring/OrkesClientProperties.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 Conductor Authors.
+ * <p>
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.orkes.conductor.client.spring;
+
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import lombok.Getter;
+
+
+@Component
+@Getter
+public class OrkesClientProperties {
+    private final String keyId;
+    private final String secret;
+    // for backwards compatibility
+    private final String conductorServerUrl;
+    private final String securityKeyId;
+    private final String securitySecret;
+
+    public OrkesClientProperties(@Value("${conductor.client.keyId:${conductor.client.key-id:#{null}}}") String keyId,
+                                 @Value("${conductor.client.secret:#{null}}") String secret,
+                                 // for backwards compatibility
+                                 @Value("${conductor.server.url:#{null}}") String conductorServerUrl,
+                                 @Value("${conductor.security.client.keyId:${conductor.security.client.key-id:#{null}}}") String securityKeyId,
+                                 @Value("${conductor.security.client.secret:#{null}}") String securitySecret) {
+        this.keyId = keyId;
+        this.secret = secret;
+        this.conductorServerUrl = conductorServerUrl;
+        this.securityKeyId = securityKeyId;
+        this.securitySecret = securitySecret;
+    }
+}
diff --git a/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/java/io/orkes/conductor/client/spring/OrkesConductorClientAutoConfiguration.java b/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/java/io/orkes/conductor/client/spring/OrkesConductorClientAutoConfiguration.java
index acc602df6..f8b758030 100644
--- a/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/java/io/orkes/conductor/client/spring/OrkesConductorClientAutoConfiguration.java
+++ b/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/java/io/orkes/conductor/client/spring/OrkesConductorClientAutoConfiguration.java
@@ -12,11 +12,16 @@
  */
 package io.orkes.conductor.client.spring;
 
-
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.env.Environment;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Primary;
+
+import com.netflix.conductor.client.spring.ClientProperties;
 
 import io.orkes.conductor.client.ApiClient;
 import io.orkes.conductor.client.AuthorizationClient;
@@ -30,106 +35,98 @@
 
 import lombok.extern.slf4j.Slf4j;
 
-@Configuration(proxyBeanMethods = false)
+@AutoConfiguration
 @Slf4j
+@EnableConfigurationProperties(ClientProperties.class)
+@Import(OrkesClientProperties.class)
 public class OrkesConductorClientAutoConfiguration {
 
-    // Keeping these for backwards compatibility
-    public static final String CONDUCTOR_SERVER_URL ="conductor.server.url";
-    public static final String CONDUCTOR_SECURITY_CLIENT_KEY_ID ="conductor.security.client.key-id";
-    public static final String CONDUCTOR_SECURITY_CLIENT_SECRET ="conductor.security.client.secret";
-
-    // Properties should be placed under "conductor.client"
-    public static final String CONDUCTOR_CLIENT_BASE_PATH = "conductor.client.basepath";
-    public static final String CONDUCTOR_CLIENT_KEY_ID = "conductor.client.key-id";
-    public static final String CONDUCTOR_CLIENT_SECRET = "conductor.client.secret";
-    public static final String CONDUCTOR_CLIENT_CONNECT_TIMEOUT = "conductor.client.timeout.connect";
-    public static final String CONDUCTOR_CLIENT_READ_TIMEOUT = "conductor.client.timeout.read";
-    public static final String CONDUCTOR_CLIENT_WRITE_TIMEOUT = "conductor.client.timeout.write";
-    public static final String CONDUCTOR_CLIENT_VERIFYING_SSL = "conductor.client.verifying-ssl";
-
     @Bean
+    @Primary
     @ConditionalOnMissingBean
-    public ApiClient orkesConductorClient(Environment env) {
-        ApiClient.ApiClientBuilder builder = ApiClient.builder();
-
-        String basePath = env.getProperty(CONDUCTOR_CLIENT_BASE_PATH);
+    public ApiClient orkesConductorClient(ClientProperties clientProperties,
+                                          OrkesClientProperties orkesClientProperties) {
+        var basePath = StringUtils.isBlank(clientProperties.getRootUri()) ? clientProperties.getBasePath() : clientProperties.getRootUri();
         if (basePath == null) {
-            basePath = env.getProperty(CONDUCTOR_SERVER_URL);
-        }
-
-        String keyId = env.getProperty(CONDUCTOR_CLIENT_KEY_ID);
-        if (keyId == null) {
-            keyId = env.getProperty(CONDUCTOR_SECURITY_CLIENT_KEY_ID);
-        }
-
-        String secret = env.getProperty(CONDUCTOR_CLIENT_SECRET);
-        if (secret == null) {
-            secret = env.getProperty(CONDUCTOR_SECURITY_CLIENT_SECRET);
-        }
-
-        Long connectTimeout = env.getProperty(CONDUCTOR_CLIENT_CONNECT_TIMEOUT, Long.class);
-        if (connectTimeout != null) {
-            builder.connectTimeout(connectTimeout);
+            basePath = orkesClientProperties.getConductorServerUrl();
         }
 
-        Long readTimeout = env.getProperty(CONDUCTOR_CLIENT_READ_TIMEOUT, Long.class);
-        if (readTimeout != null) {
-            builder.readTimeout(readTimeout);
-        }
-
-        Long writeTimeout = env.getProperty(CONDUCTOR_CLIENT_WRITE_TIMEOUT, Long.class);
-        if (writeTimeout != null) {
-            builder.writeTimeout(writeTimeout);
+        if (basePath == null) {
+            return null;
         }
 
-        Boolean verifyingSsl = env.getProperty(CONDUCTOR_CLIENT_VERIFYING_SSL, Boolean.class);
-        if (verifyingSsl != null) {
-            builder.verifyingSsl(verifyingSsl);
+        String key = null;
+        String secret = null;
+        if (orkesClientProperties.getKeyId() != null) {
+            key = orkesClientProperties.getKeyId();
+            secret = orkesClientProperties.getSecret();
+        } else if (orkesClientProperties.getSecurityKeyId() != null) {
+            key = orkesClientProperties.getSecurityKeyId();
+            secret = orkesClientProperties.getSecuritySecret();
         }
 
-        return builder
+        return ApiClient.builder()
                 .basePath(basePath)
-                .credentials(keyId, secret)
+                .credentials(key, secret)
+                .connectTimeout(clientProperties.getTimeout().getConnect())
+                .readTimeout(clientProperties.getTimeout().getRead())
+                .writeTimeout(clientProperties.getTimeout().getWrite())
+                .verifyingSsl(clientProperties.isVerifyingSsl())
                 .build();
     }
 
     @Bean
+    @ConditionalOnBean(ApiClient.class)
+    @ConditionalOnMissingBean
     public OrkesClients orkesClients(ApiClient client) {
         return new OrkesClients(client);
     }
 
     @Bean
+    @ConditionalOnBean(ApiClient.class)
+    @ConditionalOnMissingBean
     public OrkesTaskClient orkesTaskClient(OrkesClients clients) {
         return clients.getTaskClient();
     }
 
     @Bean
+    @ConditionalOnBean(ApiClient.class)
+    @ConditionalOnMissingBean
     public OrkesMetadataClient orkesMetadataClient(OrkesClients clients) {
         return clients.getMetadataClient();
     }
 
     @Bean
+    @ConditionalOnBean(ApiClient.class)
+    @ConditionalOnMissingBean
     public OrkesWorkflowClient orkesWorkflowClient(OrkesClients clients) {
         return clients.getWorkflowClient();
     }
 
     @Bean
+    @ConditionalOnBean(ApiClient.class)
+    @ConditionalOnMissingBean
     public AuthorizationClient orkesAuthorizationClient(OrkesClients clients) {
         return clients.getAuthorizationClient();
     }
 
     @Bean
+    @ConditionalOnBean(ApiClient.class)
+    @ConditionalOnMissingBean
     public OrkesEventClient orkesEventClient(OrkesClients clients) {
         return clients.getEventClient();
     }
 
     @Bean
+    @ConditionalOnBean(ApiClient.class)
+    @ConditionalOnMissingBean
     public SchedulerClient orkesSchedulerClient(OrkesClients clients) {
         return clients.getSchedulerClient();
     }
 
     @Bean
+    @ConditionalOnBean(ApiClient.class)
+    @ConditionalOnMissingBean
     public SecretClient orkesSecretClient(OrkesClients clients) {
         return clients.getSecretClient();
     }
diff --git a/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..6dcfe1446
--- /dev/null
+++ b/conductor-clients/java/conductor-java-sdk/orkes-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+io.orkes.conductor.client.spring.OrkesConductorClientAutoConfiguration