DATACMNS-669 - Register QuerydslPredicateArgumentResolver as bean.

If no QuerydslBinderCustomizer is configured on @QuerydslPredicate specifically, we now also consider the domain types repository as candidate for binding customization.

The lookup algorithm for explicitly configured customizers has been refined to try to find a bean in the BeanFactory first, resorting to on-the-fly creation eventually.

QuerydslPredicateArgumentResolver is now registered as a lazily initialized bean and its usage is guarded by checking presence of Querydsl itself.

The according test needs to do some reflection in order to stay out of trouble when types are loaded by different class loaders required to hide certain types.

Original pull request: #132.
This commit is contained in:
Christoph Strobl
2015-07-16 11:40:01 +02:00
committed by Oliver Gierke
parent cccfa5e5c5
commit 64c8549db4
4 changed files with 235 additions and 10 deletions

View File

@@ -0,0 +1,105 @@
/*
* 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.web.config;
import static org.junit.Assert.*;
import static org.springframework.test.util.ReflectionTestUtils.*;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver;
import org.springframework.instrument.classloading.ShadowingClassLoader;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
/**
* @author Christoph Strobl
*/
public class SpringDataWebConfigurationUnitTests {
/**
* @see DATACMNS-669
*/
@Test
public void shouldNotAddQuerydslPredicateArgumentResolverWhenQuerydslNotPresent() throws ClassNotFoundException,
InstantiationException, IllegalAccessException {
ClassLoader classLoader = initClassLoader();
Object config = classLoader.loadClass("org.springframework.data.web.config.SpringDataWebConfiguration")
.newInstance();
setField(config, "context",
classLoader.loadClass("org.springframework.web.context.support.GenericWebApplicationContext").newInstance());
setField(
config,
"conversionService",
classLoader.loadClass(
"org.springframework.data.web.config.SpringDataWebConfigurationUnitTests$ObjectFactoryImpl").newInstance());
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
invokeMethod(config, "addArgumentResolvers", argumentResolvers);
for (Object resolver : argumentResolvers) {
if (resolver instanceof QuerydslPredicateArgumentResolver) {
fail("QuerydslPredicateArgumentResolver should not be present when Querydsl not on path");
}
}
}
private ClassLoader initClassLoader() {
ClassLoader classLoader = new ShadowingClassLoader(URLClassLoader.getSystemClassLoader()) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("com.mysema")) {
throw new ClassNotFoundException();
}
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.startsWith("com.mysema")) {
throw new ClassNotFoundException();
}
return super.findClass(name);
}
};
return classLoader;
}
public static class ObjectFactoryImpl implements ObjectFactory<ConversionService> {
@Override
public ConversionService getObject() throws BeansException {
return null;
}
}
}

View File

@@ -17,20 +17,28 @@ package org.springframework.data.web.querydsl;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.querydsl.QUser;
import org.springframework.data.querydsl.User;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import com.mysema.query.types.Path;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.expr.BooleanExpression;
import com.mysema.query.types.path.StringPath;
@@ -218,6 +226,53 @@ public class QuerydslPredicateArgumentResolverUnitTests {
assertThat(type, is((TypeInformation) ClassTypeInformation.from(User.class)));
}
/**
* @see DATACMNS-669
*/
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void createBindingsShouldHonorQuerydslBinderCustomizerHookWhenPresent() {
Repositories repositories = mock(Repositories.class);
RepositoryInformation repoInfo = mock(RepositoryInformation.class);
when(repositories.hasRepositoryFor(User.class)).thenReturn(true);
when(repositories.getRepositoryFor(User.class)).thenReturn(new SampleRepo());
resolver = new QuerydslPredicateArgumentResolver(null);
ReflectionTestUtils.setField(resolver, "repositories", repositories);
QuerydslBindings bindings = resolver.createBindings(null, User.class);
MultiValueBinding<Path<Object>, Object> binding = bindings.getBindingForPath(PropertyPath.from("firstname",
User.class));
assertThat(binding.bind((Path) QUser.user.firstname, Collections.singleton("rand")),
is((Predicate) QUser.user.firstname.contains("rand")));
}
/**
* @see DATACMNS-669
*/
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void shouldReuseExistingQuerydslBinderCustomizer() {
AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class);
when(beanFactory.getBean(SpecificBinding.class)).thenReturn(new SpecificBinding());
QuerydslPredicate annotation = getMethodParameterFor("specificFind", Predicate.class).getParameterAnnotation(
QuerydslPredicate.class);
resolver = new QuerydslPredicateArgumentResolver(null);
ReflectionTestUtils.setField(resolver, "beanFactory", beanFactory);
QuerydslBindings bindings = resolver.createBindings(annotation, User.class);
MultiValueBinding<Path<Object>, Object> binding = bindings.getBindingForPath(PropertyPath.from("firstname",
User.class));
assertThat(binding.bind((Path) QUser.user.firstname, Collections.singleton("rand")),
is((Predicate) QUser.user.firstname.eq("RAND")));
}
private static MethodParameter getMethodParameterFor(String methodName, Class<?>... args) throws RuntimeException {
try {
@@ -265,4 +320,19 @@ public class QuerydslPredicateArgumentResolverUnitTests {
User specificFind(@QuerydslPredicate(bindings = SpecificBinding.class) Predicate predicate);
}
public static class SampleRepo implements QuerydslBinderCustomizer<QUser> {
@Override
public void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(QUser.user.firstname).single(new SingleValueBinding<StringPath, String>() {
@Override
public Predicate bind(StringPath path, String value) {
return path.contains(value);
}
});
}
}
}