Skip to content

Commit f3a810d

Browse files
Allow multiple customizers in Springboot
1 parent eaad70d commit f3a810d

16 files changed

Lines changed: 282 additions & 161 deletions

temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/AutoConfigurationUtils.java

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@
88
import io.temporal.spring.boot.TemporalOptionsCustomizer;
99
import io.temporal.spring.boot.autoconfigure.properties.NonRootNamespaceProperties;
1010
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
11-
import java.util.ArrayList;
12-
import java.util.List;
13-
import java.util.Map;
11+
import java.util.*;
1412
import java.util.Map.Entry;
15-
import java.util.Objects;
1613
import java.util.stream.Collectors;
1714
import javax.annotation.Nullable;
15+
import org.springframework.beans.factory.ListableBeanFactory;
1816
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
17+
import org.springframework.core.OrderComparator;
18+
import org.springframework.core.Ordered;
19+
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
20+
import org.springframework.core.annotation.Order;
1921

2022
class AutoConfigurationUtils {
2123

@@ -94,16 +96,51 @@ static List<WorkerInterceptor> chooseWorkerInterceptors(
9496
return workerInterceptor;
9597
}
9698

97-
static <T> TemporalOptionsCustomizer<T> chooseTemporalCustomizerBean(
99+
private static Comparator<Object> beanFactoryAwareOrderComparator(
100+
ListableBeanFactory beanFactory) {
101+
return OrderComparator.INSTANCE.withSourceProvider(
102+
o -> {
103+
if (!(o instanceof Map.Entry)) {
104+
throw new IllegalStateException("Unexpected object type: " + o);
105+
}
106+
Map.Entry<String, TemporalOptionsCustomizer<?>> entry =
107+
(Map.Entry<String, TemporalOptionsCustomizer<?>>) o;
108+
// The AnnotationAwareOrderComparator does not have the "withSourceProvider" method
109+
// The OrderComparator.withSourceProvider does not properly account for the annotations
110+
Integer priority = AnnotationAwareOrderComparator.INSTANCE.getPriority(entry.getValue());
111+
if (priority != null) {
112+
return (Ordered) () -> priority;
113+
}
114+
115+
// Consult the bean factory method for annotations
116+
String beanName = entry.getKey();
117+
if (beanName != null) {
118+
Order order = beanFactory.findAnnotationOnBean(beanName, Order.class);
119+
if (order != null) {
120+
return (Ordered) order::value;
121+
}
122+
}
123+
124+
// Nothing present
125+
return null;
126+
});
127+
}
128+
129+
static <T> List<TemporalOptionsCustomizer<T>> chooseTemporalCustomizerBeans(
130+
ListableBeanFactory beanFactory,
98131
Map<String, TemporalOptionsCustomizer<T>> customizerMap,
99132
Class<T> genericOptionsBuilderClass,
100133
TemporalProperties properties) {
101134
if (Objects.isNull(customizerMap) || customizerMap.isEmpty()) {
102135
return null;
103136
}
104137
List<NonRootNamespaceProperties> nonRootNamespaceProperties = properties.getNamespaces();
138+
// If we only have one namespace (the root one), use all customizers
105139
if (Objects.isNull(nonRootNamespaceProperties) || nonRootNamespaceProperties.isEmpty()) {
106-
return customizerMap.values().stream().findFirst().orElse(null);
140+
return customizerMap.entrySet().stream()
141+
.sorted(beanFactoryAwareOrderComparator(beanFactory))
142+
.map(Entry::getValue)
143+
.collect(Collectors.toList());
107144
}
108145
// Non-root namespace bean names, such as "nsWorkerFactoryCustomizer", "nsWorkerCustomizer"
109146
List<String> nonRootBeanNames =
@@ -117,9 +154,9 @@ static <T> TemporalOptionsCustomizer<T> chooseTemporalCustomizerBean(
117154

118155
return customizerMap.entrySet().stream()
119156
.filter(entry -> !nonRootBeanNames.contains(entry.getKey()))
120-
.findFirst()
157+
.sorted(beanFactoryAwareOrderComparator(beanFactory))
121158
.map(Entry::getValue)
122-
.orElse(null);
159+
.collect(Collectors.toList());
123160
}
124161

125162
static String temporalCustomizerBeanName(String beanPrefix, Class<?> optionsBuilderClass) {

temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/NonRootBeanPostProcessor.java

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import io.temporal.worker.WorkerFactoryOptions.Builder;
2525
import io.temporal.worker.WorkerOptions;
2626
import io.temporal.worker.WorkflowImplementationOptions;
27+
import java.util.Collections;
2728
import java.util.List;
28-
import java.util.Optional;
2929
import javax.annotation.Nonnull;
3030
import javax.annotation.Nullable;
3131
import org.slf4j.Logger;
@@ -83,19 +83,20 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
8383
DataConverter dataConverterByNamespace = findBeanByNamespace(beanPrefix, DataConverter.class);
8484

8585
// found regarding namespace customizer bean, it can be optional
86-
TemporalOptionsCustomizer<Builder> workFactoryCustomizer =
86+
List<TemporalOptionsCustomizer<Builder>> workFactoryCustomizers =
8787
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, Builder.class);
88-
TemporalOptionsCustomizer<WorkflowServiceStubsOptions.Builder> workflowServiceStubsCustomizer =
89-
findBeanByNameSpaceForTemporalCustomizer(
90-
beanPrefix, WorkflowServiceStubsOptions.Builder.class);
91-
TemporalOptionsCustomizer<WorkerOptions.Builder> WorkerCustomizer =
88+
List<TemporalOptionsCustomizer<WorkflowServiceStubsOptions.Builder>>
89+
workflowServiceStubsCustomizers =
90+
findBeanByNameSpaceForTemporalCustomizer(
91+
beanPrefix, WorkflowServiceStubsOptions.Builder.class);
92+
List<TemporalOptionsCustomizer<WorkerOptions.Builder>> workerCustomizers =
9293
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, WorkerOptions.Builder.class);
93-
TemporalOptionsCustomizer<WorkflowClientOptions.Builder> workflowClientCustomizer =
94+
List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> workflowClientCustomizers =
9495
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, WorkflowClientOptions.Builder.class);
95-
TemporalOptionsCustomizer<ScheduleClientOptions.Builder> scheduleClientCustomizer =
96+
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleClientCustomizers =
9697
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, ScheduleClientOptions.Builder.class);
97-
TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>
98-
workflowImplementationCustomizer =
98+
List<TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>>
99+
workflowImplementationCustomizers =
99100
findBeanByNameSpaceForTemporalCustomizer(
100101
beanPrefix, WorkflowImplementationOptions.Builder.class);
101102

