diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/AutoConfigurationUtils.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/AutoConfigurationUtils.java
index 03bcbe8f1..37a7b5ce7 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/AutoConfigurationUtils.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/AutoConfigurationUtils.java
@@ -168,4 +168,47 @@ static String temporalCustomizerBeanName(String beanPrefix, Class> optionsBuil
bindingCustomizerName.substring(bindingCustomizerName.lastIndexOf(".") + 1);
return beanPrefix + bindingCustomizerName;
}
+
+ /**
+ * Filter out plugins that implement a higher-level plugin interface, as those are handled at that
+ * higher level via propagation.
+ *
+ *
The plugin hierarchy is: WorkflowServiceStubsPlugin -> WorkflowClientPlugin -> WorkerPlugin.
+ * A plugin implementing a higher-level interface will be registered at that level and propagate
+ * down automatically, so it should not also be registered at lower levels.
+ *
+ * @param plugins the list of plugins to filter (may be null)
+ * @param excludeType the plugin interface to exclude
+ * @return the filtered list, or null if input was null or all plugins were filtered out
+ */
+ static @Nullable List filterPlugins(@Nullable List plugins, Class> excludeType) {
+ if (plugins == null) {
+ return null;
+ }
+ if (plugins.isEmpty()) {
+ return null;
+ }
+ List filtered = new ArrayList<>();
+ for (T plugin : plugins) {
+ if (!excludeType.isInstance(plugin)) {
+ filtered.add(plugin);
+ }
+ }
+ return filtered.isEmpty() ? null : filtered;
+ }
+
+ /**
+ * Sort plugins by @Order and @Priority annotations for consistent ordering.
+ *
+ * @param plugins the list of plugins to sort (may be null)
+ * @return the sorted list, or null if input was null
+ */
+ static @Nullable List sortPlugins(@Nullable List plugins) {
+ if (plugins == null || plugins.isEmpty()) {
+ return plugins;
+ }
+ List sorted = new ArrayList<>(plugins);
+ AnnotationAwareOrderComparator.sort(sorted);
+ return sorted;
+ }
}
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/NonRootBeanPostProcessor.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/NonRootBeanPostProcessor.java
index a0079036c..674ec7b15 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/NonRootBeanPostProcessor.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/NonRootBeanPostProcessor.java
@@ -5,11 +5,14 @@
import io.opentracing.Tracer;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
+import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClient;
import io.temporal.client.schedules.ScheduleClientOptions;
+import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
+import io.temporal.serviceclient.WorkflowServiceStubsPlugin;
import io.temporal.spring.boot.TemporalOptionsCustomizer;
import io.temporal.spring.boot.autoconfigure.properties.ConnectionProperties;
import io.temporal.spring.boot.autoconfigure.properties.NonRootNamespaceProperties;
@@ -23,7 +26,9 @@
import io.temporal.worker.WorkerFactory;
import io.temporal.worker.WorkerFactoryOptions.Builder;
import io.temporal.worker.WorkerOptions;
+import io.temporal.worker.WorkerPlugin;
import io.temporal.worker.WorkflowImplementationOptions;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -50,6 +55,10 @@ public class NonRootBeanPostProcessor implements BeanPostProcessor, BeanFactoryA
private @Nullable Tracer tracer;
private @Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment;
private @Nullable Scope metricsScope;
+ private @Nullable List serviceStubsPlugins;
+ private @Nullable List workflowClientPlugins;
+ private @Nullable List scheduleClientPlugins;
+ private @Nullable List workerPlugins;
public NonRootBeanPostProcessor(@Nonnull TemporalProperties temporalProperties) {
this.temporalProperties = temporalProperties;
@@ -78,6 +87,20 @@ public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull Stri
findBean(
"temporalTestWorkflowEnvironmentAdapter", TestWorkflowEnvironmentAdapter.class);
}
+ // Collect all plugin types
+ serviceStubsPlugins = findAllBeans(WorkflowServiceStubsPlugin.class);
+ // Filter plugins so each is only registered at its highest applicable level
+ workflowClientPlugins =
+ AutoConfigurationUtils.filterPlugins(
+ findAllBeans(WorkflowClientPlugin.class), WorkflowServiceStubsPlugin.class);
+ scheduleClientPlugins =
+ AutoConfigurationUtils.filterPlugins(
+ findAllBeans(ScheduleClientPlugin.class), WorkflowServiceStubsPlugin.class);
+ workerPlugins =
+ AutoConfigurationUtils.filterPlugins(
+ AutoConfigurationUtils.filterPlugins(
+ findAllBeans(WorkerPlugin.class), WorkflowServiceStubsPlugin.class),
+ WorkflowClientPlugin.class);
namespaceProperties.forEach(this::injectBeanByNonRootNamespace);
}
}
@@ -124,7 +147,8 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
connectionProperties,
metricsScope,
testWorkflowEnvironment,
- workflowServiceStubsCustomizers);
+ workflowServiceStubsCustomizers,
+ serviceStubsPlugins);
WorkflowServiceStubs workflowServiceStubs = serviceStubsTemplate.getWorkflowServiceStubs();
NonRootNamespaceTemplate namespaceTemplate =
@@ -142,7 +166,10 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
workerCustomizers,
workflowClientCustomizers,
scheduleClientCustomizers,
- workflowImplementationCustomizers);
+ workflowImplementationCustomizers,
+ workflowClientPlugins,
+ scheduleClientPlugins,
+ workerPlugins);
ClientTemplate clientTemplate = namespaceTemplate.getClientTemplate();
WorkflowClient workflowClient = clientTemplate.getWorkflowClient();
@@ -200,6 +227,16 @@ private T findBeanByNamespace(String beanPrefix, Class clazz) {
return null;
}
+ private @Nullable List findAllBeans(Class clazz) {
+ try {
+ List beans = new ArrayList<>(beanFactory.getBeansOfType(clazz).values());
+ return AutoConfigurationUtils.sortPlugins(beans);
+ } catch (NoSuchBeanDefinitionException ignore) {
+ // No beans of this type defined - this is expected for optional plugins
+ }
+ return null;
+ }
+
private List> findBeanByNameSpaceForTemporalCustomizer(
String beanPrefix, Class genericOptionsBuilderClass) {
String beanName =
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/RootNamespaceAutoConfiguration.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/RootNamespaceAutoConfiguration.java
index 718fd6445..c5cd2a492 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/RootNamespaceAutoConfiguration.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/RootNamespaceAutoConfiguration.java
@@ -3,13 +3,16 @@
import io.opentracing.Tracer;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
+import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClient;
import io.temporal.client.schedules.ScheduleClientOptions;
+import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkerInterceptor;
import io.temporal.common.interceptors.WorkflowClientInterceptor;
import io.temporal.serviceclient.WorkflowServiceStubs;
+import io.temporal.serviceclient.WorkflowServiceStubsPlugin;
import io.temporal.spring.boot.TemporalOptionsCustomizer;
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
import io.temporal.spring.boot.autoconfigure.template.ClientTemplate;
@@ -20,6 +23,7 @@
import io.temporal.worker.WorkerFactory;
import io.temporal.worker.WorkerFactoryOptions;
import io.temporal.worker.WorkerOptions;
+import io.temporal.worker.WorkerPlugin;
import io.temporal.worker.WorkflowImplementationOptions;
import java.util.Collection;
import java.util.List;
@@ -87,7 +91,10 @@ public NamespaceTemplate rootNamespaceTemplate(
scheduleCustomizerMap,
@Autowired(required = false) @Nullable
Map>
- workflowImplementationCustomizerMap) {
+ workflowImplementationCustomizerMap,
+ @Autowired(required = false) @Nullable List workflowClientPlugins,
+ @Autowired(required = false) @Nullable List scheduleClientPlugins,
+ @Autowired(required = false) @Nullable List workerPlugins) {
DataConverter chosenDataConverter =
AutoConfigurationUtils.chooseDataConverter(dataConverters, mainDataConverter, properties);
List chosenClientInterceptors =
@@ -121,6 +128,31 @@ public NamespaceTemplate rootNamespaceTemplate(
WorkflowImplementationOptions.Builder.class,
properties);
+ // Sort plugins by @Order/@Priority for consistent ordering
+ List sortedClientPlugins =
+ AutoConfigurationUtils.sortPlugins(workflowClientPlugins);
+ List sortedSchedulePlugins =
+ AutoConfigurationUtils.sortPlugins(scheduleClientPlugins);
+ List sortedWorkerPlugins = AutoConfigurationUtils.sortPlugins(workerPlugins);
+
+ // Filter plugins so each is only registered at its highest applicable level.
+ // WorkflowServiceStubsPlugin is handled at ServiceStubsAutoConfiguration level and propagates
+ // down.
+ // WorkflowClientPlugin (not WorkflowServiceStubsPlugin) is handled here and propagates to
+ // workers.
+ // ScheduleClientPlugin (not WorkflowServiceStubsPlugin) is handled here.
+ // WorkerPlugin (not WorkflowServiceStubsPlugin, not WorkflowClientPlugin) is handled here.
+ List filteredClientPlugins =
+ AutoConfigurationUtils.filterPlugins(sortedClientPlugins, WorkflowServiceStubsPlugin.class);
+ List filteredSchedulePlugins =
+ AutoConfigurationUtils.filterPlugins(
+ sortedSchedulePlugins, WorkflowServiceStubsPlugin.class);
+ List filteredWorkerPlugins =
+ AutoConfigurationUtils.filterPlugins(
+ AutoConfigurationUtils.filterPlugins(
+ sortedWorkerPlugins, WorkflowServiceStubsPlugin.class),
+ WorkflowClientPlugin.class);
+
return new NamespaceTemplate(
properties,
workflowServiceStubs,
@@ -134,7 +166,10 @@ public NamespaceTemplate rootNamespaceTemplate(
workerCustomizer,
clientCustomizer,
scheduleCustomizer,
- workflowImplementationCustomizer);
+ workflowImplementationCustomizer,
+ filteredClientPlugins,
+ filteredSchedulePlugins,
+ filteredWorkerPlugins);
}
/** Client */
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/ServiceStubsAutoConfiguration.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/ServiceStubsAutoConfiguration.java
index 761883df0..022cd7f9c 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/ServiceStubsAutoConfiguration.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/ServiceStubsAutoConfiguration.java
@@ -4,6 +4,7 @@
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import io.temporal.serviceclient.WorkflowServiceStubsOptions.Builder;
+import io.temporal.serviceclient.WorkflowServiceStubsPlugin;
import io.temporal.spring.boot.TemporalOptionsCustomizer;
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
import io.temporal.spring.boot.autoconfigure.template.ServiceStubsTemplate;
@@ -42,15 +43,19 @@ public ServiceStubsTemplate serviceStubsTemplate(
TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
@Autowired(required = false) @Nullable
Map>
- workflowServiceStubsCustomizerMap) {
+ workflowServiceStubsCustomizerMap,
+ @Autowired(required = false) @Nullable List plugins) {
List> workflowServiceStubsCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, workflowServiceStubsCustomizerMap, Builder.class, properties);
+ // Sort plugins by @Order/@Priority for consistent ordering
+ List sortedPlugins = AutoConfigurationUtils.sortPlugins(plugins);
return new ServiceStubsTemplate(
properties.getConnection(),
metricsScope,
testWorkflowEnvironment,
- workflowServiceStubsCustomizer);
+ workflowServiceStubsCustomizer,
+ sortedPlugins);
}
@Bean(name = "temporalWorkflowServiceStubs")
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/TestServerAutoConfiguration.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/TestServerAutoConfiguration.java
index c372b797f..ab21591a7 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/TestServerAutoConfiguration.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/TestServerAutoConfiguration.java
@@ -3,11 +3,14 @@
import com.uber.m3.tally.Scope;
import io.opentracing.Tracer;
import io.temporal.client.WorkflowClientOptions;
+import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClientOptions;
+import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkerInterceptor;
import io.temporal.common.interceptors.WorkflowClientInterceptor;
+import io.temporal.serviceclient.WorkflowServiceStubsPlugin;
import io.temporal.spring.boot.TemporalOptionsCustomizer;
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
import io.temporal.spring.boot.autoconfigure.template.TestWorkflowEnvironmentAdapter;
@@ -16,6 +19,7 @@
import io.temporal.testing.TestEnvironmentOptions;
import io.temporal.testing.TestWorkflowEnvironment;
import io.temporal.worker.WorkerFactoryOptions;
+import io.temporal.worker.WorkerPlugin;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -79,7 +83,10 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
Map> clientCustomizerMap,
@Autowired(required = false) @Nullable
Map>
- scheduleCustomizerMap) {
+ scheduleCustomizerMap,
+ @Autowired(required = false) @Nullable List workflowClientPlugins,
+ @Autowired(required = false) @Nullable List scheduleClientPlugins,
+ @Autowired(required = false) @Nullable List workerPlugins) {
DataConverter chosenDataConverter =
AutoConfigurationUtils.chooseDataConverter(dataConverters, mainDataConverter, properties);
List chosenClientInterceptors =
@@ -104,6 +111,35 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
+ // Sort plugins by @Order/@Priority for consistent ordering
+ List sortedClientPlugins =
+ AutoConfigurationUtils.sortPlugins(workflowClientPlugins);
+ List sortedSchedulePlugins =
+ AutoConfigurationUtils.sortPlugins(scheduleClientPlugins);
+ List sortedWorkerPlugins = AutoConfigurationUtils.sortPlugins(workerPlugins);
+
+ // Filter plugins so each is only registered at its highest applicable level.
+ // Note: TestWorkflowEnvironment creates its own internal test server and does not accept
+ // WorkflowServiceStubsPlugin directly. Any beans implementing WorkflowServiceStubsPlugin
+ // will be ignored in test mode - only their lower-level plugin functionality (if any) is used.
+ if (sortedClientPlugins != null
+ && sortedClientPlugins.stream().anyMatch(WorkflowServiceStubsPlugin.class::isInstance)) {
+ log.warn(
+ "WorkflowServiceStubsPlugin beans are present but will be ignored in test mode. "
+ + "TestWorkflowEnvironment creates its own test server and does not support "
+ + "WorkflowServiceStubsPlugin. Only WorkflowClientPlugin functionality will be used.");
+ }
+ List filteredClientPlugins =
+ AutoConfigurationUtils.filterPlugins(sortedClientPlugins, WorkflowServiceStubsPlugin.class);
+ List filteredSchedulePlugins =
+ AutoConfigurationUtils.filterPlugins(
+ sortedSchedulePlugins, WorkflowServiceStubsPlugin.class);
+ List filteredWorkerPlugins =
+ AutoConfigurationUtils.filterPlugins(
+ AutoConfigurationUtils.filterPlugins(
+ sortedWorkerPlugins, WorkflowServiceStubsPlugin.class),
+ WorkflowClientPlugin.class);
+
TestEnvironmentOptions.Builder options =
TestEnvironmentOptions.newBuilder()
.setWorkflowClientOptions(
@@ -114,7 +150,9 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
chosenScheduleClientInterceptors,
otTracer,
clientCustomizer,
- scheduleCustomizer)
+ scheduleCustomizer,
+ filteredClientPlugins,
+ filteredSchedulePlugins)
.createWorkflowClientOptions());
if (metricsScope != null) {
@@ -123,7 +161,11 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
options.setWorkerFactoryOptions(
new WorkerFactoryOptionsTemplate(
- properties, chosenWorkerInterceptors, otTracer, workerFactoryCustomizer)
+ properties,
+ chosenWorkerInterceptors,
+ otTracer,
+ workerFactoryCustomizer,
+ filteredWorkerPlugins)
.createWorkerFactoryOptions());
if (testEnvOptionsCustomizers != null) {
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ClientTemplate.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ClientTemplate.java
index f117fbc33..c9f68cb62 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ClientTemplate.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ClientTemplate.java
@@ -4,8 +4,10 @@
import io.opentracing.Tracer;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
+import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClient;
import io.temporal.client.schedules.ScheduleClientOptions;
+import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkflowClientInterceptor;
@@ -33,8 +35,9 @@ public ClientTemplate(
@Nullable WorkflowServiceStubs workflowServiceStubs,
@Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
@Nullable List> clientCustomizers,
- @Nullable
- List> scheduleCustomizers) {
+ @Nullable List> scheduleCustomizers,
+ @Nullable List workflowClientPlugins,
+ @Nullable List scheduleClientPlugins) {
this.optionsTemplate =
new WorkflowClientOptionsTemplate(
namespace,
@@ -43,11 +46,42 @@ public ClientTemplate(
scheduleClientInterceptors,
tracer,
clientCustomizers,
- scheduleCustomizers);
+ scheduleCustomizers,
+ workflowClientPlugins,
+ scheduleClientPlugins);
this.workflowServiceStubs = workflowServiceStubs;
this.testWorkflowEnvironment = testWorkflowEnvironment;
}
+ /**
+ * @deprecated Use constructor with plugins parameters
+ */
+ @Deprecated
+ public ClientTemplate(
+ @Nonnull String namespace,
+ @Nullable DataConverter dataConverter,
+ @Nullable List workflowClientInterceptors,
+ @Nullable List scheduleClientInterceptors,
+ @Nullable Tracer tracer,
+ @Nullable WorkflowServiceStubs workflowServiceStubs,
+ @Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
+ @Nullable List> clientCustomizers,
+ @Nullable
+ List> scheduleCustomizers) {
+ this(
+ namespace,
+ dataConverter,
+ workflowClientInterceptors,
+ scheduleClientInterceptors,
+ tracer,
+ workflowServiceStubs,
+ testWorkflowEnvironment,
+ clientCustomizers,
+ scheduleCustomizers,
+ null,
+ null);
+ }
+
public WorkflowClient getWorkflowClient() {
if (workflowClient == null) {
this.workflowClient = createWorkflowClient();
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/NamespaceTemplate.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/NamespaceTemplate.java
index eef891870..d80c69955 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/NamespaceTemplate.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/NamespaceTemplate.java
@@ -2,7 +2,9 @@
import io.opentracing.Tracer;
import io.temporal.client.WorkflowClientOptions;
+import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClientOptions;
+import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkerInterceptor;
@@ -12,6 +14,7 @@
import io.temporal.spring.boot.autoconfigure.properties.NamespaceProperties;
import io.temporal.worker.WorkerFactoryOptions;
import io.temporal.worker.WorkerOptions;
+import io.temporal.worker.WorkerPlugin;
import io.temporal.worker.WorkflowImplementationOptions;
import java.util.List;
import javax.annotation.Nonnull;
@@ -36,6 +39,9 @@ public class NamespaceTemplate {
scheduleCustomizers;
private final @Nullable List>
workflowImplementationCustomizers;
+ private final @Nullable List workflowClientPlugins;
+ private final @Nullable List scheduleClientPlugins;
+ private final @Nullable List workerPlugins;
private ClientTemplate clientTemplate;
private WorkersTemplate workersTemplate;
@@ -56,7 +62,10 @@ public NamespaceTemplate(
@Nullable List> scheduleCustomizers,
@Nullable
List>
- workflowImplementationCustomizers) {
+ workflowImplementationCustomizers,
+ @Nullable List workflowClientPlugins,
+ @Nullable List scheduleClientPlugins,
+ @Nullable List workerPlugins) {
this.namespaceProperties = namespaceProperties;
this.workflowServiceStubs = workflowServiceStubs;
this.dataConverter = dataConverter;
@@ -71,6 +80,49 @@ public NamespaceTemplate(
this.clientCustomizers = clientCustomizers;
this.scheduleCustomizers = scheduleCustomizers;
this.workflowImplementationCustomizers = workflowImplementationCustomizers;
+ this.workflowClientPlugins = workflowClientPlugins;
+ this.scheduleClientPlugins = scheduleClientPlugins;
+ this.workerPlugins = workerPlugins;
+ }
+
+ /**
+ * @deprecated Use constructor with plugins parameters
+ */
+ @Deprecated
+ public NamespaceTemplate(
+ @Nonnull NamespaceProperties namespaceProperties,
+ @Nonnull WorkflowServiceStubs workflowServiceStubs,
+ @Nullable DataConverter dataConverter,
+ @Nullable List workflowClientInterceptors,
+ @Nullable List scheduleClientInterceptors,
+ @Nullable List workerInterceptors,
+ @Nullable Tracer tracer,
+ @Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
+ @Nullable
+ List> workerFactoryCustomizers,
+ @Nullable List> workerCustomizers,
+ @Nullable List> clientCustomizers,
+ @Nullable List> scheduleCustomizers,
+ @Nullable
+ List>
+ workflowImplementationCustomizers) {
+ this(
+ namespaceProperties,
+ workflowServiceStubs,
+ dataConverter,
+ workflowClientInterceptors,
+ scheduleClientInterceptors,
+ workerInterceptors,
+ tracer,
+ testWorkflowEnvironment,
+ workerFactoryCustomizers,
+ workerCustomizers,
+ clientCustomizers,
+ scheduleCustomizers,
+ workflowImplementationCustomizers,
+ null,
+ null,
+ null);
}
public ClientTemplate getClientTemplate() {
@@ -85,7 +137,9 @@ public ClientTemplate getClientTemplate() {
workflowServiceStubs,
testWorkflowEnvironment,
clientCustomizers,
- scheduleCustomizers);
+ scheduleCustomizers,
+ workflowClientPlugins,
+ scheduleClientPlugins);
}
return clientTemplate;
}
@@ -101,7 +155,8 @@ public WorkersTemplate getWorkersTemplate() {
testWorkflowEnvironment,
workerFactoryCustomizers,
workerCustomizers,
- workflowImplementationCustomizers);
+ workflowImplementationCustomizers,
+ workerPlugins);
}
return this.workersTemplate;
}
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/NonRootNamespaceTemplate.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/NonRootNamespaceTemplate.java
index f2292e385..7f62bf97b 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/NonRootNamespaceTemplate.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/NonRootNamespaceTemplate.java
@@ -2,7 +2,9 @@
import io.opentracing.Tracer;
import io.temporal.client.WorkflowClientOptions;
+import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClientOptions;
+import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkerInterceptor;
@@ -12,6 +14,7 @@
import io.temporal.spring.boot.autoconfigure.properties.NonRootNamespaceProperties;
import io.temporal.worker.WorkerFactoryOptions;
import io.temporal.worker.WorkerOptions;
+import io.temporal.worker.WorkerPlugin;
import io.temporal.worker.WorkflowImplementationOptions;
import java.util.List;
import javax.annotation.Nonnull;
@@ -40,7 +43,10 @@ public NonRootNamespaceTemplate(
@Nullable List> scheduleCustomizers,
@Nullable
List>
- workflowImplementationCustomizers) {
+ workflowImplementationCustomizers,
+ @Nullable List workflowClientPlugins,
+ @Nullable List scheduleClientPlugins,
+ @Nullable List workerPlugins) {
super(
namespaceProperties,
workflowServiceStubs,
@@ -54,10 +60,55 @@ public NonRootNamespaceTemplate(
workerCustomizers,
clientCustomizers,
scheduleCustomizers,
- workflowImplementationCustomizers);
+ workflowImplementationCustomizers,
+ workflowClientPlugins,
+ scheduleClientPlugins,
+ workerPlugins);
this.beanFactory = beanFactory;
}
+ /**
+ * @deprecated Use constructor with plugins parameters
+ */
+ @Deprecated
+ public NonRootNamespaceTemplate(
+ @Nonnull BeanFactory beanFactory,
+ @Nonnull NonRootNamespaceProperties namespaceProperties,
+ @Nonnull WorkflowServiceStubs workflowServiceStubs,
+ @Nullable DataConverter dataConverter,
+ @Nullable List workflowClientInterceptors,
+ @Nullable List scheduleClientInterceptors,
+ @Nullable List workerInterceptors,
+ @Nullable Tracer tracer,
+ @Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
+ @Nullable
+ List> workerFactoryCustomizers,
+ @Nullable List> workerCustomizers,
+ @Nullable List> clientCustomizers,
+ @Nullable List> scheduleCustomizers,
+ @Nullable
+ List>
+ workflowImplementationCustomizers) {
+ this(
+ beanFactory,
+ namespaceProperties,
+ workflowServiceStubs,
+ dataConverter,
+ workflowClientInterceptors,
+ scheduleClientInterceptors,
+ workerInterceptors,
+ tracer,
+ testWorkflowEnvironment,
+ workerFactoryCustomizers,
+ workerCustomizers,
+ clientCustomizers,
+ scheduleCustomizers,
+ workflowImplementationCustomizers,
+ null,
+ null,
+ null);
+ }
+
@Override
public WorkersTemplate getWorkersTemplate() {
WorkersTemplate workersTemplate = super.getWorkersTemplate();
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ServiceStubOptionsTemplate.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ServiceStubOptionsTemplate.java
index 684faac63..dbcd8ebb2 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ServiceStubOptionsTemplate.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ServiceStubOptionsTemplate.java
@@ -5,6 +5,7 @@
import io.temporal.internal.common.ShadingHelpers;
import io.temporal.serviceclient.SimpleSslContextBuilder;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
+import io.temporal.serviceclient.WorkflowServiceStubsPlugin;
import io.temporal.spring.boot.TemporalOptionsCustomizer;
import io.temporal.spring.boot.autoconfigure.properties.ConnectionProperties;
import java.io.ByteArrayInputStream;
@@ -24,16 +25,32 @@ public class ServiceStubOptionsTemplate {
private final @Nullable Scope metricsScope;
private final @Nullable List>
workflowServiceStubsCustomizer;
+ private final @Nullable List plugins;
public ServiceStubOptionsTemplate(
@Nonnull ConnectionProperties connectionProperties,
@Nullable Scope metricsScope,
@Nullable
List>
- workflowServiceStubsCustomizer) {
+ workflowServiceStubsCustomizer,
+ @Nullable List plugins) {
this.connectionProperties = connectionProperties;
this.metricsScope = metricsScope;
this.workflowServiceStubsCustomizer = workflowServiceStubsCustomizer;
+ this.plugins = plugins;
+ }
+
+ /**
+ * @deprecated Use constructor with plugins parameter
+ */
+ @Deprecated
+ public ServiceStubOptionsTemplate(
+ @Nonnull ConnectionProperties connectionProperties,
+ @Nullable Scope metricsScope,
+ @Nullable
+ List>
+ workflowServiceStubsCustomizer) {
+ this(connectionProperties, metricsScope, workflowServiceStubsCustomizer, null);
}
public WorkflowServiceStubsOptions createServiceStubOptions() {
@@ -59,6 +76,10 @@ public WorkflowServiceStubsOptions createServiceStubOptions() {
stubsOptionsBuilder.setMetricsScope(metricsScope);
}
+ if (plugins != null && !plugins.isEmpty()) {
+ stubsOptionsBuilder.setPlugins(plugins.toArray(new WorkflowServiceStubsPlugin[0]));
+ }
+
if (workflowServiceStubsCustomizer != null) {
for (TemporalOptionsCustomizer
workflowServiceStubsCustomizer : workflowServiceStubsCustomizer) {
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ServiceStubsTemplate.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ServiceStubsTemplate.java
index 7d6597143..1db7c4b34 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ServiceStubsTemplate.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ServiceStubsTemplate.java
@@ -3,6 +3,7 @@
import com.uber.m3.tally.Scope;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
+import io.temporal.serviceclient.WorkflowServiceStubsPlugin;
import io.temporal.spring.boot.TemporalOptionsCustomizer;
import io.temporal.spring.boot.autoconfigure.properties.ConnectionProperties;
import java.util.List;
@@ -18,6 +19,7 @@ public class ServiceStubsTemplate {
private final @Nullable List>
workflowServiceStubsCustomizers;
+ private final @Nullable List plugins;
private WorkflowServiceStubs workflowServiceStubs;
@@ -27,11 +29,32 @@ public ServiceStubsTemplate(
@Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
@Nullable
List>
- workflowServiceStubsCustomizers) {
+ workflowServiceStubsCustomizers,
+ @Nullable List plugins) {
this.connectionProperties = connectionProperties;
this.metricsScope = metricsScope;
this.testWorkflowEnvironment = testWorkflowEnvironment;
this.workflowServiceStubsCustomizers = workflowServiceStubsCustomizers;
+ this.plugins = plugins;
+ }
+
+ /**
+ * @deprecated Use constructor with plugins parameter
+ */
+ @Deprecated
+ public ServiceStubsTemplate(
+ @Nonnull ConnectionProperties connectionProperties,
+ @Nullable Scope metricsScope,
+ @Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
+ @Nullable
+ List>
+ workflowServiceStubsCustomizers) {
+ this(
+ connectionProperties,
+ metricsScope,
+ testWorkflowEnvironment,
+ workflowServiceStubsCustomizers,
+ null);
}
public WorkflowServiceStubs getWorkflowServiceStubs() {
@@ -54,7 +77,10 @@ private WorkflowServiceStubs createServiceStubs() {
workflowServiceStubs =
WorkflowServiceStubs.newServiceStubs(
new ServiceStubOptionsTemplate(
- connectionProperties, metricsScope, workflowServiceStubsCustomizers)
+ connectionProperties,
+ metricsScope,
+ workflowServiceStubsCustomizers,
+ plugins)
.createServiceStubOptions());
}
}
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkerFactoryOptionsTemplate.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkerFactoryOptionsTemplate.java
index b7b44d44f..baa037402 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkerFactoryOptionsTemplate.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkerFactoryOptionsTemplate.java
@@ -7,6 +7,7 @@
import io.temporal.spring.boot.TemporalOptionsCustomizer;
import io.temporal.spring.boot.autoconfigure.properties.NamespaceProperties;
import io.temporal.worker.WorkerFactoryOptions;
+import io.temporal.worker.WorkerPlugin;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -18,16 +19,31 @@ public class WorkerFactoryOptionsTemplate {
private final @Nullable List workerInterceptors;
private final @Nullable Tracer tracer;
private final @Nullable List> customizer;
+ private final @Nullable List plugins;
public WorkerFactoryOptionsTemplate(
@Nonnull NamespaceProperties namespaceProperties,
@Nullable List workerInterceptors,
@Nullable Tracer tracer,
- @Nullable List> customizer) {
+ @Nullable List> customizer,
+ @Nullable List plugins) {
this.namespaceProperties = namespaceProperties;
this.workerInterceptors = workerInterceptors;
this.tracer = tracer;
this.customizer = customizer;
+ this.plugins = plugins;
+ }
+
+ /**
+ * @deprecated Use constructor with plugins parameter
+ */
+ @Deprecated
+ public WorkerFactoryOptionsTemplate(
+ @Nonnull NamespaceProperties namespaceProperties,
+ @Nullable List workerInterceptors,
+ @Nullable Tracer tracer,
+ @Nullable List> customizer) {
+ this(namespaceProperties, workerInterceptors, tracer, customizer, null);
}
public WorkerFactoryOptions createWorkerFactoryOptions() {
@@ -56,6 +72,10 @@ public WorkerFactoryOptions createWorkerFactoryOptions() {
}
options.setWorkerInterceptors(interceptors.toArray(new WorkerInterceptor[0]));
+ if (plugins != null && !plugins.isEmpty()) {
+ options.setPlugins(plugins.toArray(new WorkerPlugin[0]));
+ }
+
if (customizer != null) {
for (TemporalOptionsCustomizer customizer : customizer) {
options = customizer.customize(options);
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 870ede282..0192fd4bf 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
@@ -62,6 +62,7 @@ public class WorkersTemplate implements BeanFactoryAware, EnvironmentAware {
private final @Nullable List> workerCustomizers;
private final @Nullable List>
workflowImplementationCustomizers;
+ private final @Nullable List plugins;
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
@@ -81,7 +82,8 @@ public WorkersTemplate(
@Nullable List> workerCustomizers,
@Nullable
List>
- workflowImplementationCustomizers) {
+ workflowImplementationCustomizers,
+ @Nullable List plugins) {
this.namespaceProperties = namespaceProperties;
this.workerInterceptors = workerInterceptors;
this.tracer = tracer;
@@ -91,6 +93,35 @@ public WorkersTemplate(
this.workerFactoryCustomizers = workerFactoryCustomizers;
this.workerCustomizers = workerCustomizers;
this.workflowImplementationCustomizers = workflowImplementationCustomizers;
+ this.plugins = plugins;
+ }
+
+ /**
+ * @deprecated Use constructor with plugins parameter
+ */
+ @Deprecated
+ public WorkersTemplate(
+ @Nonnull NamespaceProperties namespaceProperties,
+ @Nullable ClientTemplate clientTemplate,
+ @Nullable List workerInterceptors,
+ @Nullable Tracer tracer,
+ @Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
+ @Nullable
+ List> workerFactoryCustomizers,
+ @Nullable List> workerCustomizers,
+ @Nullable
+ List>
+ workflowImplementationCustomizers) {
+ this(
+ namespaceProperties,
+ clientTemplate,
+ workerInterceptors,
+ tracer,
+ testWorkflowEnvironment,
+ workerFactoryCustomizers,
+ workerCustomizers,
+ workflowImplementationCustomizers,
+ null);
}
public NamespaceProperties getNamespaceProperties() {
@@ -126,7 +157,11 @@ WorkerFactory createWorkerFactory(WorkflowClient workflowClient) {
} else {
WorkerFactoryOptions workerFactoryOptions =
new WorkerFactoryOptionsTemplate(
- namespaceProperties, workerInterceptors, tracer, workerFactoryCustomizers)
+ namespaceProperties,
+ workerInterceptors,
+ tracer,
+ workerFactoryCustomizers,
+ plugins)
.createWorkerFactoryOptions();
return WorkerFactory.newInstance(workflowClient, workerFactoryOptions);
}
diff --git a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkflowClientOptionsTemplate.java b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkflowClientOptionsTemplate.java
index 02ef555d9..ca4e56457 100644
--- a/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkflowClientOptionsTemplate.java
+++ b/temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/WorkflowClientOptionsTemplate.java
@@ -2,7 +2,9 @@
import io.opentracing.Tracer;
import io.temporal.client.WorkflowClientOptions;
+import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClientOptions;
+import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkflowClientInterceptor;
@@ -25,6 +27,8 @@ public class WorkflowClientOptionsTemplate {
clientCustomizers;
private final @Nullable List>
scheduleCustomizers;
+ private final @Nullable List workflowClientPlugins;
+ private final @Nullable List scheduleClientPlugins;
public WorkflowClientOptionsTemplate(
@Nonnull String namespace,
@@ -33,8 +37,9 @@ public WorkflowClientOptionsTemplate(
@Nullable List scheduleClientInterceptors,
@Nullable Tracer tracer,
@Nullable List> clientCustomizers,
- @Nullable
- List> scheduleCustomizers) {
+ @Nullable List> scheduleCustomizers,
+ @Nullable List workflowClientPlugins,
+ @Nullable List scheduleClientPlugins) {
this.namespace = namespace;
this.dataConverter = dataConverter;
this.workflowClientInterceptors = workflowClientInterceptors;
@@ -42,6 +47,33 @@ public WorkflowClientOptionsTemplate(
this.tracer = tracer;
this.clientCustomizers = clientCustomizers;
this.scheduleCustomizers = scheduleCustomizers;
+ this.workflowClientPlugins = workflowClientPlugins;
+ this.scheduleClientPlugins = scheduleClientPlugins;
+ }
+
+ /**
+ * @deprecated Use constructor with plugins parameters
+ */
+ @Deprecated
+ public WorkflowClientOptionsTemplate(
+ @Nonnull String namespace,
+ @Nullable DataConverter dataConverter,
+ @Nullable List workflowClientInterceptors,
+ @Nullable List scheduleClientInterceptors,
+ @Nullable Tracer tracer,
+ @Nullable List> clientCustomizers,
+ @Nullable
+ List> scheduleCustomizers) {
+ this(
+ namespace,
+ dataConverter,
+ workflowClientInterceptors,
+ scheduleClientInterceptors,
+ tracer,
+ clientCustomizers,
+ scheduleCustomizers,
+ null,
+ null);
}
public WorkflowClientOptions createWorkflowClientOptions() {
@@ -62,6 +94,10 @@ public WorkflowClientOptions createWorkflowClientOptions() {
options.setInterceptors(interceptors.toArray(new WorkflowClientInterceptor[0]));
+ if (workflowClientPlugins != null && !workflowClientPlugins.isEmpty()) {
+ options.setPlugins(workflowClientPlugins.toArray(new WorkflowClientPlugin[0]));
+ }
+
if (clientCustomizers != null) {
for (TemporalOptionsCustomizer customizer :
clientCustomizers) {
@@ -79,6 +115,10 @@ public ScheduleClientOptions createScheduleClientOptions() {
options.setInterceptors(scheduleClientInterceptors);
}
+ if (scheduleClientPlugins != null && !scheduleClientPlugins.isEmpty()) {
+ options.setPlugins(scheduleClientPlugins.toArray(new ScheduleClientPlugin[0]));
+ }
+
if (scheduleCustomizers != null) {
for (TemporalOptionsCustomizer customizer :
scheduleCustomizers) {
diff --git a/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/PluginAutoWiringTest.java b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/PluginAutoWiringTest.java
new file mode 100644
index 000000000..5b9dc6515
--- /dev/null
+++ b/temporal-spring-boot-autoconfigure/src/test/java/io/temporal/spring/boot/autoconfigure/PluginAutoWiringTest.java
@@ -0,0 +1,415 @@
+package io.temporal.spring.boot.autoconfigure;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import io.temporal.client.WorkflowClient;
+import io.temporal.client.WorkflowClientOptions;
+import io.temporal.client.WorkflowClientPlugin;
+import io.temporal.client.schedules.ScheduleClientOptions;
+import io.temporal.client.schedules.ScheduleClientPlugin;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+import io.temporal.serviceclient.WorkflowServiceStubsOptions;
+import io.temporal.serviceclient.WorkflowServiceStubsPlugin;
+import io.temporal.worker.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import javax.annotation.Nonnull;
+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.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.core.annotation.Order;
+import org.springframework.test.context.ActiveProfiles;
+
+@SpringBootTest(classes = PluginAutoWiringTest.Configuration.class)
+@ActiveProfiles(profiles = "auto-discovery-by-task-queue")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class PluginAutoWiringTest {
+ @Autowired ConfigurableApplicationContext applicationContext;
+
+ @Autowired WorkflowClient workflowClient;
+
+ @Autowired WorkerFactory temporalWorkerFactory;
+
+ @BeforeEach
+ void setUp() {
+ applicationContext.start();
+ }
+
+ @Test
+ @Timeout(value = 10)
+ public void testPluginsAreAutoWired() {
+ // Verify that plugin beans are present in the application context
+ assertNotNull(applicationContext.getBean("clientPlugin"));
+ assertNotNull(applicationContext.getBean("workerPlugin"));
+ assertNotNull(applicationContext.getBean("comboPlugin"));
+ }
+
+ @Test
+ @Timeout(value = 10)
+ public void testClientPluginGetsCalled() {
+ // Verify client plugin got its configure method called by checking the recorded invocations
+ assertTrue(
+ Configuration.clientPluginInvocations.contains("configureWorkflowClient"),
+ "Client plugin configureWorkflowClient should have been called");
+ }
+
+ @Test
+ @Timeout(value = 10)
+ public void testWorkerPluginGetsCalled() {
+ // Verify worker plugin got its configure method called
+ assertTrue(
+ Configuration.workerPluginInvocations.contains("configureWorkerFactory"),
+ "Worker plugin configureWorkerFactory should have been called");
+ }
+
+ @Test
+ @Timeout(value = 10)
+ public void testPluginFiltering() {
+ // The combo plugin implements WorkflowServiceStubsPlugin, WorkflowClientPlugin,
+ // ScheduleClientPlugin,
+ // and WorkerPlugin. Because it implements WorkflowServiceStubsPlugin (the highest level), it
+ // gets
+ // filtered out from all lower levels (client, schedule, worker) to avoid double-registration.
+ //
+ // In normal (non-test) mode, the plugin would be registered at the stubs level and the SDK's
+ // plugin propagation system would call its lower-level methods.
+ //
+ // In test mode, WorkflowServiceStubsPlugin is not supported (TestWorkflowEnvironment creates
+ // its
+ // own server), so the combo plugin's configuration methods are NOT called at all.
+ //
+ // This verifies the filtering is working correctly:
+ // - Combo plugin should NOT have client/worker methods called directly (filtered out)
+ assertFalse(
+ Configuration.comboPluginInvocations.contains("configureWorkflowClient"),
+ "Combo plugin should be filtered out at client level (implements WorkflowServiceStubsPlugin)");
+ assertFalse(
+ Configuration.comboPluginInvocations.contains("configureWorkerFactory"),
+ "Combo plugin should be filtered out at worker level (implements WorkflowServiceStubsPlugin)");
+
+ // The regular (non-combo) client and worker plugins should still be called
+ assertTrue(
+ Configuration.clientPluginInvocations.contains("configureWorkflowClient"),
+ "Regular client plugin should still be called");
+ assertTrue(
+ Configuration.workerPluginInvocations.contains("configureWorkerFactory"),
+ "Regular worker plugin should still be called");
+ }
+
+ @Test
+ @Timeout(value = 10)
+ public void testPluginOrdering() {
+ // Verify that plugin ordering is applied (via @Order annotation)
+ // The ordered plugins should have their callbacks in the expected order
+ List record = Configuration.orderedPluginInvocations;
+
+ int firstIndex = record.indexOf("first");
+ int secondIndex = record.indexOf("second");
+ int thirdIndex = record.indexOf("third");
+
+ assertTrue(firstIndex >= 0, "First ordered plugin should have been called");
+ assertTrue(secondIndex >= 0, "Second ordered plugin should have been called");
+ assertTrue(thirdIndex >= 0, "Third ordered plugin should have been called");
+
+ assertTrue(firstIndex < secondIndex, "First should be called before second");
+ assertTrue(secondIndex < thirdIndex, "Second should be called before third");
+ }
+
+ @ComponentScan(
+ excludeFilters =
+ @ComponentScan.Filter(
+ pattern = "io\\.temporal\\.spring\\.boot\\.autoconfigure\\.byworkername\\..*",
+ type = FilterType.REGEX))
+ public static class Configuration {
+ // Track invocations for verification
+ static List clientPluginInvocations = Collections.synchronizedList(new ArrayList<>());
+ static List workerPluginInvocations = Collections.synchronizedList(new ArrayList<>());
+ static List comboPluginInvocations = Collections.synchronizedList(new ArrayList<>());
+ static List orderedPluginInvocations = Collections.synchronizedList(new ArrayList<>());
+
+ // WorkflowClientPlugin only - middle level
+ @Bean
+ public TestClientPlugin clientPlugin() {
+ return new TestClientPlugin(clientPluginInvocations);
+ }
+
+ // WorkerPlugin only - lowest level
+ @Bean
+ public TestWorkerPlugin workerPlugin() {
+ return new TestWorkerPlugin(workerPluginInvocations);
+ }
+
+ // Combo plugin implementing all interfaces - tests filtering behavior
+ @Bean
+ public TestComboPlugin comboPlugin() {
+ return new TestComboPlugin(comboPluginInvocations);
+ }
+
+ // Ordered plugins to test @Order support
+ @Bean
+ @Order(1)
+ public WorkerPlugin firstOrderedPlugin() {
+ return new OrderedWorkerPlugin("first", orderedPluginInvocations);
+ }
+
+ @Bean
+ @Order(2)
+ public WorkerPlugin secondOrderedPlugin() {
+ return new OrderedWorkerPlugin("second", orderedPluginInvocations);
+ }
+
+ @Bean
+ @Order(3)
+ public WorkerPlugin thirdOrderedPlugin() {
+ return new OrderedWorkerPlugin("third", orderedPluginInvocations);
+ }
+ }
+
+ public static class TestClientPlugin implements WorkflowClientPlugin {
+ private final List invocations;
+
+ public TestClientPlugin(List invocations) {
+ this.invocations = invocations;
+ }
+
+ @Nonnull
+ @Override
+ public String getName() {
+ return "test.client-plugin";
+ }
+
+ @Override
+ public void configureWorkflowClient(@Nonnull WorkflowClientOptions.Builder builder) {
+ invocations.add("configureWorkflowClient");
+ }
+ }
+
+ public static class TestWorkerPlugin implements WorkerPlugin {
+ private final List invocations;
+
+ public TestWorkerPlugin(List invocations) {
+ this.invocations = invocations;
+ }
+
+ @Nonnull
+ @Override
+ public String getName() {
+ return "test.worker-plugin";
+ }
+
+ @Override
+ public void configureWorkerFactory(@Nonnull WorkerFactoryOptions.Builder builder) {
+ invocations.add("configureWorkerFactory");
+ }
+
+ @Override
+ public void configureWorker(
+ @Nonnull String taskQueue, @Nonnull WorkerOptions.Builder builder) {}
+
+ @Override
+ public void initializeWorker(@Nonnull String taskQueue, @Nonnull Worker worker) {}
+
+ @Override
+ public void startWorker(
+ @Nonnull String taskQueue,
+ @Nonnull Worker worker,
+ @Nonnull BiConsumer next) {
+ next.accept(taskQueue, worker);
+ }
+
+ @Override
+ public void shutdownWorker(
+ @Nonnull String taskQueue,
+ @Nonnull Worker worker,
+ @Nonnull BiConsumer next) {
+ next.accept(taskQueue, worker);
+ }
+
+ @Override
+ public void startWorkerFactory(
+ @Nonnull WorkerFactory factory, @Nonnull Consumer next) {
+ next.accept(factory);
+ }
+
+ @Override
+ public void shutdownWorkerFactory(
+ @Nonnull WorkerFactory factory, @Nonnull Consumer next) {
+ next.accept(factory);
+ }
+
+ @Override
+ public void replayWorkflowExecution(
+ @Nonnull Worker worker,
+ @Nonnull io.temporal.common.WorkflowExecutionHistory history,
+ @Nonnull ReplayCallback next)
+ throws Exception {
+ next.replay(worker, history);
+ }
+ }
+
+ // Plugin that implements multiple interfaces - tests filtering behavior
+ public static class TestComboPlugin
+ implements WorkflowServiceStubsPlugin,
+ WorkflowClientPlugin,
+ ScheduleClientPlugin,
+ WorkerPlugin {
+ private final List invocations;
+
+ public TestComboPlugin(List invocations) {
+ this.invocations = invocations;
+ }
+
+ @Nonnull
+ @Override
+ public String getName() {
+ return "test.combo-plugin";
+ }
+
+ @Override
+ public void configureServiceStubs(@Nonnull WorkflowServiceStubsOptions.Builder builder) {
+ invocations.add("configureServiceStubs");
+ }
+
+ @Nonnull
+ @Override
+ public WorkflowServiceStubs connectServiceClient(
+ @Nonnull WorkflowServiceStubsOptions options,
+ @Nonnull Supplier next) {
+ invocations.add("connectServiceClient");
+ return next.get();
+ }
+
+ @Override
+ public void configureWorkflowClient(@Nonnull WorkflowClientOptions.Builder builder) {
+ invocations.add("configureWorkflowClient");
+ }
+
+ @Override
+ public void configureScheduleClient(@Nonnull ScheduleClientOptions.Builder builder) {
+ invocations.add("configureScheduleClient");
+ }
+
+ @Override
+ public void configureWorkerFactory(@Nonnull WorkerFactoryOptions.Builder builder) {
+ invocations.add("configureWorkerFactory");
+ }
+
+ @Override
+ public void configureWorker(
+ @Nonnull String taskQueue, @Nonnull WorkerOptions.Builder builder) {}
+
+ @Override
+ public void initializeWorker(@Nonnull String taskQueue, @Nonnull Worker worker) {}
+
+ @Override
+ public void startWorker(
+ @Nonnull String taskQueue,
+ @Nonnull Worker worker,
+ @Nonnull BiConsumer next) {
+ next.accept(taskQueue, worker);
+ }
+
+ @Override
+ public void shutdownWorker(
+ @Nonnull String taskQueue,
+ @Nonnull Worker worker,
+ @Nonnull BiConsumer next) {
+ next.accept(taskQueue, worker);
+ }
+
+ @Override
+ public void startWorkerFactory(
+ @Nonnull WorkerFactory factory, @Nonnull Consumer next) {
+ next.accept(factory);
+ }
+
+ @Override
+ public void shutdownWorkerFactory(
+ @Nonnull WorkerFactory factory, @Nonnull Consumer next) {
+ next.accept(factory);
+ }
+
+ @Override
+ public void replayWorkflowExecution(
+ @Nonnull Worker worker,
+ @Nonnull io.temporal.common.WorkflowExecutionHistory history,
+ @Nonnull ReplayCallback next)
+ throws Exception {
+ next.replay(worker, history);
+ }
+ }
+
+ // Plugin for testing ordering
+ public static class OrderedWorkerPlugin implements WorkerPlugin {
+ private final String name;
+ private final List orderRecord;
+
+ public OrderedWorkerPlugin(String name, List orderRecord) {
+ this.name = name;
+ this.orderRecord = orderRecord;
+ }
+
+ @Nonnull
+ @Override
+ public String getName() {
+ return "test.ordered." + name;
+ }
+
+ @Override
+ public void configureWorkerFactory(@Nonnull WorkerFactoryOptions.Builder builder) {
+ orderRecord.add(name);
+ }
+
+ @Override
+ public void configureWorker(
+ @Nonnull String taskQueue, @Nonnull WorkerOptions.Builder builder) {}
+
+ @Override
+ public void initializeWorker(@Nonnull String taskQueue, @Nonnull Worker worker) {}
+
+ @Override
+ public void startWorker(
+ @Nonnull String taskQueue,
+ @Nonnull Worker worker,
+ @Nonnull BiConsumer next) {
+ next.accept(taskQueue, worker);
+ }
+
+ @Override
+ public void shutdownWorker(
+ @Nonnull String taskQueue,
+ @Nonnull Worker worker,
+ @Nonnull BiConsumer next) {
+ next.accept(taskQueue, worker);
+ }
+
+ @Override
+ public void startWorkerFactory(
+ @Nonnull WorkerFactory factory, @Nonnull Consumer next) {
+ next.accept(factory);
+ }
+
+ @Override
+ public void shutdownWorkerFactory(
+ @Nonnull WorkerFactory factory, @Nonnull Consumer next) {
+ next.accept(factory);
+ }
+
+ @Override
+ public void replayWorkflowExecution(
+ @Nonnull Worker worker,
+ @Nonnull io.temporal.common.WorkflowExecutionHistory history,
+ @Nonnull ReplayCallback next)
+ throws Exception {
+ next.replay(worker, history);
+ }
+ }
+}