Skip to content

Commit 3890283

Browse files
WIP
1 parent 5914686 commit 3890283

File tree

9 files changed

+683
-47
lines changed

9 files changed

+683
-47
lines changed

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717

1818
import static org.springframework.ldap.query.LdapQueryBuilder.*;
1919

20+
import org.springframework.data.expression.ValueEvaluationContext;
2021
import org.springframework.data.ldap.repository.Query;
2122
import org.springframework.data.mapping.PersistentEntity;
2223
import org.springframework.data.mapping.PersistentProperty;
2324
import org.springframework.data.mapping.context.MappingContext;
2425
import org.springframework.data.mapping.model.EntityInstantiators;
2526
import org.springframework.data.repository.query.ValueExpressionDelegate;
26-
import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
2727
import org.springframework.ldap.core.LdapOperations;
2828
import org.springframework.ldap.query.LdapQuery;
2929
import org.springframework.util.Assert;
@@ -38,6 +38,7 @@ public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery {
3838

3939
private final Query queryAnnotation;
4040
private final ValueExpressionDelegate valueExpressionDelegate;
41+
private final StringBasedQuery stringBasedQuery;
4142

4243
/**
4344
* Construct a new instance.
@@ -77,21 +78,22 @@ public AnnotatedLdapRepositoryQuery(LdapQueryMethod queryMethod, Class<?> entity
7778
Assert.hasLength(queryMethod.getQueryAnnotation().value(), "Query filter must be specified");
7879

7980
queryAnnotation = queryMethod.getRequiredQueryAnnotation();
81+
String queryValue = queryAnnotation.value();
8082
this.valueExpressionDelegate = valueExpressionDelegate;
83+
stringBasedQuery = new StringBasedQuery(queryValue, queryMethod.getParameters(), valueExpressionDelegate);
8184
}
8285

8386
@Override
8487
protected LdapQuery createQuery(LdapParameterAccessor parameters) {
85-
86-
String evaluatedFilterValue = (String) valueExpressionDelegate.parse(queryAnnotation.value()).evaluate(valueExpressionDelegate.createValueContextProvider(getQueryMethod().getParameters())
87-
.getEvaluationContext(parameters.getBindableParameterValues()));
88-
89-
valueExpressionDelegate.getValueExpressionParser();
88+
ValueEvaluationContext evaluationContext = valueExpressionDelegate.createValueContextProvider(
89+
getQueryMethod().getParameters()).getEvaluationContext(parameters.getBindableParameterValues(), stringBasedQuery.getExpressionDependencies());
90+
String boundQuery = stringBasedQuery.bindQuery(parameters,
91+
new ContextualValueExpressionEvaluator(valueExpressionDelegate, evaluationContext));
9092

9193
return query().base(queryAnnotation.base()) //
9294
.searchScope(queryAnnotation.searchScope()) //
9395
.countLimit(queryAnnotation.countLimit()) //
9496
.timeLimit(queryAnnotation.timeLimit()) //
95-
.filter(evaluatedFilterValue);
97+
.filter(boundQuery);
9698
}
9799
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2020-2024 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+
* https://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 java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
21+
22+
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
23+
import org.springframework.data.repository.query.Parameter;
24+
import org.springframework.data.repository.query.ParameterAccessor;
25+
import org.springframework.lang.Nullable;
26+
import org.springframework.util.Assert;
27+
28+
/**
29+
* Value object capturing the binding context to provide {@link #getBindingValues() binding values} for queries.
30+
*
31+
* @author Mark Paluch
32+
* @since 3.4
33+
*/
34+
class BindingContext {
35+
36+
private final LdapParameters parameters;
37+
38+
private final ParameterAccessor parameterAccessor;
39+
40+
private final List<ParameterBinding> bindings;
41+
42+
private final ValueExpressionEvaluator evaluator;
43+
44+
/**
45+
* Create new {@link BindingContext}.
46+
*/
47+
BindingContext(LdapParameters parameters, ParameterAccessor parameterAccessor,
48+
List<ParameterBinding> bindings, ValueExpressionEvaluator evaluator) {
49+
50+
this.parameters = parameters;
51+
this.parameterAccessor = parameterAccessor;
52+
this.bindings = bindings;
53+
this.evaluator = evaluator;
54+
}
55+
56+
/**
57+
* @return {@literal true} when list of bindings is not empty.
58+
*/
59+
private boolean hasBindings() {
60+
return !bindings.isEmpty();
61+
}
62+
63+
/**
64+
* Bind values provided by {@link LdapParameterAccessor} to placeholders in {@link BindingContext} while
65+
* considering potential conversions and parameter types.
66+
*
67+
* @return {@literal null} if given {@code raw} value is empty.
68+
*/
69+
public List<Object> getBindingValues() {
70+
71+
if (!hasBindings()) {
72+
return Collections.emptyList();
73+
}
74+
75+
List<Object> parameters = new ArrayList<>(bindings.size());
76+
77+
for (ParameterBinding binding : bindings) {
78+
Object parameterValueForBinding = getParameterValueForBinding(binding);
79+
parameters.add(parameterValueForBinding);
80+
}
81+
82+
return parameters;
83+
}
84+
85+
/**
86+
* Return the value to be used for the given {@link ParameterBinding}.
87+
*
88+
* @param binding must not be {@literal null}.
89+
* @return the value used for the given {@link ParameterBinding}.
90+
*/
91+
@Nullable
92+
private Object getParameterValueForBinding(ParameterBinding binding) {
93+
94+
if (binding.isExpression()) {
95+
return evaluator.evaluate(binding.getRequiredExpression());
96+
}
97+
98+
return binding.isNamed()
99+
? parameterAccessor.getBindableValue(getParameterIndex(parameters, binding.getRequiredParameterName()))
100+
: parameterAccessor.getBindableValue(binding.getParameterIndex());
101+
}
102+
103+
private int getParameterIndex(LdapParameters parameters, String parameterName) {
104+
105+
return parameters.stream() //
106+
.filter(cassandraParameter -> cassandraParameter //
107+
.getName().filter(s -> s.equals(parameterName)) //
108+
.isPresent()) //
109+
.mapToInt(Parameter::getIndex) //
110+
.findFirst() //
111+
.orElseThrow(() -> new IllegalArgumentException(
112+
String.format("Invalid parameter name; Cannot resolve parameter [%s]", parameterName)));
113+
}
114+
115+
/**
116+
* A generic parameter binding with name or position information.
117+
*
118+
* @author Mark Paluch
119+
*/
120+
static class ParameterBinding {
121+
122+
private final int parameterIndex;
123+
private final @Nullable String expression;
124+
private final @Nullable String parameterName;
125+
126+
private ParameterBinding(int parameterIndex, @Nullable String expression, @Nullable String parameterName) {
127+
128+
this.parameterIndex = parameterIndex;
129+
this.expression = expression;
130+
this.parameterName = parameterName;
131+
}
132+
133+
static ParameterBinding expression(String expression, boolean quoted) {
134+
return new ParameterBinding(-1, expression, null);
135+
}
136+
137+
static ParameterBinding indexed(int parameterIndex) {
138+
return new ParameterBinding(parameterIndex, null, null);
139+
}
140+
141+
static ParameterBinding named(String name) {
142+
return new ParameterBinding(-1, null, name);
143+
}
144+
145+
boolean isNamed() {
146+
return (parameterName != null);
147+
}
148+
149+
int getParameterIndex() {
150+
return parameterIndex;
151+
}
152+
153+
String getParameter() {
154+
return ("?" + (isExpression() ? "expr" : "") + parameterIndex);
155+
}
156+
157+
String getRequiredExpression() {
158+
159+
Assert.state(expression != null, "ParameterBinding is not an expression");
160+
return expression;
161+
}
162+
163+
boolean isExpression() {
164+
return (this.expression != null);
165+
}
166+
167+
String getRequiredParameterName() {
168+
169+
Assert.state(parameterName != null, "ParameterBinding is not named");
170+
171+
return parameterName;
172+
}
173+
}
174+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2024 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+
* https://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.expression.ValueEvaluationContext;
19+
import org.springframework.data.expression.ValueExpression;
20+
import org.springframework.data.expression.ValueExpressionParser;
21+
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
22+
23+
/**
24+
* @author Marcin Grzejszczak
25+
* @author Mark Paluch
26+
*/
27+
class ContextualValueExpressionEvaluator implements ValueExpressionEvaluator {
28+
29+
private final ValueExpressionParser parser;
30+
31+
public ContextualValueExpressionEvaluator(ValueExpressionParser parser, ValueEvaluationContext evaluationContext) {
32+
this.parser = parser;
33+
this.evaluationContext = evaluationContext;
34+
}
35+
36+
private final ValueEvaluationContext evaluationContext;
37+
38+
@SuppressWarnings("unchecked")
39+
@Override
40+
public <T> T evaluate(String expressionString) {
41+
ValueExpression expression = parser.parse(expressionString);
42+
return (T) expression.evaluate(evaluationContext);
43+
}
44+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2016-2024 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+
* https://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 java.lang.reflect.Method;
19+
import java.util.List;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.core.ResolvableType;
23+
import org.springframework.data.repository.query.Parameter;
24+
import org.springframework.data.repository.query.Parameters;
25+
import org.springframework.data.repository.query.ParametersSource;
26+
import org.springframework.data.repository.util.QueryExecutionConverters;
27+
import org.springframework.data.repository.util.ReactiveWrapperConverters;
28+
import org.springframework.data.repository.util.ReactiveWrappers;
29+
import org.springframework.data.util.TypeInformation;
30+
31+
/**
32+
* Custom extension of {@link Parameters} discovering additional properties of query method parameters.
33+
*
34+
* @author Marcin Grzejszczak
35+
*/
36+
public class LdapParameters extends Parameters<LdapParameters, LdapParameters.LdapParameter> {
37+
38+
/**
39+
* Create a new {@link LdapParameters} instance from the given {@link Method}.
40+
*
41+
* @param parametersSource must not be {@literal null}.
42+
*/
43+
public LdapParameters(ParametersSource parametersSource) {
44+
super(parametersSource,
45+
methodParameter -> new LdapParameter(methodParameter, parametersSource.getDomainTypeInformation()));
46+
}
47+
48+
private LdapParameters(List<LdapParameter> originals) {
49+
50+
super(originals);
51+
}
52+
53+
@Override
54+
protected LdapParameters createFrom(List<LdapParameter> parameters) {
55+
return new LdapParameters(parameters);
56+
}
57+
58+
/**
59+
* Custom {@link Parameter}.
60+
*
61+
* @author Marcin Grzejszczak
62+
*/
63+
static class LdapParameter extends Parameter {
64+
65+
private final Class<?> parameterType;
66+
67+
LdapParameter(MethodParameter parameter, TypeInformation<?> domainType) {
68+
69+
super(parameter, domainType);
70+
71+
parameterType = potentiallyUnwrapParameterType(parameter);
72+
}
73+
74+
@Override
75+
public Class<?> getType() {
76+
return this.parameterType;
77+
}
78+
79+
/**
80+
* Returns the component type if the given {@link MethodParameter} is a wrapper type and the wrapper should be
81+
* unwrapped.
82+
*
83+
* @param parameter must not be {@literal null}.
84+
*/
85+
private static Class<?> potentiallyUnwrapParameterType(MethodParameter parameter) {
86+
87+
Class<?> originalType = parameter.getParameterType();
88+
89+
if (isWrapped(parameter) && shouldUnwrap(parameter)) {
90+
return ResolvableType.forMethodParameter(parameter).getGeneric(0).getRawClass();
91+
}
92+
93+
return originalType;
94+
}
95+
96+
/**
97+
* Returns whether the {@link MethodParameter} is wrapped in a wrapper type.
98+
*
99+
* @param parameter must not be {@literal null}.
100+
* @see QueryExecutionConverters
101+
*/
102+
private static boolean isWrapped(MethodParameter parameter) {
103+
return QueryExecutionConverters.supports(parameter.getParameterType())
104+
|| ReactiveWrapperConverters.supports(parameter.getParameterType());
105+
}
106+
107+
/**
108+
* Returns whether the {@link MethodParameter} should be unwrapped.
109+
*
110+
* @param parameter must not be {@literal null}.
111+
* @see QueryExecutionConverters
112+
*/
113+
private static boolean shouldUnwrap(MethodParameter parameter) {
114+
return QueryExecutionConverters.supportsUnwrapping(parameter.getParameterType())
115+
|| ReactiveWrappers.supports(parameter.getParameterType());
116+
}
117+
}
118+
119+
}

0 commit comments

Comments
 (0)