From f93d2df4f1b83713b84a45113fe01dcce100e0ba Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 12 Dec 2016 16:22:56 +0100 Subject: [PATCH] DATACMNS-941 - Support for downcasts in Querydsl web bindings. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../data/querydsl/QueryDslUtils.java | 5 + .../querydsl/binding/PathInformation.java | 76 ++++++++++ .../binding/PropertyPathInformation.java | 141 ++++++++++++++++++ .../querydsl/binding/QuerydslBindings.java | 84 ++++------- .../binding/QuerydslPathInformation.java | 95 ++++++++++++ .../binding/QuerydslPredicateBuilder.java | 62 ++------ .../data/querydsl/QueryDslUtilsUnitTests.java | 14 +- .../springframework/data/querydsl/User.java | 15 ++ .../QuerydslBindingsFactoryUnitTests.java | 5 +- .../binding/QuerydslBindingsUnitTests.java | 59 ++++++-- .../QuerydslPredicateBuilderUnitTests.java | 38 +++++ 11 files changed, 474 insertions(+), 120 deletions(-) create mode 100644 src/main/java/org/springframework/data/querydsl/binding/PathInformation.java create mode 100644 src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java create mode 100644 src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java diff --git a/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java b/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java index abc87f0fb..a6091e828 100644 --- a/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java +++ b/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java @@ -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())) { diff --git a/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java new file mode 100644 index 000000000..071bf97dd --- /dev/null +++ b/src/main/java/org/springframework/data/querydsl/binding/PathInformation.java @@ -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); +} diff --git a/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java new file mode 100644 index 000000000..463402809 --- /dev/null +++ b/src/main/java/org/springframework/data/querydsl/binding/PropertyPathInformation.java @@ -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; + } +} diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java index 69a0f45f0..14c4847e7 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java @@ -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 , T> MultiValueBinding getBindingForPath(PropertyPath path) { + public , T> MultiValueBinding getBindingForPath(PathInformation path) { Assert.notNull(path, "PropertyPath must not be null!"); @@ -212,7 +216,7 @@ public class QuerydslBindings { } } - pathAndBinding = (PathAndBinding) typeSpecs.get(path.getLeafProperty().getType()); + pathAndBinding = (PathAndBinding) 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 segments = Arrays.asList(path.toDotPath().split("\\.")); @@ -436,21 +443,10 @@ public class QuerydslBindings { * * @author Oliver Gierke */ + @RequiredArgsConstructor public final class TypeBinder { - private final Class type; - - /** - * Creates a new {@link TypeBinder} for the given type. - * - * @param type must not be {@literal null}. - */ - private TypeBinder(Class type) { - - Assert.notNull(type, "Type must not be null!"); - - this.type = type; - } + private final @NonNull Class 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, T> { - private final Path path; - private final MultiValueBinding 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 binding) { - - this.path = path; - this.binding = binding; - } - - public Path getPath() { - return path; - } - - public MultiValueBinding getBinding() { - return binding; - } + Path path; + MultiValueBinding binding; } /** @@ -513,26 +491,18 @@ public class QuerydslBindings { * * @author Oliver Gierke */ - static class MultiValueBindingAdapter, S> implements MultiValueBinding { + @RequiredArgsConstructor + static class MultiValueBindingAdapter

, T> implements MultiValueBinding { - private final SingleValueBinding delegate; - - /** - * Creates a new {@link MultiValueBindingAdapter} for the given {@link SingleValueBinding}. - * - * @param delegate must not be {@literal null}. - */ - public MultiValueBindingAdapter(SingleValueBinding delegate) { - this.delegate = delegate; - } + private final @NonNull SingleValueBinding 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 value) { - Iterator iterator = value.iterator(); + public Predicate bind(P path, Collection value) { + Iterator iterator = value.iterator(); return delegate.bind(path, iterator.hasNext() ? iterator.next() : null); } } diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java new file mode 100644 index 000000000..1f78dc3c2 --- /dev/null +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPathInformation.java @@ -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; + } +} diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java index bc53abdcd..bd230fab1 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java @@ -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> paths; + private final Map> paths; private final EntityPathResolver resolver; /** @@ -70,7 +67,7 @@ public class QuerydslPredicateBuilder { this.defaultBinding = new QuerydslDefaultBinding(); this.conversionService = conversionService; - this.paths = new HashMap>(); + this.paths = new HashMap>(); 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 values) { + private Predicate invokeBinding(PathInformation dotPath, QuerydslBindings bindings, Collection 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 convertToPropertyPathSpecificType(List source, PropertyPath path) { + private Collection convertToPropertyPathSpecificType(List 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); } /** diff --git a/src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java b/src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java index da33c453e..88e57fd93 100644 --- a/src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/querydsl/QueryDslUtilsUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.data.querydsl; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.springframework.data.querydsl.QueryDslUtils.*; import org.junit.Test; @@ -32,6 +33,17 @@ public class QueryDslUtilsUnitTests { */ @Test public void rendersDotPathForPathTraversalContainingAnyExpression() { - assertThat(QueryDslUtils.toDotPath(QUser.user.addresses.any().street), is("addresses.street")); + assertThat(toDotPath(QUser.user.addresses.any().street), is("addresses.street")); + } + + /** + * @see DATACMNS-941 + */ + @Test + public void skipsIntermediateDelegates() { + + assertThat(toDotPath(QUser.user.as(QSpecialUser.class).as(QSpecialUser.class).specialProperty), + is("specialProperty")); + assertThat(toDotPath(QUser.user.as(QSpecialUser.class).specialProperty), is("specialProperty")); } } diff --git a/src/test/java/org/springframework/data/querydsl/User.java b/src/test/java/org/springframework/data/querydsl/User.java index 0e523f53d..3a4d63bcf 100644 --- a/src/test/java/org/springframework/data/querydsl/User.java +++ b/src/test/java/org/springframework/data/querydsl/User.java @@ -45,3 +45,18 @@ public class User { this.address = address; } } + +@QueryEntity +class SpecialUser extends User { + + public String specialProperty; + + public SpecialUser(String firstname, String lastname, Address address) { + super(firstname, lastname, address); + } +} + +@QueryEntity +class UserWrapper { + public User user; +} diff --git a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java index 4e2d3228d..105ff77e9 100644 --- a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java @@ -26,7 +26,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.data.mapping.PropertyPath; import org.springframework.data.querydsl.QUser; import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.querydsl.User; @@ -76,7 +75,7 @@ public class QuerydslBindingsFactoryUnitTests { QuerydslBindings bindings = factory.createBindingsFor(null, USER_TYPE); MultiValueBinding, Object> binding = bindings - .getBindingForPath(PropertyPath.from("firstname", User.class)); + .getBindingForPath(PropertyPathInformation.of("firstname", User.class)); assertThat(binding.bind((Path) QUser.user.firstname, Collections.singleton("rand")), is((Predicate) QUser.user.firstname.contains("rand"))); @@ -97,7 +96,7 @@ public class QuerydslBindingsFactoryUnitTests { QuerydslBindings bindings = factory.createBindingsFor(SpecificBinding.class, USER_TYPE); MultiValueBinding, Object> binding = bindings - .getBindingForPath(PropertyPath.from("firstname", User.class)); + .getBindingForPath(PropertyPathInformation.of("firstname", User.class)); assertThat(binding.bind((Path) QUser.user.firstname, Collections.singleton("rand")), is((Predicate) QUser.user.firstname.eq("RAND"))); diff --git a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsUnitTests.java b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsUnitTests.java index 6a9bd23c0..808f2224b 100644 --- a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsUnitTests.java +++ b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsUnitTests.java @@ -21,7 +21,7 @@ import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.querydsl.QSpecialUser; import org.springframework.data.querydsl.QUser; import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.querydsl.User; @@ -71,7 +71,10 @@ public class QuerydslBindingsUnitTests { */ @Test public void returnsNullIfNoBindingRegisteredForPath() { - assertThat(bindings.getBindingForPath(PropertyPath.from("lastname", User.class)), nullValue()); + + PathInformation path = PropertyPathInformation.of("lastname", User.class); + + assertThat(bindings.getBindingForPath(path), nullValue()); } /** @@ -82,8 +85,9 @@ public class QuerydslBindingsUnitTests { bindings.bind(QUser.user.firstname).first(CONTAINS_BINDING); - assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("firstname", User.class)), - CONTAINS_BINDING); + PathInformation path = PropertyPathInformation.of("firstname", User.class); + + assertAdapterWithTargetBinding(bindings.getBindingForPath(path), CONTAINS_BINDING); } /** @@ -94,8 +98,9 @@ public class QuerydslBindingsUnitTests { bindings.bind(QUser.user.address.street).first(CONTAINS_BINDING); - assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("address.street", User.class)), - CONTAINS_BINDING); + PathInformation path = PropertyPathInformation.of("address.street", User.class); + + assertAdapterWithTargetBinding(bindings.getBindingForPath(path), CONTAINS_BINDING); } /** @@ -106,8 +111,9 @@ public class QuerydslBindingsUnitTests { bindings.bind(String.class).first(CONTAINS_BINDING); - assertAdapterWithTargetBinding(bindings.getBindingForPath(PropertyPath.from("address.street", User.class)), - CONTAINS_BINDING); + PathInformation path = PropertyPathInformation.of("address.street", User.class); + + assertAdapterWithTargetBinding(bindings.getBindingForPath(path), CONTAINS_BINDING); } /** @@ -118,7 +124,9 @@ public class QuerydslBindingsUnitTests { bindings.bind(String.class).first(CONTAINS_BINDING); - assertThat(bindings.getBindingForPath(PropertyPath.from("inceptionYear", User.class)), nullValue()); + PathInformation path = PropertyPathInformation.of("inceptionYear", User.class); + + assertThat(bindings.getBindingForPath(path), nullValue()); } /** @@ -261,7 +269,7 @@ public class QuerydslBindingsUnitTests { bindings.bind(QUser.user.address.city).as("city").first(CONTAINS_BINDING); - PropertyPath path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class)); + PathInformation path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class)); assertThat(path, is(notNullValue())); assertThat(bindings.isPathAvailable("city", User.class), is(true)); @@ -279,14 +287,14 @@ public class QuerydslBindingsUnitTests { bindings.including(QUser.user.address.city); bindings.bind(QUser.user.address.city).as("city").first(CONTAINS_BINDING); - PropertyPath path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class)); + PathInformation path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class)); assertThat(path, is(notNullValue())); assertThat(bindings.isPathAvailable("city", User.class), is(true)); assertThat(bindings.isPathAvailable("address.city", User.class), is(true)); - PropertyPath propertyPath = bindings.getPropertyPath("address.city", ClassTypeInformation.from(User.class)); + PathInformation propertyPath = bindings.getPropertyPath("address.city", ClassTypeInformation.from(User.class)); assertThat(propertyPath, is(notNullValue())); assertAdapterWithTargetBinding(bindings.getBindingForPath(propertyPath), CONTAINS_BINDING); @@ -300,17 +308,42 @@ public class QuerydslBindingsUnitTests { bindings.bind(QUser.user.address.city).as("city").withDefaultBinding(); - PropertyPath path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class)); + PathInformation path = bindings.getPropertyPath("city", ClassTypeInformation.from(User.class)); assertThat(path, is(notNullValue())); MultiValueBinding, Object> binding = bindings.getBindingForPath(path); assertThat(binding, is(nullValue())); } + /** + * @see DATACMNS-941 + */ + @Test + public void registersBindingForPropertyOfSubtype() { + + bindings.bind(QUser.user.as(QSpecialUser.class).specialProperty).first(ContainsBinding.INSTANCE); + + assertThat(bindings.isPathAvailable("specialProperty", User.class), is(true)); + } + private static

, S> void assertAdapterWithTargetBinding(MultiValueBinding binding, SingleValueBinding, ?> expected) { assertThat(binding, is(instanceOf(QuerydslBindings.MultiValueBindingAdapter.class))); assertThat(ReflectionTestUtils.getField(binding, "delegate"), is((Object) expected)); } + + enum ContainsBinding implements SingleValueBinding { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.binding.SingleValueBinding#bind(com.querydsl.core.types.Path, java.lang.Object) + */ + @Override + public Predicate bind(StringPath path, String value) { + return path.contains(value); + } + } } diff --git a/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java b/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java index 5b75d8b38..46098be98 100644 --- a/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilderUnitTests.java @@ -27,7 +27,9 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.junit.Before; import org.junit.Test; +import org.springframework.data.querydsl.QSpecialUser; import org.springframework.data.querydsl.QUser; +import org.springframework.data.querydsl.QUserWrapper; import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.querydsl.User; import org.springframework.data.querydsl.Users; @@ -219,4 +221,40 @@ public class QuerydslPredicateBuilderUnitTests { assertThat(predicate, is((Predicate) QUser.user.addresses.any().street.eq("VALUE"))); } + + /** + * @see DATACMNS-941 + */ + @Test + public void buildsPredicateForBindingUsingDowncast() { + + values.add("specialProperty", "VALUE"); + + QuerydslBindings bindings = new QuerydslBindings(); + bindings.bind(QUser.user.as(QSpecialUser.class).specialProperty)// + .first(QuerydslBindingsUnitTests.ContainsBinding.INSTANCE); + + Predicate predicate = builder.getPredicate(USER_TYPE, values, bindings); + + assertThat(predicate, is((Predicate) QUser.user.as(QSpecialUser.class).specialProperty.contains("VALUE"))); + } + + /** + * @see DATACMNS-941 + */ + @Test + public void buildsPredicateForBindingUsingNestedDowncast() { + + values.add("user.specialProperty", "VALUE"); + + QUserWrapper $ = QUserWrapper.userWrapper; + + QuerydslBindings bindings = new QuerydslBindings(); + bindings.bind($.user.as(QSpecialUser.class).specialProperty)// + .first(QuerydslBindingsUnitTests.ContainsBinding.INSTANCE); + + Predicate predicate = builder.getPredicate(USER_TYPE, values, bindings); + + assertThat(predicate, is((Predicate) $.user.as(QSpecialUser.class).specialProperty.contains("VALUE"))); + } }