diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersPresentCondition.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersPresentCondition.java index 599edc8ef..afcae7e12 100644 --- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersPresentCondition.java +++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/WorkersPresentCondition.java @@ -1,6 +1,7 @@ package io.temporal.spring.boot.autoconfigure; import io.temporal.spring.boot.autoconfigure.properties.WorkerProperties; +import io.temporal.spring.boot.autoconfigure.properties.WorkersAutoDiscoveryProperties; import java.util.List; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; @@ -15,11 +16,10 @@ class WorkersPresentCondition extends SpringBootCondition { private static final Bindable> WORKER_PROPERTIES_LIST = Bindable.listOf(WorkerProperties.class); - private static final Bindable> AUTO_DISCOVERY_PACKAGES_LIST = - Bindable.listOf(String.class); + private static final Bindable AUTO_DISCOVERY_BINDABLE = + Bindable.of(WorkersAutoDiscoveryProperties.class); private static final String WORKERS_KEY = "spring.temporal.workers"; - private static final String AUTO_DISCOVERY_KEY = - "spring.temporal.workers-auto-discovery.packages"; + private static final String AUTO_DISCOVERY_KEY = "spring.temporal.workers-auto-discovery"; public WorkersPresentCondition() {} @@ -34,8 +34,8 @@ public ConditionOutcome getMatchOutcome( } BindResult autoDiscoveryProperty = - Binder.get(context.getEnvironment()).bind(AUTO_DISCOVERY_KEY, AUTO_DISCOVERY_PACKAGES_LIST); - messageBuilder = ConditionMessage.forCondition("Auto Discovery Packages Set"); + Binder.get(context.getEnvironment()).bind(AUTO_DISCOVERY_KEY, AUTO_DISCOVERY_BINDABLE); + messageBuilder = ConditionMessage.forCondition("Workers Auto Discovery Set"); if (autoDiscoveryProperty.isBound()) { return ConditionOutcome.match(messageBuilder.found("property").items(AUTO_DISCOVERY_KEY)); } diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkersAutoDiscoveryProperties.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkersAutoDiscoveryProperties.java index 98bf1bf27..bbd232ffc 100644 --- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkersAutoDiscoveryProperties.java +++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/properties/WorkersAutoDiscoveryProperties.java @@ -1,17 +1,121 @@ package io.temporal.spring.boot.autoconfigure.properties; +import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; public class WorkersAutoDiscoveryProperties { - private final @Nullable List packages; + /** + * When {@code true}, enables auto-registration of all {@code @ActivityImpl}-, + * {@code @NexusServiceImpl}-, and {@code @WorkflowImpl}-annotated classes that are Spring-managed + * beans. This is the recommended option for simple use cases where all implementations are + * Spring-managed beans. It implies {@link #registerActivityBeans} and {@link + * #registerNexusServiceBeans} default to {@code true}, and also enables auto-registration of + * {@code @WorkflowImpl}-annotated classes that are Spring-managed beans, without needing {@link + * #workflowPackages}. + * + *

Classpath-scanning via {@link #workflowPackages} (for non-bean workflow classes) still + * requires explicit package configuration regardless of this flag. + */ + private final @Nullable Boolean enabled; + + private final @Nullable List workflowPackages; + private final @Nullable Boolean registerActivityBeans; + private final @Nullable Boolean registerNexusServiceBeans; + + /** + * @deprecated Use {@link #workflowPackages} instead. If set and non-empty, this property causes + * {@link #registerActivityBeans} and {@link #registerNexusServiceBeans} to default to {@code + * true}, and its entries to be considered as if they were provided through {@link + * #workflowPackages}. Setting both {@link #packages} and any of the other new properties is + * unsupported and will result in an exception. + */ + @Deprecated private final @Nullable List packages; @ConstructorBinding - public WorkersAutoDiscoveryProperties(@Nullable List packages) { + public WorkersAutoDiscoveryProperties( + @Nullable Boolean enabled, + @Nullable List workflowPackages, + @Nullable Boolean registerActivityBeans, + @Nullable Boolean registerNexusServiceBeans, + @Nullable List packages) { + if (packages != null + && !packages.isEmpty() + && (enabled != null + || workflowPackages != null + || registerActivityBeans != null + || registerNexusServiceBeans != null)) { + throw new IllegalStateException( + "spring.temporal.workers-auto-discovery.packages is deprecated and cannot be combined " + + "with enabled, workflow-packages, register-activity-beans, or register-nexus-service-beans. " + + "Migrate to the new properties and remove packages."); + } + this.enabled = enabled; + this.workflowPackages = workflowPackages; + this.registerActivityBeans = registerActivityBeans; + this.registerNexusServiceBeans = registerNexusServiceBeans; this.packages = packages; } + /** + * Returns whether {@code @WorkflowImpl}-annotated classes that are also Spring-managed beans + * should be automatically registered with matching workers (without classpath scanning). Defaults + * to {@code true} when {@link #enabled} is {@code true}, {@code false} otherwise. Workflow + * implementation classes that are not Spring-managed beans still require {@link + * #workflowPackages} for classpath scanning. + */ + public boolean isRegisterWorkflowManagedBeans() { + return Boolean.TRUE.equals(enabled); + } + + /** + * Returns whether {@code @ActivityImpl}-annotated beans should be automatically registered with + * matching workers. Defaults to {@code true} when {@link #enabled} is {@code true} or when the + * deprecated {@link #packages} property is set and non-empty; {@code false} otherwise. + */ + public boolean isRegisterActivityBeans() { + if (registerActivityBeans != null) return registerActivityBeans; + return Boolean.TRUE.equals(enabled) || (packages != null && !packages.isEmpty()); + } + + /** + * Returns whether {@code @NexusServiceImpl}-annotated beans should be automatically registered + * with matching workers. Defaults to {@code true} when {@link #enabled} is {@code true} or when + * the deprecated {@link #packages} property is set and non-empty; {@code false} otherwise. + */ + public boolean isRegisterNexusServiceBeans() { + if (registerNexusServiceBeans != null) return registerNexusServiceBeans; + return Boolean.TRUE.equals(enabled) || (packages != null && !packages.isEmpty()); + } + + /** + * Returns the list of packages to scan for {@code @WorkflowImpl} classes. When the deprecated + * {@link #packages} property is set, its entries are used; otherwise returns the entries of + * {@link #workflowPackages}. The two properties cannot be set simultaneously. + */ + public List getEffectiveWorkflowPackages() { + List result = new ArrayList<>(); + if (packages != null) result.addAll(packages); + if (workflowPackages != null) result.addAll(workflowPackages); + return result; + } + + @Nullable + public List getWorkflowPackages() { + return workflowPackages; + } + + /** + * @deprecated `packages` has unclear semantics with regard to registration of activity and nexus + * service beans; use {@link #getWorkflowPackages()} instead. + */ + @Deprecated + @DeprecatedConfigurationProperty( + replacement = "spring.temporal.workers-auto-discovery.workflow-packages", + reason = + "'packages' has unclear semantics with regard to registration of activity and nexus service beans; use 'workflow-packages' instead") @Nullable public List getPackages() { return packages; diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkersTemplate.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkersTemplate.java index 2792306af..1ab6bc7a2 100644 --- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkersTemplate.java +++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkersTemplate.java @@ -21,6 +21,7 @@ import io.temporal.spring.boot.WorkflowImpl; import io.temporal.spring.boot.autoconfigure.properties.NamespaceProperties; import io.temporal.spring.boot.autoconfigure.properties.WorkerProperties; +import io.temporal.spring.boot.autoconfigure.properties.WorkersAutoDiscoveryProperties; import io.temporal.worker.*; import io.temporal.workflow.DynamicWorkflow; import java.lang.reflect.Constructor; @@ -179,22 +180,40 @@ private Collection createWorkers(WorkerFactory workerFactory) { createWorkerFromAnExplicitConfig(workerFactory, workerProperties, workers)); } - if (namespaceProperties.getWorkersAutoDiscovery() != null - && namespaceProperties.getWorkersAutoDiscovery().getPackages() != null) { - Collection> autoDiscoveredWorkflowImplementationClasses = - autoDiscoverWorkflowImplementations(); - Map autoDiscoveredActivityBeans = autoDiscoverActivityBeans(); - Map autoDiscoveredNexusServiceBeans = autoDiscoverNexusServiceBeans(); + WorkersAutoDiscoveryProperties autoDiscovery = namespaceProperties.getWorkersAutoDiscovery(); + if (autoDiscovery != null) { + // @ActivityImpl beans are Spring beans, discoverable without classpath scanning. + if (autoDiscovery.isRegisterActivityBeans()) { + Map autoDiscoveredActivityBeans = autoDiscoverActivityBeans(); + configureActivityBeansByTaskQueue(workerFactory, workers, autoDiscoveredActivityBeans); + configureActivityBeansByWorkerName(workers, autoDiscoveredActivityBeans); + } + + // @NexusServiceImpl beans are Spring beans, discoverable without classpath scanning. + if (autoDiscovery.isRegisterNexusServiceBeans()) { + Map autoDiscoveredNexusServiceBeans = autoDiscoverNexusServiceBeans(); + configureNexusServiceBeansByTaskQueue( + workerFactory, workers, autoDiscoveredNexusServiceBeans); + configureNexusServiceBeansByWorkerName(workers, autoDiscoveredNexusServiceBeans); + } - configureWorkflowImplementationsByTaskQueue( - workerFactory, workers, autoDiscoveredWorkflowImplementationClasses); - configureActivityBeansByTaskQueue(workerFactory, workers, autoDiscoveredActivityBeans); - configureNexusServiceBeansByTaskQueue( - workerFactory, workers, autoDiscoveredNexusServiceBeans); - configureWorkflowImplementationsByWorkerName( - workers, autoDiscoveredWorkflowImplementationClasses); - configureActivityBeansByWorkerName(workers, autoDiscoveredActivityBeans); - configureNexusServiceBeansByWorkerName(workers, autoDiscoveredNexusServiceBeans); + // Workflow discovery: Spring-bean-based (enabled: true) and/or classpath-scanning (packages). + // These two sources are unioned; duplicates are harmless because Set is used internally. + Set> autoDiscoveredWorkflowImplementationClasses = new HashSet<>(); + if (autoDiscovery.isRegisterWorkflowManagedBeans()) { + autoDiscoveredWorkflowImplementationClasses.addAll(autoDiscoverWorkflowBeans()); + } + List workflowPackages = autoDiscovery.getEffectiveWorkflowPackages(); + if (!workflowPackages.isEmpty()) { + autoDiscoveredWorkflowImplementationClasses.addAll( + autoDiscoverWorkflowImplementations(workflowPackages)); + } + if (!autoDiscoveredWorkflowImplementationClasses.isEmpty()) { + configureWorkflowImplementationsByTaskQueue( + workerFactory, workers, autoDiscoveredWorkflowImplementationClasses); + configureWorkflowImplementationsByWorkerName( + workers, autoDiscoveredWorkflowImplementationClasses); + } } return workers.getWorkers(); @@ -350,12 +369,12 @@ private void configureNexusServiceBeansByWorkerName( }); } - private Collection> autoDiscoverWorkflowImplementations() { + private Collection> autoDiscoverWorkflowImplementations(List packages) { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, environment); scanner.addIncludeFilter(new AnnotationTypeFilter(WorkflowImpl.class)); Set> implementations = new HashSet<>(); - for (String pckg : namespaceProperties.getWorkersAutoDiscovery().getPackages()) { + for (String pckg : packages) { Set candidateComponents = scanner.findCandidateComponents(pckg); for (BeanDefinition beanDefinition : candidateComponents) { try { @@ -369,6 +388,12 @@ private Collection> autoDiscoverWorkflowImplementations() { return implementations; } + private Collection> autoDiscoverWorkflowBeans() { + return beanFactory.getBeansWithAnnotation(WorkflowImpl.class).values().stream() + .map(AopUtils::getTargetClass) + .collect(java.util.stream.Collectors.toSet()); + } + private Map autoDiscoverActivityBeans() { return beanFactory.getBeansWithAnnotation(ActivityImpl.class); } diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryActivitiesOnlyTest.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryActivitiesOnlyTest.java new file mode 100644 index 000000000..00b832220 --- /dev/null +++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryActivitiesOnlyTest.java @@ -0,0 +1,78 @@ +package io.temporal.spring.boot.autoconfigure; + +import static org.junit.jupiter.api.Assertions.*; + +import io.temporal.spring.boot.autoconfigure.template.WorkersTemplate; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.test.context.ActiveProfiles; + +/** + * Regression test for https://github.com/temporalio/sdk-java/issues/2780: + * {@code @ActivityImpl}-annotated beans should be auto-registered with workers even when no + * workflow packages are configured under {@code spring.temporal.workers-auto-discovery.packages}. + */ +@SpringBootTest(classes = AutoDiscoveryActivitiesOnlyTest.Configuration.class) +@ActiveProfiles(profiles = "auto-discovery-activities-only") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class AutoDiscoveryActivitiesOnlyTest { + + @Autowired ConfigurableApplicationContext applicationContext; + + @Autowired private WorkersTemplate workersTemplate; + + @BeforeEach + void setUp() { + applicationContext.start(); + } + + @Test + @Timeout(value = 10) + public void testActivityBeansRegisteredWithoutWorkflowPackages() { + assertNotNull(workersTemplate); + Map registeredInfoMap = + workersTemplate.getRegisteredInfo(); + + // One worker should have been created for the task queue specified in @ActivityImpl + assertEquals(1, registeredInfoMap.size()); + registeredInfoMap.forEach( + (taskQueue, info) -> { + assertEquals("UnitTest", taskQueue); + + // No workflow packages configured, so no workflows should be registered + assertTrue( + info.getRegisteredWorkflowInfo().isEmpty(), + "No workflows expected when packages: [] is configured"); + + // @ActivityImpl bean should be registered despite no packages being configured + assertFalse( + info.getRegisteredActivityInfo().isEmpty(), + "@ActivityImpl beans should be auto-registered without workflow packages"); + assertEquals(1, info.getRegisteredActivityInfo().size()); + assertEquals( + "io.temporal.spring.boot.autoconfigure.bytaskqueue.TestActivityImpl", + info.getRegisteredActivityInfo().get(0).getClassName()); + + // @NexusServiceImpl bean should also be registered + assertFalse( + info.getRegisteredNexusServiceInfos().isEmpty(), + "@NexusServiceImpl beans should be auto-registered without workflow packages"); + assertEquals(1, info.getRegisteredNexusServiceInfos().size()); + }); + } + + @ComponentScan( + excludeFilters = + @ComponentScan.Filter( + pattern = "io\\.temporal\\.spring\\.boot\\.autoconfigure\\.byworkername\\..*", + type = FilterType.REGEX)) + public static class Configuration {} +} diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryByTaskQueueLegacyTest.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryByTaskQueueLegacyTest.java new file mode 100644 index 000000000..3c790289c --- /dev/null +++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryByTaskQueueLegacyTest.java @@ -0,0 +1,58 @@ +package io.temporal.spring.boot.autoconfigure; + +import io.temporal.api.nexus.v1.Endpoint; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.spring.boot.autoconfigure.bytaskqueue.TestWorkflow; +import io.temporal.testing.TestWorkflowEnvironment; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.test.context.ActiveProfiles; + +/** + * Verifies that the deprecated {@code workers-auto-discovery.packages} property still correctly + * registers workflows, activities, and nexus services (backward compatibility). + */ +@SpringBootTest(classes = AutoDiscoveryByTaskQueueLegacyTest.Configuration.class) +@ActiveProfiles(profiles = "auto-discovery-by-task-queue-legacy") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class AutoDiscoveryByTaskQueueLegacyTest { + @Autowired ConfigurableApplicationContext applicationContext; + + @Autowired TestWorkflowEnvironment testWorkflowEnvironment; + + @Autowired WorkflowClient workflowClient; + Endpoint endpoint; + + @BeforeEach + void setUp() { + applicationContext.start(); + endpoint = + testWorkflowEnvironment.createNexusEndpoint("AutoDiscoveryByTaskQueueEndpoint", "UnitTest"); + } + + @AfterEach + void tearDown() { + testWorkflowEnvironment.deleteNexusEndpoint(endpoint); + } + + @Test + @Timeout(value = 10) + public void testAutoDiscovery() { + TestWorkflow testWorkflow = + workflowClient.newWorkflowStub( + TestWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue("UnitTest").build()); + testWorkflow.execute("nexus"); + } + + @ComponentScan( + excludeFilters = + @ComponentScan.Filter( + pattern = "io\\.temporal\\.spring\\.boot\\.autoconfigure\\.byworkername\\..*", + type = FilterType.REGEX)) + public static class Configuration {} +} diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryEnableTest.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryEnableTest.java new file mode 100644 index 000000000..61830e531 --- /dev/null +++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryEnableTest.java @@ -0,0 +1,75 @@ +package io.temporal.spring.boot.autoconfigure; + +import static org.junit.jupiter.api.Assertions.*; + +import io.temporal.spring.boot.autoconfigure.template.WorkersTemplate; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.test.context.ActiveProfiles; + +/** + * Verifies that {@code workers-auto-discovery.enabled: true} auto-registers {@code @WorkflowImpl} + * and {@code @ActivityImpl} Spring beans without any package scanning configured. + */ +@SpringBootTest(classes = AutoDiscoveryEnableTest.Configuration.class) +@ActiveProfiles(profiles = "auto-discovery-enable") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class AutoDiscoveryEnableTest { + + @Autowired ConfigurableApplicationContext applicationContext; + + @Autowired private WorkersTemplate workersTemplate; + + @BeforeEach + void setUp() { + applicationContext.start(); + } + + @Test + @Timeout(value = 10) + public void testEnableRegistersWorkflowAndActivityBeans() { + assertNotNull(workersTemplate); + Map registeredInfoMap = + workersTemplate.getRegisteredInfo(); + + assertEquals(1, registeredInfoMap.size()); + WorkersTemplate.RegisteredInfo info = registeredInfoMap.get("UnitTest"); + assertNotNull(info, "Expected worker on task queue 'UnitTest'"); + + // @WorkflowImpl Spring bean registered via enabled: true (no packages configured) + assertEquals( + 1, + info.getRegisteredWorkflowInfo().size(), + "@WorkflowImpl bean should be registered via enabled: true without packages"); + assertEquals( + "io.temporal.spring.boot.autoconfigure.byenable.TestWorkflow", + info.getRegisteredWorkflowInfo().get(0).getClassName()); + + // @ActivityImpl bean registered via enabled: true + assertEquals( + 1, + info.getRegisteredActivityInfo().size(), + "@ActivityImpl bean should be registered via enabled: true"); + assertEquals( + "io.temporal.spring.boot.autoconfigure.byenable.TestActivityImpl", + info.getRegisteredActivityInfo().get(0).getClassName()); + } + + // Exclude all test sub-packages except byenable to avoid interference from other test beans + @ComponentScan( + excludeFilters = + @ComponentScan.Filter( + pattern = + "io\\.temporal\\.spring\\.boot\\.autoconfigure\\." + + "(bytaskqueue|byworkername|workerversioning)\\..*", + type = FilterType.REGEX)) + public static class Configuration {} +} diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryMixedPropertiesRejectedTest.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryMixedPropertiesRejectedTest.java new file mode 100644 index 000000000..813855a71 --- /dev/null +++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/AutoDiscoveryMixedPropertiesRejectedTest.java @@ -0,0 +1,47 @@ +package io.temporal.spring.boot.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; + +public class AutoDiscoveryMixedPropertiesRejectedTest { + + @Test + void testPackagesMixedWithNewPropertiesIsRejected() { + assertThatThrownBy( + () -> + new SpringApplicationBuilder(Configuration.class) + .profiles("auto-discovery-mixed-packages-rejected") + .run() + .close()) + .satisfies( + t -> { + // Our IllegalStateException is wrapped by Spring's binding/condition machinery; + // walk the cause chain to find it. + Throwable cause = t; + while (cause != null) { + if (cause.getMessage() != null + && cause + .getMessage() + .contains("packages is deprecated and cannot be combined")) { + return; + } + cause = cause.getCause(); + } + throw new AssertionError( + "Expected cause chain to contain 'packages is deprecated and cannot be combined'," + + " but it did not. Top-level message: " + + t.getMessage()); + }); + } + + @ComponentScan( + excludeFilters = + @ComponentScan.Filter( + pattern = "io\\.temporal\\.spring\\.boot\\.autoconfigure\\.by.*", + type = FilterType.REGEX)) + public static class Configuration {} +} diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestActivity.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestActivity.java new file mode 100644 index 000000000..636920b0d --- /dev/null +++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestActivity.java @@ -0,0 +1,8 @@ +package io.temporal.spring.boot.autoconfigure.byenable; + +import io.temporal.activity.ActivityInterface; + +@ActivityInterface +public interface TestActivity { + String execute(String input); +} diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestActivityImpl.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestActivityImpl.java new file mode 100644 index 000000000..47baee03a --- /dev/null +++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestActivityImpl.java @@ -0,0 +1,15 @@ +package io.temporal.spring.boot.autoconfigure.byenable; + +import io.temporal.spring.boot.ActivityImpl; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component +@Profile("auto-discovery-enable") +@ActivityImpl(workers = "mainWorker") +public class TestActivityImpl implements TestActivity { + @Override + public String execute(String input) { + return input; + } +} diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestWorkflow.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestWorkflow.java new file mode 100644 index 000000000..9095f9a78 --- /dev/null +++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestWorkflow.java @@ -0,0 +1,10 @@ +package io.temporal.spring.boot.autoconfigure.byenable; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface TestWorkflow { + @WorkflowMethod + String execute(String input); +} diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestWorkflowImpl.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestWorkflowImpl.java new file mode 100644 index 000000000..646f6d5c2 --- /dev/null +++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/byenable/TestWorkflowImpl.java @@ -0,0 +1,28 @@ +package io.temporal.spring.boot.autoconfigure.byenable; + +import io.temporal.activity.ActivityOptions; +import io.temporal.spring.boot.WorkflowImpl; +import io.temporal.workflow.Workflow; +import java.time.Duration; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +/** + * A workflow implementation that is a Spring bean (has {@code @Component}), used to verify that + * {@code workers-auto-discovery.enabled: true} can discover workflow beans without classpath + * scanning. + */ +@Component +@Profile("auto-discovery-enable") +@WorkflowImpl(workers = "mainWorker") +public class TestWorkflowImpl implements TestWorkflow { + @Override + public String execute(String input) { + return Workflow.newActivityStub( + TestActivity.class, + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(1)) + .validateAndBuildWithDefaults()) + .execute(input); + } +} diff --git a/temporal-spring-boot-autoconfigure/src/test/resources/application.yml b/temporal-spring-boot-autoconfigure/src/test/resources/application.yml index bf0260fed..d33d50b46 100644 --- a/temporal-spring-boot-autoconfigure/src/test/resources/application.yml +++ b/temporal-spring-boot-autoconfigure/src/test/resources/application.yml @@ -28,21 +28,69 @@ spring.temporal: spring: config: activate: - on-profile: auto-discovery-by-task-queue + on-profile: auto-discovery-mixed-packages-rejected temporal: workers-auto-discovery: + # packages is deprecated and cannot be combined with register-activity-beans. This should fail. packages: - io.temporal.spring.boot.autoconfigure.bytaskqueue + register-activity-beans: true --- spring: config: activate: - on-profile: auto-discovery-by-task-queue-dynamic-suffix + on-profile: auto-discovery-enable + temporal: + workers: + - task-queue: UnitTest + name: mainWorker + workers-auto-discovery: + enabled: true + +--- +spring: + config: + activate: + on-profile: auto-discovery-activities-only + temporal: + workers-auto-discovery: + register-activity-beans: true + register-nexus-service-beans: true + +--- +spring: + config: + activate: + on-profile: auto-discovery-by-task-queue-legacy temporal: workers-auto-discovery: packages: - io.temporal.spring.boot.autoconfigure.bytaskqueue + +--- +spring: + config: + activate: + on-profile: auto-discovery-by-task-queue + temporal: + workers-auto-discovery: + workflow-packages: + - io.temporal.spring.boot.autoconfigure.bytaskqueue + register-activity-beans: true + register-nexus-service-beans: true + +--- +spring: + config: + activate: + on-profile: auto-discovery-by-task-queue-dynamic-suffix + temporal: + workers-auto-discovery: + workflow-packages: + - io.temporal.spring.boot.autoconfigure.bytaskqueue + register-activity-beans: true + register-nexus-service-beans: true default-queue: name: PropertyResolverTest @@ -56,8 +104,10 @@ spring: - task-queue: UnitTest name: mainWorker workers-auto-discovery: - packages: + workflow-packages: - io.temporal.spring.boot.autoconfigure.byworkername + register-activity-beans: true + register-nexus-service-beans: true --- spring: @@ -69,8 +119,9 @@ spring: - task-queue: UnitTest name: mainWorker workers-auto-discovery: - packages: + workflow-packages: - io.temporal.spring.boot.autoconfigure.byworkername + register-activity-beans: true --- spring: @@ -86,7 +137,7 @@ spring: activity-beans: - TestActivityImpl workers-auto-discovery: - packages: + workflow-packages: - io.temporal.spring.boot.autoconfigure.byworkername --- @@ -142,7 +193,7 @@ spring: on-profile: disable-start-workers temporal: workers-auto-discovery: - packages: + workflow-packages: - io.temporal.spring.boot.autoconfigure.bytaskqueue start-workers: false @@ -185,7 +236,7 @@ spring: test-server: enabled: false workers-auto-discovery: - packages: + workflow-packages: - io.temporal.spring.boot.autoconfigure.workerversioning workers: - task-queue: UnitTest @@ -204,7 +255,7 @@ spring: temporal: namespace: UnitTest workers-auto-discovery: - packages: + workflow-packages: - io.temporal.spring.boot.autoconfigure.workerversioning workers: - task-queue: UnitTest @@ -213,6 +264,7 @@ spring: # missing default is the key thing here deployment-version: "dname.bid" use-versioning: true + --- spring: config: @@ -221,7 +273,7 @@ spring: temporal: namespace: UnitTest workers-auto-discovery: - packages: + workflow-packages: - io.temporal.spring.boot.autoconfigure.workerversioning workers: - task-queue: UnitTest @@ -230,6 +282,7 @@ spring: use-versioning: true default-versioning-behavior: PINNED deployment-name: "dname" + --- spring: config: @@ -238,7 +291,7 @@ spring: temporal: namespace: UnitTest workers-auto-discovery: - packages: + workflow-packages: - io.temporal.spring.boot.autoconfigure.workerversioning workers: - task-queue: UnitTest