/*
* Copyright 2015-2020 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.querydsl.binding;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import com.querydsl.core.types.EntityPath;
Factory to create QuerydslBindings
using an EntityPathResolver
. Author: Oliver Gierke, Christoph Strobl Since: 1.11
/**
* Factory to create {@link QuerydslBindings} using an {@link EntityPathResolver}.
*
* @author Oliver Gierke
* @author Christoph Strobl
* @since 1.11
*/
public class QuerydslBindingsFactory implements ApplicationContextAware {
private static final String INVALID_DOMAIN_TYPE = "Unable to find Querydsl root type for detected domain type %s! User @%s's root attribute to define the domain type manually!";
private final EntityPathResolver entityPathResolver;
private final Map<TypeInformation<?>, EntityPath<?>> entityPaths;
private Optional<AutowireCapableBeanFactory> beanFactory;
private Optional<Repositories> repositories;
Creates a new QuerydslBindingsFactory
using the given EntityPathResolver
. Params: - entityPathResolver – must not be null.
/**
* Creates a new {@link QuerydslBindingsFactory} using the given {@link EntityPathResolver}.
*
* @param entityPathResolver must not be {@literal null}.
*/
public QuerydslBindingsFactory(EntityPathResolver entityPathResolver) {
Assert.notNull(entityPathResolver, "EntityPathResolver must not be null!");
this.entityPathResolver = entityPathResolver;
this.entityPaths = new ConcurrentReferenceHashMap<>();
this.beanFactory = Optional.empty();
this.repositories = Optional.empty();
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanFactory = Optional.of(applicationContext.getAutowireCapableBeanFactory());
this.repositories = Optional.of(new Repositories(applicationContext));
}
Returns the EntityPathResolver
used by the factory. Returns: the entityPathResolver
/**
* Returns the {@link EntityPathResolver} used by the factory.
*
* @return the entityPathResolver
*/
public EntityPathResolver getEntityPathResolver() {
return entityPathResolver;
}
Creates the QuerydslBindings
to be used using for the given domain type. A QuerydslBinderCustomizer
will be auto-detected. Params: - domainType – must not be null.
Returns: will never be null.
/**
* Creates the {@link QuerydslBindings} to be used using for the given domain type. A {@link QuerydslBinderCustomizer}
* will be auto-detected.
*
* @param domainType must not be {@literal null}.
* @return will never be {@literal null}.
*/
public QuerydslBindings createBindingsFor(TypeInformation<?> domainType) {
return createBindingsFor(domainType, Optional.empty());
}
Creates the QuerydslBindings
to be used using for the given domain type and a pre-defined QuerydslBinderCustomizer
. Params: - domainType – must not be null.
- customizer – the
QuerydslBinderCustomizer
to use, must not be null.
Returns: will never be null.
/**
* Creates the {@link QuerydslBindings} to be used using for the given domain type and a pre-defined
* {@link QuerydslBinderCustomizer}.
*
* @param domainType must not be {@literal null}.
* @param customizer the {@link QuerydslBinderCustomizer} to use, must not be {@literal null}.
* @return will never be {@literal null}.
*/
public QuerydslBindings createBindingsFor(TypeInformation<?> domainType,
Class<? extends QuerydslBinderCustomizer<?>> customizer) {
return createBindingsFor(domainType, Optional.of(customizer));
}
Creates the QuerydslBindings
to be used using for the given domain type and a pre-defined QuerydslBinderCustomizer
. If no customizer is given, auto-detection will be applied. Params: - domainType – must not be null.
- customizer – the
QuerydslBinderCustomizer
to use. If an empty Optional
is given customizer detection for the given domain type will be applied.
Returns:
/**
* Creates the {@link QuerydslBindings} to be used using for the given domain type and a pre-defined
* {@link QuerydslBinderCustomizer}. If no customizer is given, auto-detection will be applied.
*
* @param domainType must not be {@literal null}.
* @param customizer the {@link QuerydslBinderCustomizer} to use. If an empty {@link Optional} is given customizer
* detection for the given domain type will be applied.
* @return
*/
private QuerydslBindings createBindingsFor(TypeInformation<?> domainType,
Optional<Class<? extends QuerydslBinderCustomizer<?>>> customizer) {
Assert.notNull(customizer, "Customizer must not be null!");
Assert.notNull(domainType, "Domain type must not be null!");
EntityPath<?> path = verifyEntityPathPresent(domainType);
QuerydslBindings bindings = new QuerydslBindings();
findCustomizerForDomainType(customizer, domainType.getType()).customize(bindings, path);
return bindings;
}
Tries to detect a Querydsl query type for the given domain type candidate via the configured EntityPathResolver
. Params: - candidate – must not be null.
Throws: - IllegalStateException – to indicate the query type can't be found and manual configuration is necessary.
/**
* Tries to detect a Querydsl query type for the given domain type candidate via the configured
* {@link EntityPathResolver}.
*
* @param candidate must not be {@literal null}.
* @throws IllegalStateException to indicate the query type can't be found and manual configuration is necessary.
*/
private EntityPath<?> verifyEntityPathPresent(TypeInformation<?> candidate) {
return entityPaths.computeIfAbsent(candidate, key -> {
try {
return entityPathResolver.createPath(key.getType());
} catch (IllegalArgumentException o_O) {
throw new IllegalStateException(
String.format(INVALID_DOMAIN_TYPE, key.getType(), QuerydslPredicate.class.getSimpleName()), o_O);
}
});
}
Obtains the QuerydslBinderCustomizer
for the given domain type. Will inspect the given annotation for a dedicatedly configured one or consider the domain types's repository. Params: - annotation –
- domainType –
Returns:
/**
* Obtains the {@link QuerydslBinderCustomizer} for the given domain type. Will inspect the given annotation for a
* dedicatedly configured one or consider the domain types's repository.
*
* @param annotation
* @param domainType
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private QuerydslBinderCustomizer<EntityPath<?>> findCustomizerForDomainType(
Optional<? extends Class<? extends QuerydslBinderCustomizer>> customizer, Class<?> domainType) {
return customizer//
.filter(it -> !QuerydslBinderCustomizer.class.equals(it))//
.map(this::createQuerydslBinderCustomizer)
.orElseGet(() -> repositories.flatMap(it -> it.getRepositoryFor(domainType))//
.map(it -> it instanceof QuerydslBinderCustomizer ? (QuerydslBinderCustomizer<EntityPath<?>>) it : null)//
.orElse(NoOpCustomizer.INSTANCE));
}
Obtains a QuerydslBinderCustomizer
for the given type. Will try to obtain a bean from the BeanFactory
first or fall back to create a fresh instance through the BeanFactory
or finally falling back to a plain instantiation if no BeanFactory
is present. Params: - type – must not be null.
Returns:
/**
* Obtains a {@link QuerydslBinderCustomizer} for the given type. Will try to obtain a bean from the
* {@link org.springframework.beans.factory.BeanFactory} first or fall back to create a fresh instance through the
* {@link org.springframework.beans.factory.BeanFactory} or finally falling back to a plain instantiation if no
* {@link org.springframework.beans.factory.BeanFactory} is present.
*
* @param type must not be {@literal null}.
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private QuerydslBinderCustomizer<EntityPath<?>> createQuerydslBinderCustomizer(
Class<? extends QuerydslBinderCustomizer> type) {
return beanFactory.map(it -> {
try {
return it.getBean(type);
} catch (NoSuchBeanDefinitionException e) {
return it.createBean(type);
}
}).orElseGet(() -> BeanUtils.instantiateClass(type));
}
private static enum NoOpCustomizer implements QuerydslBinderCustomizer<EntityPath<?>> {
INSTANCE;
@Override
public void customize(QuerydslBindings bindings, EntityPath<?> root) {}
}
}