Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DATACMNS-1785 - Add QuerydslWebFluxConfiguration and QuerydslPredicateArgumentResolver #2274

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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