Skip to content

Commit 0575b08

Browse files
author
bnasslahsen
committed
QuerydslPredicateOperationCustomizer exclude static fields and support QuerydslBindings.excludeUnlistedProperties feature. Fixes #866.
1 parent 7bb0240 commit 0575b08

File tree

7 files changed

+297
-17
lines changed

7 files changed

+297
-17
lines changed

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/customisers/QuerydslPredicateOperationCustomizer.java

+37-17
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
package org.springdoc.data.rest.customisers;
2525

2626
import java.lang.reflect.Field;
27+
import java.lang.reflect.Modifier;
2728
import java.lang.reflect.Type;
2829
import java.util.ArrayList;
2930
import java.util.Arrays;
@@ -97,13 +98,12 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) {
9798
MethodParameter parameter = methodParameters[i];
9899
QuerydslPredicate predicate = parameter.getParameterAnnotation(QuerydslPredicate.class);
99100

100-
if (predicate == null) {
101+
if (predicate == null)
101102
continue;
102-
}
103103

104104
QuerydslBindings bindings = extractQdslBindings(predicate);
105105

106-
Set<String> fieldsToAdd = Arrays.stream(predicate.root().getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());
106+
Set<String> fieldsToAdd = Arrays.stream(predicate.root().getDeclaredFields()).filter(field -> !Modifier.isStatic(field.getModifiers())).map(Field::getName).collect(Collectors.toSet());
107107

108108
Map<String, Object> pathSpecMap = getPathSpec(bindings, "pathSpecs");
109109
//remove blacklisted fields
@@ -115,17 +115,40 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) {
115115

116116
fieldsToAdd.addAll(aliases);
117117
fieldsToAdd.addAll(whiteList);
118-
for (String fieldName : fieldsToAdd) {
119-
Type type = getFieldType(fieldName, pathSpecMap, predicate.root());
120-
io.swagger.v3.oas.models.parameters.Parameter newParameter = buildParam(type, fieldName);
121118

122-
parametersToAddToOperation.add(newParameter);
119+
boolean excludeUnlistedProperties = getFieldValueOfBoolean(bindings, "excludeUnlistedProperties");
120+
121+
for (String fieldName : fieldsToAdd) {
122+
Type type = getFieldType(fieldName, pathSpecMap, predicate.root(), excludeUnlistedProperties);
123+
if (type != null) {
124+
Parameter newParameter = buildParam(type, fieldName);
125+
parametersToAddToOperation.add(newParameter);
126+
}
123127
}
124128
}
125129
operation.getParameters().addAll(parametersToAddToOperation);
126130
return operation;
127131
}
128132

