Add support for projection and Stream queries.
We now support projections on repository query methods and query methods returning a Stream.
interface PersonRepository extends LdapRepository<Person> {
Stream<PersonProjection> streamAllByLastName(String lastName);
PersonProjection findByLastName(String lastname);
<T> T findByLastName(String lastname, Class<T> projection);
}
Closes #275
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
[[new-features]]
|
||||
= New and Noteworthy
|
||||
|
||||
[[new-features.2.6]]
|
||||
== What's New in Spring Data LDAP 2.6
|
||||
* <<repositories.query-streaming,Repository query methods returning a `Stream`>> from the underlying result `List`.
|
||||
* <<projections,Interface and DTO projections returned by query methods>>.
|
||||
|
||||
[[new-features.2.1]]
|
||||
== What's New in Spring Data LDAP 2.1
|
||||
* CDI extension to create LDAP repositories within a CDI container.
|
||||
|
||||
@@ -224,6 +224,7 @@ The following table provides samples of the keywords that you can use with query
|
||||
|
||||
|===
|
||||
|
||||
include::../{spring-data-commons-docs}/repository-projections.adoc[leveloffset=+2]
|
||||
|
||||
=== QueryDSL Support
|
||||
Basic QueryDSL support is included in Spring LDAP. This support includes the following:
|
||||
|
||||
@@ -15,10 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.ldap.repository.query;
|
||||
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import static org.springframework.data.ldap.repository.query.LdapQueryExecution.*;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.ldap.repository.Query;
|
||||
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.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -34,6 +41,8 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
|
||||
private final LdapQueryMethod queryMethod;
|
||||
private final Class<?> entityType;
|
||||
private final LdapOperations ldapOperations;
|
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext;
|
||||
private final EntityInstantiators instantiators;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractLdapRepositoryQuery} instance given {@link LdapQuery}, {@link Class} and
|
||||
@@ -42,8 +51,12 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param ldapOperations must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param instantiators must not be {@literal null}.
|
||||
*/
|
||||
public AbstractLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations) {
|
||||
public AbstractLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext,
|
||||
EntityInstantiators instantiators) {
|
||||
|
||||
Assert.notNull(queryMethod, "LdapQueryMethod must not be null!");
|
||||
Assert.notNull(entityType, "Entity type must not be null!");
|
||||
@@ -52,6 +65,8 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
|
||||
this.queryMethod = queryMethod;
|
||||
this.entityType = entityType;
|
||||
this.ldapOperations = ldapOperations;
|
||||
this.mappingContext = mappingContext;
|
||||
this.instantiators = instantiators;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -61,16 +76,28 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public final Object execute(Object[] parameters) {
|
||||
|
||||
LdapQuery query = createQuery(parameters);
|
||||
LdapParametersParameterAccessor parameterAccessor = new LdapParametersParameterAccessor(queryMethod, parameters);
|
||||
LdapQuery query = createQuery(parameterAccessor);
|
||||
|
||||
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||
Class<?> typeToRead = processor.getReturnedType().getDomainType();
|
||||
|
||||
ResultProcessingConverter converter = new ResultProcessingConverter(processor, mappingContext, instantiators);
|
||||
ResultProcessingExecution execution = new ResultProcessingExecution(
|
||||
getLdapQueryExecutionToWrap(typeToRead, converter), converter);
|
||||
|
||||
return execution.execute(query);
|
||||
}
|
||||
|
||||
private LdapQueryExecution getLdapQueryExecutionToWrap(Class<?> typeToRead,
|
||||
Converter<Object, Object> resultProcessing) {
|
||||
|
||||
if (queryMethod.isCollectionQuery()) {
|
||||
return ldapOperations.find(query, entityType);
|
||||
return new CollectionExecution(ldapOperations, typeToRead);
|
||||
} else if (queryMethod.isStreamQuery()) {
|
||||
return new StreamExecution(ldapOperations, typeToRead, resultProcessing);
|
||||
} else {
|
||||
try {
|
||||
return ldapOperations.findOne(query, entityType);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
}
|
||||
return new FindOneExecution(ldapOperations, typeToRead);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +107,7 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected abstract LdapQuery createQuery(Object[] parameters);
|
||||
protected abstract LdapQuery createQuery(LdapParameterAccessor parameters);
|
||||
|
||||
/**
|
||||
* @return
|
||||
|
||||
@@ -18,6 +18,10 @@ package org.springframework.data.ldap.repository.query;
|
||||
import static org.springframework.ldap.query.LdapQueryBuilder.*;
|
||||
|
||||
import org.springframework.data.ldap.repository.Query;
|
||||
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.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -38,10 +42,14 @@ public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
|
||||
* @param queryMethod the QueryMethod.
|
||||
* @param entityType the managed class.
|
||||
* @param ldapOperations the LdapOperations instance to use.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param instantiators must not be {@literal null}.
|
||||
*/
|
||||
public AnnotatedLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations) {
|
||||
public AnnotatedLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext,
|
||||
EntityInstantiators instantiators) {
|
||||
|
||||
super(queryMethod, entityType, ldapOperations);
|
||||
super(queryMethod, entityType, ldapOperations, mappingContext, instantiators);
|
||||
|
||||
Assert.notNull(queryMethod.getQueryAnnotation(), "Annotation must be present");
|
||||
Assert.hasLength(queryMethod.getQueryAnnotation().value(), "Query filter must be specified");
|
||||
@@ -50,15 +58,15 @@ public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.ldap.repository.query.AbstractLdapRepositoryQuery#createQuery(java.lang.Object[])
|
||||
* @see org.springframework.data.ldap.repository.query.AbstractLdapRepositoryQuery#createQuery(org.springframework.data.ldap.repository.query.LdapParameterAccessor)
|
||||
*/
|
||||
@Override
|
||||
protected LdapQuery createQuery(Object[] parameters) {
|
||||
protected LdapQuery createQuery(LdapParameterAccessor parameters) {
|
||||
|
||||
return query().base(queryAnnotation.base()) //
|
||||
.searchScope(queryAnnotation.searchScope()) //
|
||||
.countLimit(queryAnnotation.countLimit()) //
|
||||
.timeLimit(queryAnnotation.timeLimit()) //
|
||||
.filter(queryAnnotation.value(), parameters);
|
||||
.filter(queryAnnotation.value(), parameters.getBindableParameterValues());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.query;
|
||||
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
|
||||
/**
|
||||
* LDAP-specific {@link ParameterAccessor} exposing all bindable parameters.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.6
|
||||
*/
|
||||
interface LdapParameterAccessor extends ParameterAccessor {
|
||||
|
||||
/**
|
||||
* Returns the bindable parameter values of the underlying query method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object[] getBindableParameterValues();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.query;
|
||||
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
|
||||
/**
|
||||
* LDAP-specific {@link ParametersParameterAccessor}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.6
|
||||
*/
|
||||
class LdapParametersParameterAccessor extends ParametersParameterAccessor implements LdapParameterAccessor {
|
||||
|
||||
/**
|
||||
* Creates a new {@link LdapParametersParameterAccessor}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param values must not be {@literal null}.
|
||||
*/
|
||||
public LdapParametersParameterAccessor(QueryMethod method, Object[] values) {
|
||||
super(method.getParameters(), values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getBindableParameterValues() {
|
||||
|
||||
Parameters<?, ?> bindableParameters = getParameters().getBindableParameters();
|
||||
int count = bindableParameters.getNumberOfParameters();
|
||||
|
||||
if (count == 0) {
|
||||
return new Object[0];
|
||||
}
|
||||
|
||||
Object[] values = new Object[count];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
values[i] = getBindableValue(i);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,12 +18,11 @@ package org.springframework.data.ldap.repository.query;
|
||||
import static org.springframework.ldap.query.LdapQueryBuilder.*;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
@@ -45,26 +44,28 @@ class LdapQueryCreator extends AbstractQueryCreator<LdapQuery, ContainerCriteria
|
||||
|
||||
private final Class<?> entityType;
|
||||
private final ObjectDirectoryMapper mapper;
|
||||
private final List<String> inputProperties;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link LdapQueryCreator}.
|
||||
*
|
||||
* @param tree must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param mapper must not be {@literal null}.
|
||||
* @param values must not be {@literal null}.
|
||||
* @param inputProperties must not be {@literal null}.
|
||||
*/
|
||||
LdapQueryCreator(PartTree tree, Parameters<?, ?> parameters, Class<?> entityType, ObjectDirectoryMapper mapper,
|
||||
Object[] values) {
|
||||
LdapQueryCreator(PartTree tree, Class<?> entityType, ObjectDirectoryMapper mapper,
|
||||
LdapParameterAccessor parameterAccessor, List<String> inputProperties) {
|
||||
|
||||
super(tree, new ParametersParameterAccessor(parameters, values));
|
||||
super(tree, parameterAccessor);
|
||||
|
||||
Assert.notNull(entityType, "Entity type must not be null!");
|
||||
Assert.notNull(mapper, "ObjectDirectoryMapper must not be null!");
|
||||
|
||||
this.entityType = entityType;
|
||||
this.mapper = mapper;
|
||||
this.inputProperties = inputProperties;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -81,6 +82,10 @@ class LdapQueryCreator extends AbstractQueryCreator<LdapQuery, ContainerCriteria
|
||||
query = query.base(entry.base());
|
||||
}
|
||||
|
||||
if (!inputProperties.isEmpty()) {
|
||||
query.attributes(inputProperties.toArray(new String[0]));
|
||||
}
|
||||
|
||||
ConditionCriteria criteria = query.where(getAttribute(part));
|
||||
|
||||
return appendCondition(part, iterator, criteria);
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.query;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
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.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Set of classes to contain query execution strategies. Depending (mostly) on the return type of a
|
||||
* {@link org.springframework.data.repository.query.QueryMethod} a {@link AbstractLdapRepositoryQuery} can be executed
|
||||
* in various flavors.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.6
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface LdapQueryExecution {
|
||||
|
||||
Object execute(LdapQuery query);
|
||||
|
||||
/**
|
||||
* {@link LdapQueryExecution} returning a single object.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
final class FindOneExecution implements LdapQueryExecution {
|
||||
|
||||
private final LdapOperations operations;
|
||||
private final Class<?> entityType;
|
||||
|
||||
FindOneExecution(LdapOperations operations, Class<?> entityType) {
|
||||
this.operations = operations;
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(LdapQuery query) {
|
||||
try {
|
||||
return operations.findOne(query, entityType);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link LdapQueryExecution} returning a list of objects.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
final class CollectionExecution implements LdapQueryExecution {
|
||||
|
||||
private final LdapOperations operations;
|
||||
private final Class<?> entityType;
|
||||
|
||||
CollectionExecution(LdapOperations operations, Class<?> entityType) {
|
||||
this.operations = operations;
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(LdapQuery query) {
|
||||
return operations.find(query, entityType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link LdapQueryExecution} for a Stream.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
final class StreamExecution implements LdapQueryExecution {
|
||||
|
||||
private final LdapOperations operations;
|
||||
private final Class<?> entityType;
|
||||
private final Converter<Object, Object> resultProcessing;
|
||||
|
||||
StreamExecution(LdapOperations operations, Class<?> entityType, Converter<Object, Object> resultProcessing) {
|
||||
this.operations = operations;
|
||||
this.entityType = entityType;
|
||||
this.resultProcessing = resultProcessing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(LdapQuery query) {
|
||||
return operations.find(query, entityType).stream().map(resultProcessing::convert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link LdapQueryExecution} that wraps the results of the given delegate with the given result processing.
|
||||
*/
|
||||
final class ResultProcessingExecution implements LdapQueryExecution {
|
||||
|
||||
private final LdapQueryExecution delegate;
|
||||
private final Converter<Object, Object> converter;
|
||||
|
||||
public ResultProcessingExecution(LdapQueryExecution delegate, Converter<Object, Object> converter) {
|
||||
this.delegate = delegate;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(LdapQuery query) {
|
||||
return converter.convert(delegate.execute(query));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
final class ResultProcessingConverter implements Converter<Object, Object> {
|
||||
|
||||
private final ResultProcessor processor;
|
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext;
|
||||
private final EntityInstantiators instantiators;
|
||||
|
||||
public ResultProcessingConverter(ResultProcessor processor,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext,
|
||||
EntityInstantiators instantiators) {
|
||||
this.processor = processor;
|
||||
this.mappingContext = mappingContext;
|
||||
this.instantiators = instantiators;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object convert(Object source) {
|
||||
|
||||
ReturnedType returnedType = processor.getReturnedType();
|
||||
|
||||
if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) {
|
||||
return source;
|
||||
}
|
||||
|
||||
if (source != null && returnedType.isInstance(source)) {
|
||||
return source;
|
||||
}
|
||||
Converter<Object, Object> converter = new DtoInstantiatingConverter(returnedType.getReturnedType(),
|
||||
mappingContext, instantiators);
|
||||
|
||||
return processor.processResult(source, converter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.ldap.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
|
||||
@@ -40,10 +48,14 @@ public class PartTreeLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param ldapOperations must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param instantiators must not be {@literal null}.
|
||||
*/
|
||||
public PartTreeLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations) {
|
||||
public PartTreeLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext,
|
||||
EntityInstantiators instantiators) {
|
||||
|
||||
super(queryMethod, entityType, ldapOperations);
|
||||
super(queryMethod, entityType, ldapOperations, mappingContext, instantiators);
|
||||
|
||||
partTree = new PartTree(queryMethod.getName(), entityType);
|
||||
parameters = queryMethod.getParameters();
|
||||
@@ -51,13 +63,21 @@ public class PartTreeLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.ldap.repository.query.AbstractLdapRepositoryQuery#createQuery(java.lang.Object[])
|
||||
* @see org.springframework.data.ldap.repository.query.AbstractLdapRepositoryQuery#createQuery(org.springframework.data.ldap.repository.query.LdapParameterAccessor)
|
||||
*/
|
||||
@Override
|
||||
protected LdapQuery createQuery(Object[] actualParameters) {
|
||||
protected LdapQuery createQuery(LdapParameterAccessor parameters) {
|
||||
|
||||
List<String> inputProperties = Collections.emptyList();
|
||||
ReturnedType returnedType = getQueryMethod().getResultProcessor().withDynamicProjection(parameters)
|
||||
.getReturnedType();
|
||||
|
||||
if (returnedType.needsCustomConstruction()) {
|
||||
inputProperties = returnedType.getInputProperties();
|
||||
}
|
||||
|
||||
org.springframework.data.ldap.repository.query.LdapQueryCreator queryCreator = new LdapQueryCreator(partTree,
|
||||
this.parameters, getEntityClass(), objectDirectoryMapper, actualParameters);
|
||||
getEntityClass(), objectDirectoryMapper, parameters, inputProperties);
|
||||
return queryCreator.createQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.data.ldap.repository.query.PartTreeLdapRepositoryQuer
|
||||
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.ProjectionFactory;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
@@ -56,6 +57,7 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
|
||||
private final LdapQueryLookupStrategy queryLookupStrategy;
|
||||
private final LdapOperations ldapOperations;
|
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext;
|
||||
private final EntityInstantiators instantiators = new EntityInstantiators();
|
||||
|
||||
/**
|
||||
* Creates a new {@link LdapRepositoryFactory}.
|
||||
@@ -66,9 +68,9 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
Assert.notNull(ldapOperations, "LdapOperations must not be null!");
|
||||
|
||||
this.queryLookupStrategy = new LdapQueryLookupStrategy(ldapOperations);
|
||||
this.ldapOperations = ldapOperations;
|
||||
this.mappingContext = new LdapMappingContext();
|
||||
this.queryLookupStrategy = new LdapQueryLookupStrategy(ldapOperations, instantiators, mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +85,7 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
|
||||
Assert.notNull(ldapOperations, "LdapOperations must not be null!");
|
||||
Assert.notNull(mappingContext, "LdapMappingContext must not be null!");
|
||||
|
||||
this.queryLookupStrategy = new LdapQueryLookupStrategy(ldapOperations);
|
||||
this.queryLookupStrategy = new LdapQueryLookupStrategy(ldapOperations, instantiators, mappingContext);
|
||||
this.ldapOperations = ldapOperations;
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
@@ -168,12 +170,15 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
|
||||
private static final class LdapQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final LdapOperations ldapOperations;
|
||||
private final EntityInstantiators instantiators;
|
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext;
|
||||
|
||||
public LdapQueryLookupStrategy(LdapOperations ldapOperations, EntityInstantiators instantiators,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext) {
|
||||
|
||||
/**
|
||||
* @param ldapOperations must not be {@literal null}.
|
||||
*/
|
||||
LdapQueryLookupStrategy(LdapOperations ldapOperations) {
|
||||
this.ldapOperations = ldapOperations;
|
||||
this.instantiators = instantiators;
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -184,9 +189,9 @@ public class LdapRepositoryFactory extends RepositoryFactorySupport {
|
||||
Class<?> domainType = metadata.getDomainType();
|
||||
|
||||
if (queryMethod.hasQueryAnnotation()) {
|
||||
return new AnnotatedLdapRepositoryQuery(queryMethod, domainType, ldapOperations);
|
||||
return new AnnotatedLdapRepositoryQuery(queryMethod, domainType, ldapOperations, mappingContext, instantiators);
|
||||
} else {
|
||||
return new PartTreeLdapRepositoryQuery(queryMethod, domainType, ldapOperations);
|
||||
return new PartTreeLdapRepositoryQuery(queryMethod, domainType, ldapOperations, mappingContext, instantiators);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
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.data.ldap.repository.support.LdapRepositoryFactory;
|
||||
import org.springframework.data.ldap.repository.support.UnitTestPerson;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link LdapRepository}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@MockitoSettings
|
||||
class LdapRepositoryUnitTests {
|
||||
|
||||
@Mock LdapOperations ldapOperations;
|
||||
|
||||
UnitTestPerson walter, hank;
|
||||
|
||||
PersonRepository repository;
|
||||
|
||||
@BeforeEach
|
||||
void before() throws Exception {
|
||||
|
||||
when(ldapOperations.getObjectDirectoryMapper()).thenReturn(new DefaultObjectDirectoryMapper());
|
||||
|
||||
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");
|
||||
|
||||
repository = new LdapRepositoryFactory(ldapOperations).getRepository(PersonRepository.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnInterfaceProjection() {
|
||||
|
||||
when(ldapOperations.findOne(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(walter);
|
||||
|
||||
PersonProjection walter = repository.findByLastName("White");
|
||||
|
||||
assertThat(walter).isNotNull();
|
||||
assertThat(walter.getLastName()).isEqualTo("White");
|
||||
|
||||
ArgumentCaptor<LdapQuery> captor = ArgumentCaptor.forClass(LdapQuery.class);
|
||||
|
||||
verify(ldapOperations).findOne(captor.capture(), any());
|
||||
|
||||
LdapQuery query = captor.getValue();
|
||||
assertThat(query.attributes()).containsOnly("lastName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnDynamicDtoProjection() {
|
||||
|
||||
when(ldapOperations.findOne(any(LdapQuery.class), eq(UnitTestPerson.class))).thenReturn(walter);
|
||||
|
||||
PersonDto walter = repository.findByLastName("White", PersonDto.class);
|
||||
|
||||
assertThat(walter).isNotNull();
|
||||
assertThat(walter.getLastName()).isEqualTo("White");
|
||||
|
||||
ArgumentCaptor<LdapQuery> captor = ArgumentCaptor.forClass(LdapQuery.class);
|
||||
|
||||
verify(ldapOperations).findOne(captor.capture(), any());
|
||||
|
||||
LdapQuery query = captor.getValue();
|
||||
assertThat(query.attributes()).isNullOrEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnInterfaceProjectionAsStream() {
|
||||
|
||||
when(ldapOperations.find(any(LdapQuery.class), eq(UnitTestPerson.class)))
|
||||
.thenReturn(Collections.singletonList(walter));
|
||||
|
||||
Stream<PersonProjection> walter = repository.streamAllByLastName("White");
|
||||
|
||||
List<PersonProjection> list = walter.collect(Collectors.toList());
|
||||
assertThat(list).hasSize(1).hasOnlyElementsOfType(PersonProjection.class);
|
||||
|
||||
ArgumentCaptor<LdapQuery> captor = ArgumentCaptor.forClass(LdapQuery.class);
|
||||
|
||||
verify(ldapOperations).find(captor.capture(), any());
|
||||
|
||||
LdapQuery query = captor.getValue();
|
||||
assertThat(query.attributes()).containsOnly("lastName");
|
||||
}
|
||||
|
||||
interface PersonRepository extends LdapRepository<UnitTestPerson> {
|
||||
|
||||
Stream<PersonProjection> streamAllByLastName(String lastName);
|
||||
|
||||
PersonProjection findByLastName(String lastname);
|
||||
|
||||
<T> T findByLastName(String lastname, Class<T> projection);
|
||||
|
||||
}
|
||||
|
||||
interface PersonProjection {
|
||||
String getLastName();
|
||||
}
|
||||
|
||||
@Data
|
||||
static class PersonDto {
|
||||
|
||||
String lastName;
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,10 @@ import java.lang.reflect.Method;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.ldap.core.mapping.LdapMappingContext;
|
||||
import org.springframework.data.ldap.repository.support.BaseUnitTestPerson;
|
||||
import org.springframework.data.ldap.repository.support.UnitTestPerson;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
@@ -135,9 +137,10 @@ class PartTreeLdapRepositoryQueryTests {
|
||||
Object... expectedParams) {
|
||||
|
||||
LdapQueryMethod queryMethod = new LdapQueryMethod(targetMethod, repositoryMetadata, factory);
|
||||
PartTreeLdapRepositoryQuery tested = new PartTreeLdapRepositoryQuery(queryMethod, entityClass, ldapTemplate);
|
||||
PartTreeLdapRepositoryQuery tested = new PartTreeLdapRepositoryQuery(queryMethod, entityClass, ldapTemplate,
|
||||
new LdapMappingContext(), new EntityInstantiators());
|
||||
|
||||
LdapQuery query = tested.createQuery(expectedParams);
|
||||
LdapQuery query = tested.createQuery(new LdapParametersParameterAccessor(queryMethod, expectedParams));
|
||||
String base = query.base().toString();
|
||||
assertThat(base).isEqualTo(expectedBase);
|
||||
assertThat(query.filter().encode()).isEqualTo(expectedFilter);
|
||||
|
||||
Reference in New Issue
Block a user