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 { +@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/conductor-client/src/main/java/com/netflix/conductor/client/http/MetadataClient.java b/conductor-clients/java/conductor-java-sdk/conductor-client/src/main/java/com/netflix/conductor/client/http/MetadataClient.java index c9e88c978..cf7c20b27 100644 --- a/conductor-clients/java/conductor-java-sdk/conductor-client/src/main/java/com/netflix/conductor/client/http/MetadataClient.java +++ b/conductor-clients/java/conductor-java-sdk/conductor-client/src/main/java/com/netflix/conductor/client/http/MetadataClient.java @@ -104,7 +104,7 @@ public WorkflowDef getWorkflowDef(String name, Integer version) { public List getAllWorkflowsWithLatestVersions() { ConductorClientRequest request = ConductorClientRequest.builder() .method(Method.GET) - .path("metadata/workflow/latest-versions") + .path("/metadata/workflow/latest-versions") .build(); ConductorClientResponse> resp = client.execute(request, new TypeReference<>() { diff --git a/conductor-clients/java/conductor-java-sdk/conductor-client/src/test/groovy/com/netflix/conductor/client/http/MetadataClientSpec.groovy b/conductor-clients/java/conductor-java-sdk/conductor-client/src/test/groovy/com/netflix/conductor/client/http/MetadataClientSpec.groovy index 9f8fd43ab..8b7130c1b 100644 --- a/conductor-clients/java/conductor-java-sdk/conductor-client/src/test/groovy/com/netflix/conductor/client/http/MetadataClientSpec.groovy +++ b/conductor-clients/java/conductor-java-sdk/conductor-client/src/test/groovy/com/netflix/conductor/client/http/MetadataClientSpec.groovy @@ -100,7 +100,7 @@ class MetadataClientSpec extends ClientSpecification { then: 1 * apiClient.execute(builder() .method(ConductorClientRequest.Method.GET) - .path('metadata/workflow/latest-versions') + .path('/metadata/workflow/latest-versions') .build(), _) >> new ConductorClientResponse(200, [:], result) ret == result } 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 { 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. + *

+ * 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.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..9eb138fc8 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,95 @@ 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); + basePath = orkesClientProperties.getConductorServerUrl(); } - 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); + if (basePath == null) { + return null; } - Long readTimeout = env.getProperty(CONDUCTOR_CLIENT_READ_TIMEOUT, Long.class); - if (readTimeout != null) { - builder.readTimeout(readTimeout); - } + var builder = ApiClient.builder() + .basePath(basePath) + .connectTimeout(clientProperties.getTimeout().getConnect()) + .readTimeout(clientProperties.getTimeout().getRead()) + .writeTimeout(clientProperties.getTimeout().getWrite()) + .verifyingSsl(clientProperties.isVerifyingSsl()); - Long writeTimeout = env.getProperty(CONDUCTOR_CLIENT_WRITE_TIMEOUT, Long.class); - if (writeTimeout != null) { - builder.writeTimeout(writeTimeout); - } - Boolean verifyingSsl = env.getProperty(CONDUCTOR_CLIENT_VERIFYING_SSL, Boolean.class); - if (verifyingSsl != null) { - builder.verifyingSsl(verifyingSsl); + if (orkesClientProperties.getKeyId() != null) { + builder.credentials(orkesClientProperties.getKeyId(), orkesClientProperties.getSecret()); + } else if (orkesClientProperties.getSecurityKeyId() != null) { + builder.credentials(orkesClientProperties.getSecurityKeyId(), orkesClientProperties.getSecuritySecret()); } - return builder - .basePath(basePath) - .credentials(keyId, secret) - .build(); + return builder.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