133+
/**
134+
* Gets field value of boolean.
135+
*
136+
* @param instance the instance
137+
* @param fieldName the field name
138+
* @return the field value of boolean
139+
*/
140+
private boolean getFieldValueOfBoolean(QuerydslBindings instance, String fieldName) {
141+
try {
142+
Field field = FieldUtils.getDeclaredField(instance.getClass(), fieldName, true);
143+
if (field != null)
144+
return (boolean) field.get(instance);
145+
}
146+
catch (IllegalAccessException e) {
147+
LOGGER.warn(e.getMessage());
148+
}
149+
return false;
150+
}
151+
129152
/**
130153
* Extract qdsl bindings querydsl bindings.
131154
*
@@ -150,6 +173,7 @@ private QuerydslBindings extractQdslBindings(QuerydslPredicate predicate) {
150173
*
151174
* @param instance the instance
152175
* @param fieldName the field name
176+
* @param alternativeFieldName the alternative field name
153177
* @return the field values
154178
*/
155179
private Set<String> getFieldValues(QuerydslBindings instance, String fieldName, String alternativeFieldName) {
@@ -209,30 +233,26 @@ private Optional<Path<?>> getPathFromPathSpec(Object instance) {
209233
* @param fieldName The name of the field used as reference to get the type
210234
* @param pathSpecMap The Qdsl path specifications as defined in the resolved bindings
211235
* @param root The root type where the paths are gotten
236+
* @param excludeUnlistedProperties the exclude unlisted properties
212237
* @return The type of the field. Returns
213238
*/
214-
private Type getFieldType(String fieldName, Map<String, Object> pathSpecMap, Class<?> root) {
239+
private Type getFieldType(String fieldName, Map<String, Object> pathSpecMap, Class<?> root, boolean excludeUnlistedProperties) {
240+
Type genericType = null;
215241
try {
216242
Object pathAndBinding = pathSpecMap.get(fieldName);
217243
Optional<Path<?>> path = getPathFromPathSpec(pathAndBinding);
218-
219-
Type genericType;
220-
Field declaredField = null;
244+
Field declaredField;
221245
if (path.isPresent()) {
222246
genericType = path.get().getType();
223-
}
224-
else {
247+
} else if (!excludeUnlistedProperties) {
225248
declaredField = root.getDeclaredField(fieldName);
226249
genericType = declaredField.getGenericType();
227250
}
228-
if (genericType != null) {
229-
return genericType;
230-
}
231251
}
232252
catch (NoSuchFieldException e) {
233253
LOGGER.warn("Field {} not found on {} : {}", fieldName, root.getName(), e.getMessage());
234254
}
235-
return String.class;
255+
return genericType;
236256
}
237257

238258
/***
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package test.org.springdoc.api.app19;
2+
3+
import java.io.Serializable;
4+
import java.time.LocalDateTime;
5+
6+
import javax.persistence.Column;
7+
import javax.persistence.Entity;
8+
import javax.persistence.EntityListeners;
9+
import javax.persistence.EnumType;
10+
import javax.persistence.Enumerated;
11+
import javax.persistence.GeneratedValue;
12+
import javax.persistence.Id;
13+
import javax.persistence.Table;
14+
15+
import io.swagger.v3.oas.annotations.media.Schema;
16+
import lombok.Data;
17+
18+
import org.springframework.data.annotation.CreatedDate;
19+
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
20+
21+
@Data
22+
@Entity
23+
@Table(schema = "application", name = "application")
24+
@Schema(description = "app")
25+
@EntityListeners(AuditingEntityListener.class)
26+
public class Application implements Serializable {
27+
private static final long serialVersionUID = 6582562282311194139L;
28+
29+
@Id
30+
@Column(length = 64)
31+
@GeneratedValue
32+
@Schema(description = "id")
33+
private String id;
34+
35+
@Column(nullable = false, length = 64)
36+
@Schema(description = "name")
37+
private String name;
38+
39+
@Column(length = 1024)
40+
@Schema(description = "description")
41+
private String description;
42+
43+
@Column(length = 32)
44+
@Schema(description = "app type")
45+
@Enumerated(EnumType.STRING)
46+
private AppType type = AppType.EXTERNAL;
47+
48+
@Column
49+
@Schema(description = "icon")
50+
private String icon;
51+
52+
@Column(nullable = false)
53+
@CreatedDate
54+
@Schema(description = "createTime")
55+
private LocalDateTime createTime;
56+
57+
@Column(length = 1024)
58+
@Schema(description = "rsa-publicKey")
59+
private String publicKey;
60+
61+
@Column(length = 16, nullable = false)
62+
@Enumerated(EnumType.STRING)
63+
@Schema(description = "status")
64+
private AuditStatus auditStatus = AuditStatus.UN_SUBMITTED;
65+
66+
@Column
67+
@Schema(description = "auditTime")
68+
private LocalDateTime auditTime;
69+
70+
public enum AuditStatus {
71+
UN_SUBMITTED,
72+
PENDING,
73+
APPROVED,
74+
FAILED
75+
}
76+
77+
public enum AppType {
78+
INNER,
79+
EXTERNAL
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package test.org.springdoc.api.app19;
2+
3+
import com.querydsl.core.types.dsl.StringExpression;
4+
5+
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
6+
import org.springframework.data.querydsl.binding.QuerydslBindings;
7+
8+
public class ApplicationPredicate implements QuerydslBinderCustomizer<QApplication> {
9+
10+
@Override
11+
public void customize(QuerydslBindings bindings, QApplication root) {
12+
bindings.excludeUnlistedProperties(true);
13+
bindings.bind(root.name).first(StringExpression::containsIgnoreCase);
14+
}
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package test.org.springdoc.api.app19;
2+
3+
import com.querydsl.core.types.Predicate;
4+
5+
import org.springframework.data.querydsl.binding.QuerydslPredicate;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.RequestParam;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
@RestController
12+
public class GreetingController {
13+
14+
@GetMapping("/test2")
15+
public ResponseEntity<?> sayHello2(@QuerydslPredicate(root = Application.class, bindings = ApplicationPredicate.class) Predicate predicate,
16+
@RequestParam String test) {
17+
return ResponseEntity.ok().build();
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package test.org.springdoc.api.app19;
2+
3+
import javax.annotation.Generated;
4+
5+
import com.querydsl.core.types.Path;
6+
import com.querydsl.core.types.PathMetadata;
7+
import com.querydsl.core.types.dsl.DateTimePath;
8+
import com.querydsl.core.types.dsl.EntityPathBase;
9+
import com.querydsl.core.types.dsl.EnumPath;
10+
import com.querydsl.core.types.dsl.StringPath;
11+
12+
import static com.querydsl.core.types.PathMetadataFactory.forVariable;
13+
14+
15+
/**
16+
* QApplication is a Querydsl query type for Application
17+
*/
18+
@Generated("com.querydsl.codegen.EntitySerializer")
19+
public class QApplication extends EntityPathBase<Application> {
20+
21+
private static final long serialVersionUID = 2120388982L;
22+
23+
public static final QApplication application = new QApplication("application");
24+
25+
public final EnumPath<Application.AuditStatus> auditStatus = createEnum("auditStatus", Application.AuditStatus.class);
26+
27+
public final DateTimePath<java.time.LocalDateTime> auditTime = createDateTime("auditTime", java.time.LocalDateTime.class);
28+
29+
public final DateTimePath<java.time.LocalDateTime> createTime = createDateTime("createTime", java.time.LocalDateTime.class);
30+
31+
public final StringPath description = createString("description");
32+
33+
public final StringPath icon = createString("icon");
34+
35+
public final StringPath id = createString("id");
36+
37+
public final StringPath name = createString("name");
38+
39+
public final StringPath publicKey = createString("publicKey");
40+
41+
public final EnumPath<Application.AppType> type = createEnum("type", Application.AppType.class);
42+
43+
public QApplication(String variable) {
44+
super(Application.class, forVariable(variable));
45+
}
46+
47+
public QApplication(Path<? extends Application> path) {
48+
super(path.getType(), path.getMetadata());
49+
}
50+
51+
public QApplication(PathMetadata metadata) {
52+
super(Application.class, metadata);
53+
}
54+
55+
}
56+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * * Copyright 2019-2020 the original author or authors.
6+
* * * *
7+
* * * * Licensed under the Apache License, Version 2.0 (the "License");
8+
* * * * you may not use this file except in compliance with the License.
9+
* * * * You may obtain a copy of the License at
10+
* * * *
11+
* * * * https://www.apache.org/licenses/LICENSE-2.0
12+
* * * *
13+
* * * * Unless required by applicable law or agreed to in writing, software
14+
* * * * distributed under the License is distributed on an "AS IS" BASIS,
15+
* * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* * * * See the License for the specific language governing permissions and
17+
* * * * limitations under the License.
18+
* * *
19+
* *
20+
*
21+
*/
22+
23+
package test.org.springdoc.api.app19;
24+
25+
import test.org.springdoc.api.AbstractSpringDocTest;
26+
27+
import org.springframework.boot.autoconfigure.SpringBootApplication;
28+
29+
public class SpringDocApp19Test extends AbstractSpringDocTest {
30+
31+
@SpringBootApplication
32+
static class SpringDocTestApp {}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/test2": {
15+
"get": {
16+
"tags": [
17+
"greeting-controller"
18+
],
19+
"operationId": "sayHello2",
20+
"parameters": [
21+
{
22+
"name": "test",
23+
"in": "query",
24+
"required": true,
25+
"schema": {
26+
"type": "string"
27+
}
28+
},
29+
{
30+
"name": "name",
31+
"in": "query",
32+
"schema": {
33+
"type": "string"
34+
}
35+
}
36+
],
37+
"responses": {
38+
"200": {
39+
"description": "OK",
40+
"content": {
41+
"application/hal+json": {
42+
"schema": {
43+
"type": "object"
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
},
52+
"components": {
53+
"schemas": {}
54+
}
55+
}

0 commit comments

Comments
 (0)