/* * Copyright 2015-2017 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.querydsl; import java.util.Arrays; import java.util.Map.Entry; import java.util.Optional; import org.springframework.core.MethodParameter; 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.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.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import com.querydsl.core.types.Predicate; /** * {@link HandlerMethodArgumentResolver} to allow injection of {@link com.mysema.query.types.Predicate} into Spring MVC * controller methods. * * @author Christoph Strobl * @author Oliver Gierke * @since 1.11 */ public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentResolver { 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) { if (Predicate.class.equals(parameter.getParameterType())) { 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; } /* * (non-Javadoc) * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) */ @Override public Predicate resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); for (Entry<String, String[]> entry : webRequest.getParameterMap().entrySet()) { parameters.put(entry.getKey(), Arrays.asList(entry.getValue())); } Optional<QuerydslPredicate> annotation = Optional .ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); TypeInformation<?> domainType = extractTypeInfo(parameter).getActualType(); Optional<Class<? extends QuerydslBinderCustomizer<?>>> bindings = annotation// .map(QuerydslPredicate::bindings)// .map(CastUtils::cast); return predicateBuilder.getPredicate(domainType, parameters, bindings.map(it -> bindingsFactory.createBindingsFor(domainType, it)) .orElseGet(() -> bindingsFactory.createBindingsFor(domainType))); } /** * 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(ClassTypeInformation.fromReturnTypeOf(parameter.getMethod()))); } private static TypeInformation<?> detectDomainType(TypeInformation<?> source) { if (source.getTypeArguments().isEmpty()) { return source; } TypeInformation<?> actualType = source.getActualType(); if (source != actualType) { return detectDomainType(actualType); } if (source instanceof Iterable) { return source; } return detectDomainType(source.getRequiredComponentType()); } }