Skip to content

Commit 4d31bd9

Browse files
author
bnasslahsen
committed
Merge branch 'gibahjoe-master'
2 parents 2594a27 + f04f31b commit 4d31bd9

File tree

10 files changed

+302
-15
lines changed

10 files changed

+302
-15
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,16 @@
7373
<webjars-locator.version>0.38</webjars-locator.version>
7474
<spring-security-oauth2.version>2.3.8.RELEASE</spring-security-oauth2.version>
7575
<classgraph.version>4.8.44</classgraph.version>
76+
<javassist.version>3.26.0-GA</javassist.version>
7677
</properties>
7778

7879
<dependencyManagement>
7980
<dependencies>
81+
<dependency>
82+
<groupId>org.javassist</groupId>
83+
<artifactId>javassist</artifactId>
84+
<version>${javassist.version}</version>
85+
</dependency>
8086
<!-- swagger dependencies -->
8187
<dependency>
8288
<groupId>io.swagger.core.v3</groupId>

springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterBuilder.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,10 @@ else if (methodParameter.getGenericParameterType() instanceof ParameterizedType)
212212
if (isFile(parameterizedType)) {
213213
return extractFileSchema(paramName, requestBodyInfo);
214214
}
215-
schemaN = calculateSchemaFromParameterizedType(components, methodParameter.getGenericParameterType(), jsonView);
215+
schemaN = SpringDocAnnotationsUtils.extractSchema(components, methodParameter.getGenericParameterType(), jsonView);
216216
}
217217
else {
218-
schemaN = SpringDocAnnotationsUtils.resolveSchemaFromType(methodParameter.getParameterType(), components, jsonView);
218+
schemaN = SpringDocAnnotationsUtils.resolveSchemaFromType(methodParameter.getParameterType(), components, jsonView, methodParameter.getParameterAnnotations());
219219
}
220220
}
221221
else {
@@ -238,10 +238,6 @@ public LocalVariableTableParameterNameDiscoverer getLocalSpringDocParameterNameD
238238
return localSpringDocParameterNameDiscoverer;
239239
}
240240

