Skip to content

Commit 2d6755f

Browse files
Rodrigo Dos SantosRodrigo Dos Santos
authored andcommitted
Add Custom QuerydslPredicateBuilder
To facilitate the use of queryDsl - related to #2266
1 parent b8804be commit 2d6755f

File tree

10 files changed

+239
-46
lines changed

10 files changed

+239
-46
lines changed

src/main/java/org/springframework/data/querydsl/binding/PathInformation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* @author Oliver Gierke
3030
* @since 1.13
3131
*/
32-
interface PathInformation {
32+
public interface PathInformation {
3333

3434
/**
3535
* The root property owner type.

src/main/java/org/springframework/data/querydsl/binding/QuerydslBindings.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ boolean isPathAvailable(String path, Class<?> type) {
184184
* @param type
185185
* @return
186186
*/
187-
boolean isPathAvailable(String path, TypeInformation<?> type) {
187+
public boolean isPathAvailable(String path, TypeInformation<?> type) {
188188

189189
Assert.notNull(path, "Path must not be null");
190190
Assert.notNull(type, "Type must not be null");
@@ -241,7 +241,7 @@ Optional<Path<?>> getExistingPath(PathInformation path) {
241241
* @return
242242
*/
243243
@Nullable
244-
PathInformation getPropertyPath(String path, TypeInformation<?> type) {
244+
public PathInformation getPropertyPath(String path, TypeInformation<?> type) {
245245

246246
Assert.notNull(path, "Path must not be null");
247247
Assert.notNull(type, "Type information must not be null");

src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ public static boolean isEmpty(Predicate predicate) {
140140
* @param values must not be {@literal null}.
141141
* @return
142142
*/
143-
private Optional<Predicate> invokeBinding(PathInformation dotPath, QuerydslBindings bindings,
144-
Collection<Object> values) {
143+
protected Optional<Predicate> invokeBinding(PathInformation dotPath, QuerydslBindings bindings,
144+
Collection<Object> values) {
145145

146146
Path<?> path = getPath(dotPath, bindings);
147147

@@ -173,7 +173,7 @@ private Path<?> getPath(PathInformation path, QuerydslBindings bindings) {
173173
* @param path must not be {@literal null}.
174174
* @return
175175
*/
176-
private Collection<Object> convertToPropertyPathSpecificType(List<?> source, PathInformation path) {
176+
protected Collection<Object> convertToPropertyPathSpecificType(List<?> source, PathInformation path) {
177177

178178
if (source.isEmpty() || isSingleElementCollectionWithEmptyItem(source)) {
179179
return Collections.emptyList();
@@ -237,7 +237,7 @@ private static TypeDescriptor getTargetTypeDescriptor(PathInformation path) {
237237
* @param source must not be {@literal null}.
238238
* @return
239239
*/
240-
private static boolean isSingleElementCollectionWithEmptyItem(List<?> source) {
240+
protected static boolean isSingleElementCollectionWithEmptyItem(List<?> source) {
241241
return source.size() == 1 && ObjectUtils.isEmpty(source.get(0));
242242
}
243243

@@ -247,7 +247,7 @@ private static boolean isSingleElementCollectionWithEmptyItem(List<?> source) {
247247
* @param builder
248248
* @return
249249
*/
250-
private static Predicate getPredicate(BooleanBuilder builder) {
250+
protected static Predicate getPredicate(BooleanBuilder builder) {
251251

252252
Predicate predicate = builder.getValue();
253253
return predicate == null ? new BooleanBuilder() : predicate;

src/main/java/org/springframework/data/web/config/QuerydslWebConfiguration.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
*/
1616
package org.springframework.data.web.config;
1717

18-
import java.util.List;
19-
18+
import com.querydsl.core.types.Predicate;
2019
import org.springframework.beans.factory.BeanFactory;
2120
import org.springframework.beans.factory.ObjectProvider;
2221
import org.springframework.beans.factory.annotation.Autowired;
@@ -29,11 +28,12 @@
2928
import org.springframework.data.querydsl.EntityPathResolver;
3029
import org.springframework.data.querydsl.SimpleEntityPathResolver;
3130
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
31+
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
3232
import org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver;
3333
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
3434
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
3535

36-
import com.querydsl.core.types.Predicate;
36+
import java.util.List;
3737

3838
/**
3939
* Querydsl-specific web configuration for Spring Data. Registers a {@link HandlerMethodArgumentResolver} that builds up
@@ -60,9 +60,15 @@ public class QuerydslWebConfiguration implements WebMvcConfigurer {
6060
@Lazy
6161
@Bean
6262
public QuerydslPredicateArgumentResolver querydslPredicateArgumentResolver() {
63-
return new QuerydslPredicateArgumentResolver(
64-
beanFactory.getBean("querydslBindingsFactory", QuerydslBindingsFactory.class),
65-
conversionService.getIfUnique(DefaultConversionService::getSharedInstance));
63+
if (beanFactory.containsBean("querydslPredicateBuilder")) { //TODO It might have a better way to check if object exists...
64+
return new QuerydslPredicateArgumentResolver(
65+
beanFactory.getBean("querydslBindingsFactory", QuerydslBindingsFactory.class),
66+
beanFactory.getBean(QuerydslPredicateBuilder.class));
67+
} else {
68+
return new QuerydslPredicateArgumentResolver(
69+
beanFactory.getBean("querydslBindingsFactory", QuerydslBindingsFactory.class),
70+
conversionService.getIfUnique(DefaultConversionService::getSharedInstance));
71+
}
6672
}
6773

6874
@Lazy

src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.core.convert.ConversionService;
2424
import org.springframework.core.convert.support.DefaultConversionService;
2525
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
26+
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
2627
import org.springframework.lang.Nullable;
2728
import org.springframework.util.LinkedMultiValueMap;
2829
import org.springframework.util.MultiValueMap;
@@ -69,6 +70,16 @@ public QuerydslPredicateArgumentResolver(QuerydslBindingsFactory factory, Conver
6970
super(factory, conversionService);
7071
}
7172

73+
/**
74+
* Create a new {@link QuerydslPredicateArgumentResolver}.
75+
*
76+
* @param factory the {@link QuerydslBindingsFactory} to use, must not be {@literal null}.
77+
* @param querydslPredicateBuilder the {@link QuerydslPredicateBuilder} to use, must not be {@literal null}.
78+
*/
79+
public QuerydslPredicateArgumentResolver(QuerydslBindingsFactory factory, QuerydslPredicateBuilder querydslPredicateBuilder) {
80+
super(factory, querydslPredicateBuilder);
81+
}
82+
7283
@Nullable
7384
@Override
7485
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,

src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,21 @@ protected QuerydslPredicateArgumentResolverSupport(QuerydslBindingsFactory facto
7272
this.predicateBuilder = new QuerydslPredicateBuilder(conversionService, factory.getEntityPathResolver());
7373
}
7474

75+
/**
76+
* Creates a new {@link QuerydslPredicateArgumentResolver} using the given {@link QuerydslPredicateBuilder}.
77+
* @param factory
78+
* @param querydslPredicateBuilder
79+
*/
80+
public QuerydslPredicateArgumentResolverSupport(QuerydslBindingsFactory factory,
81+
QuerydslPredicateBuilder querydslPredicateBuilder) {
82+
83+
Assert.notNull(factory, "QuerydslBindingsFactory must not be null");
84+
Assert.notNull(querydslPredicateBuilder, "querydslPredicateBuilder must not be null");
85+
86+
this.bindingsFactory = factory;
87+
this.predicateBuilder = querydslPredicateBuilder;
88+
}
89+
7590
public boolean supportsParameter(MethodParameter parameter) {
7691

7792
ResolvableType type = ResolvableType.forMethodParameter(parameter);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.custom.querydslpredicatebuilder;
2+
3+
import com.querydsl.core.BooleanBuilder;
4+
import com.querydsl.core.types.Predicate;
5+
import org.springframework.core.convert.support.DefaultConversionService;
6+
import org.springframework.data.querydsl.SimpleEntityPathResolver;
7+
import org.springframework.data.querydsl.binding.PathInformation;
8+
import org.springframework.data.querydsl.binding.QuerydslBindings;
9+
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
10+
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
11+
import org.springframework.data.util.TypeInformation;
12+
import org.springframework.stereotype.Component;
13+
import org.springframework.util.Assert;
14+
import org.springframework.util.MultiValueMap;
15+
16+
import java.util.Collection;
17+
import java.util.Optional;
18+
19+
@Component("querydslPredicateBuilder")
20+
public class QuerydslPredicateBuilderCustom extends QuerydslPredicateBuilder {
21+
/**
22+
* Creates a custom {@link QuerydslPredicateBuilder}
23+
*
24+
*/
25+
public QuerydslPredicateBuilderCustom() {
26+
super(DefaultConversionService.getSharedInstance(), new QuerydslBindingsFactory(SimpleEntityPathResolver.INSTANCE).getEntityPathResolver());
27+
}
28+
29+
//TODO Copy same logic as original class and just changed 'builder::or'
30+
@Override
31+
public Predicate getPredicate(TypeInformation<?> type, MultiValueMap<String, ?> values, QuerydslBindings bindings) {
32+
Assert.notNull(bindings, "Context must not be null");
33+
34+
BooleanBuilder builder = new BooleanBuilder();
35+
36+
if (values.isEmpty()) {
37+
return getPredicate(builder);
38+
}
39+
40+
for (var entry : values.entrySet()) {
41+
42+
if (isSingleElementCollectionWithEmptyItem(entry.getValue())) {
43+
continue;
44+
}
45+
46+
String path = entry.getKey();
47+
48+
if (!bindings.isPathAvailable(path, type)) {
49+
continue;
50+
}
51+
52+
PathInformation propertyPath = bindings.getPropertyPath(path, type);
53+
54+
if (propertyPath == null) {
55+
continue;
56+
}
57+
58+
Collection<Object> value = convertToPropertyPathSpecificType(entry.getValue(), propertyPath);
59+
Optional<Predicate> predicate = invokeBinding(propertyPath, bindings, value);
60+
61+
predicate.ifPresent(builder::or);
62+
}
63+
64+
return getPredicate(builder);
65+
}
66+
}

src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,35 @@
1515
*/
1616
package org.springframework.data.web.config;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
20-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
21-
22-
import java.util.Arrays;
23-
18+
import com.custom.querydslpredicatebuilder.QuerydslPredicateBuilderCustom;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
2420
import org.junit.jupiter.api.Test;
25-
2621
import org.springframework.context.ApplicationContext;
2722
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.ComponentScan;
2824
import org.springframework.context.annotation.Configuration;
2925
import org.springframework.core.convert.ConversionService;
3026
import org.springframework.data.classloadersupport.HidingClassLoader;
3127
import org.springframework.data.geo.Distance;
3228
import org.springframework.data.geo.Point;
3329
import org.springframework.data.querydsl.EntityPathResolver;
30+
import org.springframework.data.querydsl.QUser;
3431
import org.springframework.data.querydsl.SimpleEntityPathResolver;
3532
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
36-
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
37-
import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
38-
import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver;
39-
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
40-
import org.springframework.data.web.WebTestUtils;
33+
import org.springframework.data.web.*;
4134
import org.springframework.hateoas.Link;
4235
import org.springframework.test.util.ReflectionTestUtils;
4336
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
4437
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
4538
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
4639
import org.springframework.web.util.UriComponentsBuilder;
4740

48-
import com.fasterxml.jackson.databind.ObjectMapper;
41+
import java.util.Arrays;
42+
43+
import static org.assertj.core.api.Assertions.assertThat;
44+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
45+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
46+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
4947

5048
/**
5149
* Integration tests for {@link EnableSpringDataWebSupport}.
@@ -101,6 +99,17 @@ SimpleEntityPathResolver entityPathResolver() {
10199
}
102100
}
103101

102+
@Configuration
103+
@EnableWebMvc
104+
@EnableSpringDataWebSupport
105+
@ComponentScan(basePackageClasses = QuerydslPredicateBuilderCustom.class)
106+
static class SampleConfigWithCustomQuerydslPredicateBuilder {
107+
@Bean
108+
SampleController controller() {
109+
return new SampleController();
110+
}
111+
}
112+
104113
@Test // DATACMNS-330
105114
void registersBasicBeanDefinitions() throws Exception {
106115

@@ -187,6 +196,29 @@ void createsProxyForInterfaceBasedControllerMethodParameter() throws Exception {
187196
andExpect(status().isOk());
188197
}
189198

199+
@Test
200+
void createsTestForCustomPredicate() throws Exception {
201+
202+
var applicationContext = WebTestUtils.createApplicationContext(SampleConfigWithCustomQuerydslPredicateBuilder.class);
203+
var mvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
204+
205+
var builder = UriComponentsBuilder.fromUriString("/predicate");
206+
builder.queryParam("firstname", "Foo");
207+
builder.queryParam("lastname", "Bar");
208+
209+
mvc.perform(post(builder.build().toString())).
210+
andExpect(status().isOk())
211+
.andExpect(content().string(QUser.user.firstname.eq("Foo").or(QUser.user.lastname.eq("Bar")).toString()));
212+
213+
//Default should be and
214+
applicationContext = WebTestUtils.createApplicationContext(SampleConfig.class);
215+
mvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
216+
217+
mvc.perform(post(builder.build().toString())).
218+
andExpect(status().isOk())
219+
.andExpect(content().string(QUser.user.firstname.eq("Foo").and(QUser.user.lastname.eq("Bar")).toString()));
220+
}
221+
190222
@Test // DATACMNS-660
191223
void picksUpWebConfigurationMixins() {
192224

src/test/java/org/springframework/data/web/config/SampleController.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@
1515
*/
1616
package org.springframework.data.web.config;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
20-
import java.util.Collection;
21-
import java.util.Date;
22-
18+
import com.querydsl.core.types.Predicate;
19+
import org.springframework.data.querydsl.User;
20+
import org.springframework.data.querydsl.binding.QuerydslPredicate;
2321
import org.springframework.data.web.ProjectedPayload;
2422
import org.springframework.format.annotation.DateTimeFormat;
2523
import org.springframework.format.annotation.DateTimeFormat.ISO;
2624
import org.springframework.stereotype.Controller;
2725
import org.springframework.web.bind.annotation.RequestMapping;
26+
import org.springframework.web.bind.annotation.ResponseBody;
27+
28+
import java.util.Collection;
29+
import java.util.Date;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
2832

2933
/**
3034
* @author Oliver Gierke
@@ -52,6 +56,12 @@ String someMethod(SampleDto sampleDto) {
5256
return "view";
5357
}
5458

59+
@RequestMapping("/predicate")
60+
@ResponseBody
61+
String generateCustomPredicate(@QuerydslPredicate(root = User.class) Predicate predicate) {
62+
return predicate.toString();
63+
}
64+
5565
@ProjectedPayload
5666
interface SampleDto {
5767

0 commit comments

Comments
 (0)