Skip to content

Commit 58b2d60

Browse files
marcingrzejszczakmp911de
authored andcommitted
Adds LdapParam and LdapEncoder
fixes gh-509
1 parent b2c3717 commit 58b2d60

File tree

6 files changed

+251
-11
lines changed

6 files changed

+251
-11
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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;
17+
18+
/**
19+
* Allows plugging in custom encoding for {@link LdapParam}.
20+
*
21+
* @author Marcin Grzejszczak
22+
* @since 3.5.0
23+
*/
24+
public interface LdapEncoder {
25+
26+
/**
27+
* Escape a value for use in a filter.
28+
* @param value the value to escape.
29+
* @return a properly escaped representation of the supplied value.
30+
*/
31+
String filterEncode(String value);
32+
33+
/**
34+
* Default implementation of {@link LdapEncoder} that uses
35+
* {@link org.springframework.ldap.support.LdapEncoder}.
36+
*/
37+
class DefaultLdapEncoder implements LdapEncoder {
38+
39+
@Override
40+
public String filterEncode(String value) {
41+
return org.springframework.ldap.support.LdapEncoder.filterEncode(value);
42+
}
43+
}
44+
45+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
import org.springframework.core.annotation.AliasFor;
25+
import org.springframework.data.repository.query.Param;
26+
27+
/**
28+
* A {@link Param} alias that allows passing of custom {@link LdapEncoder}.
29+
*
30+
* @author Marcin Grzejszczak
31+
* @since 3.5.0
32+
*/
33+
@Target(ElementType.PARAMETER)
34+
@Retention(RetentionPolicy.RUNTIME)
35+
@Documented
36+
public @interface LdapParam {
37+
38+
@AliasFor(annotation = Param.class)
39+
String value();
40+
41+
/**
42+
* {@link LdapEncoder} to instantiate to encode query parameters.
43+
*
44+
* @return {@link LdapEncoder} class
45+
*/
46+
Class<? extends LdapEncoder> encoder() default LdapEncoder.DefaultLdapEncoder.class;
47+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 java.lang.reflect.Method;
19+
import java.util.List;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.data.geo.Distance;
23+
import org.springframework.data.ldap.repository.LdapEncoder;
24+
import org.springframework.data.ldap.repository.LdapParam;
25+
import org.springframework.data.repository.query.Parameter;
26+
import org.springframework.data.repository.query.Parameters;
27+
import org.springframework.data.repository.query.ParametersSource;
28+
import org.springframework.data.util.TypeInformation;
29+
import org.springframework.lang.Nullable;
30+
31+
/**
32+
* Custom extension of {@link Parameters} discovering additional
33+
*
34+
* @author Marcin Grzejszczak
35+
* @since 3.5.0
36+
*/
37+
public class LdapParameters extends Parameters<LdapParameters, LdapParameters.LdapParameter> {
38+
39+
private final TypeInformation<?> domainType;
40+
41+
/**
42+
* Creates a new {@link LdapParameters} instance from the given {@link Method} and {@link LdapQueryMethod}.
43+
*
44+
* @param parametersSource must not be {@literal null}.
45+
*/
46+
public LdapParameters(ParametersSource parametersSource) {
47+
48+
super(parametersSource, methodParameter -> new LdapParameter(methodParameter,
49+
parametersSource.getDomainTypeInformation()));
50+
51+
this.domainType = parametersSource.getDomainTypeInformation();
52+
}
53+
54+
private LdapParameters(List<LdapParameter> parameters, TypeInformation<?> domainType) {
55+
56+
super(parameters);
57+
this.domainType = domainType;
58+
}
59+
60+
@Override
61+
protected LdapParameters createFrom(List<LdapParameter> parameters) {
62+
return new LdapParameters(parameters, this.domainType);
63+
}
64+
65+
@Nullable
66+
LdapEncoder encoderForParameterWithName(String parameterName) {
67+
for (LdapParameter parameter : this) {
68+
if (parameterName.equals(parameter.getName().orElse(null))) {
69+
LdapParam ldapParam = parameter.parameter.getParameterAnnotation(LdapParam.class);
70+
if (ldapParam == null) {
71+
return null;
72+
}
73+
Class<? extends LdapEncoder> encoder = ldapParam.encoder();
74+
try {
75+
return encoder.getDeclaredConstructor().newInstance();
76+
} catch (Exception e) {
77+
throw new IllegalStateException(e);
78+
}
79+
}
80+
}
81+
return null;
82+
}
83+
84+
/**
85+
* Custom {@link Parameter} implementation adding parameters of type {@link Distance} to the special ones.
86+
*
87+
* @author Marcin Grzejszczak
88+
*/
89+
protected static class LdapParameter extends Parameter {
90+
91+
private final MethodParameter parameter;
92+
93+
/**
94+
* Creates a new {@link LdapParameter}.
95+
*
96+
* @param parameter must not be {@literal null}.
97+
* @param domainType must not be {@literal null}.
98+
*/
99+
LdapParameter(MethodParameter parameter, TypeInformation<?> domainType) {
100+
super(parameter, domainType);
101+
this.parameter = parameter;
102+
}
103+
}
104+
105+
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.springframework.data.ldap.repository.Query;
2222
import org.springframework.data.projection.ProjectionFactory;
2323
import org.springframework.data.repository.core.RepositoryMetadata;
24-
import org.springframework.data.repository.query.Parameters;
2524
import org.springframework.data.repository.query.ParametersSource;
2625
import org.springframework.data.repository.query.QueryMethod;
2726
import org.springframework.lang.Nullable;
@@ -50,6 +49,16 @@ public LdapQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFac
5049
this.method = method;
5150
}
5251

52+
@Override
53+
protected LdapParameters createParameters(ParametersSource parametersSource) {
54+
return new LdapParameters(parametersSource);
55+
}
56+
57+
@Override
58+
public LdapParameters getParameters() {
59+
return (LdapParameters) super.getParameters();
60+
}
61+
5362
/**
5463
* Check whether the target method is annotated with {@link org.springframework.data.ldap.repository.Query}.
5564
*

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

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

18-
import static org.springframework.data.ldap.repository.query.StringBasedQuery.BindingContext.*;
19-
2018
import java.util.ArrayList;
2119
import java.util.Collections;
2220
import java.util.List;
@@ -39,6 +37,8 @@
3937
import org.springframework.util.Assert;
4038
import org.springframework.util.StringUtils;
4139

40+
import static org.springframework.data.ldap.repository.query.StringBasedQuery.BindingContext.ParameterBinding;
41+
4242
/**
4343
* String-based Query abstracting a query with parameter bindings.
4444
*
@@ -48,7 +48,7 @@
4848
class StringBasedQuery {
4949

5050
private final String query;
51-
private final Parameters<?, ?> parameters;
51+
private final LdapParameters parameters;
5252
private final List<ParameterBinding> queryParameterBindings = new ArrayList<>();
5353
private final ExpressionDependencies expressionDependencies;
5454

@@ -59,7 +59,7 @@ class StringBasedQuery {
5959
* @param parameters must not be {@literal null}.
6060
* @param expressionParser must not be {@literal null}.
6161
*/
62-
public StringBasedQuery(String query, Parameters<?, ?> parameters, ValueExpressionDelegate expressionParser) {
62+
public StringBasedQuery(String query, LdapParameters parameters, ValueExpressionDelegate expressionParser) {
6363

6464
this.query = ParameterBindingParser.parseAndCollectParameterBindingsFromQueryIntoBindings(query,
6565
this.queryParameterBindings, expressionParser);
@@ -298,15 +298,15 @@ public static String bind(String input, List<Object> parameters) {
298298
*/
299299
static class BindingContext {
300300

301-
private final Parameters<?, ?> parameters;
301+
private final LdapParameters parameters;
302302
private final ParameterAccessor parameterAccessor;
303303
private final List<ParameterBinding> bindings;
304304
private final Function<ValueExpression, Object> evaluator;
305305

306306
/**
307307
* Create new {@link BindingContext}.
308308
*/
309-
BindingContext(Parameters<?, ?> parameters, ParameterAccessor parameterAccessor, List<ParameterBinding> bindings,
309+
BindingContext(LdapParameters parameters, ParameterAccessor parameterAccessor, List<ParameterBinding> bindings,
310310
Function<ValueExpression, Object> evaluator) {
311311

312312
this.parameters = parameters;
@@ -356,11 +356,20 @@ private Object getParameterValueForBinding(ParameterBinding binding) {
356356
if (binding.isExpression()) {
357357
return evaluator.apply(binding.getRequiredExpression());
358358
}
359-
360359
Object value = binding.isNamed()
361360
? parameterAccessor.getBindableValue(getParameterIndex(parameters, binding.getRequiredParameterName()))
362361
: parameterAccessor.getBindableValue(binding.getParameterIndex());
363-
return value == null ? null : LdapEncoder.filterEncode(value.toString());
362+
363+
if (value == null) {
364+
return null;
365+
}
366+
367+
org.springframework.data.ldap.repository.LdapEncoder customLdapEncoder = parameters.encoderForParameterWithName(
368+
binding.getRequiredParameterName());
369+
if (customLdapEncoder != null) {
370+
return customLdapEncoder.filterEncode(value.toString());
371+
}
372+
return LdapEncoder.filterEncode(value.toString());
364373
}
365374

366375
private int getParameterIndex(Parameters<?, ?> parameters, String parameterName) {

src/test/java/org/springframework/data/ldap/repository/query/AnnotatedLdapRepositoryQueryUnitTests.java

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

18-
import static org.assertj.core.api.Assertions.*;
19-
2018
import java.util.List;
2119

2220
import org.junit.jupiter.api.Test;
2321
import org.mockito.Mockito;
2422

2523
import org.springframework.data.ldap.core.mapping.LdapMappingContext;
24+
import org.springframework.data.ldap.repository.LdapEncoder;
25+
import org.springframework.data.ldap.repository.LdapParam;
2626
import org.springframework.data.ldap.repository.LdapRepository;
2727
import org.springframework.data.ldap.repository.Query;
2828
import org.springframework.data.mapping.model.EntityInstantiators;
@@ -32,6 +32,8 @@
3232
import org.springframework.ldap.core.LdapOperations;
3333
import org.springframework.ldap.query.LdapQuery;
3434

35+
import static org.assertj.core.api.Assertions.assertThat;
36+
3537
/**
3638
* Unit tests for {@link AnnotatedLdapRepositoryQuery}
3739
*
@@ -79,6 +81,18 @@ void shouldEncodeBase() throws NoSuchMethodException {
7981
assertThat(ldapQuery.base()).hasToString("cn=John\\29");
8082
}
8183

84+
@Test
85+
void shouldEncodeWithCustomEncoder() throws NoSuchMethodException {
86+
87+
LdapQueryMethod method = queryMethod("customEncoder", String.class);
88+
AnnotatedLdapRepositoryQuery query = repositoryQuery(method);
89+
90+
LdapQuery ldapQuery = query.createQuery(
91+
new LdapParametersParameterAccessor(method, new Object[] { "Doe" }));
92+
93+
assertThat(ldapQuery.filter().encode()).isEqualTo("(cn=Doebar)");
94+
}
95+
8296
private LdapQueryMethod queryMethod(String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
8397
return new LdapQueryMethod(QueryRepository.class.getMethod(methodName, parameterTypes),
8498
new DefaultRepositoryMetadata(QueryRepository.class), new SpelAwareProxyProjectionFactory());
@@ -100,5 +114,16 @@ interface QueryRepository extends LdapRepository<SchemaEntry> {
100114
@Query(base = ":dc", value = "(cn=:fullName)")
101115
List<SchemaEntry> baseNamedParameters(String fullName, String dc);
102116

117+
@Query(value = "(cn=:fullName)")
118+
List<SchemaEntry> customEncoder(@LdapParam(value = "fullName", encoder = MyEncoder.class) String fullName);
119+
120+
}
121+
122+
static class MyEncoder implements LdapEncoder {
123+
124+
@Override
125+
public String filterEncode(String value) {
126+
return value + "bar";
127+
}
103128
}
104129
}

0 commit comments

Comments
 (0)