Skip to content

Commit 9c9ef80

Browse files
committed
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
1 parent 79fe924 commit 9c9ef80

File tree

12 files changed

+518
-36
lines changed

12 files changed

+518
-36
lines changed

src/main/asciidoc/new-features.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
[[new-features]]
22
= New and Noteworthy
33

4+
[[new-features.2.6]]
5+
== What's New in Spring Data LDAP 2.6
6+
* <<repositories.query-streaming,Repository query methods returning a `Stream`>> from the underlying result `List`.
7+
* <<projections,Interface and DTO projections returned by query methods>>.
8+
49
[[new-features.2.1]]
510
== What's New in Spring Data LDAP 2.1
611
* CDI extension to create LDAP repositories within a CDI container.

src/main/asciidoc/reference/ldap-repositories.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ The following table provides samples of the keywords that you can use with query
224224

225225
|===
226226

227+
include::../{spring-data-commons-docs}/repository-projections.adoc[leveloffset=+2]
227228

228229
=== QueryDSL Support
229230
Basic QueryDSL support is included in Spring LDAP. This support includes the following:

src/main/java/org/springframework/data/ldap/repository/query/AbstractLdapRepositoryQuery.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@
1515
*/
1616
package org.springframework.data.ldap.repository.query;
1717

18-
import org.springframework.dao.EmptyResultDataAccessException;
18+
import static org.springframework.data.ldap.repository.query.LdapQueryExecution.*;
19+
20+
import org.springframework.core.convert.converter.Converter;
1921
import org.springframework.data.ldap.repository.Query;
22+
import org.springframework.data.mapping.PersistentEntity;
23+
import org.springframework.data.mapping.PersistentProperty;
24+
import org.springframework.data.mapping.context.MappingContext;
25+
import org.springframework.data.mapping.model.EntityInstantiators;
2026
import org.springframework.data.repository.query.QueryMethod;
2127
import org.springframework.data.repository.query.RepositoryQuery;
28+
import org.springframework.data.repository.query.ResultProcessor;
2229
import org.springframework.ldap.core.LdapOperations;
2330
import org.springframework.ldap.query.LdapQuery;
2431
import org.springframework.util.Assert;
@@ -34,6 +41,8 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
3441
private final LdapQueryMethod queryMethod;
3542
private final Class<?> entityType;
3643
private final LdapOperations ldapOperations;
44+
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext;
45+
private final EntityInstantiators instantiators;
3746

3847
/**
3948
* Creates a new {@link AbstractLdapRepositoryQuery} instance given {@link LdapQuery}, {@link Class} and
@@ -42,8 +51,12 @@ public abstract class AbstractLdapRepositoryQuery implements RepositoryQuery {
4251
* @param queryMethod must not be {@literal null}.
4352
* @param entityType must not be {@literal null}.
4453
* @param ldapOperations must not be {@literal null}.
54+
* @param mappingContext must not be {@literal null}.
55+
* @param instantiators must not be {@literal null}.
4556
*/
46-
public AbstractLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations) {
57+
public AbstractLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations,
58+
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext,
59+
EntityInstantiators instantiators) {
4760

4861
Assert.notNull(queryMethod, "LdapQueryMethod must not be null!");
4962
Assert.notNull(entityType, "Entity type must not be null!");
@@ -52,6 +65,8 @@ public AbstractLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityT
5265
this.queryMethod = queryMethod;
5366
this.entityType = entityType;
5467
this.ldapOperations = ldapOperations;
68+
this.mappingContext = mappingContext;
69+
this.instantiators = instantiators;
5570
}
5671

