From 79fe9244f4cda8258c149c1a04e9f49e772a3495 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 31 Aug 2021 14:39:46 +0200 Subject: [PATCH] Allow queries using Pageable for the first page only. Closes #274 --- .../support/LdapRepositoryFactory.java | 39 +++++++++- .../support/QuerydslLdapRepository.java | 78 +++++++++++++++---- .../QuerydslLdapRepositoryUnitTests.java | 42 +++++++++- 3 files changed, 143 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactory.java b/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactory.java index 4a06e7b..13230a2 100644 --- a/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactory.java +++ b/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactory.java @@ -17,6 +17,7 @@ package org.springframework.data.ldap.repository.support; import static org.springframework.data.querydsl.QuerydslUtils.*; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Optional; @@ -115,7 +116,15 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport { */ @Override protected Object getTargetRepository(RepositoryInformation information) { - return getTargetRepositoryViaReflection(information, ldapOperations, mappingContext, + + boolean acceptsMappingContext = acceptsMappingContext(information); + + if (acceptsMappingContext) { + return getTargetRepositoryViaReflection(information, ldapOperations, mappingContext, + ldapOperations.getObjectDirectoryMapper(), information.getDomainType()); + } + + return getTargetRepositoryViaReflection(information, ldapOperations, ldapOperations.getObjectDirectoryMapper(), information.getDomainType()); } @@ -130,9 +139,35 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport { return Optional.of(queryLookupStrategy); } + /** + * Allow creation of repository base classes that do not accept a {@link LdapMappingContext} that was introduced with + * version 2.6. + * + * @param information + * @return + */ + private static boolean acceptsMappingContext(RepositoryInformation information) { + + Class repositoryBaseClass = information.getRepositoryBaseClass(); + + Constructor[] declaredConstructors = repositoryBaseClass.getDeclaredConstructors(); + + boolean acceptsMappingContext = false; + + for (Constructor declaredConstructor : declaredConstructors) { + Class[] parameterTypes = declaredConstructor.getParameterTypes(); + + if (parameterTypes.length == 4 && parameterTypes[1].isAssignableFrom(LdapMappingContext.class)) { + acceptsMappingContext = true; + } + } + + return acceptsMappingContext; + } + private static final class LdapQueryLookupStrategy implements QueryLookupStrategy { - private LdapOperations ldapOperations; + private final LdapOperations ldapOperations; /** * @param ldapOperations must not be {@literal null}. diff --git a/src/main/java/org/springframework/data/ldap/repository/support/QuerydslLdapRepository.java b/src/main/java/org/springframework/data/ldap/repository/support/QuerydslLdapRepository.java index 9de17cd..643f87e 100644 --- a/src/main/java/org/springframework/data/ldap/repository/support/QuerydslLdapRepository.java +++ b/src/main/java/org/springframework/data/ldap/repository/support/QuerydslLdapRepository.java @@ -30,7 +30,6 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; @@ -46,6 +45,7 @@ import org.springframework.data.projection.ProjectionInformation; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.query.FluentQuery; +import org.springframework.data.support.PageableExecutionUtils; import org.springframework.lang.Nullable; import org.springframework.ldap.core.LdapOperations; import org.springframework.ldap.odm.core.ObjectDirectoryMapper; @@ -139,29 +139,40 @@ public class QuerydslLdapRepository extends SimpleLdapRepository */ @Override public long count(Predicate predicate) { - return findAll(predicate).size(); + return findBy(predicate, FluentQuery.FetchableFluentQuery::count); } /* (non-Javadoc) * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#exists(com.querydsl.core.types.Predicate) */ public boolean exists(Predicate predicate) { - return count(predicate) > 0; + return findBy(predicate, FluentQuery.FetchableFluentQuery::exists); } /* (non-Javadoc) * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Sort) */ public Iterable findAll(Predicate predicate, Sort sort) { - throw new UnsupportedOperationException(); - } + Assert.notNull(sort, "Pageable must not be null!"); + + if (sort.isUnsorted()) { + return findAll(predicate); + } + + throw new UnsupportedOperationException("Sorting is not supported"); + } /* (non-Javadoc) * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.OrderSpecifier[]) */ public Iterable findAll(OrderSpecifier... orders) { - throw new UnsupportedOperationException(); + + if (orders.length == 0) { + return findAll(); + } + + throw new UnsupportedOperationException("Sorting is not supported"); } /* (non-Javadoc) @@ -169,7 +180,12 @@ public class QuerydslLdapRepository extends SimpleLdapRepository */ @Override public Iterable findAll(Predicate predicate, OrderSpecifier... orders) { - throw new UnsupportedOperationException(); + + if (orders.length == 0) { + return findAll(predicate); + } + + throw new UnsupportedOperationException("Sorting is not supported"); } /* (non-Javadoc) @@ -177,7 +193,20 @@ public class QuerydslLdapRepository extends SimpleLdapRepository */ @Override public Page findAll(Predicate predicate, Pageable pageable) { - throw new UnsupportedOperationException(); + + Assert.notNull(pageable, "Pageable must not be null!"); + + if (pageable.isUnpaged()) { + return PageableExecutionUtils.getPage(findAll(predicate), pageable, () -> count(predicate)); + } + + if (pageable.getSort().isUnsorted() && pageable.getPageNumber() == 0) { + + return PageableExecutionUtils.getPage(queryFor(predicate, q -> q.countLimit(pageable.getPageSize())).list(), + pageable, () -> count(predicate)); + } + + throw new UnsupportedOperationException("Pagination and Sorting is not supported"); } /* @@ -189,7 +218,6 @@ public class QuerydslLdapRepository extends SimpleLdapRepository public R findBy(Predicate predicate, Function, R> queryFunction) { - Assert.notNull(predicate, "Predicate must not be null!"); Assert.notNull(queryFunction, "Query function must not be null!"); return queryFunction.apply(new FluentQuerydsl<>(predicate, (Class) entityType)); @@ -202,6 +230,9 @@ public class QuerydslLdapRepository extends SimpleLdapRepository } private QuerydslLdapQuery queryFor(Predicate predicate, Consumer queryBuilderConsumer) { + + Assert.notNull(predicate, "Predicate must not be null!"); + return new QuerydslLdapQuery<>(ldapOperations, entityType, queryBuilderConsumer).where(predicate); } @@ -281,7 +312,7 @@ public class QuerydslLdapRepository extends SimpleLdapRepository } T one = results.get(0); - return getConversionFunction(entityType, resultType).apply(one); + return getConversionFunction().apply(one); } /* @@ -299,9 +330,10 @@ public class QuerydslLdapRepository extends SimpleLdapRepository } T one = results.get(0); - return getConversionFunction(entityType, resultType).apply(one); + return getConversionFunction().apply(one); } + /* * (non-Javadoc) * @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all() @@ -319,7 +351,21 @@ public class QuerydslLdapRepository extends SimpleLdapRepository public Page page(Pageable pageable) { Assert.notNull(pageable, "Pageable must not be null!"); - throw new UnsupportedOperationException(); + + if (pageable.isUnpaged()) { + return PageableExecutionUtils.getPage(all(), pageable, this::count); + } + + if (pageable.getSort().isUnsorted() && pageable.getPageNumber() == 0) { + + Function conversionFunction = getConversionFunction(); + + return PageableExecutionUtils.getPage( + findTop(pageable.getPageSize()).stream().map(conversionFunction).collect(Collectors.toList()), pageable, + this::count); + } + + throw new UnsupportedOperationException("Pagination and Sorting is not supported"); } /* @@ -329,7 +375,7 @@ public class QuerydslLdapRepository extends SimpleLdapRepository @Override public Stream stream() { - Function conversionFunction = getConversionFunction(entityType, resultType); + Function conversionFunction = getConversionFunction(); return search(null, QuerydslLdapQuery::list).stream().map(conversionFunction); } @@ -390,6 +436,10 @@ public class QuerydslLdapRepository extends SimpleLdapRepository return o -> (P) converter.convert(o); } + private Function getConversionFunction() { + return getConversionFunction(entityType, resultType); + } + private List getProjection() { if (projection.isEmpty()) { @@ -410,5 +460,7 @@ public class QuerydslLdapRepository extends SimpleLdapRepository return projection; } + } + } diff --git a/src/test/java/org/springframework/data/ldap/repository/support/QuerydslLdapRepositoryUnitTests.java b/src/test/java/org/springframework/data/ldap/repository/support/QuerydslLdapRepositoryUnitTests.java index 2bff347..e164dd3 100644 --- a/src/test/java/org/springframework/data/ldap/repository/support/QuerydslLdapRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/ldap/repository/support/QuerydslLdapRepositoryUnitTests.java @@ -35,6 +35,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.FluentQuery; import org.springframework.ldap.core.ContextMapper; import org.springframework.ldap.core.LdapOperations; @@ -166,6 +169,43 @@ class QuerydslLdapRepositoryUnitTests { assertThat(all).hasOnlyElementsOfType(PersonProjection.class); } + @Test // GH-269 + void findByShouldReturnFirstPage() { + + when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))) + .thenReturn(Collections.singletonList(walter)); + when(ldapOperations.search(any(LdapQuery.class), any(ContextMapper.class))).thenReturn(Arrays.asList(true, true)); + + Page page = repository.findBy(QPerson.person.fullName.eq("Walter"), + it -> it.as(PersonProjection.class).page(PageRequest.of(0, 1, Sort.unsorted()))); + assertThat(page.getContent().get(0).getLastName()).isEqualTo("White"); + assertThat(page.getTotalPages()).isEqualTo(2); + + ArgumentCaptor captor = ArgumentCaptor.forClass(LdapQuery.class); + + verify(ldapOperations).find(captor.capture(), any()); + + LdapQuery query = captor.getValue(); + + assertThat(query.countLimit()).isEqualTo(1); + } + + @Test // GH-269 + void shouldRejectNextPage() { + + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> repository.findBy(QPerson.person.fullName.eq("Walter"), + it -> it.as(PersonProjection.class).page(PageRequest.of(1, 1, Sort.unsorted())))); + } + + @Test // GH-269 + void shouldRejectSortedPage() { + + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> repository.findBy(QPerson.person.fullName.eq("Walter"), + it -> it.as(PersonProjection.class).page(PageRequest.of(0, 1, Sort.by(Sort.Direction.ASC, "foo"))))); + } + @Test // GH-269 void findByShouldReturnStream() { @@ -191,7 +231,7 @@ class QuerydslLdapRepositoryUnitTests { @Test // GH-269 void findByShouldReturnCount() { - when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter, hank)); + when(ldapOperations.search(any(LdapQuery.class), any(ContextMapper.class))).thenReturn(Arrays.asList(true, true)); long count = repository.findBy(QPerson.person.fullName.eq("Walter"), FluentQuery.FetchableFluentQuery::count);