Accept Object values in QuerydslPredicateBuilder.

We now accept Object-typed values to allow broader reuse of QuerydslPredicateBuilder. The conversion into the actual value considers the origin type and checks assignability before employing the ConversionService.

Closes #2573
This commit is contained in:
Mark Paluch
2022-03-10 10:21:46 +01:00
parent e3639eaf44
commit 9df8a32b85

View File

@@ -32,9 +32,11 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils; import org.springframework.util.ObjectUtils;
import com.querydsl.core.BooleanBuilder; import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Path; import com.querydsl.core.types.Path;
@@ -81,8 +83,7 @@ public class QuerydslPredicateBuilder {
* @param bindings the {@link QuerydslBindings} for the predicate. * @param bindings the {@link QuerydslBindings} for the predicate.
* @return the {@link Predicate}. * @return the {@link Predicate}.
*/ */
public Predicate getPredicate(TypeInformation<?> type, MultiValueMap<String, String> values, public Predicate getPredicate(TypeInformation<?> type, MultiValueMap<String, ?> values, QuerydslBindings bindings) {
QuerydslBindings bindings) {
Assert.notNull(bindings, "Context must not be null!"); Assert.notNull(bindings, "Context must not be null!");
@@ -92,9 +93,9 @@ public class QuerydslPredicateBuilder {
return getPredicate(builder); return getPredicate(builder);
} }
for (Entry<String, List<String>> entry : values.entrySet()) { for (Entry<String, ? extends List<?>> entry : values.entrySet()) {
if (isSingleElementCollectionWithoutText(entry.getValue())) { if (isSingleElementCollectionWithEmptyItem(entry.getValue())) {
continue; continue;
} }
@@ -165,33 +166,44 @@ public class QuerydslPredicateBuilder {
/** /**
* Converts the given source values into a collection of elements that are of the given {@link PropertyPath}'s type. * Converts the given source values into a collection of elements that are of the given {@link PropertyPath}'s type.
* Considers a single element list with an empty {@link String} an empty collection because this basically indicates * Considers a single element list with an empty object an empty collection because this basically indicates the
* the property having been submitted but no value provided. * property having been submitted but no value provided.
* *
* @param source must not be {@literal null}. * @param source must not be {@literal null}.
* @param path must not be {@literal null}. * @param path must not be {@literal null}.
* @return * @return
*/ */
private Collection<Object> convertToPropertyPathSpecificType(List<String> source, PathInformation path) { private Collection<Object> convertToPropertyPathSpecificType(List<?> source, PathInformation path) {
Class<?> targetType = path.getLeafType(); Class<?> targetType = path.getLeafType();
if (source.isEmpty() || isSingleElementCollectionWithoutText(source)) { if (source.isEmpty() || isSingleElementCollectionWithEmptyItem(source)) {
return Collections.emptyList(); return Collections.emptyList();
} }
Collection<Object> target = new ArrayList<>(source.size()); Collection<Object> target = new ArrayList<>(source.size());
for (String value : source) { for (Object value : source) {
target.add(getValue(path, targetType, value));
target.add(conversionService.canConvert(String.class, targetType)
? conversionService.convert(value, TypeDescriptor.forObject(value), getTargetTypeDescriptor(path))
: value);
} }
return target; return target;
} }
@Nullable
private Object getValue(PathInformation path, Class<?> targetType, Object value) {
if (ClassUtils.isAssignableValue(targetType, value)) {
return value;
}
if (conversionService.canConvert(value.getClass(), targetType)) {
return conversionService.convert(value, TypeDescriptor.forObject(value), getTargetTypeDescriptor(path));
}
return value;
}
/** /**
* Returns the target {@link TypeDescriptor} for the given {@link PathInformation} by either inspecting the field or * Returns the target {@link TypeDescriptor} for the given {@link PathInformation} by either inspecting the field or
* property (the latter preferred) to pick up annotations potentially defined for formatting purposes. * property (the latter preferred) to pick up annotations potentially defined for formatting purposes.
@@ -213,21 +225,21 @@ public class QuerydslPredicateBuilder {
.nested(new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty), 0); .nested(new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty), 0);
if (result == null) { if (result == null) {
throw new IllegalStateException(String.format("Could not obtain TypeDesciptor for PathInformation %s!", path)); throw new IllegalStateException(String.format("Could not obtain TypeDescriptor for PathInformation %s!", path));
} }
return result; return result;
} }
/** /**
* Returns whether the given collection has exactly one element that doesn't contain any text. This is basically an * Returns whether the given collection has exactly one element that is empty (i.e. doesn't contain text). This is
* indicator that a request parameter has been submitted but no value for it. * basically an indicator that a request parameter has been submitted but no value for it.
* *
* @param source must not be {@literal null}. * @param source must not be {@literal null}.
* @return * @return
*/ */
private static boolean isSingleElementCollectionWithoutText(List<String> source) { private static boolean isSingleElementCollectionWithEmptyItem(List<?> source) {
return source.size() == 1 && !StringUtils.hasLength(source.get(0)); return source.size() == 1 && ObjectUtils.isEmpty(source.get(0));
} }
/** /**