5772
/* (non-Javadoc)
@@ -61,16 +76,28 @@ public AbstractLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityT
6176
@SuppressWarnings("ConstantConditions")
6277
public final Object execute(Object[] parameters) {
6378

64-
LdapQuery query = createQuery(parameters);
79+
LdapParametersParameterAccessor parameterAccessor = new LdapParametersParameterAccessor(queryMethod, parameters);
80+
LdapQuery query = createQuery(parameterAccessor);
81+
82+
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
83+
Class<?> typeToRead = processor.getReturnedType().getDomainType();
84+
85+
ResultProcessingConverter converter = new ResultProcessingConverter(processor, mappingContext, instantiators);
86+
ResultProcessingExecution execution = new ResultProcessingExecution(
87+
getLdapQueryExecutionToWrap(typeToRead, converter), converter);
88+
89+
return execution.execute(query);
90+
}
91+
92+
private LdapQueryExecution getLdapQueryExecutionToWrap(Class<?> typeToRead,
93+
Converter<Object, Object> resultProcessing) {
6594

6695
if (queryMethod.isCollectionQuery()) {
67-
return ldapOperations.find(query, entityType);
96+
return new CollectionExecution(ldapOperations, typeToRead);
97+
} else if (queryMethod.isStreamQuery()) {
98+
return new StreamExecution(ldapOperations, typeToRead, resultProcessing);
6899
} else {
69-
try {
70-
return ldapOperations.findOne(query, entityType);
71-
} catch (EmptyResultDataAccessException e) {
72-
return null;
73-
}
100+
return new FindOneExecution(ldapOperations, typeToRead);
74101
}
75102
}
76103

@@ -80,7 +107,7 @@ public final Object execute(Object[] parameters) {
80107
* @param parameters must not be {@literal null}.
81108
* @return
82109
*/
83-
protected abstract LdapQuery createQuery(Object[] parameters);
110+
protected abstract LdapQuery createQuery(LdapParameterAccessor parameters);
84111

85112
/**
86113
* @return

src/main/java/org/springframework/data/ldap/repository/query/AnnotatedLdapRepositoryQuery.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
import static org.springframework.ldap.query.LdapQueryBuilder.*;
1919

2020
import org.springframework.data.ldap.repository.Query;
21+
import org.springframework.data.mapping.PersistentEntity;
22+
import org.springframework.data.mapping.PersistentProperty;
23+
import org.springframework.data.mapping.context.MappingContext;
24+
import org.springframework.data.mapping.model.EntityInstantiators;
2125
import org.springframework.ldap.core.LdapOperations;
2226
import org.springframework.ldap.query.LdapQuery;
2327
import org.springframework.util.Assert;
@@ -38,10 +42,14 @@ public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
3842
* @param queryMethod the QueryMethod.
3943
* @param entityType the managed class.
4044
* @param ldapOperations the LdapOperations instance to use.
45+
* @param mappingContext must not be {@literal null}.
46+
* @param instantiators must not be {@literal null}.
4147
*/
42-
public AnnotatedLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations) {
48+
public AnnotatedLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entityType, LdapOperations ldapOperations,
49+
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext,
50+
EntityInstantiators instantiators) {
4351

44-
super(queryMethod, entityType, ldapOperations);
52+
super(queryMethod, entityType, ldapOperations, mappingContext, instantiators);
4553

4654
Assert.notNull(queryMethod.getQueryAnnotation(), "Annotation must be present");
4755
Assert.hasLength(queryMethod.getQueryAnnotation().value(), "Query filter must be specified");
@@ -50,15 +58,15 @@ public AnnotatedLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entity
5058
}
5159

