diff --git a/src/main/java/org/springframework/data/aot/AuditingBeanRegistrationAotProcessor.java b/src/main/java/org/springframework/data/aot/AuditingBeanRegistrationAotProcessor.java new file mode 100644 index 000000000..c9a246164 --- /dev/null +++ b/src/main/java/org/springframework/data/aot/AuditingBeanRegistrationAotProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright 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.data.aot; + +import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; +import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; +import org.springframework.beans.factory.support.RegisteredBean; +import org.springframework.data.aot.hint.AuditingHints; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.domain.ReactiveAuditorAware; +import org.springframework.data.repository.util.ReactiveWrappers; +import org.springframework.data.repository.util.ReactiveWrappers.ReactiveLibrary; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; + +/** + * @author Christoph Strobl + * @since 3.0 + */ +public class AuditingBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor { + + @Nullable + @Override + public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { + + if (isAuditingHandler(registeredBean)) { + return (generationContext, beanRegistrationCode) -> new AuditingHints.AuditingRuntimeHints() + .registerHints(generationContext.getRuntimeHints(), registeredBean.getBeanFactory().getBeanClassLoader()); + } + if (ReactiveWrappers.isAvailable(ReactiveLibrary.PROJECT_REACTOR) && isReactiveAuditorAware(registeredBean)) { + return (generationContext, beanRegistrationCode) -> new AuditingHints.ReactiveAuditingRuntimeHints() + .registerHints(generationContext.getRuntimeHints(), registeredBean.getBeanFactory().getBeanClassLoader()); + } + return null; + } + + boolean isAuditingHandler(RegisteredBean bean) { + return ClassUtils.isAssignable(AuditorAware.class, bean.getBeanClass()); + } + + boolean isReactiveAuditorAware(RegisteredBean bean) { + return ClassUtils.isAssignable(ReactiveAuditorAware.class, bean.getBeanClass()); + } +} diff --git a/src/main/resources/META-INF/spring/aot.factories b/src/main/resources/META-INF/spring/aot.factories index 208d901e6..bb9825009 100644 --- a/src/main/resources/META-INF/spring/aot.factories +++ b/src/main/resources/META-INF/spring/aot.factories @@ -2,3 +2,5 @@ org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ org.springframework.data.aot.SpringDataBeanFactoryInitializationAotProcessor org.springframework.aot.hint.RuntimeHintsRegistrar=\ org.springframework.data.aot.hint.RepositoryRuntimeHints +org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\ + org.springframework.data.aot.AuditingBeanRegistrationAotProcessor diff --git a/src/test/java/org/springframework/data/aot/AuditingBeanRegistrationAotProcessorUnitTests.java b/src/test/java/org/springframework/data/aot/AuditingBeanRegistrationAotProcessorUnitTests.java new file mode 100644 index 000000000..e2b1104f4 --- /dev/null +++ b/src/test/java/org/springframework/data/aot/AuditingBeanRegistrationAotProcessorUnitTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 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.data.aot; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.aot.BeanRegistrationContributionAssert.*; + +import reactor.core.publisher.Mono; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RegisteredBean; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.domain.ReactiveAuditorAware; + +/** + * @author Christoph Strobl + */ +class AuditingBeanRegistrationAotProcessorUnitTests { + + DefaultListableBeanFactory beanFactory; + + @BeforeEach + void beforeEach() { + beanFactory = new DefaultListableBeanFactory(); + } + + @Test // GH-2593 + void contributesProxyForAuditorAwareInterface() { + + String beanName = "auditorAware"; + beanFactory.registerBeanDefinition("auditorAware", + BeanDefinitionBuilder.rootBeanDefinition(MyAuditorAware.class).getBeanDefinition()); + + BeanRegistrationAotContribution beanRegistrationAotContribution = new AuditingBeanRegistrationAotProcessor() + .processAheadOfTime(RegisteredBean.of(beanFactory, beanName)); + assertThatAotContribution(beanRegistrationAotContribution).codeContributionSatisfies(code -> { + code.contributesJdkProxyFor(AuditorAware.class); + }); + } + + @Test // GH-2593 + void contributesProxyForReactriveAuditorAwareInterface() { + + String beanName = "auditorAware"; + beanFactory.registerBeanDefinition("auditorAware", + BeanDefinitionBuilder.rootBeanDefinition(MyReactiveAuditorAware.class).getBeanDefinition()); + + BeanRegistrationAotContribution beanRegistrationAotContribution = new AuditingBeanRegistrationAotProcessor() + .processAheadOfTime(RegisteredBean.of(beanFactory, beanName)); + assertThatAotContribution(beanRegistrationAotContribution).codeContributionSatisfies(code -> { + code.contributesJdkProxyFor(ReactiveAuditorAware.class); + }); + } + + @Test // GH-2593 + void ignoresNonAuditorAware() { + + String beanName = "auditorAware"; + beanFactory.registerBeanDefinition("auditorAware", + BeanDefinitionBuilder.rootBeanDefinition(Nothing.class).getBeanDefinition()); + + BeanRegistrationAotContribution beanRegistrationAotContribution = new AuditingBeanRegistrationAotProcessor() + .processAheadOfTime(RegisteredBean.of(beanFactory, beanName)); + assertThat(beanRegistrationAotContribution).isNull(); + } + + static class Nothing { + public Optional getCurrentAuditor() { + return Optional.empty(); + } + } + + static class MyAuditorAware implements AuditorAware { + + @Override + public Optional getCurrentAuditor() { + return Optional.empty(); + } + } + + static class MyReactiveAuditorAware implements ReactiveAuditorAware { + + @Override + public Mono getCurrentAuditor() { + return null; + } + } +} diff --git a/src/test/java/org/springframework/data/aot/BeanRegistrationContributionAssert.java b/src/test/java/org/springframework/data/aot/BeanRegistrationContributionAssert.java new file mode 100644 index 000000000..7273d929f --- /dev/null +++ b/src/test/java/org/springframework/data/aot/BeanRegistrationContributionAssert.java @@ -0,0 +1,55 @@ +/* + * Copyright 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.data.aot; + +import static org.mockito.Mockito.*; + +import java.util.function.Consumer; + +import org.assertj.core.api.AbstractAssert; +import org.springframework.aot.generate.ClassNameGenerator; +import org.springframework.aot.generate.DefaultGenerationContext; +import org.springframework.aot.generate.InMemoryGeneratedFiles; +import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; +import org.springframework.beans.factory.aot.BeanRegistrationCode; + +/** + * @author Christoph Strobl + */ +public class BeanRegistrationContributionAssert extends AbstractAssert { + + protected BeanRegistrationContributionAssert(BeanRegistrationAotContribution beanRegistrationAotContribution) { + super(beanRegistrationAotContribution, BeanRegistrationContributionAssert.class); + } + + public static BeanRegistrationContributionAssert assertThatAotContribution(BeanRegistrationAotContribution actual) { + return new BeanRegistrationContributionAssert(actual); + } + public BeanRegistrationContributionAssert codeContributionSatisfies( + Consumer assertWith) { + + BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class); + + DefaultGenerationContext generationContext = new DefaultGenerationContext(new ClassNameGenerator(Object.class), + new InMemoryGeneratedFiles()); + + this.actual.applyTo(generationContext, mockBeanRegistrationCode); + + assertWith.accept(new CodeContributionAssert(generationContext)); + + return this; + } +}