Skip to content

Commit

Permalink
Adds LdapParam and LdapEncoder
Browse files Browse the repository at this point in the history
fixes gh-509
  • Loading branch information
marcingrzejszczak committed Oct 23, 2024
1 parent 047f55a commit 21ef2a7
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2024 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;

/**
* Allows plugging in custom encoding for {@link LdapParam}.
*
* @author Marcin Grzejszczak
* @since 3.5.0
*/
public interface LdapEncoder {

/**
* Escape a value for use in a filter.
* @param value the value to escape.
* @return a properly escaped representation of the supplied value.
*/
String filterEncode(String value);

/**
* Default implementation of {@link LdapEncoder} that uses
* {@link org.springframework.ldap.support.LdapEncoder}.
*/
class DefaultLdapEncoder implements LdapEncoder {

@Override
public String filterEncode(String value) {
return org.springframework.ldap.support.LdapEncoder.filterEncode(value);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2016-2024 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;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.query.Param;

/**
* A {@link Param} alias that allows passing of custom {@link LdapEncoder}.
*
* @author Marcin Grzejszczak
* @since 3.5.0
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LdapParam {

@AliasFor(annotation = Param.class)
String value();

/**
* {@link LdapEncoder} to instantiate to encode query parameters.
*
* @return {@link LdapEncoder} class
*/
Class<? extends LdapEncoder> encoder() default LdapEncoder.DefaultLdapEncoder.class;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2024 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 java.lang.reflect.Method;
import java.util.List;

import org.springframework.core.MethodParameter;
import org.springframework.data.geo.Distance;
import org.springframework.data.ldap.repository.LdapEncoder;
import org.springframework.data.ldap.repository.LdapParam;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersSource;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;

/**
* Custom extension of {@link Parameters} discovering additional
*
* @author Marcin Grzejszczak
* @since 3.5.0
*/
public class LdapParameters extends Parameters<LdapParameters, LdapParameters.LdapParameter> {

private final TypeInformation<?> domainType;

/**
* Creates a new {@link LdapParameters} instance from the given {@link Method} and {@link LdapQueryMethod}.
*
* @param parametersSource must not be {@literal null}.
*/
public LdapParameters(ParametersSource parametersSource) {

super(parametersSource, methodParameter -> new LdapParameter(methodParameter,
parametersSource.getDomainTypeInformation()));

this.domainType = parametersSource.getDomainTypeInformation();
}

private LdapParameters(List<LdapParameter> parameters, TypeInformation<?> domainType) {

super(parameters);
this.domainType = domainType;
}

@Override
protected LdapParameters createFrom(List<LdapParameter> parameters) {
return new LdapParameters(parameters, this.domainType);
}

@Nullable
LdapEncoder encoderForParameterWithName(String parameterName) {
for (LdapParameter parameter : this) {
if (parameterName.equals(parameter.getName().orElse(null))) {
LdapParam ldapParam = parameter.parameter.getParameterAnnotation(LdapParam.class);
if (ldapParam == null) {
return null;
}
Class<? extends LdapEncoder> encoder = ldapParam.encoder();
try {
return encoder.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
return null;
}

/**
* Custom {@link Parameter} implementation adding parameters of type {@link Distance} to the special ones.
*
* @author Marcin Grzejszczak
*/
protected static class LdapParameter extends Parameter {

private final MethodParameter parameter;

/**
* Creates a new {@link LdapParameter}.
*
* @param parameter must not be {@literal null}.
* @param domainType must not be {@literal null}.
*/
LdapParameter(MethodParameter parameter, TypeInformation<?> domainType) {
super(parameter, domainType);
this.parameter = parameter;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.springframework.data.ldap.repository.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersSource;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -50,6 +49,16 @@ public LdapQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFac
this.method = method;
}

@Override
protected LdapParameters createParameters(ParametersSource parametersSource) {
return new LdapParameters(parametersSource);
}

@Override
public LdapParameters getParameters() {
return (LdapParameters) super.getParameters();
}

/**
* Check whether the target method is annotated with {@link org.springframework.data.ldap.repository.Query}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*/
package org.springframework.data.ldap.repository.query;

import static org.springframework.data.ldap.repository.query.StringBasedQuery.BindingContext.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand All @@ -39,6 +37,8 @@
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import static org.springframework.data.ldap.repository.query.StringBasedQuery.BindingContext.ParameterBinding;

/**
* String-based Query abstracting a query with parameter bindings.
*
Expand All @@ -48,7 +48,7 @@
class StringBasedQuery {

private final String query;
private final Parameters<?, ?> parameters;
private final LdapParameters parameters;
private final List<ParameterBinding> queryParameterBindings = new ArrayList<>();
private final ExpressionDependencies expressionDependencies;

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

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

private final Parameters<?, ?> parameters;
private final LdapParameters parameters;
private final ParameterAccessor parameterAccessor;
private final List<ParameterBinding> bindings;
private final Function<ValueExpression, Object> evaluator;

/**
* Create new {@link BindingContext}.
*/
BindingContext(Parameters<?, ?> parameters, ParameterAccessor parameterAccessor, List<ParameterBinding> bindings,
BindingContext(LdapParameters parameters, ParameterAccessor parameterAccessor, List<ParameterBinding> bindings,
Function<ValueExpression, Object> evaluator) {

this.parameters = parameters;
Expand Down Expand Up @@ -356,11 +356,20 @@ private Object getParameterValueForBinding(ParameterBinding binding) {
if (binding.isExpression()) {
return evaluator.apply(binding.getRequiredExpression());
}

Object value = binding.isNamed()
? parameterAccessor.getBindableValue(getParameterIndex(parameters, binding.getRequiredParameterName()))
: parameterAccessor.getBindableValue(binding.getParameterIndex());
return value == null ? null : LdapEncoder.filterEncode(value.toString());

if (value == null) {
return null;
}

org.springframework.data.ldap.repository.LdapEncoder customLdapEncoder = parameters.encoderForParameterWithName(
binding.getRequiredParameterName());
if (customLdapEncoder != null) {
return customLdapEncoder.filterEncode(value.toString());
}
return LdapEncoder.filterEncode(value.toString());
}

private int getParameterIndex(Parameters<?, ?> parameters, String parameterName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
*/
package org.springframework.data.ldap.repository.query;

import static org.assertj.core.api.Assertions.*;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import org.springframework.data.ldap.core.mapping.LdapMappingContext;
import org.springframework.data.ldap.repository.LdapEncoder;
import org.springframework.data.ldap.repository.LdapParam;
import org.springframework.data.ldap.repository.LdapRepository;
import org.springframework.data.ldap.repository.Query;
import org.springframework.data.mapping.model.EntityInstantiators;
Expand All @@ -32,6 +32,8 @@
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.query.LdapQuery;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Unit tests for {@link AnnotatedLdapRepositoryQuery}
*
Expand Down Expand Up @@ -79,6 +81,18 @@ void shouldEncodeBase() throws NoSuchMethodException {
assertThat(ldapQuery.base()).hasToString("cn=John\\29");
}

@Test
void shouldEncodeWithCustomEncoder() throws NoSuchMethodException {

LdapQueryMethod method = queryMethod("customEncoder", String.class);
AnnotatedLdapRepositoryQuery query = repositoryQuery(method);

LdapQuery ldapQuery = query.createQuery(
new LdapParametersParameterAccessor(method, new Object[] { "Doe" }));

assertThat(ldapQuery.filter().encode()).isEqualTo("(cn=Doebar)");
}

private LdapQueryMethod queryMethod(String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
return new LdapQueryMethod(QueryRepository.class.getMethod(methodName, parameterTypes),
new DefaultRepositoryMetadata(QueryRepository.class), new SpelAwareProxyProjectionFactory());
Expand All @@ -100,5 +114,16 @@ interface QueryRepository extends LdapRepository<SchemaEntry> {
@Query(base = ":dc", value = "(cn=:fullName)")
List<SchemaEntry> baseNamedParameters(String fullName, String dc);

@Query(value = "(cn=:fullName)")
List<SchemaEntry> customEncoder(@LdapParam(value = "fullName", encoder = MyEncoder.class) String fullName);

}

static class MyEncoder implements LdapEncoder {

@Override
public String filterEncode(String value) {
return value + "bar";
}
}
}

0 comments on commit 21ef2a7

Please sign in to comment.