DATACMNS-669 - Polishing.

Javadoc on MultiValueBinding. Hide QuerydslBindings.bind(…), ….include(…) and ….exclude(…) methods that take String arguments to reduce the probability of no query class being present.

Refined the value handling for empty request values which are represented as String[] containing an empty String. We now massage those into an empty Collection for MultiValueBindings and a null value for SingleValueInvocations.

We currently skip property values that match this pattern entirely but are prepared to allow a configuration flip switch to handle those scenarios in the future. We currently stick to skipping those values to prevent the bindings from having to deal with null values.

We now also allow the bindings to return null to indicate they don't want to get applied in the resulting predicate at all.

Switched to interface based customization to make it easier to use customizations with Spring Data REST. Added Querydsl-specific adapter of RepositoryInvoker to allow Spring Data REST to hook in the execution of the predicate obtained from request parameters.

Original pull request: #132.
This commit is contained in:
Oliver Gierke
2015-07-15 16:13:10 +02:00
parent dc49101850
commit cccfa5e5c5
14 changed files with 506 additions and 136 deletions

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2015 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;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.support.RepositoryInvoker;
import org.springframework.util.MultiValueMap;
import com.mysema.query.types.Predicate;
/**
* Unit tests for {@link QuerydslRepositoryInvokerAdapter}.
*
* @author Oliver Gierke
* @soundtrack Emilie Nicolas - Grown Up
*/
@RunWith(MockitoJUnitRunner.class)
public class QuerydslRepositoryInvokerAdapterUnitTests {
@Mock RepositoryInvoker delegate;
@Mock QueryDslPredicateExecutor<Object> executor;
@Mock Predicate predicate;
QuerydslRepositoryInvokerAdapter adapter;
@Before
public void setUp() {
this.adapter = new QuerydslRepositoryInvokerAdapter(delegate, executor, predicate);
}
/**
* @see DATACMNS-669
*/
@Test
public void forwardsFindAllToExecutorWithPredicate() {
Sort sort = new Sort("firstname");
adapter.invokeFindAll(sort);
verify(executor, times(1)).findAll(predicate, sort);
verify(delegate, times(0)).invokeFindAll(sort);
}
/**
* @see DATACMNS-669
*/
@Test
public void forwardsFindAllWithPageableToExecutorWithPredicate() {
PageRequest pageable = new PageRequest(0, 10);
adapter.invokeFindAll(pageable);
verify(executor, times(1)).findAll(predicate, pageable);
verify(delegate, times(0)).invokeFindAll(pageable);
}
/**
* @see DATACMNS-669
*/
@Test
@SuppressWarnings({ "deprecation", "unchecked" })
public void forwardsMethodsToDelegate() {
adapter.hasDeleteMethod();
verify(delegate, times(1)).hasDeleteMethod();
adapter.hasFindAllMethod();
verify(delegate, times(1)).hasFindAllMethod();
adapter.hasFindOneMethod();
verify(delegate, times(1)).hasFindOneMethod();
adapter.hasSaveMethod();
verify(delegate, times(1)).hasSaveMethod();
adapter.invokeDelete(any(Serializable.class));
verify(delegate, times(1)).invokeDelete(any(Serializable.class));
adapter.invokeFindOne(any(Serializable.class));
verify(delegate, times(1)).invokeFindOne(any(Serializable.class));
adapter.invokeQueryMethod(any(Method.class), any(Map.class), any(Pageable.class), any(Sort.class));
verify(delegate, times(1)).invokeQueryMethod(any(Method.class), any(Map.class), any(Pageable.class),
any(Sort.class));
adapter.invokeQueryMethod(any(Method.class), (MultiValueMap<String, String>) any(MultiValueMap.class),
any(Pageable.class), any(Sort.class));
verify(delegate, times(1)).invokeQueryMethod(any(Method.class),
(MultiValueMap<String, String>) any(MultiValueMap.class), any(Pageable.class), any(Sort.class));
adapter.invokeSave(any());
verify(delegate, times(1)).invokeSave(any());
}
}

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.data.web.querydsl;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collections;
@@ -32,14 +33,15 @@ import com.mysema.query.types.Predicate;
/**
* @author Christoph Strobl
* @author Oliver Gierke
*/
public class QuerydslDefaultBindingUnitTests {
QuerydslDefaultBinding builder;
QuerydslDefaultBinding binding;
@Before
public void setUp() {
builder = new QuerydslDefaultBinding();
binding = new QuerydslDefaultBinding();
}
/**
@@ -48,9 +50,9 @@ public class QuerydslDefaultBindingUnitTests {
@Test
public void shouldCreatePredicateCorrectlyWhenPropertyIsInRoot() {
Predicate predicate = builder.bind(QUser.user.firstname, Collections.singleton("tam"));
Predicate predicate = binding.bind(QUser.user.firstname, Collections.singleton("tam"));
assertThat(predicate, is(QUser.user.firstname.eq("tam")));
assertPredicate(predicate, is(QUser.user.firstname.eq("tam")));
}
/**
@@ -59,31 +61,20 @@ public class QuerydslDefaultBindingUnitTests {
@Test
public void shouldCreatePredicateCorrectlyWhenPropertyIsInNestedElement() {
Predicate predicate = builder.bind(QUser.user.address.city, Collections.singleton("two rivers"));
Predicate predicate = binding.bind(QUser.user.address.city, Collections.singleton("two rivers"));
Assert.assertThat(predicate.toString(), is(QUser.user.address.city.eq("two rivers").toString()));
}
/**
* @see DATACMNS-669
*/
@Test
public void shouldCreatePredicateCorrectlyWhenValueIsNull() {
Predicate predicate = builder.bind(QUser.user.firstname, Collections.emptySet());
assertThat(predicate, is(QUser.user.firstname.isNull()));
}
/**
* @see DATACMNS-669
*/
@Test
public void shouldCreatePredicateWithContainingWhenPropertyIsCollectionLikeAndValueIsObject() {
Predicate predicate = builder.bind(QUser.user.nickNames, Collections.singleton("dragon reborn"));
Predicate predicate = binding.bind(QUser.user.nickNames, Collections.singleton("dragon reborn"));
assertThat(predicate, is(QUser.user.nickNames.contains("dragon reborn")));
assertPredicate(predicate, is(QUser.user.nickNames.contains("dragon reborn")));
}
/**
@@ -92,17 +83,22 @@ public class QuerydslDefaultBindingUnitTests {
@Test
public void shouldCreatePredicateWithInWhenPropertyIsAnObjectAndValueIsACollection() {
Predicate predicate = builder.bind(QUser.user.firstname, Arrays.asList("dragon reborn", "shadowkiller"));
Predicate predicate = binding.bind(QUser.user.firstname, Arrays.asList("dragon reborn", "shadowkiller"));
assertThat(predicate, is(QUser.user.firstname.in(Arrays.asList("dragon reborn", "shadowkiller"))));
assertPredicate(predicate, is(QUser.user.firstname.in(Arrays.asList("dragon reborn", "shadowkiller"))));
}
@Test
public void testname() {
assertThat(binding.bind(QUser.user.lastname, Collections.emptySet()), is(nullValue()));
}
/*
* just to satisfy generic type boundaries o_O
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void assertThat(Predicate predicate, Matcher<? extends Expression> matcher) {
private void assertPredicate(Predicate predicate, Matcher<? extends Expression> matcher) {
Assert.assertThat((Expression) predicate, Is.<Expression> is((Matcher<Expression>) matcher));
}
}

View File

@@ -205,18 +205,6 @@ public class QuerydslPredicateArgumentResolverUnitTests {
assertThat(predicate.toString(), is(QUser.user.inceptionYear.eq(973L).toString()));
}
/**
* @see DATACMNS-669
*/
@Test
public void createBindingContextShouldUseQuerydslPredicationAnntotationDefaultBindingIfNotAnnotated() {
Object bindings = ReflectionTestUtils.invokeMethod(resolver, "createBindings",
getMethodParameterFor("predicateWithoutAnnotation", Predicate.class));
assertThat(bindings, is(instanceOf(QuerydslBindings.class)));
}
/**
* @see DATACMNS-669
*/
@@ -239,11 +227,11 @@ public class QuerydslPredicateArgumentResolverUnitTests {
}
}
static class SpecificBinding extends QuerydslBindings {
static class SpecificBinding implements QuerydslBinderCustomizer<QUser> {
public SpecificBinding() {
public void customize(QuerydslBindings bindings, QUser user) {
bind("firstname").using(new SingleValueBinding<StringPath, String>() {
bindings.bind("firstname").using(new SingleValueBinding<StringPath, String>() {
@Override
public Predicate bind(StringPath path, String value) {
@@ -251,7 +239,7 @@ public class QuerydslPredicateArgumentResolverUnitTests {
}
});
bind(QUser.user.lastname).single(new SingleValueBinding<StringPath, String>() {
bindings.bind(user.lastname).single(new SingleValueBinding<StringPath, String>() {
@Override
public Predicate bind(StringPath path, String value) {
@@ -259,7 +247,7 @@ public class QuerydslPredicateArgumentResolverUnitTests {
}
});
excluding("address");
bindings.excluding("address");
}
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.web.querydsl;
import static java.util.Collections.*;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
@@ -24,7 +23,6 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.querydsl.QUser;
import org.springframework.data.querydsl.User;
@@ -33,9 +31,9 @@ import org.springframework.data.util.ClassTypeInformation;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.collections.CollQueryFactory;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.path.StringPath;
/**
* Unit tests for {@link QuerydslPredicateBuilder}.
@@ -45,13 +43,16 @@ import com.mysema.query.types.Predicate;
*/
public class QuerydslPredicateBuilderUnitTests {
static final ClassTypeInformation<User> USER_TYPE = ClassTypeInformation.from(User.class);
static final QuerydslBindings DEFAULT_BINDINGS = new QuerydslBindings();
QuerydslPredicateBuilder builder;
MultiValueMap<String, String> values;
@Before
public void setUp() {
builder = new QuerydslPredicateBuilder(new DefaultConversionService());
this.builder = new QuerydslPredicateBuilder(new DefaultConversionService());
this.values = new LinkedMultiValueMap<String, String>();
}
/**
@@ -67,7 +68,7 @@ public class QuerydslPredicateBuilderUnitTests {
*/
@Test(expected = IllegalArgumentException.class)
public void getPredicateShouldThrowErrorWhenBindingContextIsNull() {
builder.getPredicate(new MutablePropertyValues(), null, null);
builder.getPredicate(values, null, null);
}
/**
@@ -76,8 +77,7 @@ public class QuerydslPredicateBuilderUnitTests {
@Test
public void getPredicateShouldReturnEmptyPredicateWhenPropertiesAreEmpty() {
assertThat(builder.getPredicate(new MutablePropertyValues(), DEFAULT_BINDINGS, ClassTypeInformation.OBJECT),
is((Predicate) new BooleanBuilder()));
assertThat(builder.getPredicate(values, DEFAULT_BINDINGS, ClassTypeInformation.OBJECT), is(nullValue()));
}
/**
@@ -86,8 +86,9 @@ public class QuerydslPredicateBuilderUnitTests {
@Test
public void resolveArgumentShouldCreateSingleStringParameterPredicateCorrectly() throws Exception {
Predicate predicate = builder.getPredicate(new MutablePropertyValues(singletonMap("firstname", "Oliver")),
DEFAULT_BINDINGS, ClassTypeInformation.from(User.class));
values.add("firstname", "Oliver");
Predicate predicate = builder.getPredicate(values, DEFAULT_BINDINGS, USER_TYPE);
assertThat(predicate, is((Predicate) QUser.user.firstname.eq("Oliver")));
@@ -103,8 +104,9 @@ public class QuerydslPredicateBuilderUnitTests {
@Test
public void resolveArgumentShouldCreateNestedStringParameterPredicateCorrectly() throws Exception {
Predicate predicate = builder.getPredicate(new MutablePropertyValues(singletonMap("address.city", "Linz")),
DEFAULT_BINDINGS, ClassTypeInformation.from(User.class));
values.add("address.city", "Linz");
Predicate predicate = builder.getPredicate(values, DEFAULT_BINDINGS, USER_TYPE);
assertThat(predicate, is((Predicate) QUser.user.address.city.eq("Linz")));
@@ -114,16 +116,37 @@ public class QuerydslPredicateBuilderUnitTests {
assertThat(result, hasItem(Users.CHRISTOPH));
}
/**
* @see DATACMNS-669
*/
@Test
public void ignoresNonDomainTypeProperties() {
MultiValueMap<String, String> values = new LinkedMultiValueMap<String, String>();
values.add("firstname", "rand");
values.add("lastname".toUpperCase(), "al'thor");
Predicate predicate = builder.getPredicate(new MutablePropertyValues(values), new QuerydslBindings(),
ClassTypeInformation.from(User.class));
Predicate predicate = builder.getPredicate(values, new QuerydslBindings(), USER_TYPE);
assertThat(predicate, is((Predicate) QUser.user.firstname.eq("rand")));
}
/**
* @see DATACMNS-669
*/
@Test
public void forwardsNullForEmptyParameterToSingleValueBinder() {
values.add("lastname", null);
QuerydslBindings bindings = new QuerydslBindings();
bindings.bind(QUser.user.lastname).single(new SingleValueBinding<StringPath, String>() {
@Override
public Predicate bind(StringPath path, String value) {
return value == null ? null : path.contains(value);
}
});
builder.getPredicate(values, bindings, USER_TYPE);
}
}