QuerydslPredicateArgumentResolverBeanPostProcessor.java
/*******************************************************************************
* Copyright (c) 2018 @gt_tech
*
* 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.bitbucket.gt_tech.spring.data.querydsl.value.operators.experimental;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.bitbucket.gt_tech.spring.data.querydsl.value.operators.ExpressionProviderFactory;
import org.bitbucket.gt_tech.spring.data.querydsl.value.operators.Operator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
import org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.util.Date;
import java.util.Optional;
/**
* Advanced experimental feature of this component - an implementation of
* {@link BeanPostProcessor} that overrides the default
* {@link QuerydslPredicateArgumentResolver} by providing it a no-op
* {@link ConversionService} and in-turn disabling it's strong type-conversion.
* This allows for String values decorated with value-operators by client to
* reach expression-provided even for non StringPath.
*
* <p>
* Note that by providing a delegate ConversionService and explicit Class
* types for delegated conversions, a high degree of control can be achieved
* when users are using direct bindings for certain fields (for e.g. Date)
* </p>
*
* <p>
* If this isn't available, then the {@link QuerydslPredicateArgumentResolver}
* will attempt to perform type-conversion which will fail for non-StringPath
* (for. e.g. EnumPath) when values are decorated with value-operators -
* {@link Operator}
* </p>
*
* @author gt_tech
*
*/
public class QuerydslPredicateArgumentResolverBeanPostProcessor implements BeanPostProcessor {
private final QuerydslBindingsFactory querydslBindingsFactory;
private final ConversionService conversionServiceDelegate;
private final Class[] delegatedConversions;
/*
* No-op conversion service
*/
private final ConversionService delegationAwareConversionService = new ConversionService() {
@Override
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
if ( isDelegatedConversion(sourceType) || isDelegatedConversion(targetType))
return conversionServiceDelegate.canConvert(sourceType, targetType);
return false;
}
@Override
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
if ( isDelegatedConversion(sourceType.getType()) || isDelegatedConversion(targetType.getType()))
return conversionServiceDelegate.canConvert(sourceType, targetType);
return false;
}
@Override
public <T> T convert(Object source, Class<T> targetType) {
if ( isDelegatedConversion(source.getClass()) || isDelegatedConversion(targetType))
return conversionServiceDelegate.convert(source, targetType);
throw new UnsupportedOperationException("Overridden ConversionService in "
+ "QuerydslPredicateArgumentResolver does not " + "support conversion");
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if ( isDelegatedConversion(sourceType.getType()) || isDelegatedConversion(targetType.getType()))
return conversionServiceDelegate.convert(source, sourceType, targetType);
throw new UnsupportedOperationException("Overridden ConversionService in "
+ "QuerydslPredicateArgumentResolver does not " + "support conversion");
}
private boolean isDelegatedConversion(Class<?> type) {
boolean result = false;
if ( conversionServiceDelegate != null && type != null && delegatedConversions != null ) {
for (Class c : delegatedConversions) {
if (c.equals(type)) {
result = true;
break;
}
}
}
return result;
}
};
/**
* Constructor: Replaces {@link QuerydslPredicateArgumentResolver} with a no-op conversion service
* @param querydslBindingsFactory
*/
public QuerydslPredicateArgumentResolverBeanPostProcessor(QuerydslBindingsFactory querydslBindingsFactory) {
this(querydslBindingsFactory, null, new Class[]{});
}
/**
* Constructor: Replaces {@link QuerydslPredicateArgumentResolver} with a no-op conversion service with the exception of following types
* conversion that would be handed over to provided delegated service - {@link Date}, {@link LocalDate},
* {@link Timestamp}
*
* @param querydslBindingsFactory
*/
public QuerydslPredicateArgumentResolverBeanPostProcessor(QuerydslBindingsFactory querydslBindingsFactory, ConversionService conversionServiceDelegate) {
this(querydslBindingsFactory, conversionServiceDelegate, new Class[]{Date.class, LocalDate.class, Timestamp.class});
}
public QuerydslPredicateArgumentResolverBeanPostProcessor(QuerydslBindingsFactory querydslBindingsFactory, ConversionService conversionServiceDelegate, Class[] delegatedConversions) {
Validate.notNull(querydslBindingsFactory, "QuerydslBindingsFactory must not be null");
this.querydslBindingsFactory = querydslBindingsFactory;
this.conversionServiceDelegate = conversionServiceDelegate;
this.delegatedConversions = delegatedConversions;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Object target = bean;
if (target != null && QuerydslPredicateArgumentResolver.class.isAssignableFrom(target.getClass())) {
ExpressionProviderFactory.setSupportsUnTypedValues(true);
try {
try {
// Spring Boot 2.x
return ConstructorUtils.invokeConstructor(QuerydslPredicateArgumentResolver.class,
new Object[] { querydslBindingsFactory, Optional.of(delegationAwareConversionService) });
} catch (NoSuchMethodException | NoSuchMethodError e) {
// Spring boot 1.5.x
return ConstructorUtils.invokeConstructor(QuerydslPredicateArgumentResolver.class,
new Object[] { querydslBindingsFactory, delegationAwareConversionService });
}
} catch (Throwable t) {
// phew
throw new RuntimeException("Failed to post-process QuerydslPredicateArgumentResolver", t);
}
}
return target;
}
/**
* Implementing default method as-is since Spring Boot 1.5.x specific
* dependencies don't have default methods so if library users use this with
* an older spring, the runtime would fail. This is implemented as a
* fail-safe mechanism.
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}