Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
* Beans of this class can be added to Spring context to get a fine control over *Options objects
* that are created by Temporal Spring Boot Autoconfigure module.
*
* <p>Only one bean of each generic type can be added to Spring context.
* <p>Multiple beans of each generic type can be added to Spring context. They will be ordered,
* taking into account {@link org.springframework.core.Ordered Ordered} and {@link
* org.springframework.core.annotation.Order @Order} values of the target
*
* @param <T> Temporal Options Builder to customize. Respected types: {@link
* WorkflowServiceStubsOptions.Builder}, {@link WorkflowClientOptions.Builder}, {@link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
import io.temporal.spring.boot.TemporalOptionsCustomizer;
import io.temporal.spring.boot.autoconfigure.properties.NonRootNamespaceProperties;
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;

class AutoConfigurationUtils {

Expand Down Expand Up @@ -94,32 +97,68 @@ static List<WorkerInterceptor> chooseWorkerInterceptors(
return workerInterceptor;
}

static <T> TemporalOptionsCustomizer<T> chooseTemporalCustomizerBean(
/**
* Create a comparator that can extract @Order and @Priority from beans in the given bean factory.
* This is needed because the default OrderComparator doesn't know about the bean factory and
* therefore can't look up annotations on beans.
*/
private static Comparator<Object> beanFactoryAwareOrderComparator(
ListableBeanFactory beanFactory) {
return OrderComparator.INSTANCE.withSourceProvider(
o -> {
if (!(o instanceof Map.Entry)) {
throw new IllegalStateException("Unexpected object type: " + o);
}
Map.Entry<String, TemporalOptionsCustomizer<?>> entry =
(Map.Entry<String, TemporalOptionsCustomizer<?>>) o;
// Check if the bean itself has a Priority annotation
Integer priority = AnnotationAwareOrderComparator.INSTANCE.getPriority(entry.getValue());
if (priority != null) {
return (Ordered) () -> priority;
}

// Check if the bean factory method or the bean has an Order annotations
String beanName = entry.getKey();
if (beanName != null) {
Order order = beanFactory.findAnnotationOnBean(beanName, Order.class);
if (order != null) {
return (Ordered) order::value;
}
}

// Nothing present
return null;
});
}

static <T> List<TemporalOptionsCustomizer<T>> chooseTemporalCustomizerBeans(
ListableBeanFactory beanFactory,
Map<String, TemporalOptionsCustomizer<T>> customizerMap,
Class<T> genericOptionsBuilderClass,
TemporalProperties properties) {
if (Objects.isNull(customizerMap) || customizerMap.isEmpty()) {
return null;
}
List<NonRootNamespaceProperties> nonRootNamespaceProperties = properties.getNamespaces();
if (Objects.isNull(nonRootNamespaceProperties) || nonRootNamespaceProperties.isEmpty()) {
return customizerMap.values().stream().findFirst().orElse(null);
Stream<Entry<String, TemporalOptionsCustomizer<T>>> customizerStream =
customizerMap.entrySet().stream();
if (!(Objects.isNull(nonRootNamespaceProperties) || nonRootNamespaceProperties.isEmpty())) {
// Non-root namespace bean names, such as "nsWorkerFactoryCustomizer", "nsWorkerCustomizer"
List<String> nonRootBeanNames =
nonRootNamespaceProperties.stream()
.map(
ns ->
temporalCustomizerBeanName(
MoreObjects.firstNonNull(ns.getAlias(), ns.getNamespace()),
genericOptionsBuilderClass))
.collect(Collectors.toList());
customizerStream =
customizerStream.filter(entry -> !nonRootBeanNames.contains(entry.getKey()));
}
// Non-root namespace bean names, such as "nsWorkerFactoryCustomizer", "nsWorkerCustomizer"
List<String> nonRootBeanNames =
nonRootNamespaceProperties.stream()
.map(
ns ->
temporalCustomizerBeanName(
MoreObjects.firstNonNull(ns.getAlias(), ns.getNamespace()),
genericOptionsBuilderClass))
.collect(Collectors.toList());

return customizerMap.entrySet().stream()
.filter(entry -> !nonRootBeanNames.contains(entry.getKey()))
.findFirst()
return customizerStream
.sorted(beanFactoryAwareOrderComparator(beanFactory))
.map(Entry::getValue)
.orElse(null);
.collect(Collectors.toList());
}

static String temporalCustomizerBeanName(String beanPrefix, Class<?> optionsBuilderClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
import io.temporal.worker.WorkerFactoryOptions.Builder;
import io.temporal.worker.WorkerOptions;
import io.temporal.worker.WorkflowImplementationOptions;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
Expand Down Expand Up @@ -83,19 +84,30 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
DataConverter dataConverterByNamespace = findBeanByNamespace(beanPrefix, DataConverter.class);

// found regarding namespace customizer bean, it can be optional
TemporalOptionsCustomizer<Builder> workFactoryCustomizer =
List<TemporalOptionsCustomizer<Builder>> workFactoryCustomizers =
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, Builder.class);
TemporalOptionsCustomizer<WorkflowServiceStubsOptions.Builder> workflowServiceStubsCustomizer =
findBeanByNameSpaceForTemporalCustomizer(
beanPrefix, WorkflowServiceStubsOptions.Builder.class);
TemporalOptionsCustomizer<WorkerOptions.Builder> WorkerCustomizer =
List<TemporalOptionsCustomizer<WorkflowServiceStubsOptions.Builder>>
workflowServiceStubsCustomizers =
findBeanByNameSpaceForTemporalCustomizer(
beanPrefix, WorkflowServiceStubsOptions.Builder.class);
List<TemporalOptionsCustomizer<WorkerOptions.Builder>> workerCustomizers =
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, WorkerOptions.Builder.class);
TemporalOptionsCustomizer<WorkflowClientOptions.Builder> workflowClientCustomizer =
List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> workflowClientCustomizers =
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, WorkflowClientOptions.Builder.class);
TemporalOptionsCustomizer<ScheduleClientOptions.Builder> scheduleClientCustomizer =
if (workflowClientCustomizers != null) {
workflowClientCustomizers =
workflowClientCustomizers.stream()
.map(
c ->
(TemporalOptionsCustomizer<WorkflowClientOptions.Builder>)
(WorkflowClientOptions.Builder o) ->
c.customize(o).setNamespace(ns.getNamespace()))
.collect(Collectors.toList());
}
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleClientCustomizers =
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, ScheduleClientOptions.Builder.class);
TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>
workflowImplementationCustomizer =
List<TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>>
workflowImplementationCustomizers =
findBeanByNameSpaceForTemporalCustomizer(
beanPrefix, WorkflowImplementationOptions.Builder.class);

Expand All @@ -107,7 +119,7 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
connectionProperties,
metricsScope,
testWorkflowEnvironment,
workflowServiceStubsCustomizer);
workflowServiceStubsCustomizers);
WorkflowServiceStubs workflowServiceStubs = serviceStubsTemplate.getWorkflowServiceStubs();

