Browse Source

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.
pull/133/head
Christoph Strobl 11 years ago committed by Oliver Gierke
parent
commit
64c8549db4
  1. 15
      src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java
  2. 51
      src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java
  3. 105
      src/test/java/org/springframework/data/web/config/SpringDataWebConfigurationUnitTests.java
  4. 70
      src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java

15
src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java

@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Qualifier; @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.geo.format.DistanceFormatter;
import org.springframework.data.geo.format.PointFormatter;
@ -68,6 +69,17 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter { @@ -68,6 +69,17 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter {
return new SortHandlerMethodArgumentResolver();
}
/**
* Default QuerydslPredicateArgumentResolver.
*
* @return
*/
@Bean
@Lazy
public QuerydslPredicateArgumentResolver querydslPredicateArgumentResolver() {
return new QuerydslPredicateArgumentResolver(conversionService.getObject());
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#addFormatters(org.springframework.format.FormatterRegistry)
@ -100,7 +112,7 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter { @@ -100,7 +112,7 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter {
argumentResolvers.add(pageableResolver());
if (QueryDslUtils.QUERY_DSL_PRESENT) {
argumentResolvers.add(new QuerydslPredicateArgumentResolver(conversionService.getObject()));
argumentResolvers.add(querydslPredicateArgumentResolver());
}
ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver(
@ -110,4 +122,5 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter { @@ -110,4 +122,5 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter {
argumentResolvers.add(resolver);
}
}

51
src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java

@ -20,6 +20,7 @@ import java.util.Map.Entry; @@ -20,6 +20,7 @@ import java.util.Map.Entry;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@ -27,6 +28,7 @@ import org.springframework.core.MethodParameter; @@ -27,6 +28,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.LinkedMultiValueMap;
@ -53,6 +55,8 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR @@ -53,6 +55,8 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR
private AutowireCapableBeanFactory beanFactory;
private Repositories repositories;
/**
* Creates a new {@link QuerydslPredicateArgumentResolver} using the given {@link ConversionService}.
*
@ -69,7 +73,9 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR @@ -69,7 +73,9 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanFactory = applicationContext.getAutowireCapableBeanFactory();
this.repositories = new Repositories(applicationContext);
}
/*
@ -127,17 +133,48 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR @@ -127,17 +133,48 @@ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentR
EntityPath<?> path = SimpleEntityPathResolver.INSTANCE.createPath(domainType);
QuerydslBinderCustomizer customizer = findCustomizerForDomainType(annotation, domainType);
QuerydslBindings bindings = new QuerydslBindings();
if (customizer != null) {
customizer.customize(bindings, path);
}
return bindings;
}
@SuppressWarnings("unchecked")
private QuerydslBinderCustomizer<EntityPath<?>> findCustomizerForDomainType(QuerydslPredicate annotation,
Class<?> domainType) {
if (annotation == null || (annotation != null && annotation.bindings().equals(QuerydslBinderCustomizer.class))) {
if (repositories != null && repositories.hasRepositoryFor(domainType)) {
Object repository = this.repositories.getRepositoryFor(domainType);
if (repository instanceof QuerydslBinderCustomizer) {
return (QuerydslBinderCustomizer<EntityPath<?>>) repository;
}
}
if (annotation == null || annotation.bindings().equals(QuerydslBinderCustomizer.class)) {
return bindings;
return null;
}
Class<? extends QuerydslBinderCustomizer> type = annotation.bindings();
QuerydslBinderCustomizer<EntityPath<?>> customizer = beanFactory != null ? beanFactory.createBean(type)
: BeanUtils.instantiateClass(type);
customizer.customize(bindings, path);
return createQuerydslBinderCustomizer(annotation.bindings());
}
return bindings;
@SuppressWarnings({ "unchecked", "rawtypes" })
private QuerydslBinderCustomizer<EntityPath<?>> createQuerydslBinderCustomizer(
Class<? extends QuerydslBinderCustomizer> type) {
if (beanFactory == null) {
return BeanUtils.instantiateClass(type);
}
try {
return beanFactory.getBean(type);
} catch (NoSuchBeanDefinitionException e) {
}
return beanFactory.createBean(type);
}
}

105
src/test/java/org/springframework/data/web/config/SpringDataWebConfigurationUnitTests.java

@ -0,0 +1,105 @@ @@ -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;
}
}
}

70
src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java

@ -17,20 +17,28 @@ package org.springframework.data.web.querydsl; @@ -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 { @@ -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 { @@ -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);
}
});
}
}
}

Loading…
Cancel
Save