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

@@ -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"));
}
}

View File

@@ -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;
}

View File

@@ -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<Path<Object>, 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<Path<Object>, 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")));

View File

@@ -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<Path<? extends Object>, 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 <P extends Path<? extends S>, S> void assertAdapterWithTargetBinding(MultiValueBinding<P, S> binding,
SingleValueBinding<? extends Path<?>, ?> expected) {
assertThat(binding, is(instanceOf(QuerydslBindings.MultiValueBindingAdapter.class)));
assertThat(ReflectionTestUtils.getField(binding, "delegate"), is((Object) expected));
}
enum ContainsBinding implements SingleValueBinding<StringPath, String> {
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);
}
}
}

View File

@@ -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")));
}
}