@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user