@@ -107,7 +108,7 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
107108
connectionProperties,
108109
metricsScope,
109110
testWorkflowEnvironment,
110-
workflowServiceStubsCustomizer);
111+
workflowServiceStubsCustomizers);
111112
WorkflowServiceStubs workflowServiceStubs = serviceStubsTemplate.getWorkflowServiceStubs();
112113

113114
NonRootNamespaceTemplate namespaceTemplate =
@@ -121,16 +122,11 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
121122
null,
122123
tracer,
123124
testWorkflowEnvironment,
124-
workFactoryCustomizer,
125-
WorkerCustomizer,
126-
builder ->
127-
// Must make sure the namespace is set at the end of the builder chain
128-
Optional.ofNullable(workflowClientCustomizer)
129-
.map(c -> c.customize(builder))
130-
.orElse(builder)
131-
.setNamespace(ns.getNamespace()),
132-
scheduleClientCustomizer,
133-
workflowImplementationCustomizer);
125+
workFactoryCustomizers,
126+
workerCustomizers,
127+
workflowClientCustomizers,
128+
scheduleClientCustomizers,
129+
workflowImplementationCustomizers);
134130

135131
ClientTemplate clientTemplate = namespaceTemplate.getClientTemplate();
136132
WorkflowClient workflowClient = clientTemplate.getWorkflowClient();
@@ -188,18 +184,17 @@ private <T> T findBeanByNamespace(String beanPrefix, Class<T> clazz) {
188184
return null;
189185
}
190186

