QuerydslPredicateArgumentResolverBeanPostProcessor.java

  1. /*******************************************************************************
  2.  * Copyright (c) 2018 @gt_tech
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *     http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  *******************************************************************************/
  16. package org.bitbucket.gt_tech.spring.data.querydsl.value.operators.experimental;

  17. import org.apache.commons.lang3.Validate;
  18. import org.apache.commons.lang3.reflect.ConstructorUtils;
  19. import org.bitbucket.gt_tech.spring.data.querydsl.value.operators.ExpressionProviderFactory;
  20. import org.bitbucket.gt_tech.spring.data.querydsl.value.operators.Operator;
  21. import org.springframework.beans.BeansException;
  22. import org.springframework.beans.factory.config.BeanPostProcessor;
  23. import org.springframework.core.convert.ConversionService;
  24. import org.springframework.core.convert.TypeDescriptor;
  25. import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
  26. import org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver;

  27. import java.sql.Timestamp;
  28. import java.time.LocalDate;
  29. import java.util.Date;
  30. import java.util.Optional;

  31. /**
  32.  * Advanced experimental feature of this component - an implementation of
  33.  * {@link BeanPostProcessor} that overrides the default
  34.  * {@link QuerydslPredicateArgumentResolver} by providing it a no-op
  35.  * {@link ConversionService} and in-turn disabling it's strong type-conversion.
  36.  * This allows for String values decorated with value-operators by client to
  37.  * reach expression-provided even for non StringPath.
  38.  *
  39.  * <p>
  40.  *     Note that by providing a delegate ConversionService and explicit Class
  41.  *     types for delegated conversions, a high degree of control can be achieved
  42.  *     when users are using direct bindings for certain fields (for e.g. Date)
  43.  * </p>
  44.  *
  45.  * <p>
  46.  * If this isn't available, then the {@link QuerydslPredicateArgumentResolver}
  47.  * will attempt to perform type-conversion which will fail for non-StringPath
  48.  * (for. e.g. EnumPath) when values are decorated with value-operators -
  49.  * {@link Operator}
  50.  * </p>
  51.  *
  52.  * @author gt_tech
  53.  *
  54.  */
  55. public class QuerydslPredicateArgumentResolverBeanPostProcessor implements BeanPostProcessor {

  56.     private final QuerydslBindingsFactory querydslBindingsFactory;

  57.     private final ConversionService conversionServiceDelegate;

  58.     private final Class[] delegatedConversions;

  59.     /*
  60.      * No-op conversion service
  61.      */
  62.     private final ConversionService delegationAwareConversionService = new ConversionService() {

  63.         @Override
  64.         public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
  65.             if ( isDelegatedConversion(sourceType) || isDelegatedConversion(targetType))
  66.                 return conversionServiceDelegate.canConvert(sourceType, targetType);

  67.             return false;
  68.         }

  69.         @Override
  70.         public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
  71.             if ( isDelegatedConversion(sourceType.getType()) || isDelegatedConversion(targetType.getType()))
  72.                 return conversionServiceDelegate.canConvert(sourceType, targetType);

  73.             return false;
  74.         }

  75.         @Override
  76.         public <T> T convert(Object source, Class<T> targetType) {

  77.             if ( isDelegatedConversion(source.getClass()) || isDelegatedConversion(targetType))
  78.                 return conversionServiceDelegate.convert(source, targetType);

  79.             throw new UnsupportedOperationException("Overridden ConversionService in "
  80.                     + "QuerydslPredicateArgumentResolver does not " + "support conversion");
  81.         }

  82.         @Override
  83.         public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {

  84.             if ( isDelegatedConversion(sourceType.getType()) || isDelegatedConversion(targetType.getType()))
  85.                 return conversionServiceDelegate.convert(source, sourceType, targetType);

  86.             throw new UnsupportedOperationException("Overridden ConversionService in "
  87.                     + "QuerydslPredicateArgumentResolver does not " + "support conversion");
  88.         }

  89.         private boolean isDelegatedConversion(Class<?> type) {
  90.             boolean result = false;

  91.             if ( conversionServiceDelegate != null && type != null && delegatedConversions != null ) {
  92.                 for (Class c : delegatedConversions) {
  93.                     if (c.equals(type)) {
  94.                         result = true;
  95.                         break;
  96.                     }
  97.                 }
  98.             }
  99.             return result;
  100.         }
  101.     };

  102.     /**
  103.      * Constructor: Replaces {@link QuerydslPredicateArgumentResolver} with a no-op conversion service
  104.      * @param querydslBindingsFactory
  105.      */
  106.     public QuerydslPredicateArgumentResolverBeanPostProcessor(QuerydslBindingsFactory querydslBindingsFactory) {
  107.         this(querydslBindingsFactory, null, new Class[]{});
  108.     }


  109.     /**
  110.      * Constructor: Replaces {@link QuerydslPredicateArgumentResolver} with a no-op conversion service with the exception of following types
  111.      * conversion that would be handed over to provided delegated service - {@link Date}, {@link LocalDate},
  112.      * {@link Timestamp}
  113.      *
  114.      * @param querydslBindingsFactory
  115.      */
  116.     public QuerydslPredicateArgumentResolverBeanPostProcessor(QuerydslBindingsFactory querydslBindingsFactory, ConversionService conversionServiceDelegate) {
  117.         this(querydslBindingsFactory, conversionServiceDelegate, new Class[]{Date.class, LocalDate.class, Timestamp.class});
  118.     }

  119.     public QuerydslPredicateArgumentResolverBeanPostProcessor(QuerydslBindingsFactory querydslBindingsFactory, ConversionService conversionServiceDelegate, Class[] delegatedConversions) {
  120.         Validate.notNull(querydslBindingsFactory, "QuerydslBindingsFactory must not be null");
  121.         this.querydslBindingsFactory = querydslBindingsFactory;
  122.         this.conversionServiceDelegate = conversionServiceDelegate;
  123.         this.delegatedConversions = delegatedConversions;
  124.     }

  125.     @Override
  126.     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  127.         Object target = bean;
  128.         if (target != null && QuerydslPredicateArgumentResolver.class.isAssignableFrom(target.getClass())) {
  129.             ExpressionProviderFactory.setSupportsUnTypedValues(true);
  130.             try {
  131.                 try {
  132.                     // Spring Boot 2.x
  133.                     return ConstructorUtils.invokeConstructor(QuerydslPredicateArgumentResolver.class,
  134.                             new Object[] { querydslBindingsFactory, Optional.of(delegationAwareConversionService) });
  135.                 } catch (NoSuchMethodException | NoSuchMethodError e) {
  136.                     // Spring boot 1.5.x
  137.                     return ConstructorUtils.invokeConstructor(QuerydslPredicateArgumentResolver.class,
  138.                             new Object[] { querydslBindingsFactory, delegationAwareConversionService });
  139.                 }
  140.             } catch (Throwable t) {
  141.                 // phew
  142.                 throw new RuntimeException("Failed to post-process QuerydslPredicateArgumentResolver", t);
  143.             }
  144.         }
  145.         return target;
  146.     }

  147.     /**
  148.      * Implementing default method as-is since Spring Boot 1.5.x specific
  149.      * dependencies don't have default methods so if library users use this with
  150.      * an older spring, the runtime would fail. This is implemented as a
  151.      * fail-safe mechanism.
  152.      */
  153.     @Override
  154.     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  155.         return bean;
  156.     }
  157. }