diff --git a/pom.xml b/pom.xml index 9be85dfb76..aa67aa96e3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,13 @@ - + 4.0.0 org.springframework.data spring-data-commons - 2.5.0-SNAPSHOT + 2.5.0-GH-206-SNAPSHOT Spring Data Core diff --git a/src/main/asciidoc/repositories.adoc b/src/main/asciidoc/repositories.adoc index 9de76bbb3e..daab2cbdb6 100644 --- a/src/main/asciidoc/repositories.adoc +++ b/src/main/asciidoc/repositories.adoc @@ -1667,6 +1667,8 @@ interface UserRepository extends CrudRepository, <5> Exclude the `password` property from `Predicate` resolution. ==== +Additionally, you can register a `DefaultQuerydslBinderCustomizer` bean to apply default Querydsl bindings before applying specific bindings from the repository or `@QuerydslPredicate`. + [[core.repository-populators]] === Repository Populators diff --git a/src/main/java/org/springframework/data/querydsl/binding/DefaultQuerydslBinderCustomizer.java b/src/main/java/org/springframework/data/querydsl/binding/DefaultQuerydslBinderCustomizer.java new file mode 100644 index 0000000000..c9cd5dd87f --- /dev/null +++ b/src/main/java/org/springframework/data/querydsl/binding/DefaultQuerydslBinderCustomizer.java @@ -0,0 +1,27 @@ +/* + * Copyright 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.querydsl.binding; + +import com.querydsl.core.types.EntityPath; + +/** + * A component for {@link QuerydslBindings} customization acting as default customizer the given entity path regardless + * of the domain type. Instances can be registered with the application context to be applied. + * + * @author Mark Paluch + * @since 2.5 + */ +public interface DefaultQuerydslBinderCustomizer extends QuerydslBinderCustomizer> {} diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactory.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactory.java index aebb35dba6..c82dc68d91 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactory.java +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactory.java @@ -15,8 +15,10 @@ */ package org.springframework.data.querydsl.binding; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; @@ -37,6 +39,7 @@ * * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch * @since 1.11 */ public class QuerydslBindingsFactory implements ApplicationContextAware { @@ -48,6 +51,7 @@ public class QuerydslBindingsFactory implements ApplicationContextAware { private Optional beanFactory; private Optional repositories; + private QuerydslBinderCustomizer> defaultCustomizer; /** * Creates a new {@link QuerydslBindingsFactory} using the given {@link EntityPathResolver}. @@ -62,6 +66,7 @@ public QuerydslBindingsFactory(EntityPathResolver entityPathResolver) { this.entityPaths = new ConcurrentReferenceHashMap<>(); this.beanFactory = Optional.empty(); this.repositories = Optional.empty(); + this.defaultCustomizer = NoOpCustomizer.INSTANCE; } /* @@ -73,6 +78,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws this.beanFactory = Optional.of(applicationContext.getAutowireCapableBeanFactory()); this.repositories = Optional.of(new Repositories(applicationContext)); + this.defaultCustomizer = findDefaultCustomizer(); } /** @@ -126,6 +132,7 @@ private QuerydslBindings createBindingsFor(TypeInformation domainType, EntityPath path = verifyEntityPathPresent(domainType); QuerydslBindings bindings = new QuerydslBindings(); + defaultCustomizer.customize(bindings, path); findCustomizerForDomainType(customizer, domainType.getType()).customize(bindings, path); return bindings; @@ -151,9 +158,32 @@ private EntityPath verifyEntityPathPresent(TypeInformation candidate) { }); } + /** + * Obtains registered {@link DefaultQuerydslBinderCustomizer} instances from the + * {@link org.springframework.beans.factory.BeanFactory}. + * + * @return + */ + private QuerydslBinderCustomizer> findDefaultCustomizer() { + return beanFactory.map(this::getDefaultQuerydslBinderCustomizer).orElse(NoOpCustomizer.INSTANCE); + } + + private QuerydslBinderCustomizer> getDefaultQuerydslBinderCustomizer( + AutowireCapableBeanFactory beanFactory) { + + List customizers = beanFactory + .getBeanProvider(DefaultQuerydslBinderCustomizer.class).stream().collect(Collectors.toList()); + + return (bindings, root) -> { + for (DefaultQuerydslBinderCustomizer defaultQuerydslBinderCustomizer : customizers) { + defaultQuerydslBinderCustomizer.customize(bindings, root); + } + }; + } + /** * 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. + * dedicated configured one or consider the domain type's repository. * * @param annotation * @param domainType @@ -194,7 +224,7 @@ private QuerydslBinderCustomizer> createQuerydslBinderCustomizer( }).orElseGet(() -> BeanUtils.instantiateClass(type)); } - private static enum NoOpCustomizer implements QuerydslBinderCustomizer> { + private enum NoOpCustomizer implements QuerydslBinderCustomizer> { INSTANCE; diff --git a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java index dd88099137..9cc8178c0e 100755 --- a/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/querydsl/binding/QuerydslBindingsFactoryUnitTests.java @@ -23,7 +23,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.data.querydsl.QUser; import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.querydsl.User; @@ -33,6 +35,7 @@ import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.servlet.ModelAndView; +import com.querydsl.core.types.EntityPath; import com.querydsl.core.types.Path; import com.querydsl.core.types.Predicate; @@ -95,6 +98,27 @@ void shouldReuseExistingQuerydslBinderCustomizer() { }); } + @Test // #206 + @SuppressWarnings({ "unchecked", "rawtypes" }) + void shouldApplyDefaultCustomizers() { + + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean(DefaultCustomizer.class); + context.refresh(); + + QuerydslBindingsFactory factory = new QuerydslBindingsFactory(SimpleEntityPathResolver.INSTANCE); + factory.setApplicationContext(context); + + QuerydslBindings bindings = factory.createBindingsFor(USER_TYPE, SpecificBinding.class); + Optional, Object>> binding = bindings + .getBindingForPath(PropertyPathInformation.of("inceptionYear", User.class)); + + assertThat(binding).hasValueSatisfying(it -> { + Optional bind = it.bind((Path) QUser.user.inceptionYear, Collections.singleton(1L)); + assertThat(bind).hasValue(QUser.user.inceptionYear.gt(1L)); + }); + } + @Test // DATACMNS-669 void rejectsPredicateResolutionIfDomainTypeCantBeAutoDetected() { @@ -123,4 +147,14 @@ public void customize(QuerydslBindings bindings, QUser user) { bindings.bind(QUser.user.firstname).firstOptional((path, value) -> value.map(path::contains)); } } + + static class DefaultCustomizer implements DefaultQuerydslBinderCustomizer { + + @Override + public void customize(QuerydslBindings bindings, EntityPath root) { + + bindings.bind(QUser.user.inceptionYear).first((path, value) -> QUser.user.inceptionYear.gt(value)); + } + } + }