From 3765d4a985c9e90a971501ccd7afcb501019f647 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 28 Apr 2022 14:51:38 +0200 Subject: [PATCH 1/4] Prepare issue branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 43642a8183..7ac5925a94 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 2.7.0-SNAPSHOT + 2.7.0-GH-2558-SNAPSHOT Spring Data Core From db74bcaf2707d6edd14329ad6997df316d65914a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 28 Apr 2022 15:20:24 +0200 Subject: [PATCH 2/4] Polishing. Rename test. --- ...urceReaderRepositoryPopulatorUnitTests.java} | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) rename src/test/java/org/springframework/data/repository/init/{ResourceReaderRepositoryInitializerUnitTests.java => ResourceReaderRepositoryPopulatorUnitTests.java} (84%) diff --git a/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java b/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulatorUnitTests.java similarity index 84% rename from src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java rename to src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulatorUnitTests.java index 94f92e82ea..c2d0d6927d 100755 --- a/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java +++ b/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulatorUnitTests.java @@ -37,12 +37,13 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** - * Unit tests for {@link UnmarshallingRepositoryInitializer}. + * Unit tests for {@link ResourceReaderRepositoryPopulator}. * * @author Oliver Gierke + * @author Mark Paluch */ @SpringJUnitConfig(classes = SampleConfiguration.class) -class ResourceReaderRepositoryInitializerUnitTests { +class ResourceReaderRepositoryPopulatorUnitTests { @Autowired ProductRepository productRepository; @Autowired Repositories repositories; @@ -63,7 +64,7 @@ void setUp() { void storesSingleObjectCorrectly() throws Exception { Product reference = new Product(); - setUpReferenceAndInititalize(reference); + setUpReferenceAndInitialize(reference); verify(productRepository).save(reference); } @@ -74,7 +75,7 @@ void storesCollectionOfObjectsCorrectly() throws Exception { Product product = new Product(); Collection reference = Collections.singletonList(product); - setUpReferenceAndInititalize(reference); + setUpReferenceAndInitialize(reference); verify(productRepository, times(1)).save(product); } @@ -82,13 +83,13 @@ void storesCollectionOfObjectsCorrectly() throws Exception { @Test // DATACMNS-224 void emitsRepositoriesPopulatedEventIfPublisherConfigured() throws Exception { - RepositoryPopulator populator = setUpReferenceAndInititalize(new User(), publisher); + RepositoryPopulator populator = setUpReferenceAndInitialize(new User(), publisher); ApplicationEvent event = new RepositoriesPopulatedEvent(populator, repositories); verify(publisher, times(1)).publishEvent(event); } - private RepositoryPopulator setUpReferenceAndInititalize(Object reference, ApplicationEventPublisher publish) + private RepositoryPopulator setUpReferenceAndInitialize(Object reference, ApplicationEventPublisher publish) throws Exception { when(reader.readFrom(any(), any())).thenReturn(reference); @@ -102,7 +103,7 @@ private RepositoryPopulator setUpReferenceAndInititalize(Object reference, Appli return populator; } - private RepositoryPopulator setUpReferenceAndInititalize(Object reference) throws Exception { - return setUpReferenceAndInititalize(reference, null); + private RepositoryPopulator setUpReferenceAndInitialize(Object reference) throws Exception { + return setUpReferenceAndInitialize(reference, null); } } From 00f77a83a570508faf9476951c1acd40c250f225 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 28 Apr 2022 15:48:36 +0200 Subject: [PATCH 3/4] Support reactive repositories in ResourceReaderRepositoryPopulator. We now support reactive repositories in addition to imperative repositories when populating repositories from a resource reader. Instead of RepositoryInvokerFactory, we now use a RepositoryPersisterFactory to avoid introducing reactive support to RepositoryInvokerFactory. --- .../ResourceReaderRepositoryPopulator.java | 198 ++++++++++++++++- .../support/DummyRepositoryFactoryBean.java | 4 +- ...rceReaderRepositoryPopulatorUnitTests.java | 206 ++++++++++++++++++ 3 files changed, 393 insertions(+), 15 deletions(-) create mode 100755 src/test/java/org/springframework/data/repository/init/ReactiveResourceReaderRepositoryPopulatorUnitTests.java diff --git a/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java b/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java index e962e37b9b..ef27fd1e6b 100644 --- a/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java +++ b/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java @@ -15,35 +15,49 @@ */ package org.springframework.data.repository.init; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + import java.io.IOException; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.reactivestreams.Publisher; + import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.data.repository.support.DefaultRepositoryInvokerFactory; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultCrudMethods; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.data.repository.support.Repositories; -import org.springframework.data.repository.support.RepositoryInvoker; -import org.springframework.data.repository.support.RepositoryInvokerFactory; +import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * A {@link RepositoryPopulator} using a {@link ResourceReader} to read objects from the configured {@link Resource}s. * * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch * @since 1.4 */ public class ResourceReaderRepositoryPopulator implements RepositoryPopulator, ApplicationEventPublisherAware { - private static final Log logger = LogFactory.getLog(ResourceReaderRepositoryPopulator.class); + private static final Log logger = LogFactory.getLog(ResourceReaderRepositoryPopulator.class); private final ResourceReader reader; private final @Nullable ClassLoader classLoader; @@ -114,7 +128,7 @@ public void populate(Repositories repositories) { Assert.notNull(repositories, "Repositories must not be null!"); - RepositoryInvokerFactory invokerFactory = new DefaultRepositoryInvokerFactory(repositories); + RepositoryPersisterFactory persisterFactory = new RepositoryPersisterFactory(repositories); for (Resource resource : resources) { @@ -125,13 +139,13 @@ public void populate(Repositories repositories) { if (result instanceof Collection) { for (Object element : (Collection) result) { if (element != null) { - persist(element, invokerFactory); + persist(element, persisterFactory); } else { logger.info("Skipping null element found in unmarshal result!"); } } } else { - persist(result, invokerFactory); + persist(result, persisterFactory); } } @@ -158,12 +172,172 @@ private Object readObjectFrom(Resource resource) { * Persists the given {@link Object} using a suitable repository. * * @param object must not be {@literal null}. - * @param invokerFactory must not be {@literal null}. + * @param persisterFactory must not be {@literal null}. + */ + private void persist(Object object, RepositoryPersisterFactory persisterFactory) { + + RepositoryPersister persister = persisterFactory.getPersisterFor(object.getClass()); + logger.debug(String.format("Persisting %s using repository %s", object, persister)); + persister.save(object); + } + + /** + * Factory to create {@link RepositoryPersister} instances. + */ + static class RepositoryPersisterFactory { + + private final Map, RepositoryPersister> persisters = new HashMap<>(); + private final Repositories repositories; + + public RepositoryPersisterFactory(Repositories repositories) { + this.repositories = repositories; + } + + /** + * Obtain a {@link RepositoryPersister}. + * + * @param domainType + * @return + */ + public RepositoryPersister getPersisterFor(Class domainType) { + return persisters.computeIfAbsent(domainType, this::createPersisterFor); + } + + private RepositoryPersister createPersisterFor(Class domainType) { + + RepositoryInformation repositoryInformation = repositories.getRequiredRepositoryInformation(domainType); + Object repository = repositories.getRepositoryFor(domainType).orElseThrow( + () -> new IllegalArgumentException(String.format("No repository found for domain type: %s", domainType))); + + if (repositoryInformation.isReactiveRepository()) { + return repository instanceof ReactiveCrudRepository ? new ReactiveCrudRepositoryPersister(repository) + : new ReflectiveReactivePersister(repositoryInformation, repository); + } + + if (repository instanceof CrudRepository) { + return new CrudRepositoryPersister(repository); + } + + return new ReflectivePersister(repositoryInformation, repository); + } + } + + /** + * Interface defining a save method to persist an object within a repository. + */ + interface RepositoryPersister { + + /** + * Saves the {@code object} in an appropriate repository. + * + * @param object + */ + void save(Object object); + } + + /** + * Reflection variant of a {@link RepositoryPersister}. + */ + private static class ReflectivePersister implements RepositoryPersister { + + private final CrudMethods methods; + private final Object repository; + + public ReflectivePersister(RepositoryMetadata metadata, Object repository) { + this.methods = new DefaultCrudMethods(metadata); + this.repository = repository; + } + + @Override + public void save(Object object) { + + doPersist(object); + } + + Object doPersist(Object object) { + Method method = methods.getSaveMethod()// + .orElseThrow(() -> new IllegalStateException("Repository doesn't have a save-method declared!")); + + return ReflectionUtils.invokeMethod(method, repository, object); + } + + @Override + public String toString() { + return repository.toString(); + } + } + + /** + * Reactive extension to save objects in a reactive repository. + */ + private static class ReflectiveReactivePersister extends ReflectivePersister { + + public ReflectiveReactivePersister(RepositoryMetadata metadata, Object repository) { + super(metadata, repository); + } + + @Override + public void save(Object object) { + + Object wrapper = doPersist(object); + + Publisher publisher = ReactiveWrapperConverters.toWrapper(wrapper, Publisher.class); + + if (!(publisher instanceof Mono)) { + publisher = Flux.from(publisher).collectList(); + } + + Mono.from(publisher).block(); + } + } + + /** + * {@link RepositoryPersister} to operate with {@link CrudRepository}. */ - private void persist(Object object, RepositoryInvokerFactory invokerFactory) { + private static class CrudRepositoryPersister implements RepositoryPersister { - RepositoryInvoker invoker = invokerFactory.getInvokerFor(object.getClass()); - logger.debug(String.format("Persisting %s using repository %s", object, invoker)); - invoker.invokeSave(object); + private final CrudRepository repository; + + @SuppressWarnings("unchecked") + public CrudRepositoryPersister(Object repository) { + + Assert.isInstanceOf(CrudRepository.class, repository); + this.repository = (CrudRepository) repository; + } + + @Override + public void save(Object object) { + repository.save(object); + } + + @Override + public String toString() { + return repository.toString(); + } + } + + /** + * {@link RepositoryPersister} to operate with {@link ReactiveCrudRepository}. + */ + private static class ReactiveCrudRepositoryPersister implements RepositoryPersister { + + private final ReactiveCrudRepository repository; + + @SuppressWarnings("unchecked") + public ReactiveCrudRepositoryPersister(Object repository) { + + Assert.isInstanceOf(ReactiveCrudRepository.class, repository); + this.repository = (ReactiveCrudRepository) repository; + } + + @Override + public void save(Object object) { + repository.save(object).block(); + } + + @Override + public String toString() { + return repository.toString(); + } } } diff --git a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactoryBean.java b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactoryBean.java index 6ef6bb3c10..7714509e1b 100644 --- a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactoryBean.java +++ b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactoryBean.java @@ -17,15 +17,13 @@ import static org.mockito.Mockito.*; -import java.io.Serializable; - import org.springframework.data.mapping.context.SampleMappingContext; import org.springframework.data.repository.Repository; /** * @author Oliver Gierke */ -public class DummyRepositoryFactoryBean, S, ID extends Serializable> +public class DummyRepositoryFactoryBean, S, ID> extends RepositoryFactoryBeanSupport { private final T repository; diff --git a/src/test/java/org/springframework/data/repository/init/ReactiveResourceReaderRepositoryPopulatorUnitTests.java b/src/test/java/org/springframework/data/repository/init/ReactiveResourceReaderRepositoryPopulatorUnitTests.java new file mode 100755 index 0000000000..f2a83eece6 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/init/ReactiveResourceReaderRepositoryPopulatorUnitTests.java @@ -0,0 +1,206 @@ +/* + * Copyright 2012-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.repository.init; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import io.reactivex.rxjava3.core.Single; +import reactor.core.publisher.Mono; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.data.repository.reactive.RxJava3CrudRepository; +import org.springframework.data.repository.support.Repositories; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +/** + * Unit tests for {@link ResourceReaderRepositoryPopulator} using reactive repositories. + * + * @author Mark Paluch + */ +@SpringJUnitConfig(classes = ReactiveResourceReaderRepositoryPopulatorUnitTests.ReactiveSampleConfiguration.class) +class ReactiveResourceReaderRepositoryPopulatorUnitTests { + + @Autowired ReactivePersonRepository personRepository; + @Autowired ReactiveContactRepository contactRepository; + @Autowired RxJavaUserRepository userRepository; + @Autowired Repositories repositories; + + ApplicationEventPublisher publisher; + ResourceReader reader; + Resource resource; + + @BeforeEach + void setUp() { + + this.reader = mock(ResourceReader.class); + this.publisher = mock(ApplicationEventPublisher.class); + this.resource = mock(Resource.class); + } + + @Test + void storesSingleUsingReactiveRepositoryObjectCorrectly() throws Exception { + + ReactivePerson reference = new ReactivePerson(); + when(personRepository.save(reference)).thenReturn(Mono.just(reference)); + setUpReferenceAndInitialize(reference); + + verify(personRepository).save(reference); + } + + @Test + void storesSingleUsingSimpleReactiveRepositoryObjectCorrectly() throws Exception { + + ReactiveContact reference = new ReactiveContact(); + when(contactRepository.save(reference)).thenReturn(Mono.just(reference)); + setUpReferenceAndInitialize(reference); + + verify(contactRepository).save(reference); + } + + @Test + void storesSingleUsingRxJavaRepositoryObjectCorrectly() throws Exception { + + ReactiveUser reference = new ReactiveUser(); + when(userRepository.save(reference)).thenReturn(Single.just(reference)); + setUpReferenceAndInitialize(reference); + + verify(userRepository).save(reference); + } + + @Test + void emitsRepositoriesPopulatedEventIfPublisherConfigured() throws Exception { + + ReactivePerson reference = new ReactivePerson(); + when(personRepository.save(reference)).thenReturn(Mono.just(reference)); + RepositoryPopulator populator = setUpReferenceAndInitialize(reference, publisher); + + ApplicationEvent event = new RepositoriesPopulatedEvent(populator, repositories); + verify(publisher, times(1)).publishEvent(event); + } + + private RepositoryPopulator setUpReferenceAndInitialize(Object reference, ApplicationEventPublisher publish) + throws Exception { + + when(reader.readFrom(any(), any())).thenReturn(reference); + + ResourceReaderRepositoryPopulator populator = new ResourceReaderRepositoryPopulator(reader); + populator.setResources(resource); + populator.setApplicationEventPublisher(publisher); + populator.populate(repositories); + + return populator; + } + + private RepositoryPopulator setUpReferenceAndInitialize(Object reference) throws Exception { + return setUpReferenceAndInitialize(reference, null); + } + + @Configuration + static class ReactiveSampleConfiguration { + + @Autowired ApplicationContext context; + + @Bean + Repositories repositories() { + return new Repositories(context); + } + + @Bean + ReactivePersonRepository personRepository() { + return mock(ReactivePersonRepository.class); + } + + @Bean + RepositoryFactoryBeanSupport, ReactivePerson, Object> personRepositoryFactory( + ReactivePersonRepository personRepository) { + + DummyRepositoryFactoryBean, ReactivePerson, Object> factoryBean = new DummyRepositoryFactoryBean<>( + ReactivePersonRepository.class); + factoryBean.setCustomImplementation(personRepository); + return factoryBean; + } + + @Bean + ReactiveContactRepository contactRepository() { + return mock(ReactiveContactRepository.class); + } + + @Bean + RepositoryFactoryBeanSupport, ReactiveContact, Object> contactRepositoryFactory( + ReactiveContactRepository contactRepository) { + + DummyRepositoryFactoryBean, ReactiveContact, Object> factoryBean = new DummyRepositoryFactoryBean<>( + ReactiveContactRepository.class); + factoryBean.setCustomImplementation(contactRepository); + return factoryBean; + } + + @Bean + RxJavaUserRepository userRepository() { + return mock(RxJavaUserRepository.class); + } + + @Bean + RepositoryFactoryBeanSupport, ReactiveUser, Object> userRepositoryFactory( + RxJavaUserRepository userRepository) { + + DummyRepositoryFactoryBean, ReactiveUser, Object> factoryBean = new DummyRepositoryFactoryBean<>( + RxJavaUserRepository.class); + factoryBean.setCustomImplementation(userRepository); + return factoryBean; + } + } + + static class ReactivePerson { + + } + + static class ReactiveContact { + + } + + static class ReactiveUser { + + } + + interface ReactivePersonRepository extends ReactiveCrudRepository { + + } + + interface ReactiveContactRepository extends Repository { + + Mono save(ReactiveContact contact); + + } + + interface RxJavaUserRepository extends RxJava3CrudRepository { + + } +} From a744eb33a662f69f40b1b8518b349574addc0269 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 11 May 2022 10:36:27 +0200 Subject: [PATCH 4/4] Polishing. Reuse CRUD metdata from repository information and add some tests for the persister factory. --- .../ResourceReaderRepositoryPopulator.java | 7 +- ...rceReaderRepositoryPopulatorUnitTests.java | 11 +- .../RepositoryPersisterFactoryUnitTests.java | 105 ++++++++++++++++++ 3 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 src/test/java/org/springframework/data/repository/init/RepositoryPersisterFactoryUnitTests.java diff --git a/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java b/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java index ef27fd1e6b..23c7a7afec 100644 --- a/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java +++ b/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java @@ -29,7 +29,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; - import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.io.Resource; @@ -207,7 +206,7 @@ private RepositoryPersister createPersisterFor(Class domainType) { RepositoryInformation repositoryInformation = repositories.getRequiredRepositoryInformation(domainType); Object repository = repositories.getRepositoryFor(domainType).orElseThrow( - () -> new IllegalArgumentException(String.format("No repository found for domain type: %s", domainType))); + () -> new IllegalStateException(String.format("No repository found for domain type: %s", domainType))); if (repositoryInformation.isReactiveRepository()) { return repository instanceof ReactiveCrudRepository ? new ReactiveCrudRepositoryPersister(repository) @@ -244,13 +243,13 @@ private static class ReflectivePersister implements RepositoryPersister { private final Object repository; public ReflectivePersister(RepositoryMetadata metadata, Object repository) { - this.methods = new DefaultCrudMethods(metadata); + + this.methods = metadata.getCrudMethods(); this.repository = repository; } @Override public void save(Object object) { - doPersist(object); } diff --git a/src/test/java/org/springframework/data/repository/init/ReactiveResourceReaderRepositoryPopulatorUnitTests.java b/src/test/java/org/springframework/data/repository/init/ReactiveResourceReaderRepositoryPopulatorUnitTests.java index f2a83eece6..ef72603121 100755 --- a/src/test/java/org/springframework/data/repository/init/ReactiveResourceReaderRepositoryPopulatorUnitTests.java +++ b/src/test/java/org/springframework/data/repository/init/ReactiveResourceReaderRepositoryPopulatorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * 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. @@ -43,6 +43,7 @@ * Unit tests for {@link ResourceReaderRepositoryPopulator} using reactive repositories. * * @author Mark Paluch + * @author Christoph Strobl */ @SpringJUnitConfig(classes = ReactiveResourceReaderRepositoryPopulatorUnitTests.ReactiveSampleConfiguration.class) class ReactiveResourceReaderRepositoryPopulatorUnitTests { @@ -64,7 +65,7 @@ void setUp() { this.resource = mock(Resource.class); } - @Test + @Test // GH-2558 void storesSingleUsingReactiveRepositoryObjectCorrectly() throws Exception { ReactivePerson reference = new ReactivePerson(); @@ -74,7 +75,7 @@ void storesSingleUsingReactiveRepositoryObjectCorrectly() throws Exception { verify(personRepository).save(reference); } - @Test + @Test // GH-2558 void storesSingleUsingSimpleReactiveRepositoryObjectCorrectly() throws Exception { ReactiveContact reference = new ReactiveContact(); @@ -84,7 +85,7 @@ void storesSingleUsingSimpleReactiveRepositoryObjectCorrectly() throws Exception verify(contactRepository).save(reference); } - @Test + @Test // GH-2558 void storesSingleUsingRxJavaRepositoryObjectCorrectly() throws Exception { ReactiveUser reference = new ReactiveUser(); @@ -105,7 +106,7 @@ void emitsRepositoriesPopulatedEventIfPublisherConfigured() throws Exception { verify(publisher, times(1)).publishEvent(event); } - private RepositoryPopulator setUpReferenceAndInitialize(Object reference, ApplicationEventPublisher publish) + private RepositoryPopulator setUpReferenceAndInitialize(Object reference, ApplicationEventPublisher publisher) throws Exception { when(reader.readFrom(any(), any())).thenReturn(reference); diff --git a/src/test/java/org/springframework/data/repository/init/RepositoryPersisterFactoryUnitTests.java b/src/test/java/org/springframework/data/repository/init/RepositoryPersisterFactoryUnitTests.java new file mode 100644 index 0000000000..6e45a15bd3 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/init/RepositoryPersisterFactoryUnitTests.java @@ -0,0 +1,105 @@ +/* + * 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.repository.init; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.init.ResourceReaderRepositoryPopulator.RepositoryPersisterFactory; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.data.repository.support.Repositories; + +/** + * @author Christoph Strobl + */ +@ExtendWith(MockitoExtension.class) +class RepositoryPersisterFactoryUnitTests { + + @Mock Repositories repositories; + @Mock RepositoryInformation repoInfo; + RepositoryPersisterFactory factory; + + @BeforeEach + void beforeEach() { + factory = new RepositoryPersisterFactory(repositories); + } + + @Test // GH-2558 + void errorsOnNoRepoFoundForType() { + + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> factory.getPersisterFor(Object.class)) + .withMessageContaining("No repository found"); + } + + @Test // GH-2558 + void usesCrudRepoPersisterForNonReactiveCrudRepo() { + + CrudRepository crudRepository = mock(CrudRepository.class); + when(repositories.getRequiredRepositoryInformation(any())).thenReturn(repoInfo); + when(repoInfo.isReactiveRepository()).thenReturn(false); + when(repositories.getRepositoryFor(Mockito.any())).thenReturn(Optional.of(crudRepository)); + + assertThat(factory.getPersisterFor(Object.class)) + .satisfies(it -> it.getClass().getName().contains("CrudRepositoryPersister")); + } + + @Test // GH-2558 + void usesReactiveCrudRepoPersisterForReactiveCrudRepo() { + + ReactiveCrudRepository crudRepository = mock(ReactiveCrudRepository.class); + when(repositories.getRequiredRepositoryInformation(any())).thenReturn(repoInfo); + when(repoInfo.isReactiveRepository()).thenReturn(true); + when(repositories.getRepositoryFor(Mockito.any())).thenReturn(Optional.of(crudRepository)); + + assertThat(factory.getPersisterFor(Object.class)) + .satisfies(it -> it.getClass().getName().contains("ReactiveCrudRepositoryPersister")); + } + + @Test // GH-2558 + void usesReflectiveRepoPersisterForNonReactiveNonCrudRepo() { + + Repository repository = mock(Repository.class); + when(repositories.getRequiredRepositoryInformation(any())).thenReturn(repoInfo); + when(repoInfo.isReactiveRepository()).thenReturn(false); + when(repositories.getRepositoryFor(Mockito.any())).thenReturn(Optional.of(repository)); + + assertThat(factory.getPersisterFor(Object.class)) + .satisfies(it -> it.getClass().getName().contains("ReflectivePersister")); + } + + @Test // GH-2558 + void usesReactiveReflectiveRepoPersisterForReactiveNonCrudRepo() { + + Repository repository = mock(Repository.class); + when(repositories.getRequiredRepositoryInformation(any())).thenReturn(repoInfo); + when(repoInfo.isReactiveRepository()).thenReturn(true); + when(repositories.getRepositoryFor(Mockito.any())).thenReturn(Optional.of(repository)); + + assertThat(factory.getPersisterFor(Object.class)) + .satisfies(it -> it.getClass().getName().contains("ReflectiveReactivePersister")); + } +}