diff --git a/common/cdc-debezium-common/pom.xml b/common/cdc-debezium-common/pom.xml index 0a1092b8..0aa44d60 100644 --- a/common/cdc-debezium-common/pom.xml +++ b/common/cdc-debezium-common/pom.xml @@ -74,11 +74,6 @@ org.springframework.integration spring-integration-ip - - org.springframework.cloud.fn - config-common - ${revision} - diff --git a/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizationAutoConfiguration.java b/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizationAutoConfiguration.java new file mode 100644 index 00000000..758ed4a6 --- /dev/null +++ b/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizationAutoConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.common.config; + +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; + +/** + * An auto-configuration to expose customizer strategy beans to accept user-provided component customizers. + * + * @author Artem Bilan + * + * @since 1.2.1 + * + * @see ComponentCustomizerBeanPostProcessor + */ +public class ComponentCustomizationAutoConfiguration { + + @Bean + @ConditionalOnBean(ComponentCustomizer.class) + static BeanPostProcessor componentCustomizerBeanPostProcessor() { + return new ComponentCustomizerBeanPostProcessor(); + } + +} diff --git a/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizer.java b/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizer.java new file mode 100644 index 00000000..e77917cf --- /dev/null +++ b/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.common.config; + +/** + * The customizer contract to apply to beans in the application context which are marked + * with the {@link CustomizationAware} annotation and their type is matching to generic + * type of the instance of this interface. + *

+ * The bean for {@link ComponentCustomizer} has to be declared as a {@code static} bean + * method to avoid early bean initialization syndrome when not all bean post processors + * are configured into the application context yet. Its dependencies have to be {@code static} + * as well, or all of related beans can be declared in the dedicated {@code @Configuration} class. + * + * @param the target component (bean) type in the application context to customize. + * + * @author Artem Bilan + * + * @since 1.2.1 + */ +@FunctionalInterface +public interface ComponentCustomizer { + + void customize(T component, String beanName); + +} diff --git a/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizerBeanPostProcessor.java b/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizerBeanPostProcessor.java new file mode 100644 index 00000000..4343ded9 --- /dev/null +++ b/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/ComponentCustomizerBeanPostProcessor.java @@ -0,0 +1,93 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.common.config; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.ResolvableType; +import org.springframework.core.log.LogMessage; +import org.springframework.core.type.MethodMetadata; + +/** + * The {@link MergedBeanDefinitionPostProcessor} to apply a {@link ComponentCustomizer} for a bean + * in process if this bean is marked with the {@link CustomizationAware} annotation and + * a customizer for a type of this bean is present in the application context. + * + * @author Artem Bilan + * + * @since 1.2.1 + */ +class ComponentCustomizerBeanPostProcessor implements MergedBeanDefinitionPostProcessor, BeanFactoryAware { + + private static final Log logger = LogFactory.getLog(ComponentCustomizerBeanPostProcessor.class); + + private final Set customizationAwareBeanNames = new HashSet<>(); + + private BeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + if (beanDefinition instanceof AnnotatedBeanDefinition) { + AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition; + MethodMetadata factoryMethodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata(); + if (factoryMethodMetadata != null && + factoryMethodMetadata.isAnnotated(CustomizationAware.class.getName())) { + + this.customizationAwareBeanNames.add(beanName); + } + } + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (this.customizationAwareBeanNames.remove(beanName)) { + ResolvableType integrationComponentCustomizerType = + ResolvableType.forClassWithGenerics(ComponentCustomizer.class, + AopProxyUtils.ultimateTargetClass(bean)); + + ComponentCustomizer componentCustomizer = + this.beanFactory.>getBeanProvider(integrationComponentCustomizerType) + .getIfAvailable(); + + if (componentCustomizer != null) { + if (logger.isDebugEnabled()) { + logger.debug(LogMessage.format("Use '%s' for '%s' customization...", componentCustomizer, beanName)); + } + componentCustomizer.customize(bean, beanName); + } + } + + return bean; + } + +} diff --git a/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/CustomizationAware.java b/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/CustomizationAware.java new file mode 100644 index 00000000..b9c78b23 --- /dev/null +++ b/common/config-common/src/main/java/org/springframework/cloud/fn/common/config/CustomizationAware.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.common.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The annotation to mark a {@code @Bean} method for end user-provided customization + * via {@link ComponentCustomizer} with this bean type. + * + * @author Artem Bilan + * + * @since 1.2.1 + */ +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CustomizationAware { + +} diff --git a/common/config-common/src/main/resources/META-INF/spring.factories b/common/config-common/src/main/resources/META-INF/spring.factories index b41d7bb4..09ff6a11 100644 --- a/common/config-common/src/main/resources/META-INF/spring.factories +++ b/common/config-common/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - org.springframework.cloud.fn.common.config.SpelExpressionConverterConfiguration + org.springframework.cloud.fn.common.config.SpelExpressionConverterConfiguration,\ + org.springframework.cloud.fn.common.config.ComponentCustomizationAutoConfiguration diff --git a/common/twitter-common/pom.xml b/common/twitter-common/pom.xml index da8a54a3..1bae1d73 100644 --- a/common/twitter-common/pom.xml +++ b/common/twitter-common/pom.xml @@ -28,12 +28,6 @@ org.springframework.integration spring-integration-ip - - org.springframework.cloud.fn - config-common - ${project.version} - - diff --git a/consumer/analytics-consumer/pom.xml b/consumer/analytics-consumer/pom.xml index ae10d3ba..1bd5040d 100644 --- a/consumer/analytics-consumer/pom.xml +++ b/consumer/analytics-consumer/pom.xml @@ -21,11 +21,6 @@ payload-converter-function ${project.version} - - org.springframework.cloud.fn - config-common - ${project.version} - com.fasterxml.jackson.core jackson-databind @@ -35,20 +30,6 @@ io.micrometer micrometer-core - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-configuration-processor - provided - - - org.springframework.boot - spring-boot-starter-test - test - org.springframework.boot diff --git a/consumer/elasticsearch-consumer/pom.xml b/consumer/elasticsearch-consumer/pom.xml index 9836ae81..b1b3b99a 100644 --- a/consumer/elasticsearch-consumer/pom.xml +++ b/consumer/elasticsearch-consumer/pom.xml @@ -22,34 +22,15 @@ org.springframework.boot spring-boot-starter-data-elasticsearch - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-configuration-processor - provided - org.springframework spring-messaging - - org.springframework.cloud.fn - config-common - ${revision} - org.springframework.cloud.fn payload-converter-function ${revision} - - org.springframework.boot - spring-boot-starter-test - test - org.testcontainers testcontainers @@ -68,10 +49,5 @@ ${test-containers.version} test - - org.awaitility - awaitility - test - diff --git a/consumer/geode-consumer/pom.xml b/consumer/geode-consumer/pom.xml index 2bca879a..13d6ba16 100644 --- a/consumer/geode-consumer/pom.xml +++ b/consumer/geode-consumer/pom.xml @@ -26,11 +26,6 @@ geode-common ${project.version} - - org.springframework.cloud.fn - config-common - ${project.version} - org.springframework.geode spring-geode-starter-test diff --git a/consumer/s3-consumer/pom.xml b/consumer/s3-consumer/pom.xml index b601e165..02e7740e 100644 --- a/consumer/s3-consumer/pom.xml +++ b/consumer/s3-consumer/pom.xml @@ -27,12 +27,6 @@ ${project.version} - - org.springframework.cloud.fn - config-common - ${project.version} - - org.springframework spring-web diff --git a/consumer/wavefront-consumer/pom.xml b/consumer/wavefront-consumer/pom.xml index df44459c..88c992f2 100644 --- a/consumer/wavefront-consumer/pom.xml +++ b/consumer/wavefront-consumer/pom.xml @@ -25,12 +25,6 @@ spring-boot-starter-web - - org.springframework.cloud.fn - config-common - ${project.version} - - diff --git a/consumer/zeromq-consumer/pom.xml b/consumer/zeromq-consumer/pom.xml index 29f066ad..4d59a1dc 100644 --- a/consumer/zeromq-consumer/pom.xml +++ b/consumer/zeromq-consumer/pom.xml @@ -14,13 +14,6 @@ ZeroMQ consumer - - - org.springframework.cloud.fn - config-common - ${project.version} - - org.springframework.integration diff --git a/function/aggregator-function/pom.xml b/function/aggregator-function/pom.xml index 7c262fa2..2a04da42 100644 --- a/function/aggregator-function/pom.xml +++ b/function/aggregator-function/pom.xml @@ -15,22 +15,6 @@ Spring Native Function for Aggregator - - org.springframework.cloud.fn - config-common - ${project.version} - - - org.springframework.boot - spring-boot-starter-integration - - - - org.springframework.boot - spring-boot-configuration-processor - provided - - org.springframework.integration @@ -99,29 +83,6 @@ postgresql runtime - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - io.projectreactor - reactor-test - test - - - org.springframework.integration - spring-integration-test - test - diff --git a/function/filter-function/pom.xml b/function/filter-function/pom.xml index a1554b48..8eaa29c0 100644 --- a/function/filter-function/pom.xml +++ b/function/filter-function/pom.xml @@ -15,45 +15,11 @@ Spring Native Function for applying filter SpEL expressions - - org.springframework.cloud.fn - config-common - ${project.version} - org.springframework.cloud.fn payload-converter-function ${project.version} - - org.springframework.boot - spring-boot-starter-integration - - - org.springframework.boot - spring-boot-starter-json - - - org.springframework.boot - spring-boot-configuration-processor - provided - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - io.projectreactor - reactor-test - test - diff --git a/function/http-request-function/pom.xml b/function/http-request-function/pom.xml index 4b3a2fe9..db6b4a2f 100644 --- a/function/http-request-function/pom.xml +++ b/function/http-request-function/pom.xml @@ -19,13 +19,6 @@ - - - org.springframework.cloud.fn - config-common - ${project.version} - - org.springframework.boot spring-boot-starter-webflux diff --git a/spring-functions-parent/pom.xml b/spring-functions-parent/pom.xml index ec39e52a..7d0f3907 100644 --- a/spring-functions-parent/pom.xml +++ b/spring-functions-parent/pom.xml @@ -19,7 +19,11 @@ - + + org.springframework.cloud.fn + config-common + ${revision} + org.springframework.boot diff --git a/supplier/file-supplier/src/main/java/org/springframework/cloud/fn/supplier/file/FileSupplierConfiguration.java b/supplier/file-supplier/src/main/java/org/springframework/cloud/fn/supplier/file/FileSupplierConfiguration.java index fe2b483b..7f8a0e0c 100644 --- a/supplier/file-supplier/src/main/java/org/springframework/cloud/fn/supplier/file/FileSupplierConfiguration.java +++ b/supplier/file-supplier/src/main/java/org/springframework/cloud/fn/supplier/file/FileSupplierConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.fn.common.config.CustomizationAware; import org.springframework.cloud.fn.common.file.FileConsumerProperties; import org.springframework.cloud.fn.common.file.FileReadingMode; import org.springframework.cloud.fn.common.file.FileUtils; @@ -92,6 +93,7 @@ public class FileSupplierConfiguration { } @Bean + @CustomizationAware public FileInboundChannelAdapterSpec fileMessageSource(FileListFilter fileListFilter) { return Files.inboundAdapter(this.fileSupplierProperties.getDirectory()) .filter(fileListFilter); diff --git a/supplier/file-supplier/src/test/java/org/springframework/cloud/fn/supplier/file/AbstractFileSupplierTests.java b/supplier/file-supplier/src/test/java/org/springframework/cloud/fn/supplier/file/AbstractFileSupplierTests.java index e128b678..f52660df 100644 --- a/supplier/file-supplier/src/test/java/org/springframework/cloud/fn/supplier/file/AbstractFileSupplierTests.java +++ b/supplier/file-supplier/src/test/java/org/springframework/cloud/fn/supplier/file/AbstractFileSupplierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2020 the original author or authors. + * Copyright 2020-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.cloud.fn.supplier.file; import java.nio.file.Path; +import java.util.Date; import java.util.function.Supplier; import org.junit.jupiter.api.AfterAll; @@ -27,6 +28,10 @@ import reactor.core.publisher.Flux; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.fn.common.config.ComponentCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.integration.file.FileReadingMessageSource; +import org.springframework.integration.file.dsl.FileInboundChannelAdapterSpec; import org.springframework.messaging.Message; import org.springframework.test.annotation.DirtiesContext; @@ -57,5 +62,19 @@ public class AbstractFileSupplierTests { @SpringBootApplication static class FileSupplierTestApplication { + + @Bean + static ComponentCustomizer fileInboundChannelAdapterSpecCustomizer() { + return (adapterSpec, beanName) -> adapterSpec.watchEvents(FileReadingMessageSource.WatchEventType.DELETE); + } + + @Bean + static ComponentCustomizer fakeCustomizer() { + return (date, beanName) -> { + throw new RuntimeException("Must not happen"); + }; + } + } + } diff --git a/supplier/file-supplier/src/test/java/org/springframework/cloud/fn/supplier/file/DefaultFileSupplierTests.java b/supplier/file-supplier/src/test/java/org/springframework/cloud/fn/supplier/file/DefaultFileSupplierTests.java index 26aa92fe..ad11e887 100644 --- a/supplier/file-supplier/src/test/java/org/springframework/cloud/fn/supplier/file/DefaultFileSupplierTests.java +++ b/supplier/file-supplier/src/test/java/org/springframework/cloud/fn/supplier/file/DefaultFileSupplierTests.java @@ -31,6 +31,7 @@ import org.springframework.integration.file.FileHeaders; import org.springframework.integration.file.FileReadingMessageSource; import org.springframework.integration.jdbc.metadata.JdbcMetadataStore; import org.springframework.integration.metadata.ConcurrentMetadataStore; +import org.springframework.integration.test.util.TestUtils; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.messaging.Message; import org.springframework.test.context.TestPropertySource; @@ -104,6 +105,14 @@ public class DefaultFileSupplierTests extends AbstractFileSupplierTests { assertThat(metadataStoreContent.get(0)).startsWith("local-file-system-metadata-"); assertThat(metadataStoreContent.get(0)).endsWith("first.file"); assertThat(metadataStoreContent.get(1)).endsWith("test.file"); + + /* See AbstractFileSupplierTests.FileSupplierTestApplication.fileInboundChannelAdapterSpecCustomizer() - + through the ComponentCustomizer and @CustomizationAware on the FileSupplierConfiguration.fileMessageSource() + the provided customization is populated down to the bean under testing. + */ + assertThat(TestUtils.getPropertyValue(this.fileMessageSource, "watchEvents", + FileReadingMessageSource.WatchEventType[].class)) + .isEqualTo(new FileReadingMessageSource.WatchEventType[]{ FileReadingMessageSource.WatchEventType.DELETE }); } } diff --git a/supplier/geode-supplier/pom.xml b/supplier/geode-supplier/pom.xml index 25bdae12..6f66a4eb 100644 --- a/supplier/geode-supplier/pom.xml +++ b/supplier/geode-supplier/pom.xml @@ -31,12 +31,6 @@ ${project.version} - - org.springframework.cloud.fn - config-common - ${project.version} - - org.hibernate.validator hibernate-validator @@ -48,11 +42,6 @@ spring-boot-configuration-processor - - org.springframework.boot - spring-boot-starter-test - test - org.springframework.geode spring-geode-starter-logging diff --git a/supplier/mongodb-supplier/pom.xml b/supplier/mongodb-supplier/pom.xml index 38c27e97..52ca71b8 100644 --- a/supplier/mongodb-supplier/pom.xml +++ b/supplier/mongodb-supplier/pom.xml @@ -32,12 +32,6 @@ ${spring-cloud-function.version} - - org.springframework.cloud.fn - config-common - ${project.version} - - org.springframework.cloud.fn splitter-function diff --git a/supplier/sftp-supplier/pom.xml b/supplier/sftp-supplier/pom.xml index 9b42a8c9..9cc6ee3c 100644 --- a/supplier/sftp-supplier/pom.xml +++ b/supplier/sftp-supplier/pom.xml @@ -14,12 +14,6 @@ sftp supplier - - - org.springframework.boot - spring-boot-starter-json - true - org.springframework.boot spring-boot-starter-logging @@ -30,11 +24,6 @@ spring-integration-sftp - - org.springframework.cloud.fn - config-common - ${project.version} - org.springframework.cloud.fn file-common