Add FluentQuery support to QuerydslLdapRepository.

Closes #269.
This commit is contained in:
Mark Paluch
2021-08-31 14:18:25 +02:00
parent 7fa477c145
commit 1fe292834a
8 changed files with 719 additions and 18 deletions

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2015-2021 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.ldap.repository.query;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.util.Assert;
/**
* {@link Converter} to instantiate DTOs from fully equipped domain objects.
*
* @author Mark Paluch
*/
public class DtoInstantiatingConverter implements Converter<Object, Object> {
private final Class<?> targetType;
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context;
private final EntityInstantiator instantiator;
/**
* Creates a new {@link Converter} to instantiate DTOs.
*
* @param dtoType must not be {@literal null}.
* @param context must not be {@literal null}.
* @param entityInstantiators must not be {@literal null}.
*/
public DtoInstantiatingConverter(Class<?> dtoType,
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
EntityInstantiators entityInstantiators) {
Assert.notNull(dtoType, "DTO type must not be null!");
Assert.notNull(context, "MappingContext must not be null!");
Assert.notNull(entityInstantiators, "EntityInstantiators must not be null!");
this.targetType = dtoType;
this.context = context;
this.instantiator = entityInstantiators.getInstantiatorFor(context.getRequiredPersistentEntity(dtoType));
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public Object convert(Object source) {
if (targetType.isInterface()) {
return source;
}
PersistentEntity<?, ?> sourceEntity = context.getRequiredPersistentEntity(source.getClass());
PersistentPropertyAccessor<?> sourceAccessor = sourceEntity.getPropertyAccessor(source);
PersistentEntity<?, ?> targetEntity = context.getRequiredPersistentEntity(targetType);
PreferredConstructor<?, ? extends PersistentProperty<?>> constructor = targetEntity.getPersistenceConstructor();
Object dto = instantiator.createInstance(targetEntity, new ParameterValueProvider() {
@Override
public Object getParameterValue(Parameter parameter) {
return sourceAccessor.getProperty(sourceEntity.getPersistentProperty(parameter.getName()));
}
});
PersistentPropertyAccessor<?> dtoAccessor = targetEntity.getPropertyAccessor(dto);
targetEntity.doWithProperties((SimplePropertyHandler) property -> {
if (constructor.isConstructorParameter(property)) {
return;
}
dtoAccessor.setProperty(property,
sourceAccessor.getProperty(sourceEntity.getPersistentProperty(property.getName())));
});
return dto;
}
}

View File

@@ -20,9 +20,13 @@ import static org.springframework.data.querydsl.QuerydslUtils.*;
import java.lang.reflect.Method;
import java.util.Optional;
import org.springframework.data.ldap.core.mapping.LdapMappingContext;
import org.springframework.data.ldap.repository.query.AnnotatedLdapRepositoryQuery;
import org.springframework.data.ldap.repository.query.LdapQueryMethod;
import org.springframework.data.ldap.repository.query.PartTreeLdapRepositoryQuery;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.core.EntityInformation;
@@ -50,6 +54,7 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
private final LdapQueryLookupStrategy queryLookupStrategy;
private final LdapOperations ldapOperations;
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext;
/**
* Creates a new {@link LdapRepositoryFactory}.
@@ -62,6 +67,24 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
this.queryLookupStrategy = new LdapQueryLookupStrategy(ldapOperations);
this.ldapOperations = ldapOperations;
this.mappingContext = new LdapMappingContext();
}
/**
* Creates a new {@link LdapRepositoryFactory}.
*
* @param ldapOperations must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
LdapRepositoryFactory(LdapOperations ldapOperations,
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext) {
Assert.notNull(ldapOperations, "LdapOperations must not be null!");
Assert.notNull(mappingContext, "LdapMappingContext must not be null!");
this.queryLookupStrategy = new LdapQueryLookupStrategy(ldapOperations);
this.ldapOperations = ldapOperations;
this.mappingContext = mappingContext;
}
/* (non-Javadoc)
@@ -92,7 +115,8 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
*/
@Override
protected Object getTargetRepository(RepositoryInformation information) {
return getTargetRepositoryViaReflection(information, ldapOperations, ldapOperations.getObjectDirectoryMapper(),
return getTargetRepositoryViaReflection(information, ldapOperations, mappingContext,
ldapOperations.getObjectDirectoryMapper(),
information.getDomainType());
}

View File

@@ -18,6 +18,8 @@ package org.springframework.data.ldap.repository.support;
import javax.naming.Name;
import org.springframework.data.ldap.core.mapping.LdapMappingContext;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
@@ -39,6 +41,7 @@ public class LdapRepositoryFactoryBean<T extends Repository<S, Name>, S>
private @Nullable LdapOperations ldapOperations;
private boolean mappingContextConfigured = false;
private @Nullable MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext;
/**
* Creates a new {@link LdapRepositoryFactoryBean} for the given repository interface.
@@ -64,6 +67,7 @@ public class LdapRepositoryFactoryBean<T extends Repository<S, Name>, S>
public void setMappingContext(MappingContext<?, ?> mappingContext) {
super.setMappingContext(mappingContext);
this.mappingContext = mappingContext;
this.mappingContextConfigured = true;
}
@@ -76,7 +80,8 @@ public class LdapRepositoryFactoryBean<T extends Repository<S, Name>, S>
Assert.state(ldapOperations != null, "LdapOperations must be set");
return new LdapRepositoryFactory(ldapOperations);
return mappingContext != null ? new LdapRepositoryFactory(ldapOperations, mappingContext)
: new LdapRepositoryFactory(ldapOperations);
}
/*

View File

@@ -18,10 +18,13 @@ package org.springframework.data.ldap.repository.support;
import static org.springframework.ldap.query.LdapQueryBuilder.*;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.filter.AbsoluteTrueFilter;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.util.Assert;
import com.querydsl.core.DefaultQueryMetadata;
@@ -40,8 +43,9 @@ import com.querydsl.core.types.Predicate;
public class QuerydslLdapQuery<K> implements FilteredClause<QuerydslLdapQuery<K>> {
private final LdapOperations ldapOperations;
private final Class<? extends K> entityType;
private final Class<K> entityType;
private final LdapSerializer filterGenerator;
private final Consumer<LdapQueryBuilder> queryCustomizer;
private QueryMixin<QuerydslLdapQuery<K>> queryMixin = new QueryMixin<>(this, new DefaultQueryMetadata().noValidate());
@@ -52,7 +56,7 @@ public class QuerydslLdapQuery<K> implements FilteredClause<QuerydslLdapQuery<K>
* @param entityPath must not be {@literal null}.
*/
public QuerydslLdapQuery(LdapOperations ldapOperations, EntityPath<K> entityPath) {
this(ldapOperations, entityPath.getType());
this(ldapOperations, (Class<K>) entityPath.getType());
}
/**
@@ -61,13 +65,30 @@ public class QuerydslLdapQuery<K> implements FilteredClause<QuerydslLdapQuery<K>
* @param ldapOperations must not be {@literal null}.
* @param entityType must not be {@literal null}.
*/
public QuerydslLdapQuery(LdapOperations ldapOperations, Class<? extends K> entityType) {
public QuerydslLdapQuery(LdapOperations ldapOperations, Class<K> entityType) {
this(ldapOperations, entityType, it -> {
});
}
/**
* Creates a new {@link QuerydslLdapQuery}.
*
* @param ldapOperations must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param queryCustomizer must not be {@literal null}.
* @since 2.6
*/
public QuerydslLdapQuery(LdapOperations ldapOperations, Class<K> entityType,
Consumer<LdapQueryBuilder> queryCustomizer) {
Assert.notNull(ldapOperations, "LdapOperations must not be null!");
Assert.notNull(entityType, "Type must not be null!");
Assert.notNull(queryCustomizer, "Query customizer must not be null!");
this.ldapOperations = ldapOperations;
this.entityType = entityType;
this.queryCustomizer = queryCustomizer;
this.filterGenerator = new LdapSerializer(ldapOperations.getObjectDirectoryMapper(), this.entityType);
}
@@ -89,10 +110,17 @@ public class QuerydslLdapQuery<K> implements FilteredClause<QuerydslLdapQuery<K>
LdapQuery ldapQuery = buildQuery();
if (ldapQuery.filter() instanceof AbsoluteTrueFilter) {
return (List<K>) ldapOperations.findAll(entityType);
return ldapOperations.findAll(entityType);
}
return (List<K>) ldapOperations.find(ldapQuery, entityType);
return ldapOperations.find(ldapQuery, entityType);
}
<T> List<T> search(ContextMapper<T> mapper) {
LdapQuery ldapQuery = buildQuery();
return ldapOperations.search(ldapQuery, mapper);
}
public K uniqueResult() {
@@ -102,6 +130,10 @@ public class QuerydslLdapQuery<K> implements FilteredClause<QuerydslLdapQuery<K>
LdapQuery buildQuery() {
Predicate where = queryMixin.getMetadata().getWhere();
return where != null ? query().filter(filterGenerator.handle(where)) : query().filter(new AbsoluteTrueFilter());
LdapQueryBuilder builder = query();
queryCustomizer.accept(builder);
return where != null ? builder.filter(filterGenerator.handle(where)) : builder.filter(new AbsoluteTrueFilter());
}
}

View File

@@ -15,16 +15,42 @@
*/
package org.springframework.data.ldap.repository.support;
import java.beans.FeatureDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.ldap.core.mapping.LdapMappingContext;
import org.springframework.data.ldap.repository.query.DtoInstantiatingConverter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.EntityInstantiators;
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.lang.Nullable;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.util.Assert;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
@@ -36,10 +62,14 @@ import com.querydsl.core.types.Predicate;
* @author Eddu Melendez
* @author Mark Paluch
*/
public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T> implements QuerydslPredicateExecutor<T> {
public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T>
implements QuerydslPredicateExecutor<T>, BeanFactoryAware, BeanClassLoaderAware {
private final LdapOperations ldapOperations;
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context;
private final Class<T> entityType;
private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
private final EntityInstantiators entityInstantiators = new EntityInstantiators();
/**
* Creates a new {@link QuerydslLdapRepository}.
@@ -49,10 +79,42 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T> implement
* @param entityType must not be {@literal null}.
*/
public QuerydslLdapRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm, Class<T> entityType) {
super(ldapOperations, odm, entityType);
this.ldapOperations = ldapOperations;
this.entityType = entityType;
this.context = new LdapMappingContext();
}
/**
* Creates a new {@link QuerydslLdapRepository}.
*
* @param ldapOperations must not be {@literal null}.
* @param context must not be {@literal null}.
* @param odm must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @since 2.6
*/
QuerydslLdapRepository(LdapOperations ldapOperations,
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
ObjectDirectoryMapper odm, Class<T> entityType) {
super(ldapOperations, context, odm, entityType);
this.ldapOperations = ldapOperations;
this.context = context;
this.entityType = entityType;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
projectionFactory.setBeanClassLoader(classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
projectionFactory.setBeanFactory(beanFactory);
}
/*
@@ -61,12 +123,7 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T> implement
*/
@Override
public Optional<T> findOne(Predicate predicate) {
try {
return Optional.of(queryFor(predicate).uniqueResult());
} catch (EmptyResultDataAccessException o_O) {
return Optional.empty();
}
return findBy(predicate, Function.identity()).one();
}
/* (non-Javadoc)
@@ -99,9 +156,6 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T> implement
throw new UnsupportedOperationException();
}
private QuerydslLdapQuery<T> queryFor(Predicate predicate) {
return new QuerydslLdapQuery<>(ldapOperations, entityType).where(predicate);
}
/* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.OrderSpecifier[])
@@ -125,4 +179,236 @@ public class QuerydslLdapRepository<T> extends SimpleLdapRepository<T> implement
public Page<T> findAll(Predicate predicate, Pageable pageable) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findBy(com.querydsl.core.types.Predicate, java.util.function.Function)
*/
@Override
@SuppressWarnings("unchecked")
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));
}
private QuerydslLdapQuery<T> queryFor(Predicate predicate) {
return queryFor(predicate, it -> {
});
}
private QuerydslLdapQuery<T> queryFor(Predicate predicate, Consumer<LdapQueryBuilder> queryBuilderConsumer) {
return new QuerydslLdapQuery<>(ldapOperations, entityType, queryBuilderConsumer).where(predicate);
}
/**
* {@link org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery} using {@link Example}.
*
* @author Mark Paluch
* @since 2.6
*/
class FluentQuerydsl<R> implements FluentQuery.FetchableFluentQuery<R> {
private final Predicate predicate;
private final Sort sort;
private final Class<R> resultType;
private final List<String> projection;
FluentQuerydsl(Predicate predicate, Class<R> resultType) {
this(predicate, Sort.unsorted(), resultType, Collections.emptyList());
}
FluentQuerydsl(Predicate predicate, Sort sort, Class<R> resultType, List<String> projection) {
this.predicate = predicate;
this.sort = sort;
this.resultType = resultType;
this.projection = projection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#sortBy(org.springframework.data.domain.Sort)
*/
@Override
public FetchableFluentQuery<R> sortBy(Sort sort) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#as(java.lang.Class)
*/
@Override
public <R1> FetchableFluentQuery<R1> as(Class<R1> resultType) {
Assert.notNull(projection, "Projection target type must not be null!");
return new FluentQuerydsl<>(predicate, sort, resultType, projection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#project(java.util.Collection)
*/
@Override
public FetchableFluentQuery<R> project(Collection<String> properties) {
Assert.notNull(properties, "Projection properties must not be null!");
return new FluentQuerydsl<>(predicate, sort, resultType, new ArrayList<>(properties));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
*/
@Nullable
@Override
public R oneValue() {
List<T> results = findTop(2);
if (results.isEmpty()) {
return null;
}
if (results.size() > 1) {
throw new IncorrectResultSizeDataAccessException(1);
}
T one = results.get(0);
return getConversionFunction(entityType, resultType).apply(one);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#firstValue()
*/
@Nullable
@Override
public R firstValue() {
List<T> results = findTop(2);
if (results.isEmpty()) {
return null;
}
T one = results.get(0);
return getConversionFunction(entityType, resultType).apply(one);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
*/
@Override
public List<R> all() {
return stream().collect(Collectors.toList());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
*/
@Override
public Page<R> page(Pageable pageable) {
Assert.notNull(pageable, "Pageable must not be null!");
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
*/
@Override
public Stream<R> stream() {
Function<Object, R> conversionFunction = getConversionFunction(entityType, resultType);
return search(null, QuerydslLdapQuery::list).stream().map(conversionFunction);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
*/
@Override
public long count() {
return search(null, q -> q.search(it -> true)).size();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
*/
@Override
public boolean exists() {
return !search(1, q -> q.search(it -> true)).isEmpty();
}
private List<T> findTop(int limit) {
return search(limit, QuerydslLdapQuery::list);
}
private <S> S search(@Nullable Integer limit, Function<QuerydslLdapQuery<T>, S> searchFunction) {
QuerydslLdapQuery<T> q = queryFor(predicate, query -> {
List<String> projection = getProjection();
if (!projection.isEmpty()) {
query.attributes(projection.toArray(new String[0]));
}
if (limit != null) {
query.countLimit(limit);
}
});
return searchFunction.apply(q);
}
@SuppressWarnings("unchecked")
private <P> Function<Object, P> getConversionFunction(Class<?> inputType, Class<P> targetType) {
if (targetType.isAssignableFrom(inputType)) {
return (Function<Object, P>) Function.identity();
}
if (targetType.isInterface()) {
return o -> projectionFactory.createProjection(targetType, o);
}
DtoInstantiatingConverter converter = new DtoInstantiatingConverter(targetType, context, entityInstantiators);
return o -> (P) converter.convert(o);
}
private List<String> getProjection() {
if (projection.isEmpty()) {
if (resultType.isAssignableFrom(entityType)) {
return projection;
}
if (resultType.isInterface()) {
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(resultType);
if (projectionInformation.isClosed()) {
return projectionInformation.getInputProperties().stream().map(FeatureDescriptor::getName)
.collect(Collectors.toList());
}
}
}
return projection;
}
}
}

View File

@@ -27,6 +27,9 @@ import javax.naming.Name;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Persistable;
import org.springframework.data.ldap.repository.LdapRepository;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
import org.springframework.ldap.NameNotFoundException;
@@ -70,6 +73,27 @@ public class SimpleLdapRepository<T> implements LdapRepository<T> {
this.entityType = entityType;
}
/**
* Creates a new {@link SimpleLdapRepository}.
*
* @param ldapOperations must not be {@literal null}.
* @param odm must not be {@literal null}.
* @param entityType must not be {@literal null}.
*/
SimpleLdapRepository(LdapOperations ldapOperations,
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
ObjectDirectoryMapper odm, Class<T> entityType) {
Assert.notNull(ldapOperations, "LdapOperations must not be null!");
Assert.notNull(context, "MappingContext must not be null!");
Assert.notNull(odm, "ObjectDirectoryMapper must not be null!");
Assert.notNull(entityType, "Entity type must not be null!");
this.ldapOperations = ldapOperations;
this.odm = odm;
this.entityType = entityType;
}
// -------------------------------------------------------------------------
// Methods from CrudRepository
// -------------------------------------------------------------------------

View File

@@ -0,0 +1,223 @@
/*
* Copyright 2021 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
*
* http://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.ldap.repository.support;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.Data;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.naming.ldap.LdapName;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoSettings;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper;
import org.springframework.ldap.query.LdapQuery;
/**
* Unit tests for {@link QuerydslLdapRepository}.
*
* @author Mark Paluch
*/
@MockitoSettings
class QuerydslLdapRepositoryUnitTests {
@Mock LdapOperations ldapOperations;
UnitTestPerson walter, hank;
QuerydslLdapRepository<UnitTestPerson> repository;
@BeforeEach
void before() throws Exception {
when(ldapOperations.getObjectDirectoryMapper()).thenReturn(new DefaultObjectDirectoryMapper());
repository = new QuerydslLdapRepository<>(ldapOperations, ldapOperations.getObjectDirectoryMapper(),
UnitTestPerson.class);
walter = new UnitTestPerson(new LdapName("cn=walter"), "Walter", "White", Collections.emptyList(), "US",
"Heisenberg", "000");
hank = new UnitTestPerson(new LdapName("cn=hank"), "Hank", "Schrader", Collections.emptyList(), "US", "DEA", "000");
}
@Test // GH-269
void findByShouldReturnFirst() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter, hank),
Collections.emptyList());
UnitTestPerson first = repository.findBy(QPerson.person.fullName.eq("Walter"),
FluentQuery.FetchableFluentQuery::firstValue);
assertThat(first).isEqualTo(walter);
first = repository.findBy(QPerson.person.fullName.eq("Walter"), Function.identity()).firstValue();
assertThat(first).isNull();
}
@Test // GH-269
void findByShouldReturnOne() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter, hank),
Collections.singletonList(walter));
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(
() -> repository.findBy(QPerson.person.fullName.eq("Walter"), FluentQuery.FetchableFluentQuery::one));
UnitTestPerson one = repository.findBy(QPerson.person.fullName.eq("Walter"),
FluentQuery.FetchableFluentQuery::oneValue);
assertThat(one).isEqualTo(walter);
}
@Test // GH-269
void findByShouldReturnFirstWithProjection() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter));
PersonProjection interfaceProjection = repository.findBy(QPerson.person.fullName.eq("Walter"),
it -> it.as(PersonProjection.class).firstValue());
assertThat(interfaceProjection.getLastName()).isEqualTo("White");
PersonDto dto = repository.findBy(QPerson.person.fullName.eq("Walter"), it -> it.as(PersonDto.class).firstValue());
assertThat(dto.getLastName()).isEqualTo("White");
ArgumentCaptor<LdapQuery> captor = ArgumentCaptor.forClass(LdapQuery.class);
verify(ldapOperations, times(2)).find(captor.capture(), any());
List<LdapQuery> queries = captor.getAllValues();
assertThat(queries.get(0).attributes()).containsOnly("lastName");
assertThat(queries.get(1).attributes()).isNullOrEmpty();
}
@Test // GH-269
void findByShouldReturnOneWithProjection() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class)))
.thenReturn(Collections.singletonList(walter));
PersonProjection interfaceProjection = repository.findBy(QPerson.person.fullName.eq("Walter"),
it -> it.as(PersonProjection.class).oneValue());
assertThat(interfaceProjection.getLastName()).isEqualTo("White");
PersonDto dto = repository.findBy(QPerson.person.fullName.eq("Walter"), it -> it.as(PersonDto.class).oneValue());
assertThat(dto.getLastName()).isEqualTo("White");
ArgumentCaptor<LdapQuery> captor = ArgumentCaptor.forClass(LdapQuery.class);
verify(ldapOperations, times(2)).find(captor.capture(), any());
List<LdapQuery> queries = captor.getAllValues();
assertThat(queries.get(0).attributes()).containsOnly("lastName");
assertThat(queries.get(1).attributes()).isNullOrEmpty();
}
@Test // GH-269
void findByShouldReturnAll() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter, hank));
List<UnitTestPerson> all = repository.findBy(QPerson.person.fullName.eq("Walter"),
FluentQuery.FetchableFluentQuery::all);
assertThat(all).contains(walter);
}
@Test // GH-269
void findByShouldReturnAllWithProjection() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter, hank));
Stream<PersonProjection> all = repository.findBy(QPerson.person.fullName.eq("Walter"),
q -> q.as(PersonProjection.class).stream());
assertThat(all).hasOnlyElementsOfType(PersonProjection.class);
}
@Test // GH-269
void findByShouldReturnStream() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter, hank));
List<UnitTestPerson> all = repository.findBy(QPerson.person.fullName.eq("Walter"),
FluentQuery.FetchableFluentQuery::all);
assertThat(all).contains(walter);
}
@Test // GH-269
void findByShouldReturnStreamWithProjection() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter, hank));
Stream<PersonProjection> all = repository.findBy(QPerson.person.fullName.eq("Walter"),
q -> q.as(PersonProjection.class).stream());
assertThat(all).hasOnlyElementsOfType(PersonProjection.class);
}
@Test // GH-269
void findByShouldReturnCount() {
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(Arrays.asList(walter, hank));
long count = repository.findBy(QPerson.person.fullName.eq("Walter"), FluentQuery.FetchableFluentQuery::count);
assertThat(count).isEqualTo(2);
}
@Test // GH-269
void findByShouldReturnExists() {
when(ldapOperations.search(any(LdapQuery.class), any(ContextMapper.class))).thenReturn(Arrays.asList(true, true),
Collections.emptyList());
boolean exists = repository.findBy(QPerson.person.fullName.eq("Walter"), FluentQuery.FetchableFluentQuery::exists);
assertThat(exists).isTrue();
exists = repository.findBy(QPerson.person.fullName.eq("Walter"), FluentQuery.FetchableFluentQuery::exists);
assertThat(exists).isFalse();
}
interface PersonProjection {
String getLastName();
}
@Data
static class PersonDto {
String lastName;
}
}

View File

@@ -16,6 +16,10 @@
package org.springframework.data.ldap.repository.support;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import javax.naming.Name;
@@ -30,6 +34,8 @@ import org.springframework.ldap.odm.annotations.Transient;
* @author Mattias Hellborg Arthursson
*/
@Entry(objectClasses = {"inetOrgPerson", "organizationalPerson", "person", "top"})
@AllArgsConstructor
@NoArgsConstructor
public class UnitTestPerson {
@Id
private Name dn;