241-
private Schema calculateSchemaFromParameterizedType(Components components, Type paramType, JsonView jsonView) {
242-
return SpringDocAnnotationsUtils.extractSchema(components, paramType, jsonView);
243-
}
244-
245241
private Schema extractFileSchema(String paramName, RequestBodyInfo requestBodyInfo) {
246242
Schema schemaN = getFileSchema(requestBodyInfo);
247243
ArraySchema schemaFile = new ArraySchema();

springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocAnnotationsUtils.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package org.springdoc.core;
2020

21+
import java.lang.annotation.Annotation;
2122
import java.lang.reflect.Type;
2223
import java.util.LinkedHashMap;
2324
import java.util.Map;
@@ -40,8 +41,8 @@
4041
public class SpringDocAnnotationsUtils extends AnnotationsUtils {
4142

4243
public static Schema resolveSchemaFromType(Class<?> schemaImplementation, Components components,
43-
JsonView jsonView) {
44-
Schema schemaObject = extractSchema(components, schemaImplementation, jsonView);
44+
JsonView jsonView, Annotation[] annotations) {
45+
Schema schemaObject = extractSchema(components, schemaImplementation, jsonView, annotations);
4546
if (schemaObject != null && StringUtils.isBlank(schemaObject.get$ref())
4647
&& StringUtils.isBlank(schemaObject.getType())) {
4748
// default to string
@@ -50,11 +51,11 @@ public static Schema resolveSchemaFromType(Class<?> schemaImplementation, Compon
5051
return schemaObject;
5152
}
5253

53-
public static Schema extractSchema(Components components, Type returnType, JsonView jsonView) {
54+
public static Schema extractSchema(Components components, Type returnType, JsonView jsonView, Annotation[] annotations) {
5455
Schema schemaN = null;
5556
ResolvedSchema resolvedSchema = ModelConverters.getInstance()
5657
.resolveAsResolvedSchema(
57-
new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonView));
58+
new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonView).ctxAnnotations(annotations));
5859
if (resolvedSchema.schema != null) {
5960
schemaN = resolvedSchema.schema;
6061
Map<String, Schema> schemaMap = resolvedSchema.referencedSchemas;
@@ -75,6 +76,10 @@ else if (!componentSchemas.containsKey(entry.getKey())) {
7576
return schemaN;
7677
}
7778

79+
public static Schema extractSchema(Components components, Type genericParameterType, JsonView jsonView) {
80+
return extractSchema(components, genericParameterType, jsonView, null);
81+
}
82+
7883
public static Optional<Content> getContent(io.swagger.v3.oas.annotations.media.Content[] annotationContents,
7984
String[] classTypes, String[] methodTypes, Schema schema, Components components,
8085
JsonView jsonViewAnnotation) {

springdoc-openapi-data-rest/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@
1919
<groupId>org.springframework.data</groupId>
2020
<artifactId>spring-data-rest-core</artifactId>
2121
</dependency>
22+
<dependency>
23+
<groupId>com.querydsl</groupId>
24+
<artifactId>querydsl-core</artifactId>
25+
<optional>true</optional>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.javassist</groupId>
29+
<artifactId>javassist</artifactId>
30+
<optional>true</optional>
31+
</dependency>
2232
<dependency>
2333
<groupId>org.hibernate.validator</groupId>
2434
<artifactId>hibernate-validator</artifactId>

springdoc-openapi-data-rest/src/main/java/org/springdoc/core/SpringDocDataRestConfiguration.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@
3030
import io.swagger.v3.oas.models.media.ObjectSchema;
3131
import io.swagger.v3.oas.models.media.StringSchema;
3232
import org.springdoc.core.converters.Pageable;
33+
import org.springdoc.core.converters.QueryDslPredicateConverter;
3334
import org.springdoc.core.converters.RepresentationModelLinksOASMixin;
3435
import org.springdoc.core.customizers.OpenApiCustomiser;
3536

3637
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
38+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3739
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3840
import org.springframework.context.annotation.Bean;
3941
import org.springframework.context.annotation.Configuration;
42+
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
4043
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
4144
import org.springframework.hateoas.Link;
4245
import org.springframework.hateoas.Links;
@@ -54,9 +57,16 @@ public class SpringDocDataRestConfiguration {
5457
.replaceWithClass(org.springframework.data.domain.PageRequest.class, Pageable.class);
5558
}
5659

57-
@Configuration
58-
@ConditionalOnClass(RepositoryRestConfiguration.class)
59-
class HalProviderConfiguration {
60+
@Bean
61+
@ConditionalOnMissingBean
62+
@ConditionalOnClass(QuerydslBindingsFactory.class)
63+
public QueryDslPredicateConverter qdslConverter(QuerydslBindingsFactory querydslBindingsFactory) {
64+
return new QueryDslPredicateConverter(querydslBindingsFactory);
65+
}
66+
67+
@Configuration
68+
@ConditionalOnClass(RepositoryRestConfiguration.class)
69+
class HalProviderConfiguration {
6070

6171
@Bean
6272
public HalProvider halProvider(Optional<RepositoryRestConfiguration> repositoryRestConfiguration) {
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
*
3+
* * Copyright 2019-2020 the original author or authors.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * https://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package org.springdoc.core.converters;
20+
21+
import java.lang.reflect.Field;
22+
import java.lang.reflect.Modifier;
23+
import java.util.Arrays;
24+
import java.util.Collections;
25+
import java.util.Iterator;
26+
import java.util.Optional;
27+
import java.util.Set;
28+
import java.util.stream.Collectors;
29+
30+
import com.fasterxml.jackson.databind.JavaType;
31+
import com.querydsl.core.types.Predicate;
32+
import io.swagger.v3.core.converter.AnnotatedType;
33+
import io.swagger.v3.core.converter.ModelConverter;
34+
import io.swagger.v3.core.converter.ModelConverterContext;
35+
import io.swagger.v3.core.util.Json;
36+
import io.swagger.v3.oas.models.media.Schema;
37+
import javassist.CannotCompileException;
38+
import javassist.ClassPool;
39+
import javassist.CtClass;
40+
import javassist.CtField;
41+
import org.slf4j.Logger;
42+
import org.slf4j.LoggerFactory;
43+
44+
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
45+
import org.springframework.data.querydsl.binding.QuerydslBindings;
46+
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
47+
import org.springframework.data.querydsl.binding.QuerydslPredicate;
48+
import org.springframework.data.util.CastUtils;
49+
import org.springframework.data.util.ClassTypeInformation;
50+
import org.springframework.data.util.TypeInformation;
51+
52+
53+
public class QueryDslPredicateConverter implements ModelConverter {
54+
55+
private static final Logger LOGGER = LoggerFactory.getLogger(QueryDslPredicateConverter.class);
56+
57+
private QuerydslBindingsFactory querydslBindingsFactory;
58+
59+
public QueryDslPredicateConverter(QuerydslBindingsFactory querydslBindingsFactory) {
60+
this.querydslBindingsFactory = querydslBindingsFactory;
61+
}
62+
63+
@Override
64+
public Schema resolve(AnnotatedType annotatedType, ModelConverterContext modelConverterContext, Iterator<ModelConverter> iterator) {
65+
JavaType javaType = Json.mapper().constructType(annotatedType.getType());
66+
if (javaType != null) {
67+
Class<?> cls = javaType.getRawClass();
68+
if (Predicate.class.isAssignableFrom(cls)) {
69+
Optional<QuerydslPredicate> predicate = Arrays.stream(annotatedType.getCtxAnnotations()).filter(QuerydslPredicate.class::isInstance).map(QuerydslPredicate.class::cast).findAny();
70+
if (predicate.isPresent()) {
71+
try {
72+
Class<?> tClass = getClassFromPredicate(predicate.get());
73+
return this.resolve(new AnnotatedType(tClass).jsonViewAnnotation(annotatedType.getJsonViewAnnotation()).resolveAsRef(true), modelConverterContext, iterator);
74+
}
75+
catch (CannotCompileException e) {
76+
LOGGER.warn("Can not compile : {}", e.getMessage());
77+
}
78+
}
79+
}
80+
}
81+
return iterator.hasNext() ? iterator.next().resolve(annotatedType, modelConverterContext, iterator) : null;
82+
}
83+
84+
private Class<?> getClassFromPredicate(QuerydslPredicate predicate) throws CannotCompileException {
85+
ClassTypeInformation<?> classTypeInformation = ClassTypeInformation.from(predicate.root());
86+
TypeInformation<?> domainType = classTypeInformation.getRequiredActualType();
87+
Optional<Class<? extends QuerydslBinderCustomizer<?>>> bindingsAnnotation = Optional.of(predicate)
88+
.map(QuerydslPredicate::bindings)
89+
.map(CastUtils::cast);
90+
QuerydslBindings bindings = bindingsAnnotation
91+
.map(it -> querydslBindingsFactory.createBindingsFor(domainType, it))
92+
.orElseGet(() -> querydslBindingsFactory.createBindingsFor(domainType));
93+
String generatedClassName = "com.springdoc.core." + predicate.bindings().getSimpleName() + "G";
94+
ClassPool classPool = ClassPool.getDefault();
95+
CtClass classPoolOrNull = classPool.getOrNull(generatedClassName);
96+
if (classPoolOrNull == null) {
97+
classPoolOrNull = classPool.makeClass(generatedClassName);
98+
Set<String> fieldsToAdd = Arrays.stream(predicate.root().getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());
99+
//remove blacklisted fields
100+
Set<String> blacklist = getBindingFieldValues(bindings, "blackList");
101+
fieldsToAdd.removeIf(blacklist::contains);
102+
Set<String> whiteList = getBindingFieldValues(bindings, "whiteList");
103+
Set<String> aliases = getBindingFieldValues(bindings, "aliases");
104+
fieldsToAdd.addAll(aliases);
105+
fieldsToAdd.addAll(whiteList);
106+
for (String fieldName : fieldsToAdd) {
107+
CtField f = new CtField(CtClass.charType, fieldName, classPoolOrNull);
108+
f.setModifiers(Modifier.PUBLIC);
109+
classPoolOrNull.addField(f);
110+
}
111+
}
112+
return classPoolOrNull.toClass(this.getClass().getClassLoader(), this.getClass().getProtectionDomain());
113+
}
114+
115+
private Set<String> getBindingFieldValues(QuerydslBindings instance, String fieldName) {
116+
try {
117+
Field field = instance.getClass().getDeclaredField(fieldName);
118+
if (Modifier.isPrivate(field.getModifiers())) {
119+
field.setAccessible(true);
120+
}
121+
return (Set<String>) field.get(instance);
122+
}
123+
catch (NoSuchFieldException | IllegalAccessException e) {
124+
LOGGER.warn("NoSuchFieldException or IllegalAccessException occured : {}", e.getMessage());
125+
}
126+
return Collections.emptySet();
127+
}
128+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package test.org.springdoc.api.app98;
2+
3+
/**
4+
* @author Gibah Joseph
5+
6+
* Mar, 2020
7+
**/
8+
public class DummyEntity {
9+
private String name;
10+
private String code;
11+
12+
public String getName() {
13+
return name;
14+
}
15+
16+
public void setName(String name) {
17+
this.name = name;
18+
}
19+
20+
public String getCode() {
21+
return code;
22+
}
23+
24+
public void setCode(String code) {
25+
this.code = code;
26+
}
27+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package test.org.springdoc.api.app98;
2+
3+
import com.querydsl.core.types.Path;
4+
import com.querydsl.core.types.PathMetadata;
5+
import com.querydsl.core.types.dsl.EntityPathBase;
6+
import com.querydsl.core.types.dsl.StringPath;
7+
8+
import static com.querydsl.core.types.PathMetadataFactory.forVariable;
9+
10+
/**
11+
* @author Gibah Joseph
12+
13+
* Mar, 2020
14+
**/
15+
public class QDummyEntity extends EntityPathBase<DummyEntity> {
16+
17+
public static final QDummyEntity dummyEntity = new QDummyEntity("dummyEntity");
18+
private static final long serialVersionUID = -1184258693L;
19+
public final StringPath code = createString("code");
20+
21+
public final StringPath name = createString("name");
22+
23+
public final StringPath shortName = createString("shortName");
24+
25+
public QDummyEntity(String variable) {
26+
super(DummyEntity.class, forVariable(variable));
27+
}
28+
29+
public QDummyEntity(Path<? extends DummyEntity> path) {
30+
super(path.getType(), path.getMetadata());
31+
}
32+
33+
public QDummyEntity(PathMetadata metadata) {
34+
super(DummyEntity.class, metadata);
35+
}
36+
}

0 commit comments

Comments
 (0)