DATACMNS-883 - Improved collection path binding for Querydsl-backed web requests.

Improved the dot-path translation of existing Querydsl Path instances by not relying on the toString() representation of the metadata but manually traversing the elements up to the root. Extracted the translation method into QueryDslUtils.

Furthermore, we now automatically insert an CollectionPathBase.any() step for Path instances of that type when reifying the paths from request attributes.
This commit is contained in:
Oliver Gierke
2016-07-10 11:39:40 +02:00
parent 8599ecf019
commit f2a6d63dbb
7 changed files with 159 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 the original author or authors.
* Copyright 2011-2016 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.
@@ -15,17 +15,61 @@
*/
package org.springframework.data.querydsl;
import lombok.experimental.UtilityClass;
import org.springframework.util.StringUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
/**
* Utility class for Querydsl.
*
* @author Oliver Gierke
*/
public abstract class QueryDslUtils {
@UtilityClass
public class QueryDslUtils {
public static final boolean QUERY_DSL_PRESENT = org.springframework.util.ClassUtils
.isPresent("com.querydsl.core.types.Predicate", QueryDslUtils.class.getClassLoader());
private QueryDslUtils() {
/**
* Returns the property path for the given {@link Path}.
*
* @param path can be {@literal null}.
* @return
*/
public static String toDotPath(Path<?> path) {
return toDotPath(path, "");
}
/**
* Recursively builds up the dot path for the given {@link Path} instance by walking up the individual segments until
* the root.
*
* @param path can be {@literal null}.
* @param tail must not be {@literal null}.
* @return
*/
private static String toDotPath(Path<?> path, String tail) {
if (path == null) {
return tail;
}
PathMetadata metadata = path.getMetadata();
Path<?> parent = metadata.getParent();
if (parent == null) {
return tail;
}
Object element = metadata.getElement();
if (element == null || !StringUtils.hasText(element.toString())) {
return toDotPath(parent, tail);
}
return toDotPath(parent, StringUtils.hasText(tail) ? String.format("%s.%s", element, tail) : element.toString());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -15,6 +15,8 @@
*/
package org.springframework.data.querydsl.binding;
import static org.springframework.data.querydsl.QueryDslUtils.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -32,7 +34,6 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.core.types.Predicate;
/**
@@ -158,8 +159,34 @@ public class QuerydslBindings {
return this;
}
public boolean isPathVisible(String path, Class<?> type) {
return getPropertyPath(path, ClassTypeInformation.from(type)) != null;
/**
* Returns whether the given path is available on the given type.
*
* @param path must not be {@literal null}.
* @param type must not be {@literal null}.
* @return
*/
boolean isPathAvailable(String path, Class<?> type) {
Assert.notNull(path, "Path must not be null!");
Assert.notNull(type, "Type must not be null!");
return isPathAvailable(path, ClassTypeInformation.from(type));
}
/**
* Returns whether the given path is available on the given type.
*
* @param path must not be {@literal null}.
* @param type
* @return
*/
boolean isPathAvailable(String path, TypeInformation<?> type) {
Assert.notNull(path, "Path must not be null!");
Assert.notNull(type, "Type must not be null!");
return getPropertyPath(path, type) != null;
}
/**
@@ -198,6 +225,8 @@ public class QuerydslBindings {
*/
Path<?> getExistingPath(PropertyPath path) {
Assert.notNull(path, "PropertyPath must not be null!");
PathAndBinding<?, ?> pathAndBuilder = pathSpecs.get(path.toDotPath());
return pathAndBuilder == null ? null : pathAndBuilder.getPath();
}
@@ -209,6 +238,8 @@ public class QuerydslBindings {
*/
PropertyPath getPropertyPath(String path, TypeInformation<?> type) {
Assert.notNull(path, "Path must not be null!");
if (!isPathVisible(path)) {
return null;
}
@@ -272,23 +303,6 @@ public class QuerydslBindings {
return whiteList.contains(path);
}
/**
* Returns the property path for the given {@link Path}.
*
* @param path can be {@literal null}.
* @return
*/
private static String toDotPath(Path<?> path) {
if (path == null) {
return "";
}
PathMetadata metadata = path.getMetadata();
return path.toString().substring(metadata.getRootPath().getMetadata().getName().length() + 1);
}
/**
* A binder for {@link Path}s.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -41,6 +41,7 @@ import org.springframework.util.StringUtils;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.CollectionPathBase;
/**
* Builder assembling {@link Predicate} out of {@link PropertyValues}.
@@ -101,7 +102,7 @@ public class QuerydslPredicateBuilder {
String path = entry.getKey();
if (!bindings.isPathVisible(path, type.getType())) {
if (!bindings.isPathAvailable(path, type)) {
continue;
}
@@ -179,6 +180,10 @@ public class QuerydslPredicateBuilder {
*/
private Path<?> reifyPath(PropertyPath path, Path<?> base) {
if (base instanceof CollectionPathBase) {
return reifyPath(path, (Path<?>) ((CollectionPathBase<?, ?, ?>) base).any());
}
Path<?> entityPath = base != null ? base : resolver.createPath(path.getOwningType().getType());
Field field = ReflectionUtils.findField(entityPath.getClass(), path.getSegment());