191-
private <T> TemporalOptionsCustomizer<T> findBeanByNameSpaceForTemporalCustomizer(
187+
private <T> List<TemporalOptionsCustomizer<T>> findBeanByNameSpaceForTemporalCustomizer(
192188
String beanPrefix, Class<T> genericOptionsBuilderClass) {
193189
String beanName =
194190
AutoConfigurationUtils.temporalCustomizerBeanName(beanPrefix, genericOptionsBuilderClass);
195191
try {
196-
TemporalOptionsCustomizer genericOptionsCustomizer =
192+
TemporalOptionsCustomizer<T> genericOptionsCustomizer =
197193
beanFactory.getBean(beanName, TemporalOptionsCustomizer.class);
198-
return (TemporalOptionsCustomizer<T>) genericOptionsCustomizer;
194+
return Collections.singletonList(genericOptionsCustomizer);
199195
} catch (BeansException e) {
200196
log.warn("No TemporalOptionsCustomizer found for {}. ", beanName);
201197
if (genericOptionsBuilderClass.isAssignableFrom(Builder.class)) {
202-
// print tips once
203198
log.debug(
204199
"No TemporalOptionsCustomizer found for {}. \n You can add Customizer bean to do by namespace customization. \n "
205200
+ "Note: bean name should start with namespace name and end with Customizer, and the middle part should be the customizer "

temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/RootNamespaceAutoConfiguration.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,25 @@ public NamespaceTemplate rootNamespaceTemplate(
9898
scheduleClientInterceptors, properties);
9999
List<WorkerInterceptor> chosenWorkerInterceptors =
100100
AutoConfigurationUtils.chooseWorkerInterceptors(workerInterceptors, properties);
101-
TemporalOptionsCustomizer<WorkerFactoryOptions.Builder> workerFactoryCustomizer =
102-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
103-
workerFactoryCustomizerMap, WorkerFactoryOptions.Builder.class, properties);
104-
TemporalOptionsCustomizer<WorkerOptions.Builder> workerCustomizer =
105-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
106-
workerCustomizerMap, WorkerOptions.Builder.class, properties);
107-
TemporalOptionsCustomizer<WorkflowClientOptions.Builder> clientCustomizer =
108-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
109-
clientCustomizerMap, WorkflowClientOptions.Builder.class, properties);
110-
TemporalOptionsCustomizer<ScheduleClientOptions.Builder> scheduleCustomizer =
111-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
112-
scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
113-
TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>
101+
List<TemporalOptionsCustomizer<WorkerFactoryOptions.Builder>> workerFactoryCustomizer =
102+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
103+
beanFactory,
104+
workerFactoryCustomizerMap,
105+
WorkerFactoryOptions.Builder.class,
106+
properties);
107+
List<TemporalOptionsCustomizer<WorkerOptions.Builder>> workerCustomizer =
108+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
109+
beanFactory, workerCustomizerMap, WorkerOptions.Builder.class, properties);
110+
List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> clientCustomizer =
111+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
112+
beanFactory, clientCustomizerMap, WorkflowClientOptions.Builder.class, properties);
113+
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleCustomizer =
114+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
115+
beanFactory, scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
116+
List<TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>>
114117
workflowImplementationCustomizer =
115-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
118+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
119+
beanFactory,
116120
workflowImplementationCustomizerMap,
117121
WorkflowImplementationOptions.Builder.class,
118122
properties);

temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/ServiceStubsAutoConfiguration.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
99
import io.temporal.spring.boot.autoconfigure.template.ServiceStubsTemplate;
1010
import io.temporal.spring.boot.autoconfigure.template.TestWorkflowEnvironmentAdapter;
11+
import java.util.List;
1112
import java.util.Map;
1213
import javax.annotation.Nullable;
1314
import org.springframework.beans.factory.annotation.Autowired;
1415
import org.springframework.beans.factory.annotation.Qualifier;
16+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
1517
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
1618
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
1719
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -26,6 +28,12 @@
2628
"${spring.temporal.test-server.enabled:false} || '${spring.temporal.connection.target:}'.length() > 0")
2729
public class ServiceStubsAutoConfiguration {
2830

31+
ConfigurableListableBeanFactory beanFactory;
32+
33+
public ServiceStubsAutoConfiguration(ConfigurableListableBeanFactory beanFactory) {
34+
this.beanFactory = beanFactory;
35+
}
36+
2937
@Bean(name = "temporalServiceStubsTemplate")
3038
public ServiceStubsTemplate serviceStubsTemplate(
3139
TemporalProperties properties,
@@ -35,9 +43,9 @@ public ServiceStubsTemplate serviceStubsTemplate(
3543
@Autowired(required = false) @Nullable
3644
Map<String, TemporalOptionsCustomizer<WorkflowServiceStubsOptions.Builder>>
3745
workflowServiceStubsCustomizerMap) {
38-
TemporalOptionsCustomizer<Builder> workflowServiceStubsCustomizer =
39-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
40-
workflowServiceStubsCustomizerMap, Builder.class, properties);
46+
List<TemporalOptionsCustomizer<Builder>> workflowServiceStubsCustomizer =
47+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
48+
beanFactory, workflowServiceStubsCustomizerMap, Builder.class, properties);
4149
return new ServiceStubsTemplate(
4250
properties.getConnection(),
4351
metricsScope,

temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/TestServerAutoConfiguration.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.slf4j.LoggerFactory;
2424
import org.springframework.beans.factory.annotation.Autowired;
2525
import org.springframework.beans.factory.annotation.Qualifier;
26+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2627
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2728
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2829
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -43,6 +44,12 @@ public class TestServerAutoConfiguration {
4344

4445
private static final Logger log = LoggerFactory.getLogger(TestServerAutoConfiguration.class);
4546

47+
private final ConfigurableListableBeanFactory beanFactory;
48+
49+
public TestServerAutoConfiguration(ConfigurableListableBeanFactory beanFactory) {
50+
this.beanFactory = beanFactory;
51+
}
52+
4653
@Bean(name = "temporalTestWorkflowEnvironmentAdapter")
4754
public TestWorkflowEnvironmentAdapter testTestWorkflowEnvironmentAdapter(
4855
@Qualifier("temporalTestWorkflowEnvironment")
@@ -64,7 +71,7 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
6471
List<ScheduleClientInterceptor> scheduleClientInterceptors,
6572
@Autowired(required = false) @Nullable List<WorkerInterceptor> workerInterceptors,
6673
@Autowired(required = false) @Nullable
67-
TemporalOptionsCustomizer<TestEnvironmentOptions.Builder> testEnvOptionsCustomizer,
74+
List<TemporalOptionsCustomizer<TestEnvironmentOptions.Builder>> testEnvOptionsCustomizers,
6875
@Autowired(required = false) @Nullable
6976
Map<String, TemporalOptionsCustomizer<WorkerFactoryOptions.Builder>>
7077
workerFactoryCustomizerMap,
@@ -84,15 +91,18 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
8491
List<WorkerInterceptor> chosenWorkerInterceptors =
8592
AutoConfigurationUtils.chooseWorkerInterceptors(workerInterceptors, properties);
8693

87-
TemporalOptionsCustomizer<WorkerFactoryOptions.Builder> workerFactoryCustomizer =
88-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
89-
workerFactoryCustomizerMap, WorkerFactoryOptions.Builder.class, properties);
90-
TemporalOptionsCustomizer<WorkflowClientOptions.Builder> clientCustomizer =
91-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
92-
clientCustomizerMap, WorkflowClientOptions.Builder.class, properties);
93-
TemporalOptionsCustomizer<ScheduleClientOptions.Builder> scheduleCustomizer =
94-
AutoConfigurationUtils.chooseTemporalCustomizerBean(
95-
scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
94+
List<TemporalOptionsCustomizer<WorkerFactoryOptions.Builder>> workerFactoryCustomizer =
95+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
96+
beanFactory,
97+
workerFactoryCustomizerMap,
98+
WorkerFactoryOptions.Builder.class,
99+
properties);
100+
List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> clientCustomizer =
101+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
102+
beanFactory, clientCustomizerMap, WorkflowClientOptions.Builder.class, properties);
103+
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleCustomizer =
104+
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
105+
beanFactory, scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
96106

97107
TestEnvironmentOptions.Builder options =
98108
TestEnvironmentOptions.newBuilder()
@@ -116,8 +126,11 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
116126
properties, chosenWorkerInterceptors, otTracer, workerFactoryCustomizer)
117127
.createWorkerFactoryOptions());
118128

119-
if (testEnvOptionsCustomizer != null) {
120-
options = testEnvOptionsCustomizer.customize(options);
129+
if (testEnvOptionsCustomizers != null) {
130+
for (TemporalOptionsCustomizer<TestEnvironmentOptions.Builder> testEnvOptionsCustomizer :
131+
testEnvOptionsCustomizers) {
132+
options = testEnvOptionsCustomizer.customize(options);
133+
}
121134
}
122135

123136
return TestWorkflowEnvironment.newInstance(options.build());

temporal-spring-boot-autoconfigure/src/main/java/io/temporal/spring/boot/autoconfigure/template/ClientTemplate.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,18 @@ public ClientTemplate(
3232
@Nullable Tracer tracer,
3333
@Nullable WorkflowServiceStubs workflowServiceStubs,
3434
@Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
35-
@Nullable TemporalOptionsCustomizer<WorkflowClientOptions.Builder> clientCustomizer,
36-
@Nullable TemporalOptionsCustomizer<ScheduleClientOptions.Builder> scheduleCustomer) {
35+
@Nullable List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> clientCustomizers,
36+
@Nullable
37+
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleCustomizers) {
3738
this.optionsTemplate =
3839
new WorkflowClientOptionsTemplate(
3940
namespace,
4041
dataConverter,
4142
workflowClientInterceptors,
4243
scheduleClientInterceptors,
4344
tracer,
44-
clientCustomizer,
45-
scheduleCustomer);
45+
clientCustomizers,
46+
scheduleCustomizers);
4647
this.workflowServiceStubs = workflowServiceStubs;
4748
this.testWorkflowEnvironment = testWorkflowEnvironment;
4849
}

0 commit comments

Comments
 (0)