Skip to content

Commit

Permalink
DATACMNS-1785 - Add ReactiveQuerydslPredicateArgumentResolver and Rea…
Browse files Browse the repository at this point in the history
…ctiveQuerydslWebConfiguration.

We now provide ReactiveQuerydslPredicateArgumentResolver to resolve Querydsl Predicates when using Spring WebFlux.

Related ticket: #2200.
Original pull request: #2274.
  • Loading branch information
matiasah authored and mp911de committed Jan 25, 2021
1 parent a8b5f43 commit a839690
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 93 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2015-2021 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
*
* https://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 java.util.Optional;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
import org.springframework.data.web.querydsl.ReactiveQuerydslPredicateArgumentResolver;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;

/**
* Querydsl-specific web configuration for Spring Data. Registers a {@link HandlerMethodArgumentResolver} that builds up
* {@link Predicate}s from web requests.
*
* @author Matías Hermosilla
* @since 1.11
* @soundtrack Anika Nilles - Alter Ego
*/
@Configuration(proxyBeanMethods = false)
public class ReactiveQuerydslWebConfiguration implements WebFluxConfigurer {

@Autowired
@Qualifier("webFluxConversionService") ObjectFactory<ConversionService> conversionService;
@Autowired ObjectProvider<EntityPathResolver> resolver;
@Autowired BeanFactory beanFactory;

/**
* Default {@link ReactiveQuerydslPredicateArgumentResolver} to create Querydsl {@link Predicate} instances for
* Spring WebFlux controller methods.
*
* @return
*/
@Lazy
@Bean
public ReactiveQuerydslPredicateArgumentResolver querydslPredicateArgumentResolver() {
return new ReactiveQuerydslPredicateArgumentResolver(
beanFactory.getBean("querydslBindingsFactory", QuerydslBindingsFactory.class),
Optional.of(conversionService.getObject()));
}

@Lazy
@Bean
public QuerydslBindingsFactory querydslBindingsFactory() {
return new QuerydslBindingsFactory(resolver.getIfUnique(() -> SimpleEntityPathResolver.INSTANCE));
}

@Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
configurer.addCustomResolver(beanFactory.getBean("querydslPredicateArgumentResolver",
ReactiveQuerydslPredicateArgumentResolver.class));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,18 @@
*/
package org.springframework.data.web.querydsl;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.Optional;

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
import org.springframework.data.querydsl.binding.QuerydslPredicate;
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
import org.springframework.data.util.CastUtils;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
Expand All @@ -49,50 +45,15 @@
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Matías Hermosilla
* @since 1.11
*/
public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentResolver {
public class QuerydslPredicateArgumentResolver extends QuerydslPredicateArgumentResolverSupport
implements HandlerMethodArgumentResolver {

private static final ResolvableType PREDICATE = ResolvableType.forClass(Predicate.class);
private static final ResolvableType OPTIONAL_OF_PREDICATE = ResolvableType.forClassWithGenerics(Optional.class,
PREDICATE);

private final QuerydslBindingsFactory bindingsFactory;
private final QuerydslPredicateBuilder predicateBuilder;

/**
* Creates a new {@link QuerydslPredicateArgumentResolver} using the given {@link ConversionService}.
*
* @param factory
* @param conversionService defaults to {@link DefaultConversionService} if {@literal null}.
*/
public QuerydslPredicateArgumentResolver(QuerydslBindingsFactory factory,
Optional<ConversionService> conversionService) {

this.bindingsFactory = factory;
this.predicateBuilder = new QuerydslPredicateBuilder(conversionService.orElseGet(DefaultConversionService::new),
factory.getEntityPathResolver());
}

/*
* (non-Javadoc)
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {

ResolvableType type = ResolvableType.forMethodParameter(parameter);

if (PREDICATE.isAssignableFrom(type) || OPTIONAL_OF_PREDICATE.isAssignableFrom(type)) {
return true;
}

if (parameter.hasParameterAnnotation(QuerydslPredicate.class)) {
throw new IllegalArgumentException(String.format("Parameter at position %s must be of type Predicate but was %s.",
parameter.getParameterIndex(), parameter.getParameterType()));
}

return false;
super(factory, conversionService);
}

/*
Expand Down Expand Up @@ -133,54 +94,4 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC
: result;
}

/**
* Obtains the domain type information from the given method parameter. Will favor an explicitly registered on through
* {@link QuerydslPredicate#root()} but use the actual type of the method's return type as fallback.
*
* @param parameter must not be {@literal null}.
* @return
*/
static TypeInformation<?> extractTypeInfo(MethodParameter parameter) {

Optional<QuerydslPredicate> annotation = Optional
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class));

return annotation.filter(it -> !Object.class.equals(it.root()))//
.<TypeInformation<?>> map(it -> ClassTypeInformation.from(it.root()))//
.orElseGet(() -> detectDomainType(parameter));
}

private static TypeInformation<?> detectDomainType(MethodParameter parameter) {

Method method = parameter.getMethod();

if (method == null) {
throw new IllegalArgumentException("Method parameter is not backed by a method!");
}

return detectDomainType(ClassTypeInformation.fromReturnTypeOf(method));
}

private static TypeInformation<?> detectDomainType(TypeInformation<?> source) {

if (source.getTypeArguments().isEmpty()) {
return source;
}

TypeInformation<?> actualType = source.getActualType();

if (actualType == null) {
throw new IllegalArgumentException(String.format("Could not determine domain type from %s!", source));
}

if (source != actualType) {
return detectDomainType(actualType);
}

if (source instanceof Iterable) {
return source;
}

return detectDomainType(source.getRequiredComponentType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2015-2021 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
*
* https://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.querydsl;

import java.lang.reflect.Method;
import java.util.Optional;

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
import org.springframework.data.querydsl.binding.QuerydslPredicate;
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;

import com.querydsl.core.types.Predicate;

/**
* {@link HandlerMethodArgumentResolver} to allow injection of {@link com.querydsl.core.types.Predicate} into Spring MVC
* controller methods.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Matías Hermosilla
* @since 1.11
*/
public abstract class QuerydslPredicateArgumentResolverSupport {

private static final ResolvableType PREDICATE = ResolvableType.forClass(Predicate.class);
protected static final ResolvableType OPTIONAL_OF_PREDICATE = ResolvableType.forClassWithGenerics(Optional.class,
PREDICATE);

protected final QuerydslBindingsFactory bindingsFactory;
protected final QuerydslPredicateBuilder predicateBuilder;

/**
* Creates a new {@link QuerydslPredicateArgumentResolver} using the given {@link ConversionService}.
*
* @param factory
* @param conversionService defaults to {@link DefaultConversionService} if {@literal null}.
*/
public QuerydslPredicateArgumentResolverSupport(QuerydslBindingsFactory factory,
Optional<ConversionService> conversionService) {

this.bindingsFactory = factory;
this.predicateBuilder = new QuerydslPredicateBuilder(conversionService.orElseGet(DefaultConversionService::new),
factory.getEntityPathResolver());
}

/*
* (non-Javadoc)
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
*/
public boolean supportsParameter(MethodParameter parameter) {

ResolvableType type = ResolvableType.forMethodParameter(parameter);

if (PREDICATE.isAssignableFrom(type) || OPTIONAL_OF_PREDICATE.isAssignableFrom(type)) {
return true;
}

if (parameter.hasParameterAnnotation(QuerydslPredicate.class)) {
throw new IllegalArgumentException(
String.format("Parameter at position %s must be of type Predicate but was %s.",
parameter.getParameterIndex(), parameter.getParameterType()));
}

return false;
}

/**
* Obtains the domain type information from the given method parameter. Will favor an explicitly registered on
* through {@link QuerydslPredicate#root()} but use the actual type of the method's return type as fallback.
*
* @param parameter must not be {@literal null}.
* @return
*/
protected static TypeInformation<?> extractTypeInfo(MethodParameter parameter) {

Optional<QuerydslPredicate> annotation = Optional
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class));

return annotation.filter(it -> !Object.class.equals(it.root()))//
.<TypeInformation<?>> map(it -> ClassTypeInformation.from(it.root()))//
.orElseGet(() -> detectDomainType(parameter));
}

private static TypeInformation<?> detectDomainType(MethodParameter parameter) {

Method method = parameter.getMethod();

if (method == null) {
throw new IllegalArgumentException("Method parameter is not backed by a method!");
}

return detectDomainType(ClassTypeInformation.fromReturnTypeOf(method));
}

private static TypeInformation<?> detectDomainType(TypeInformation<?> source) {

if (source.getTypeArguments().isEmpty()) {
return source;
}

TypeInformation<?> actualType = source.getActualType();

if (actualType == null) {
throw new IllegalArgumentException(String.format("Could not determine domain type from %s!", source));
}

if (source != actualType) {
return detectDomainType(actualType);
}

if (source instanceof Iterable) {
return source;
}

return detectDomainType(source.getRequiredComponentType());
}

}
Loading

0 comments on commit a839690

Please sign in to comment.