diff --git a/.gitignore b/.gitignore index b80b599dc8..1fb5dd59bb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ target .gradle /build /*/build -/out +**/out /lib dummy $buildDir diff --git a/build.gradle b/build.gradle index 6225aafc29..b7678d474d 100644 --- a/build.gradle +++ b/build.gradle @@ -24,20 +24,22 @@ ext { // We have to use specific versions with platforms until Maven 4.0.0 is released // See https://github.com/temporalio/sdk-java/issues/1033 // - // grpcVersion = '[1.34.0,)!!1.44.1' + // grpcVersion = '[1.34.0,)!!1.47.1' // jacksonVersion = '[2.9.0,)!!2.13.1' - // micrometerVersion = '[1.0.0,)!!1.8.3' + // micrometerVersion = '[1.0.0,)!!1.9.1' grpcVersion = '1.47.0' jacksonVersion = '2.13.1' micrometerVersion = '1.9.1' + springBootVersion = '2.4.0' + slf4jVersion = '[1.4.0,)!!1.7.36' protoVersion = '[3.10.0,3.99)!!3.20.1' annotationApiVersion = '1.3.2' guavaVersion = '[10.0,)!!31.1-jre' - jsonPathVersion = '2.7.0' tallyVersion = '[0.4.0,)!!0.11.1' + + jsonPathVersion = '2.7.0' gsonVersion = '[2.0,)!!2.9.0' - slf4jVersion = '[1.4.0,)!!1.7.36' logbackVersion = '1.2.11' mockitoVersion = '4.6.1' diff --git a/settings.gradle b/settings.gradle index 26dc285e6e..957bdb1e41 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,3 +5,5 @@ include 'temporal-testing' include 'temporal-test-server' include 'temporal-opentracing' include 'temporal-kotlin' +include 'temporal-spring-boot-autoconfigure-alpha' +include 'temporal-spring-boot-starter-alpha' diff --git a/temporal-sdk/build.gradle b/temporal-sdk/build.gradle index bf83e275e7..0f60f464a9 100644 --- a/temporal-sdk/build.gradle +++ b/temporal-sdk/build.gradle @@ -17,9 +17,7 @@ dependencies { api "com.fasterxml.jackson.core:jackson-databind" implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8" - if (!JavaVersion.current().isJava8()) { - implementation 'javax.annotation:javax.annotation-api:1.3.2' - } + // compileOnly and testImplementation because this dependency is needed only to work with json format of history // which shouldn't be needed for any production usage of temporal-sdk. // It's useful only for unit tests and debugging. diff --git a/temporal-sdk/src/main/java/io/temporal/internal/replay/ChildWorkflowTaskFailedException.java b/temporal-sdk/src/main/java/io/temporal/internal/replay/ChildWorkflowTaskFailedException.java index f8d851c786..1b5aef519f 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/replay/ChildWorkflowTaskFailedException.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/replay/ChildWorkflowTaskFailedException.java @@ -28,9 +28,9 @@ * machines in case of child workflow task execution failure and contains an original unparsed * Failure message with details from the attributes in the exception. * - *

This class is needed to don't make Failure -> Exception conversion inside the state machines. - * So the state machine forms ChildWorkflowFailure without cause and parse the original Failure, so - * the outside code may join them together. + *

This class is needed to don't make Failure -> Exception conversion inside the state + * machines. So the state machine forms ChildWorkflowFailure without cause and parse the original + * Failure, so the outside code may join them together. */ public class ChildWorkflowTaskFailedException extends RuntimeException { diff --git a/temporal-serviceclient/build.gradle b/temporal-serviceclient/build.gradle index f01110e8c5..7b34aaab21 100644 --- a/temporal-serviceclient/build.gradle +++ b/temporal-serviceclient/build.gradle @@ -13,8 +13,8 @@ dependencies { api "io.grpc:grpc-stub" //Part of WorkflowServiceStubs API api "io.grpc:grpc-netty-shaded" //Part of WorkflowServiceStubs API, specifically SslContext api "com.google.protobuf:protobuf-java-util:$protoVersion" //proto request and response objects are a part of this module's API - if (!JavaVersion.current().isJava8()) { - //needed for the generated grpc stubs + if (JavaVersion.current().isJava9Compatible()) { + //needed for the generated grpc stubs and is not a part of JDK since java 9 implementation "javax.annotation:javax.annotation-api:$annotationApiVersion" } diff --git a/temporal-spring-boot-autoconfigure-alpha/build.gradle b/temporal-spring-boot-autoconfigure-alpha/build.gradle new file mode 100644 index 0000000000..cb95f6ca19 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/build.gradle @@ -0,0 +1,30 @@ +description = '''Spring Boot AutoConfigure for Temporal Java SDK''' + +dependencies { + api(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) + + api project(':temporal-sdk') + compileOnly project(':temporal-testing') + + implementation "org.springframework.boot:spring-boot" + implementation "org.springframework.boot:spring-boot-autoconfigure" + + // this dependency is not mandatory for the module to work, but it provides a better IDE experience developing the module + // it's needed to auto-generate configuration metadata. See for more details: + // https://docs.spring.io/spring-boot/docs/2.4.0/reference/html/appendix-configuration-metadata.html#configuration-metadata-annotation-processor + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:$springBootVersion" + + testImplementation project(':temporal-testing') + + testImplementation "org.mockito:mockito-core:${mockitoVersion}" + testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: "${logbackVersion}" + + testImplementation "org.springframework.boot:spring-boot-starter-test" +} + +tasks.test { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } +} \ No newline at end of file diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/ActivityImpl.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/ActivityImpl.java new file mode 100644 index 0000000000..044d9df95e --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/ActivityImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface ActivityImpl { + String[] taskQueues(); +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/WorkflowImpl.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/WorkflowImpl.java new file mode 100644 index 0000000000..2f72fdc148 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/WorkflowImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface WorkflowImpl { + String[] taskQueues(); +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/ClientAutoConfiguration.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/ClientAutoConfiguration.java new file mode 100644 index 0000000000..a01832f53f --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/ClientAutoConfiguration.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.serviceclient.WorkflowServiceStubsOptions; +import io.temporal.spring.boot.autoconfigure.properties.ClientProperties; +import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties; +import io.temporal.testing.TestWorkflowEnvironment; +import javax.annotation.Nullable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.support.BeanDefinitionValidationException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** Provides a client based on `spring.temporal.client` section */ +@Configuration +@EnableConfigurationProperties(TemporalProperties.class) +@ConditionalOnClass(name = "io.temporal.client.WorkflowClient") +@ConditionalOnProperty(prefix = "spring.temporal", name = "client.target") +public class ClientAutoConfiguration { + + @Bean(name = "temporalWorkflowClient") + @ConditionalOnMissingBean(name = "temporalWorkflowClient") + public WorkflowClient client( + TemporalProperties temporalProperties, + @Qualifier("temporalTestWorkflowEnvironment") @Autowired(required = false) @Nullable + TestWorkflowEnvironment testWorkflowEnvironment) { + ClientProperties clientProperties = temporalProperties.getClient(); + WorkflowServiceStubs workflowServiceStubs; + switch (temporalProperties.getClient().getTarget().toLowerCase()) { + case ClientProperties.TARGET_IN_PROCESS_TEST_SERVER: + if (testWorkflowEnvironment == null) { + throw new BeanDefinitionValidationException( + "Test workflow environment is not available. " + + "Please make sure that you have enabled the 'temporal-test' module " + + "and configured `spring.temporal.testServer.enabled: true`."); + } + return testWorkflowEnvironment.getWorkflowClient(); + case ClientProperties.TARGET_LOCAL_SERVICE: + workflowServiceStubs = WorkflowServiceStubs.newLocalServiceStubs(); + break; + default: + WorkflowServiceStubsOptions.Builder stubsOptionsBuilder = + WorkflowServiceStubsOptions.newBuilder(); + + if (clientProperties.getTarget() != null) { + stubsOptionsBuilder.setTarget(clientProperties.getTarget()); + } + + workflowServiceStubs = + WorkflowServiceStubs.newServiceStubs( + stubsOptionsBuilder.validateAndBuildWithDefaults()); + } + + WorkflowClientOptions.Builder clientOptionsBuilder = WorkflowClientOptions.newBuilder(); + + if (clientProperties.getNamespace() != null) { + clientOptionsBuilder.setNamespace(clientProperties.getNamespace()); + } + + return WorkflowClient.newInstance( + workflowServiceStubs, clientOptionsBuilder.validateAndBuildWithDefaults()); + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/TestServerAutoConfiguration.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/TestServerAutoConfiguration.java new file mode 100644 index 0000000000..d18d6ca326 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/TestServerAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties; +import io.temporal.testing.TestWorkflowEnvironment; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** Provides a client based on `spring.temporal.testServer` section */ +@Configuration +@EnableConfigurationProperties(TemporalProperties.class) +@ConditionalOnClass(name = "io.temporal.testing.TestWorkflowEnvironment") +@ConditionalOnProperty( + prefix = "spring.temporal", + name = "testServer.enabled", + havingValue = "true") +public class TestServerAutoConfiguration { + @Bean(name = "temporalTestWorkflowEnvironment", destroyMethod = "close") + public TestWorkflowEnvironment testServer() { + return TestWorkflowEnvironment.newInstance(); + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoConfiguration.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoConfiguration.java new file mode 100644 index 0000000000..c6c60f2473 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoConfiguration.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.client.WorkflowClient; +import io.temporal.common.metadata.POJOWorkflowImplMetadata; +import io.temporal.common.metadata.POJOWorkflowInterfaceMetadata; +import io.temporal.common.metadata.POJOWorkflowMethodMetadata; +import io.temporal.spring.boot.WorkflowImpl; +import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties; +import io.temporal.spring.boot.autoconfigure.properties.WorkerProperties; +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import java.util.*; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionValidationException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.*; +import org.springframework.context.event.ContextRefreshedEvent; + +/** Provides a client based on `spring.temporal.workers` section */ +@Configuration +@EnableConfigurationProperties(TemporalProperties.class) +@ConditionalOnClass(name = "io.temporal.worker.WorkerFactory") +@Conditional(WorkersPresentCondition.class) +@Import({WorkersAutoConfiguration.ActivityExplicitConfigProcessor.class}) +public class WorkersAutoConfiguration { + private static final Logger log = LoggerFactory.getLogger(WorkersAutoConfiguration.class); + + private final TemporalProperties properties; + private final ConfigurableListableBeanFactory beanFactory; + + public WorkersAutoConfiguration( + TemporalProperties properties, ConfigurableListableBeanFactory beanFactory) { + this.properties = properties; + this.beanFactory = beanFactory; + } + + @Bean(name = "temporalWorkerFactory", destroyMethod = "shutdown") + public WorkerFactory workerFactory( + WorkflowClient workflowClient, + @Qualifier("temporalTestWorkflowEnvironment") @Autowired(required = false) @Nullable + TestWorkflowEnvironment testWorkflowEnvironment) { + if (testWorkflowEnvironment != null) { + return testWorkflowEnvironment.getWorkerFactory(); + } else { + return WorkerFactory.newInstance(workflowClient); + } + } + + @Bean(name = "temporalWorkers") + public Collection workers( + WorkerFactory workerFactory, + @Qualifier("autoDiscoveredWorkflowImplementations") @Autowired(required = false) @Nullable + Collection> autoDiscoveredWorkflowImplementationClasses) { + Set workers = + properties.getWorkers().stream() + .map(workerProperties -> newWorker(workerFactory, workerProperties)) + .collect(Collectors.toSet()); + + if (autoDiscoveredWorkflowImplementationClasses != null) { + for (Class clazz : autoDiscoveredWorkflowImplementationClasses) { + WorkflowImpl annotation = clazz.getAnnotation(WorkflowImpl.class); + for (String taskQueue : annotation.taskQueues()) { + log.info( + "Registering auto-discovered workflow class {} on a task queue {}", clazz, taskQueue); + Worker worker = workerFactory.newWorker(taskQueue); + configureWorkflowImplementation(worker, clazz); + workers.add(worker); + } + } + } + + workers.forEach( + worker -> beanFactory.registerSingleton("temporalWorker-" + worker.getTaskQueue(), worker)); + + return workers; + } + + @ConditionalOnProperty(prefix = "spring.temporal", name = "startWorkers", matchIfMissing = true) + @Bean + public WorkerFactoryStarter workerFactoryStarter(WorkerFactory workerFactory) { + return new WorkerFactoryStarter(workerFactory); + } + + Worker newWorker(WorkerFactory workerFactory, WorkerProperties workerProperties) { + Worker worker = workerFactory.newWorker(workerProperties.getTaskQueue()); + + Collection> workflowClasses = workerProperties.getWorkflowClasses(); + if (workflowClasses != null) { + workflowClasses.forEach( + clazz -> { + log.info( + "Registering configured workflow class {} on a task queue {}", + clazz, + workerProperties.getTaskQueue()); + configureWorkflowImplementation(worker, clazz); + }); + } + + return worker; + } + + @SuppressWarnings("unchecked") + private void configureWorkflowImplementation(Worker worker, Class clazz) { + // TODO this code is a copy-past of a portion of + // POJOWorkflowImplementationFactory#registerWorkflowImplementationType + // we should refactor it to separate the logic into reusable methods + boolean hasWorkflowMethod = false; + POJOWorkflowImplMetadata workflowMetadata = POJOWorkflowImplMetadata.newInstance(clazz); + for (POJOWorkflowInterfaceMetadata workflowInterface : + workflowMetadata.getWorkflowInterfaces()) { + Optional workflowMethod = workflowInterface.getWorkflowMethod(); + if (workflowMethod.isPresent()) { + // TODO this is ugly. POJOWorkflowMethodMetadata needs to be generified + worker.addWorkflowImplementationFactory( + (Class) workflowInterface.getInterfaceClass(), + () -> (T) beanFactory.createBean(clazz)); + hasWorkflowMethod = true; + break; + } + } + + if (!hasWorkflowMethod) { + throw new BeanDefinitionValidationException(clazz + " doesn't have workflowMethod"); + } + } + + public static class WorkerFactoryStarter implements ApplicationListener { + private final WorkerFactory workerFactory; + + public WorkerFactoryStarter(WorkerFactory workerFactory) { + this.workerFactory = workerFactory; + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + workerFactory.start(); + } + } + + public static class ActivityExplicitConfigProcessor implements BeanPostProcessor { + private final ObjectFactory propertiesProvider; + private final ObjectFactory workerFactoryProvider; + private Map> activityBeanToTaskQueues; + + // By using ObjectFactory we avoid eager initialization of the properties and workerFactory + // which leads to a ton of warnings like + // "PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean + // 'spring.temporal-io.temporal.spring.boot.autoconfigure.properties.TemporalProperties' of type + // [io.temporal.spring.boot.autoconfigure.properties.TemporalProperties] is not eligible for + // getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)" + // because BeanPostProcessors are created before other beans and eager dependency on other beans + // messes with an initialization ordering. + public ActivityExplicitConfigProcessor( + ObjectFactory propertiesProvider, + ObjectFactory workerFactoryProvider) { + this.propertiesProvider = propertiesProvider; + this.workerFactoryProvider = workerFactoryProvider; + } + + @Override + public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName) + throws BeansException { + if (activityBeanToTaskQueues == null) { + // don't move it to init method, PostConstruct, constructor or any other steps that happen + // before BeanPostProcessor#postProcessAfterInitialization + // Otherwise you will get a nasty warning in console + // "PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean + // 'spring.temporal-io.temporal.spring.boot.autoconfigure.properties.TemporalProperties' of + // type [io.temporal.spring.boot.autoconfigure.properties.TemporalProperties] is not + // eligible for getting processed by all BeanPostProcessors (for example: not eligible for + // auto-proxying)" + // because it will lead to an early usage of TemporalProperties before BeanPostProcessor + // could be called on it. + // For more context of the warning: + // https://www.baeldung.com/spring-not-eligible-for-auto-proxying + parseConfig(); + } + + if (activityBeanToTaskQueues.containsKey(beanName)) { + activityBeanToTaskQueues.get(beanName).stream() + .map(workerFactoryProvider.getObject()::newWorker) + .forEach( + worker -> { + log.info( + "Registering configured activity bean '{}' class {} on task queue {}", + beanName, + AopUtils.getTargetClass(bean), + worker.getTaskQueue()); + worker.registerActivitiesImplementations(bean); + }); + } + return bean; + } + + private void parseConfig() { + activityBeanToTaskQueues = new HashMap<>(); + for (WorkerProperties workerProperties : propertiesProvider.getObject().getWorkers()) { + Collection activityBeans = workerProperties.getActivityBeans(); + if (activityBeans != null) { + activityBeans.forEach( + activityBeanName -> + activityBeanToTaskQueues + .computeIfAbsent(activityBeanName, k -> new ArrayList<>()) + .add(workerProperties.getTaskQueue())); + } + } + } + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoDiscoveryAutoConfiguration.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoDiscoveryAutoConfiguration.java new file mode 100644 index 0000000000..b07f156952 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoDiscoveryAutoConfiguration.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.spring.boot.ActivityImpl; +import io.temporal.spring.boot.WorkflowImpl; +import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import java.util.*; +import javax.annotation.Nonnull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.support.BeanDefinitionValidationException; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.*; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.filter.AnnotationTypeFilter; + +/** + * Works only if `spring.temporal.workersAutoDiscovery` is true and responsible for auto-discovery + * of workflow and activity implementations that are annotated with {@link + * io.temporal.spring.boot.ActivityImpl} and {@link io.temporal.spring.boot.WorkflowImpl} + */ +@Configuration +@EnableConfigurationProperties(TemporalProperties.class) +@Import({WorkersAutoDiscoveryAutoConfiguration.ActivityAutoDiscoveryProcessor.class}) +@Conditional(WorkersAutoDiscoveryPackagesPresentCondition.class) +public class WorkersAutoDiscoveryAutoConfiguration { + private static final Logger log = + LoggerFactory.getLogger(WorkersAutoDiscoveryAutoConfiguration.class); + + private final TemporalProperties properties; + + public WorkersAutoDiscoveryAutoConfiguration( + TemporalProperties properties) { + this.properties = properties; + } + + @Bean(name = "autoDiscoveredWorkflowImplementations") + public Collection> autoDiscoveredWorkflowImplementations() { + ClassPathScanningCandidateComponentProvider scanner = + new ClassPathScanningCandidateComponentProvider(false); + scanner.addIncludeFilter(new AnnotationTypeFilter(WorkflowImpl.class)); + Set> implementations = new HashSet<>(); + for (String pckg : properties.getWorkersAutoDiscovery().getPackages()) { + Set candidateComponents = scanner.findCandidateComponents(pckg); + for (BeanDefinition beanDefinition : candidateComponents) { + try { + implementations.add(Class.forName(beanDefinition.getBeanClassName())); + } catch (ClassNotFoundException e) { + throw new BeanDefinitionValidationException( + "Fail loading class for bean definition " + beanDefinition, e); + } + } + } + return implementations; + } + + /** + * Activities to be registered on a worker need to be already instantiated. This is the reason + * auto-discovery of activities has to be done in a post processor, when all bean definitions are + * loaded and instances are created by Spring. + */ + public static class ActivityAutoDiscoveryProcessor implements BeanPostProcessor { + private final ObjectFactory workerFactoryProvider; + + public ActivityAutoDiscoveryProcessor(ObjectFactory workerFactoryProvider) { + this.workerFactoryProvider = workerFactoryProvider; + } + + @Override + public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName) + throws BeansException { + Class targetClass = AopUtils.getTargetClass(bean); + ActivityImpl activityAnnotation = + AnnotationUtils.findAnnotation(targetClass, ActivityImpl.class); + if (activityAnnotation != null) { + for (String taskQueue : activityAnnotation.taskQueues()) { + log.info( + "Registering auto-discovered activity bean '{}' of class {} on task queue {}", + beanName, + targetClass, + taskQueue); + Worker worker = workerFactoryProvider.getObject().newWorker(taskQueue); + worker.registerActivitiesImplementations(bean); + } + } + return bean; + } + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoDiscoveryPackagesPresentCondition.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoDiscoveryPackagesPresentCondition.java new file mode 100644 index 0000000000..b346a753d5 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersAutoDiscoveryPackagesPresentCondition.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.OnPropertyListCondition; + +class WorkersAutoDiscoveryPackagesPresentCondition extends OnPropertyListCondition { + public WorkersAutoDiscoveryPackagesPresentCondition() { + super( + "spring.temporal.workersAutoDiscovery.packages".toLowerCase(), + () -> ConditionMessage.forCondition("Present Workers Auto Discovery Packages")); + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersPresentCondition.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersPresentCondition.java new file mode 100644 index 0000000000..5dfbe228ee --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersPresentCondition.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.spring.boot.autoconfigure.properties.WorkerProperties; +import java.util.List; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +class WorkersPresentCondition extends SpringBootCondition { + private static final Bindable> WORKER_PROPERTIES_LIST = + Bindable.listOf(WorkerProperties.class); + private static final String KEY = "spring.temporal.workers"; + + public WorkersPresentCondition() {} + + @Override + public ConditionOutcome getMatchOutcome( + ConditionContext context, AnnotatedTypeMetadata metadata) { + BindResult property = Binder.get(context.getEnvironment()).bind(KEY, WORKER_PROPERTIES_LIST); + ConditionMessage.Builder messageBuilder = ConditionMessage.forCondition("Present Workers"); + if (property.isBound()) { + return ConditionOutcome.match(messageBuilder.found("property").items(KEY)); + } + return ConditionOutcome.noMatch(messageBuilder.didNotFind("property").items(KEY)); + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/ClientProperties.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/ClientProperties.java new file mode 100644 index 0000000000..86a660106f --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/ClientProperties.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure.properties; + +import com.google.common.base.MoreObjects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.springframework.boot.context.properties.ConstructorBinding; + +@ConstructorBinding +public class ClientProperties { + public static final String TARGET_IN_PROCESS_TEST_SERVER = "inprocess"; + + public static final String TARGET_LOCAL_SERVICE = "local"; + + @Nonnull private final String target; + @Nonnull private final String namespace; + + public ClientProperties(@Nonnull String target, @Nullable String namespace) { + this.target = target; + this.namespace = MoreObjects.firstNonNull(namespace, "default"); + } + + /** + * @see io.temporal.serviceclient.WorkflowServiceStubsOptions.Builder#setTarget(String) + */ + @Nonnull + public String getTarget() { + return target; + } + + /** + * @see io.temporal.client.WorkflowClientOptions.Builder#setNamespace(String) + */ + @Nonnull + public String getNamespace() { + return namespace; + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/TemporalProperties.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/TemporalProperties.java new file mode 100644 index 0000000000..f28572d4da --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/TemporalProperties.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure.properties; + +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import javax.annotation.Nullable; + +@ConfigurationProperties(prefix = "spring.temporal") +@ConstructorBinding +public class TemporalProperties { + @NestedConfigurationProperty private final TestServerProperties testServer; + @NestedConfigurationProperty private final WorkersAutoDiscoveryProperties workersAutoDiscovery; + @NestedConfigurationProperty private final ClientProperties client; + private final List workers; + private final @Nullable Boolean startWorkers; + + public TemporalProperties( + TestServerProperties testServer, + WorkersAutoDiscoveryProperties workersAutoDiscovery, + ClientProperties client, + List workers, + @Nullable Boolean startWorkers) { + this.testServer = testServer; + this.workersAutoDiscovery = workersAutoDiscovery; + this.client = client; + this.workers = workers; + this.startWorkers = startWorkers; + } + + public TestServerProperties getTestServer() { + return testServer; + } + + public WorkersAutoDiscoveryProperties getWorkersAutoDiscovery() { + return workersAutoDiscovery; + } + + public ClientProperties getClient() { + return client; + } + + public List getWorkers() { + return workers; + } + + @Nullable + public Boolean getStartWorkers() { + return startWorkers; + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/TestServerProperties.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/TestServerProperties.java new file mode 100644 index 0000000000..10aa920639 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/TestServerProperties.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure.properties; + +import org.springframework.boot.context.properties.ConstructorBinding; + +@ConstructorBinding +public class TestServerProperties { + private final boolean enabled; + + public TestServerProperties(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkerProperties.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkerProperties.java new file mode 100644 index 0000000000..0b55a95b1a --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkerProperties.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure.properties; + +import java.util.Collection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.springframework.boot.context.properties.ConstructorBinding; + +@ConstructorBinding +public class WorkerProperties { + private @Nonnull String taskQueue; + + private @Nullable Collection> workflowClasses; + + private @Nullable Collection activityBeans; + + public WorkerProperties( + @Nonnull String taskQueue, + @Nullable Collection> workflowClasses, + @Nullable Collection activityBeans) { + this.taskQueue = taskQueue; + this.workflowClasses = workflowClasses; + this.activityBeans = activityBeans; + } + + @Nonnull + public String getTaskQueue() { + return taskQueue; + } + + public void setTaskQueue(String taskQueue) { + this.taskQueue = taskQueue; + } + + @Nullable + public Collection> getWorkflowClasses() { + return workflowClasses; + } + + public void setWorkflowClasses(Collection> workflowClasses) { + this.workflowClasses = workflowClasses; + } + + @Nullable + public Collection getActivityBeans() { + return activityBeans; + } + + public void setActivityBeans(Collection activityBeans) { + this.activityBeans = activityBeans; + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkersAutoDiscoveryProperties.java b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkersAutoDiscoveryProperties.java new file mode 100644 index 0000000000..ea525ddd55 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkersAutoDiscoveryProperties.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure.properties; + +import java.util.List; +import org.springframework.boot.context.properties.ConstructorBinding; + +@ConstructorBinding +public class WorkersAutoDiscoveryProperties { + private final List packages; + + public WorkersAutoDiscoveryProperties(List packages) { + this.packages = packages; + } + + public List getPackages() { + return packages; + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/main/resources/META-INF/spring.factories b/temporal-spring-boot-autoconfigure-alpha/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..2ede87e67d --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +io.temporal.spring.boot.autoconfigure.TestServerAutoConfiguration,\ +io.temporal.spring.boot.autoconfigure.ClientAutoConfiguration,\ +io.temporal.spring.boot.autoconfigure.WorkersAutoConfiguration,\ +io.temporal.spring.boot.autoconfigure.WorkersAutoDiscoveryAutoConfiguration diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryTest.java b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryTest.java new file mode 100644 index 0000000000..0698b4ab89 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.testing.TestWorkflowEnvironment; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(classes = AutoDiscoveryTest.Configuration.class) +@ActiveProfiles(profiles = "auto-discovery") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class AutoDiscoveryTest { + @Autowired TestWorkflowEnvironment testWorkflowEnvironment; + + @Autowired WorkflowClient workflowClient; + + @Test + public void testAutoDiscovery() { + TestWorkflow testWorkflow = + workflowClient.newWorkflowStub( + TestWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue("UnitTest").build()); + testWorkflow.execute("input"); + } + + @EnableAutoConfiguration + @ComponentScan + public static class Configuration {} +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/CustomClientConfigTest.java b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/CustomClientConfigTest.java new file mode 100644 index 0000000000..5ef347d1ac --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/CustomClientConfigTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.temporal.client.WorkflowClient; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(classes = CustomClientConfigTest.Configuration.class) +@ActiveProfiles(profiles = "custom-namespace") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class CustomClientConfigTest { + @Autowired protected WorkflowClient workflowClient; + + @Test + public void shouldCustomizeNamespace() { + assertEquals("custom", workflowClient.getOptions().getNamespace()); + } + + @EnableAutoConfiguration + public static class Configuration {} +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/ExplicitConfigTest.java b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/ExplicitConfigTest.java new file mode 100644 index 0000000000..aadce6f235 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/ExplicitConfigTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.testing.TestWorkflowEnvironment; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(classes = ExplicitConfigTest.Configuration.class) +@ActiveProfiles(profiles = "explicit-config") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ExplicitConfigTest { + @Autowired TestWorkflowEnvironment testWorkflowEnvironment; + + @Autowired WorkflowClient workflowClient; + + @Test + public void testExplicitConfig() { + TestWorkflow testWorkflow = + workflowClient.newWorkflowStub( + TestWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue("UnitTest").build()); + testWorkflow.execute("input"); + } + + @EnableAutoConfiguration + @ComponentScan + public static class Configuration {} +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestActivity.java b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestActivity.java new file mode 100644 index 0000000000..2aa4e17fce --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestActivity.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.activity.ActivityInterface; + +@ActivityInterface +public interface TestActivity { + String execute(String input); +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestActivityImpl.java b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestActivityImpl.java new file mode 100644 index 0000000000..da85595c72 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestActivityImpl.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.spring.boot.ActivityImpl; +import org.springframework.stereotype.Component; + +@Component("TestActivityImpl") +@ActivityImpl(taskQueues = "UnitTest") +public class TestActivityImpl implements TestActivity { + @Override + public String execute(String input) { + return input; + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestWorkflow.java b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestWorkflow.java new file mode 100644 index 0000000000..7741d7619e --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestWorkflow.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface TestWorkflow { + + @WorkflowMethod(name = "testWorkflow1") + String execute(String input); +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestWorkflowImpl.java b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestWorkflowImpl.java new file mode 100644 index 0000000000..4cfbd637cd --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/java/io/temporal/spring/boot/autoconfigure/TestWorkflowImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material 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.temporal.spring.boot.autoconfigure; + +import io.temporal.activity.ActivityOptions; +import io.temporal.spring.boot.WorkflowImpl; +import io.temporal.workflow.Workflow; +import java.time.Duration; + +@WorkflowImpl(taskQueues = "UnitTest") +public class TestWorkflowImpl implements TestWorkflow { + @Override + public String execute(String input) { + return Workflow.newActivityStub( + TestActivity.class, + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(1)) + .validateAndBuildWithDefaults()) + .execute("done"); + } +} diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/resources/application-custom-namespace.yml b/temporal-spring-boot-autoconfigure-alpha/src/test/resources/application-custom-namespace.yml new file mode 100644 index 0000000000..66f5616693 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/resources/application-custom-namespace.yml @@ -0,0 +1,28 @@ +# +# Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. +# +# Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Modifications copyright (C) 2017 Uber Technologies, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this material 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. +# + +spring.temporal: + startWorkers: false + testServer: + enabled: false + client: + target: local + namespace: custom + diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/resources/application.yml b/temporal-spring-boot-autoconfigure-alpha/src/test/resources/application.yml new file mode 100644 index 0000000000..9f073eca23 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/resources/application.yml @@ -0,0 +1,50 @@ +# +# Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. +# +# Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Modifications copyright (C) 2017 Uber Technologies, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this material 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. +# + +spring.temporal: + testServer: + enabled: true + client: + target: inprocess + workers: + - taskQueue: UnitTest + +--- +spring: + config: + activate: + on-profile: auto-discovery + temporal: + workersAutoDiscovery: + packages: + - io.temporal + +--- +spring: + config: + activate: + on-profile: explicit-config + temporal: + workers: + - taskQueue: UnitTest + workflowClasses: + - io.temporal.spring.boot.autoconfigure.TestWorkflowImpl + activityBeans: + - TestActivityImpl diff --git a/temporal-spring-boot-autoconfigure-alpha/src/test/resources/logback-test.xml b/temporal-spring-boot-autoconfigure-alpha/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9fd1fd8fd1 --- /dev/null +++ b/temporal-spring-boot-autoconfigure-alpha/src/test/resources/logback-test.xml @@ -0,0 +1,37 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/temporal-spring-boot-starter-alpha/build.gradle b/temporal-spring-boot-starter-alpha/build.gradle new file mode 100644 index 0000000000..8a57f4d23e --- /dev/null +++ b/temporal-spring-boot-starter-alpha/build.gradle @@ -0,0 +1,6 @@ +description = '''Spring Boot Starter for Temporal Java SDK''' + +dependencies { + implementation project(':temporal-sdk') + implementation project(':temporal-spring-boot-autoconfigure-alpha') +} \ No newline at end of file diff --git a/temporal-test-server/build.gradle b/temporal-test-server/build.gradle index 0de3930ce4..5dc0ed9982 100644 --- a/temporal-test-server/build.gradle +++ b/temporal-test-server/build.gradle @@ -22,8 +22,8 @@ dependencies { api project(':temporal-sdk') implementation("io.grpc:grpc-core") implementation("io.grpc:grpc-services") - if (!JavaVersion.current().isJava8()) { - //needed for the generated grpc stubs + if (JavaVersion.current().isJava9Compatible()) { + //needed for the generated grpc stubs and is not a part of JDK since java 9 implementation "javax.annotation:javax.annotation-api:$annotationApiVersion" }