NonRootNamespaceTemplate namespaceTemplate =
Expand All @@ -121,16 +133,11 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
null,
tracer,
testWorkflowEnvironment,
workFactoryCustomizer,
WorkerCustomizer,
builder ->
// Must make sure the namespace is set at the end of the builder chain
Optional.ofNullable(workflowClientCustomizer)
.map(c -> c.customize(builder))
.orElse(builder)
.setNamespace(ns.getNamespace()),
scheduleClientCustomizer,
workflowImplementationCustomizer);
workFactoryCustomizers,
workerCustomizers,
workflowClientCustomizers,
scheduleClientCustomizers,
workflowImplementationCustomizers);

ClientTemplate clientTemplate = namespaceTemplate.getClientTemplate();
WorkflowClient workflowClient = clientTemplate.getWorkflowClient();
Expand Down Expand Up @@ -188,18 +195,19 @@ private <T> T findBeanByNamespace(String beanPrefix, Class<T> clazz) {
return null;
}

private <T> TemporalOptionsCustomizer<T> findBeanByNameSpaceForTemporalCustomizer(
private <T> List<TemporalOptionsCustomizer<T>> findBeanByNameSpaceForTemporalCustomizer(
String beanPrefix, Class<T> genericOptionsBuilderClass) {
String beanName =
AutoConfigurationUtils.temporalCustomizerBeanName(beanPrefix, genericOptionsBuilderClass);
try {
TemporalOptionsCustomizer genericOptionsCustomizer =
// TODO(https://github.com/temporalio/sdk-java/issues/2638): Support multiple customizers in
// the non root namespace
TemporalOptionsCustomizer<T> genericOptionsCustomizer =
beanFactory.getBean(beanName, TemporalOptionsCustomizer.class);
return (TemporalOptionsCustomizer<T>) genericOptionsCustomizer;
return Collections.singletonList(genericOptionsCustomizer);
} catch (BeansException e) {
log.warn("No TemporalOptionsCustomizer found for {}. ", beanName);
if (genericOptionsBuilderClass.isAssignableFrom(Builder.class)) {
// print tips once
log.debug(
"No TemporalOptionsCustomizer found for {}. \n You can add Customizer bean to do by namespace customization. \n "
+ "Note: bean name should start with namespace name and end with Customizer, and the middle part should be the customizer "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,25 @@ public NamespaceTemplate rootNamespaceTemplate(
scheduleClientInterceptors, properties);
List<WorkerInterceptor> chosenWorkerInterceptors =
AutoConfigurationUtils.chooseWorkerInterceptors(workerInterceptors, properties);
TemporalOptionsCustomizer<WorkerFactoryOptions.Builder> workerFactoryCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
workerFactoryCustomizerMap, WorkerFactoryOptions.Builder.class, properties);
TemporalOptionsCustomizer<WorkerOptions.Builder> workerCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
workerCustomizerMap, WorkerOptions.Builder.class, properties);
TemporalOptionsCustomizer<WorkflowClientOptions.Builder> clientCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
clientCustomizerMap, WorkflowClientOptions.Builder.class, properties);
TemporalOptionsCustomizer<ScheduleClientOptions.Builder> scheduleCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>
List<TemporalOptionsCustomizer<WorkerFactoryOptions.Builder>> workerFactoryCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory,
workerFactoryCustomizerMap,
WorkerFactoryOptions.Builder.class,
properties);
List<TemporalOptionsCustomizer<WorkerOptions.Builder>> workerCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, workerCustomizerMap, WorkerOptions.Builder.class, properties);
List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> clientCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, clientCustomizerMap, WorkflowClientOptions.Builder.class, properties);
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
List<TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>>
workflowImplementationCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory,
workflowImplementationCustomizerMap,
WorkflowImplementationOptions.Builder.class,
properties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
import io.temporal.spring.boot.autoconfigure.template.ServiceStubsTemplate;
import io.temporal.spring.boot.autoconfigure.template.TestWorkflowEnvironmentAdapter;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand All @@ -26,6 +28,12 @@
"${spring.temporal.test-server.enabled:false} || '${spring.temporal.connection.target:}'.length() > 0")
public class ServiceStubsAutoConfiguration {

ConfigurableListableBeanFactory beanFactory;

public ServiceStubsAutoConfiguration(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

@Bean(name = "temporalServiceStubsTemplate")
public ServiceStubsTemplate serviceStubsTemplate(
TemporalProperties properties,
Expand All @@ -35,9 +43,9 @@ public ServiceStubsTemplate serviceStubsTemplate(
@Autowired(required = false) @Nullable
Map<String, TemporalOptionsCustomizer<WorkflowServiceStubsOptions.Builder>>
workflowServiceStubsCustomizerMap) {
TemporalOptionsCustomizer<Builder> workflowServiceStubsCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
workflowServiceStubsCustomizerMap, Builder.class, properties);
List<TemporalOptionsCustomizer<Builder>> workflowServiceStubsCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, workflowServiceStubsCustomizerMap, Builder.class, properties);
return new ServiceStubsTemplate(
properties.getConnection(),
metricsScope,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
Expand All @@ -43,6 +44,12 @@ public class TestServerAutoConfiguration {

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

private final ConfigurableListableBeanFactory beanFactory;

public TestServerAutoConfiguration(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

@Bean(name = "temporalTestWorkflowEnvironmentAdapter")
public TestWorkflowEnvironmentAdapter testTestWorkflowEnvironmentAdapter(
@Qualifier("temporalTestWorkflowEnvironment")
Expand All @@ -64,7 +71,7 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
List<ScheduleClientInterceptor> scheduleClientInterceptors,
@Autowired(required = false) @Nullable List<WorkerInterceptor> workerInterceptors,
@Autowired(required = false) @Nullable
TemporalOptionsCustomizer<TestEnvironmentOptions.Builder> testEnvOptionsCustomizer,
List<TemporalOptionsCustomizer<TestEnvironmentOptions.Builder>> testEnvOptionsCustomizers,
@Autowired(required = false) @Nullable
Map<String, TemporalOptionsCustomizer<WorkerFactoryOptions.Builder>>
workerFactoryCustomizerMap,
Expand All @@ -84,15 +91,18 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
List<WorkerInterceptor> chosenWorkerInterceptors =
AutoConfigurationUtils.chooseWorkerInterceptors(workerInterceptors, properties);

TemporalOptionsCustomizer<WorkerFactoryOptions.Builder> workerFactoryCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
workerFactoryCustomizerMap, WorkerFactoryOptions.Builder.class, properties);
TemporalOptionsCustomizer<WorkflowClientOptions.Builder> clientCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
clientCustomizerMap, WorkflowClientOptions.Builder.class, properties);
TemporalOptionsCustomizer<ScheduleClientOptions.Builder> scheduleCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBean(
scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
List<TemporalOptionsCustomizer<WorkerFactoryOptions.Builder>> workerFactoryCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory,
workerFactoryCustomizerMap,
WorkerFactoryOptions.Builder.class,
properties);
List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> clientCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, clientCustomizerMap, WorkflowClientOptions.Builder.class, properties);
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);

TestEnvironmentOptions.Builder options =
TestEnvironmentOptions.newBuilder()
Expand All @@ -116,8 +126,11 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
properties, chosenWorkerInterceptors, otTracer, workerFactoryCustomizer)
.createWorkerFactoryOptions());

if (testEnvOptionsCustomizer != null) {
options = testEnvOptionsCustomizer.customize(options);
if (testEnvOptionsCustomizers != null) {
for (TemporalOptionsCustomizer<TestEnvironmentOptions.Builder> testEnvOptionsCustomizer :
testEnvOptionsCustomizers) {
options = testEnvOptionsCustomizer.customize(options);
}
}

return TestWorkflowEnvironment.newInstance(options.build());
Expand Down
Loading
Loading