DATACMNS-941 - Support for downcasts in Querydsl web bindings.

We now support downcasts (e.g. user.as(QSpecialUser.class).…) expressions in Querydsl web binding. We do so by skipping the introduced intermediate step in the path when calculating the dot path.

Refactored the internals of the binding to now work based on a PathInformation implementation for which either a Querydsl Path or a Spring Data PropertyPath can be used. This allows us to reuse the paths of predefined bindings.

A bit of Lombok cleanup in internal implementations.
This commit is contained in:
Oliver Gierke
2016-12-12 16:22:56 +01:00
parent a696484f8d
commit f93d2df4f1
11 changed files with 474 additions and 120 deletions

View File

@@ -21,6 +21,7 @@ import org.springframework.util.StringUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.core.types.PathType;
/**
* Utility class for Querydsl.
@@ -64,6 +65,10 @@ public class QueryDslUtils {
return tail;
}
if (metadata.getPathType().equals(PathType.DELEGATE)) {
return toDotPath(parent, tail);
}
Object element = metadata.getElement();
if (element == null || !StringUtils.hasText(element.toString())) {

View File

@@ -0,0 +1,76 @@
/*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://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.querydsl.binding;
import java.beans.PropertyDescriptor;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.querydsl.EntityPathResolver;
import com.querydsl.core.types.Path;
/**
* Internal abstraction of mapped paths.
*
* @author Oliver Gierke
* @since 1.13
*/
interface PathInformation {
/**
* The type of the leaf property.
*
* @return
*/
Class<?> getLeafType();
/**
* The type of the leaf property's parent type.
*
* @return
*/
Class<?> getLeafParentType();
/**
* The name of the leaf property.
*
* @return
*/
String getLeafProperty();
/**
* The {@link PropertyDescriptor} for the leaf property.
*
* @return
*/
PropertyDescriptor getLeafPropertyDescriptor();
/**
* The dot separated representation of the current path.
*
* @return
*/
String toDotPath();
/**
* Tries to reify a Querydsl {@link Path} from the given {@link PropertyPath} and base.
*
* @param path must not be {@literal null}.
* @param base can be {@literal null}.
* @return
*/
Path<?> reifyPath(EntityPathResolver resolver);
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://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.querydsl.binding;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.ReflectionUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.CollectionPathBase;
/**
* {@link PropertyPath} based implementation of {@link PathInformation}.
*
* @author Oliver Gierke
* @since 1.13
*/
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor(staticName = "of", access = AccessLevel.PRIVATE)
class PropertyPathInformation implements PathInformation {
private final PropertyPath path;
/**
* Creates a new {@link PropertyPathInformation} for the given path and type.
*
* @param path must not be {@literal null} or empty.
* @param type must not be {@literal null}.
* @return
*/
public static PropertyPathInformation of(String path, Class<?> type) {
return PropertyPathInformation.of(PropertyPath.from(path, type));
}
/**
* Creates a new {@link PropertyPathInformation} for the given path and {@link TypeInformation}.
*
* @param path must not be {@literal null} or empty.
* @param type must not be {@literal null}.
* @return
*/
public static PropertyPathInformation of(String path, TypeInformation<?> type) {
return PropertyPathInformation.of(PropertyPath.from(path, type));
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#getLeafType()
*/
@Override
public Class<?> getLeafType() {
return path.getLeafProperty().getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#getLeafParentType()
*/
@Override
public Class<?> getLeafParentType() {
return path.getLeafProperty().getOwningType().getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#getLeafProperty()
*/
@Override
public String getLeafProperty() {
return path.getLeafProperty().getSegment();
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#getLeafPropertyDescriptor()
*/
@Override
public PropertyDescriptor getLeafPropertyDescriptor() {
return BeanUtils.getPropertyDescriptor(getLeafParentType(), getLeafProperty());
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#toDotPath()
*/
@Override
public String toDotPath() {
return path.toDotPath();
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.PathInformation#reifyPath(org.springframework.data.querydsl.EntityPathResolver)
*/
@Override
public Path<?> reifyPath(EntityPathResolver resolver) {
return reifyPath(resolver, path, null);
}
private static Path<?> reifyPath(EntityPathResolver resolver, PropertyPath path, Path<?> base) {
if (base instanceof CollectionPathBase) {
return reifyPath(resolver, path, (Path<?>) ((CollectionPathBase<?, ?, ?>) base).any());
}
Path<?> entityPath = base != null ? base : resolver.createPath(path.getOwningType().getType());
Field field = ReflectionUtils.findField(entityPath.getClass(), path.getSegment());
Object value = ReflectionUtils.getField(field, entityPath);
if (path.hasNext()) {
return reifyPath(resolver, path.next(), (Path<?>) value);
}
return (Path<?>) value;
}
}

View File

@@ -17,6 +17,10 @@ package org.springframework.data.querydsl.binding;
import static org.springframework.data.querydsl.QueryDslUtils.*;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -197,7 +201,7 @@ public class QuerydslBindings {
* @return can be {@literal null}.
*/
@SuppressWarnings("unchecked")
public <S extends Path<? extends T>, T> MultiValueBinding<S, T> getBindingForPath(PropertyPath path) {
public <S extends Path<? extends T>, T> MultiValueBinding<S, T> getBindingForPath(PathInformation path) {
Assert.notNull(path, "PropertyPath must not be null!");
@@ -212,7 +216,7 @@ public class QuerydslBindings {
}
}
pathAndBinding = (PathAndBinding<S, T>) typeSpecs.get(path.getLeafProperty().getType());
pathAndBinding = (PathAndBinding<S, T>) typeSpecs.get(path.getLeafType());
return pathAndBinding == null ? null : pathAndBinding.getBinding();
}
@@ -223,7 +227,7 @@ public class QuerydslBindings {
* @param path must not be {@literal null}.
* @return
*/
Path<?> getExistingPath(PropertyPath path) {
Path<?> getExistingPath(PathInformation path) {
Assert.notNull(path, "PropertyPath must not be null!");
@@ -232,24 +236,27 @@ public class QuerydslBindings {
}
/**
* @param path
* @param type
* Returns the {@link PathInformation} for the given path and {@link TypeInformation}.
*
* @param path must not be {@literal null}.
* @param type must not be {@literal null}.
* @return
*/
PropertyPath getPropertyPath(String path, TypeInformation<?> type) {
PathInformation getPropertyPath(String path, TypeInformation<?> type) {
Assert.notNull(path, "Path must not be null!");
Assert.notNull(type, "Type information must not be null!");
if (!isPathVisible(path)) {
return null;
}
if (pathSpecs.containsKey(path)) {
return PropertyPath.from(toDotPath(pathSpecs.get(path).getPath()), type);
return QuerydslPathInformation.of(pathSpecs.get(path).getPath());
}
try {
PropertyPath propertyPath = PropertyPath.from(path, type);
PathInformation propertyPath = PropertyPathInformation.of(path, type);
return isPathVisible(propertyPath) ? propertyPath : null;
} catch (PropertyReferenceException o_O) {
return null;
@@ -262,7 +269,7 @@ public class QuerydslBindings {
* @param path
* @return
*/
private boolean isPathVisible(PropertyPath path) {
private boolean isPathVisible(PathInformation path) {
List<String> segments = Arrays.asList(path.toDotPath().split("\\."));
@@ -436,21 +443,10 @@ public class QuerydslBindings {
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
public final class TypeBinder<T> {
private final Class<T> type;
/**
* Creates a new {@link TypeBinder} for the given type.
*
* @param type must not be {@literal null}.
*/
private TypeBinder(Class<T> type) {
Assert.notNull(type, "Type must not be null!");
this.type = type;
}
private final @NonNull Class<T> type;
/**
* Configures the given {@link SingleValueBinding} to be used for the current type.
@@ -479,32 +475,14 @@ public class QuerydslBindings {
* A pair of a {@link Path} and the registered {@link MultiValueBinding}.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.11
*/
@Value
private static class PathAndBinding<S extends Path<? extends T>, T> {
private final Path<?> path;
private final MultiValueBinding<S, T> binding;
/**
* Creates a new {@link PathAndBinding} for the given {@link Path} and {@link MultiValueBinding}.
*
* @param path must not be {@literal null}.
* @param binding must not be {@literal null}.
*/
public PathAndBinding(S path, MultiValueBinding<S, T> binding) {
this.path = path;
this.binding = binding;
}
public Path<?> getPath() {
return path;
}
public MultiValueBinding<S, T> getBinding() {
return binding;
}
Path<?> path;
MultiValueBinding<S, T> binding;
}
/**
@@ -513,26 +491,18 @@ public class QuerydslBindings {
*
* @author Oliver Gierke
*/
static class MultiValueBindingAdapter<T extends Path<? extends S>, S> implements MultiValueBinding<T, S> {
@RequiredArgsConstructor
static class MultiValueBindingAdapter<P extends Path<? extends T>, T> implements MultiValueBinding<P, T> {
private final SingleValueBinding<T, S> delegate;
/**
* Creates a new {@link MultiValueBindingAdapter} for the given {@link SingleValueBinding}.
*
* @param delegate must not be {@literal null}.
*/
public MultiValueBindingAdapter(SingleValueBinding<T, S> delegate) {
this.delegate = delegate;
}
private final @NonNull SingleValueBinding<P, T> delegate;
/*
* (non-Javadoc)
* @see org.springframework.data.web.querydsl.MultiValueBinding#bind(com.mysema.query.types.Path, java.util.Collection)
*/
@Override
public Predicate bind(T path, Collection<? extends S> value) {
Iterator<? extends S> iterator = value.iterator();
public Predicate bind(P path, Collection<? extends T> value) {
Iterator<? extends T> iterator = value.iterator();
return delegate.bind(path, iterator.hasNext() ? iterator.next() : null);
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://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.querydsl.binding;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import java.beans.PropertyDescriptor;
import org.springframework.beans.BeanUtils;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QueryDslUtils;
import com.querydsl.core.types.Path;
/**
* {@link PathInformation} based on a Querydsl {@link Path}.
*
* @author Oliver Gierke
* @since 1.13
*/
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor(staticName = "of")
class QuerydslPathInformation implements PathInformation {
private final Path<?> path;
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#getLeafType()
*/
@Override
public Class<?> getLeafType() {
return path.getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#getLeafParentType()
*/
@Override
public Class<?> getLeafParentType() {
return path.getMetadata().getParent().getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#getLeafProperty()
*/
@Override
public String getLeafProperty() {
return path.getMetadata().getElement().toString();
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#getLeafPropertyDescriptor()
*/
@Override
public PropertyDescriptor getLeafPropertyDescriptor() {
return BeanUtils.getPropertyDescriptor(getLeafParentType(), getLeafProperty());
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.MappedPath#toDotPath()
*/
@Override
public String toDotPath() {
return QueryDslUtils.toDotPath(path);
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.binding.PathInformation#reifyPath(org.springframework.data.querydsl.EntityPathResolver)
*/
public Path<?> reifyPath(EntityPathResolver resolver) {
return path;
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.data.querydsl.binding;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -25,7 +24,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyValues;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.Property;
@@ -41,7 +39,6 @@ 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}.
@@ -54,7 +51,7 @@ public class QuerydslPredicateBuilder {
private final ConversionService conversionService;
private final MultiValueBinding<?, ?> defaultBinding;
private final Map<PropertyPath, Path<?>> paths;
private final Map<PathInformation, Path<?>> paths;
private final EntityPathResolver resolver;
/**
@@ -70,7 +67,7 @@ public class QuerydslPredicateBuilder {
this.defaultBinding = new QuerydslDefaultBinding();
this.conversionService = conversionService;
this.paths = new HashMap<PropertyPath, Path<?>>();
this.paths = new HashMap<PathInformation, Path<?>>();
this.resolver = resolver;
}
@@ -106,7 +103,7 @@ public class QuerydslPredicateBuilder {
continue;
}
PropertyPath propertyPath = bindings.getPropertyPath(path, type);
PathInformation propertyPath = bindings.getPropertyPath(path, type);
if (propertyPath == null) {
continue;
@@ -132,7 +129,7 @@ public class QuerydslPredicateBuilder {
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private Predicate invokeBinding(PropertyPath dotPath, QuerydslBindings bindings, Collection<Object> values) {
private Predicate invokeBinding(PathInformation dotPath, QuerydslBindings bindings, Collection<Object> values) {
Path<?> path = getPath(dotPath, bindings);
@@ -151,7 +148,7 @@ public class QuerydslPredicateBuilder {
* @param bindings must not be {@literal null}.
* @return
*/
private Path<?> getPath(PropertyPath path, QuerydslBindings bindings) {
private Path<?> getPath(PathInformation path, QuerydslBindings bindings) {
Path<?> resolvedPath = bindings.getExistingPath(path);
@@ -165,37 +162,12 @@ public class QuerydslPredicateBuilder {
return resolvedPath;
}
resolvedPath = reifyPath(path, null);
resolvedPath = path.reifyPath(resolver);
paths.put(path, resolvedPath);
return resolvedPath;
}
/**
* Tries to reify a Querydsl {@link Path} from the given {@link PropertyPath} and base.
*
* @param path must not be {@literal null}.
* @param base can be {@literal null}.
* @return
*/
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());
Object value = ReflectionUtils.getField(field, entityPath);
if (path.hasNext()) {
return reifyPath(path.next(), (Path<?>) value);
}
return (Path<?>) value;
}
/**
* 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
@@ -205,10 +177,9 @@ public class QuerydslPredicateBuilder {
* @param path must not be {@literal null}.
* @return
*/
private Collection<Object> convertToPropertyPathSpecificType(List<String> source, PropertyPath path) {
private Collection<Object> convertToPropertyPathSpecificType(List<String> source, PathInformation path) {
PropertyPath leafProperty = path.getLeafProperty();
Class<?> targetType = leafProperty.getOwningType().getProperty(leafProperty.getSegment()).getType();
Class<?> targetType = path.getLeafType();
if (source.isEmpty() || isSingleElementCollectionWithoutText(source)) {
return Collections.emptyList();
@@ -226,26 +197,25 @@ public class QuerydslPredicateBuilder {
}
/**
* Returns the target {@link TypeDescriptor} for the given {@link PropertyPath} 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.
*
* @param path must not be {@literal null}.
* @return
*/
private static TypeDescriptor getTargetTypeDescriptor(PropertyPath path) {
private static TypeDescriptor getTargetTypeDescriptor(PathInformation path) {
PropertyPath leafProperty = path.getLeafProperty();
Class<?> owningType = leafProperty.getOwningType().getType();
PropertyDescriptor descriptor = path.getLeafPropertyDescriptor();
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(owningType, leafProperty.getSegment());
Class<?> owningType = path.getLeafParentType();
String leafProperty = path.getLeafProperty();
if (descriptor == null) {
return TypeDescriptor.nested(ReflectionUtils.findField(owningType, leafProperty.getSegment()), 0);
return TypeDescriptor.nested(ReflectionUtils.findField(owningType, leafProperty), 0);
}
return TypeDescriptor.nested(
new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty.getSegment()),
0);
return TypeDescriptor
.nested(new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty), 0);
}
/**