Allow queries using Pageable for the first page only.

Closes #274
This commit is contained in:
Mark Paluch
2021-08-31 14:39:46 +02:00
parent 1fe292834a
commit 79fe9244f4
3 changed files with 143 additions and 16 deletions

View File

@@ -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}.

View File

@@ -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<T> extends SimpleLdapRepository<T>
*/
@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<T> 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<T> 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<T> extends SimpleLdapRepository<T>
*/
@Override
public Iterable<T> 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<T> extends SimpleLdapRepository<T>
*/
@Override
public Page<T> 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<T> extends SimpleLdapRepository<T>
public <S extends T, R> R findBy(Predicate predicate,
Function<FluentQuery.FetchableFluentQuery<S>, 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<S>) entityType));
@@ -202,6 +230,9 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T>
}
private QuerydslLdapQuery<T> queryFor(Predicate predicate, Consumer<LdapQueryBuilder> queryBuilderConsumer) {
Assert.notNull(predicate, "Predicate must not be null!");
return new QuerydslLdapQuery<>(ldapOperations, entityType, queryBuilderConsumer).where(predicate);
}
@@ -281,7 +312,7 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T>
}
T one = results.get(0);
return getConversionFunction(entityType, resultType).apply(one);
return getConversionFunction().apply(one);
}
/*
@@ -299,9 +330,10 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T>
}
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<T> extends SimpleLdapRepository<T>
public Page<R> 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<Object, R> 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<T> extends SimpleLdapRepository<T>
@Override
public Stream<R> stream() {
Function<Object, R> conversionFunction = getConversionFunction(entityType, resultType);
Function<Object, R> conversionFunction = getConversionFunction();
return search(null, QuerydslLdapQuery::list).stream().map(conversionFunction);
}
@@ -390,6 +436,10 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T>
return o -> (P) converter.convert(o);
}
private Function<Object, R> getConversionFunction() {
return getConversionFunction(entityType, resultType);
}
private List<String> getProjection() {
if (projection.isEmpty()) {
@@ -410,5 +460,7 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T>
return projection;
}
}
}

View File

@@ -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<PersonProjection> 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<LdapQuery> 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);