5260
/* (non-Javadoc)
53-
* @see org.springframework.data.ldap.repository.query.AbstractLdapRepositoryQuery#createQuery(java.lang.Object[])
61+
* @see org.springframework.data.ldap.repository.query.AbstractLdapRepositoryQuery#createQuery(org.springframework.data.ldap.repository.query.LdapParameterAccessor)
5462
*/
5563
@Override
56-
protected LdapQuery createQuery(Object[] parameters) {
64+
protected LdapQuery createQuery(LdapParameterAccessor parameters) {
5765

5866
return query().base(queryAnnotation.base()) //
5967
.searchScope(queryAnnotation.searchScope()) //
6068
.countLimit(queryAnnotation.countLimit()) //
6169
.timeLimit(queryAnnotation.timeLimit()) //
62-
.filter(queryAnnotation.value(), parameters);
70+
.filter(queryAnnotation.value(), parameters.getBindableParameterValues());
6371
}
6472
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.ldap.repository.query;
17+
18+
import org.springframework.data.repository.query.ParameterAccessor;
19+
20+
/**
21+
* LDAP-specific {@link ParameterAccessor} exposing all bindable parameters.
22+
*
23+
* @author Mark Paluch
24+
* @since 2.6
25+
*/
26+
interface LdapParameterAccessor extends ParameterAccessor {
27+
28+
/**
29+
* Returns the bindable parameter values of the underlying query method.
30+
*
31+
* @return
32+
*/
33+
Object[] getBindableParameterValues();
34+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.ldap.repository.query;
17+
18+
import org.springframework.data.repository.query.Parameters;
19+
import org.springframework.data.repository.query.ParametersParameterAccessor;
20+
import org.springframework.data.repository.query.QueryMethod;
21+
22+
/**
23+
* LDAP-specific {@link ParametersParameterAccessor}.
24+
*
25+
* @author Mark Paluch
26+
* @since 2.6
27+
*/
28+
class LdapParametersParameterAccessor extends ParametersParameterAccessor implements LdapParameterAccessor {
29+
30+
/**
31+
* Creates a new {@link LdapParametersParameterAccessor}.
32+
*
33+
* @param method must not be {@literal null}.
34+
* @param values must not be {@literal null}.
35+
*/
36+
public LdapParametersParameterAccessor(QueryMethod method, Object[] values) {
37+
super(method.getParameters(), values);
38+
}
39+
40+
@Override
41+
public Object[] getBindableParameterValues() {
42+
43+
Parameters<?, ?> bindableParameters = getParameters().getBindableParameters();
44+
int count = bindableParameters.getNumberOfParameters();
45+
46+
if (count == 0) {
47+
return new Object[0];
48+
}
49+
50+
Object[] values = new Object[count];
51+
52+
for (int i = 0; i < count; i++) {
53+
values[i] = getBindableValue(i);
54+
}
55+
56+
return values;
57+
}
58+
59+
}

src/main/java/org/springframework/data/ldap/repository/query/LdapQueryCreator.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
import static org.springframework.ldap.query.LdapQueryBuilder.*;
1919

2020
import java.util.Iterator;
21+
import java.util.List;
2122

2223
import org.springframework.core.annotation.AnnotatedElementUtils;
2324
import org.springframework.data.domain.Sort;
2425
import org.springframework.data.mapping.PropertyPath;
25-
import org.springframework.data.repository.query.Parameters;
26-
import org.springframework.data.repository.query.ParametersParameterAccessor;
2726
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
2827
import org.springframework.data.repository.query.parser.Part;
2928
import org.springframework.data.repository.query.parser.PartTree;
@@ -45,26 +44,28 @@ class LdapQueryCreator extends AbstractQueryCreator<LdapQuery, ContainerCriteria
4544

4645
private final Class<?> entityType;
4746
private final ObjectDirectoryMapper mapper;
47+
private final List<String> inputProperties;
4848

4949
/**
5050
* Constructs a new {@link LdapQueryCreator}.
5151
*
5252
* @param tree must not be {@literal null}.
53-
* @param parameters must not be {@literal null}.
5453
* @param entityType must not be {@literal null}.
5554
* @param mapper must not be {@literal null}.
5655
* @param values must not be {@literal null}.
56+
* @param inputProperties must not be {@literal null}.
5757
*/
58-
LdapQueryCreator(PartTree tree, Parameters<?, ?> parameters, Class<?> entityType, ObjectDirectoryMapper mapper,
59-
Object[] values) {
58+
LdapQueryCreator(PartTree tree, Class<?> entityType, ObjectDirectoryMapper mapper,
59+
LdapParameterAccessor parameterAccessor, List<String> inputProperties) {
6060

61-
super(tree, new ParametersParameterAccessor(parameters, values));
61+
super(tree, parameterAccessor);
6262

6363
Assert.notNull(entityType, "Entity type must not be null!");
6464
Assert.notNull(mapper, "ObjectDirectoryMapper must not be null!");
6565

6666
this.entityType = entityType;
6767
this.mapper = mapper;
68+
this.inputProperties = inputProperties;
6869
}
6970

7071
/* (non-Javadoc)
@@ -81,6 +82,10 @@ protected ContainerCriteria create(Part part, Iterator<Object> iterator) {
8182
query = query.base(entry.base());
8283
}
8384

85+
if (!inputProperties.isEmpty()) {
86+
query.attributes(inputProperties.toArray(new String[0]));
87+
}
88+
8489
ConditionCriteria criteria = query.where(getAttribute(part));
8590

8691
return appendCondition(part, iterator, criteria);

0 commit comments

Comments
 (0)