Skip to content

Commit e14dd65

Browse files
author
bnasslahsen
committed
Merge branch 'springdoc-data-rest-embedded-child-name' of https://github.com/valentinabojan/springdoc-openapi into valentinabojan-springdoc-data-rest-embedded-child-name
2 parents 28e4555 + 3e7b102 commit e14dd65

File tree

7 files changed

+134
-112
lines changed

7 files changed

+134
-112
lines changed

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/HalProvider.java

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,37 @@
1818

1919
package org.springdoc.data.rest;
2020

21-
import java.util.Optional;
22-
23-
import javax.annotation.PostConstruct;
24-
25-
import io.swagger.v3.core.converter.ModelConverters;
2621
import io.swagger.v3.core.util.Json;
27-
import org.springdoc.data.rest.converters.CollectionModelContentConverter;
28-
2922
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
3023
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
24+
import org.springframework.stereotype.Component;
25+
26+
import javax.annotation.PostConstruct;
27+
import java.util.Optional;
3128

29+
@Component
3230
public class HalProvider {
3331

34-
private RepositoryRestConfiguration repositoryRestConfiguration;
35-
36-
public HalProvider(Optional<RepositoryRestConfiguration> optionalRepositoryRestConfiguration) {
37-
optionalRepositoryRestConfiguration.ifPresent(repoRestConfiguration -> this.repositoryRestConfiguration =repoRestConfiguration);
38-
}
39-
40-
@PostConstruct
41-
private void init() {
42-
if (repositoryRestConfiguration == null || repositoryRestConfiguration.useHalAsDefaultJsonMediaType()) {
43-
if (!Jackson2HalModule.isAlreadyRegisteredIn(Json.mapper()))
44-
Json.mapper().registerModule(new Jackson2HalModule());
45-
ModelConverters.getInstance()
46-
.addConverter(CollectionModelContentConverter.getConverter());
47-
}
48-
}
32+
private Optional<RepositoryRestConfiguration> repositoryRestConfigurationOptional;
33+
34+
public HalProvider(Optional<RepositoryRestConfiguration> repositoryRestConfigurationOptional) {
35+
this.repositoryRestConfigurationOptional = repositoryRestConfigurationOptional;
36+
}
37+
38+
@PostConstruct
39+
private void init() {
40+
if (!isEnabled()) {
41+
return;
42+
}
43+
44+
if (!Jackson2HalModule.isAlreadyRegisteredIn(Json.mapper())) {
45+
Json.mapper().registerModule(new Jackson2HalModule());
46+
}
47+
}
48+
49+
public boolean isEnabled() {
50+
return repositoryRestConfigurationOptional
51+
.map(RepositoryRestConfiguration::useHalAsDefaultJsonMediaType)
52+
.orElse(true);
53+
}
4954
}

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

Lines changed: 47 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
package org.springdoc.data.rest;
2020

21-
import java.util.Optional;
22-
2321
import com.fasterxml.jackson.core.JsonGenerator;
2422
import com.fasterxml.jackson.databind.SerializerProvider;
2523
import com.querydsl.core.types.Predicate;
@@ -31,79 +29,78 @@
3129
import io.swagger.v3.oas.models.media.ObjectSchema;
3230
import io.swagger.v3.oas.models.media.StringSchema;
3331
import org.springdoc.core.customizers.OpenApiCustomiser;
32+
import org.springdoc.data.rest.converters.CollectionModelContentConverter;
3433
import org.springdoc.data.rest.converters.Pageable;
3534
import org.springdoc.data.rest.converters.RepresentationModelLinksOASMixin;
3635
import org.springdoc.data.rest.customisers.QuerydslPredicateOperationCustomizer;
37-
3836
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3937
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4038
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4139
import org.springframework.context.annotation.Bean;
40+
import org.springframework.context.annotation.ComponentScan;
4241
import org.springframework.context.annotation.Configuration;
43-
import org.springframework.context.annotation.Lazy;
4442
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
4543
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
4644
import org.springframework.hateoas.Link;
4745
import org.springframework.hateoas.Links;
4846
import org.springframework.hateoas.RepresentationModel;
47+
import org.springframework.hateoas.server.LinkRelationProvider;
48+
49+
import java.util.Optional;
4950

5051
import static org.springdoc.core.Constants.SPRINGDOC_ENABLED;
5152
import static org.springdoc.core.SpringDocUtils.getConfig;
5253

5354
@Configuration
5455
@ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
56+
@ComponentScan
5557
public class SpringDocDataRestConfiguration {
5658

57-
static {
58-
getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, Pageable.class)
59-
.replaceWithClass(org.springframework.data.domain.PageRequest.class, Pageable.class);
60-
}
61-
62-
@ConditionalOnClass(value = { QuerydslBindingsFactory.class })
63-
class QuerydslProvider {
59+
static {
60+
getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, Pageable.class)
61+
.replaceWithClass(org.springframework.data.domain.PageRequest.class, Pageable.class);
62+
}
6463

65-
@Bean
66-
@ConditionalOnMissingBean
67-
@Lazy(false)
68-
QuerydslPredicateOperationCustomizer queryDslQuerydslPredicateOperationCustomizer(Optional<QuerydslBindingsFactory> querydslBindingsFactory) {
69-
if (querydslBindingsFactory.isPresent()) {
70-
getConfig().addRequestWrapperToIgnore(Predicate.class);
71-
return new QuerydslPredicateOperationCustomizer(querydslBindingsFactory.get());
72-
}
73-
return null;
74-
}
75-
}
64+
@ConditionalOnClass(value = {QuerydslBindingsFactory.class})
65+
class QuerydslProvider {
7666

67+
@Bean
68+
@ConditionalOnMissingBean
69+
QuerydslPredicateOperationCustomizer queryDslQuerydslPredicateOperationCustomizer(Optional<QuerydslBindingsFactory> querydslBindingsFactory) {
70+
if (querydslBindingsFactory.isPresent()) {
71+
getConfig().addRequestWrapperToIgnore(Predicate.class);
72+
return new QuerydslPredicateOperationCustomizer(querydslBindingsFactory.get());
73+
}
74+
return null;
75+
}
76+
}
7777

78-
@Bean
79-
@ConditionalOnMissingBean
80-
@Lazy(false)
81-
HalProvider halProvider(Optional<RepositoryRestConfiguration> repositoryRestConfiguration) {
82-
return new HalProvider(repositoryRestConfiguration);
83-
}
78+
@Bean
79+
CollectionModelContentConverter collectionModelContentConverter(HalProvider halProvider, LinkRelationProvider linkRelationProvider) {
80+
return halProvider.isEnabled() ? new CollectionModelContentConverter(linkRelationProvider) : null;
81+
}
8482

85-
/**
86-
* Registers an OpenApiCustomiser and a jackson mixin to ensure the definition of `Links` matches the serialized
87-
* output. This is done because the customer serializer converts the data to a map before serializing it.
88-
*
89-
* @see org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)
90-
*/
91-
@Bean
92-
@Lazy(false)
93-
OpenApiCustomiser linksSchemaCustomiser(Optional<RepositoryRestConfiguration> repositoryRestConfiguration) {
94-
if (!repositoryRestConfiguration.isPresent() || !repositoryRestConfiguration.get().useHalAsDefaultJsonMediaType()) {
95-
return openApi -> {
96-
};
97-
}
98-
Json.mapper().addMixIn(RepresentationModel.class, RepresentationModelLinksOASMixin.class);
83+
/**
84+
* Registers an OpenApiCustomiser and a jackson mixin to ensure the definition of `Links` matches the serialized
85+
* output. This is done because the customer serializer converts the data to a map before serializing it.
86+
*
87+
* @see org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)
88+
*/
89+
@Bean
90+
OpenApiCustomiser linksSchemaCustomiser(HalProvider halProvider) {
91+
if (!halProvider.isEnabled()) {
92+
return openApi -> {
93+
};
94+
}
95+
Json.mapper().addMixIn(RepresentationModel.class, RepresentationModelLinksOASMixin.class);
9996

100-
ResolvedSchema resolvedLinkSchema = ModelConverters.getInstance()
101-
.resolveAsResolvedSchema(new AnnotatedType(Link.class).resolveAsRef(true));
97+
ResolvedSchema resolvedLinkSchema = ModelConverters.getInstance()
98+
.resolveAsResolvedSchema(new AnnotatedType(Link.class).resolveAsRef(true));
10299

103-
return openApi -> openApi
104-
.schema("Link", resolvedLinkSchema.schema)
105-
.schema("Links", new MapSchema()
106-
.additionalProperties(new StringSchema())
107-
.additionalProperties(new ObjectSchema().$ref("#/components/schemas/Link")));
108-
}
100+
return openApi -> openApi
101+
.schema("Link", resolvedLinkSchema.schema)
102+
.schema("Links", new MapSchema()
103+
.additionalProperties(new StringSchema())
104+
.additionalProperties(new ObjectSchema().$ref("#/components/schemas/Link")));
105+
}
109106
}

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/converters/CollectionModelContentConverter.java

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,20 @@
1818

1919
package org.springdoc.data.rest.converters;
2020

21-
import java.util.Collection;
22-
import java.util.Iterator;
23-
2421
import com.fasterxml.jackson.core.JsonGenerator;
2522
import com.fasterxml.jackson.databind.SerializerProvider;
2623
import com.fasterxml.jackson.databind.type.CollectionType;
2724
import io.swagger.v3.core.converter.AnnotatedType;
2825
import io.swagger.v3.core.converter.ModelConverter;
2926
import io.swagger.v3.core.converter.ModelConverterContext;
3027
import io.swagger.v3.oas.models.media.ArraySchema;
31-
import io.swagger.v3.oas.models.media.MapSchema;
28+
import io.swagger.v3.oas.models.media.ObjectSchema;
3229
import io.swagger.v3.oas.models.media.Schema;
33-
import io.swagger.v3.oas.models.media.StringSchema;
30+
import org.springframework.hateoas.EntityModel;
31+
import org.springframework.hateoas.server.LinkRelationProvider;
32+
33+
import java.util.Collection;
34+
import java.util.Iterator;
3435

3536
/**
3637
* Override resolved schema as there is a custom serializer that converts the data to a map before serializing it.
@@ -40,27 +41,35 @@
4041
*/
4142
public class CollectionModelContentConverter implements ModelConverter {
4243

43-
private static final CollectionModelContentConverter collectionModelContentConverter = new CollectionModelContentConverter();
44+
private LinkRelationProvider linkRelationProvider;
45+
46+
public CollectionModelContentConverter(LinkRelationProvider linkRelationProvider) {
47+
this.linkRelationProvider = linkRelationProvider;
48+
}
49+
50+
@Override
51+
public Schema<?> resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
52+
if (chain.hasNext() && type != null && type.getType() instanceof CollectionType
53+
&& "_embedded".equalsIgnoreCase(type.getPropertyName())) {
54+
Schema<?> schema = chain.next().resolve(type, context, chain);
55+
if (schema instanceof ArraySchema) {
56+
Class<?> entityType = getEntityType(type);
57+
String entityClassName = linkRelationProvider.getCollectionResourceRelFor(entityType).value();
4458

45-
private CollectionModelContentConverter() {
46-
}
59+
return new ObjectSchema()
60+
.name("_embedded")
61+
.addProperties(entityClassName, schema);
62+
}
63+
}
64+
return chain.hasNext() ? chain.next().resolve(type, context, chain) : null;
65+
}
4766

48-
public static CollectionModelContentConverter getConverter() {
49-
return collectionModelContentConverter;
50-
}
67+
private Class<?> getEntityType(AnnotatedType type) {
68+
Class<?> containerEntityType = ((CollectionType) (type.getType())).getContentType().getRawClass();
5169

52-
@Override
53-
public Schema<?> resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
54-
if (chain.hasNext() && type != null && type.getType() instanceof CollectionType
55-
&& "_embedded".equalsIgnoreCase(type.getPropertyName())) {
56-
Schema<?> schema = chain.next().resolve(type, context, chain);
57-
if (schema instanceof ArraySchema) {
58-
return new MapSchema()
59-
.name("_embedded")
60-
.additionalProperties(new StringSchema())
61-
.additionalProperties(schema);
62-
}
63-
}
64-
return chain.hasNext() ? chain.next().resolve(type, context, chain) : null;
65-
}
70+
if (containerEntityType.isAssignableFrom(EntityModel.class)) {
71+
return ((CollectionType) type.getType()).getContentType().getBindings().getBoundType(0).getRawClass();
72+
}
73+
return containerEntityType;
74+
}
6675
}

springdoc-openapi-data-rest/src/test/java/test/org/springdoc/api/AbstractSpringDocTest.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@
2222
import java.nio.file.Files;
2323
import java.nio.file.Path;
2424
import java.nio.file.Paths;
25+
import java.util.List;
2526

27+
import io.swagger.v3.core.converter.ModelConverter;
2628
import io.swagger.v3.core.converter.ModelConverters;
2729
import nonapi.io.github.classgraph.utils.FileUtils;
2830
import org.junit.jupiter.api.AfterAll;
2931
import org.junit.jupiter.api.Test;
3032
import org.slf4j.Logger;
3133
import org.slf4j.LoggerFactory;
3234
import org.springdoc.core.Constants;
33-
import org.springdoc.data.rest.converters.CollectionModelContentConverter;
3435

3536
import org.springframework.beans.factory.annotation.Autowired;
3637
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -53,10 +54,16 @@ public abstract class AbstractSpringDocTest {
5354
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractSpringDocTest.class);
5455

5556
public static String className;
57+
private static List<ModelConverter> modelConverters;
5658

5759
@Autowired
5860
protected MockMvc mockMvc;
5961

62+
@Autowired
63+
private void setModelConverters(List<ModelConverter> modelConverters) {
64+
AbstractSpringDocTest.modelConverters = modelConverters;
65+
}
66+
6067
public static String getContent(String fileName) throws Exception {
6168
try {
6269
Path path = Paths.get(FileUtils.class.getClassLoader().getResource(fileName).toURI());
@@ -70,7 +77,7 @@ public static String getContent(String fileName) throws Exception {
7077

7178
@AfterAll
7279
public static void afterClass() {
73-
ModelConverters.getInstance().removeConverter(CollectionModelContentConverter.getConverter());
80+
modelConverters.forEach(ModelConverters.getInstance()::removeConverter);
7481
System.clearProperty("spring.hateoas.use-hal-as-default-json-media-type");
7582
}
7683

springdoc-openapi-data-rest/src/test/java/test/org/springdoc/api/app8/WebController.java renamed to springdoc-openapi-data-rest/src/test/java/test/org/springdoc/api/app8/AlbumController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package test.org.springdoc.api.app8;
22

3-
import java.util.Arrays;
4-
53
import org.springframework.beans.factory.annotation.Autowired;
64
import org.springframework.data.domain.Page;
75
import org.springframework.data.domain.PageImpl;
@@ -10,8 +8,10 @@
108
import org.springframework.web.bind.annotation.GetMapping;
119
import org.springframework.web.bind.annotation.RestController;
1210

11+
import java.util.Arrays;
12+
1313
@RestController
14-
public class WebController {
14+
public class AlbumController {
1515

1616
@Autowired
1717
private AlbumModelAssembler albumModelAssembler;

springdoc-openapi-data-rest/src/test/resources/results/app4.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,12 @@
130130
"properties": {
131131
"_embedded": {
132132
"type": "object",
133-
"additionalProperties": {
134-
"type": "array",
135-
"items": {
136-
"$ref": "#/components/schemas/EntityModelEmployee"
133+
"properties": {
134+
"employees": {
135+
"type": "array",
136+
"items": {
137+
"$ref": "#/components/schemas/EntityModelEmployee"
138+
}
137139
}
138140
}
139141
},

springdoc-openapi-data-rest/src/test/resources/results/app8.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"/api/albums": {
1515
"get": {
1616
"tags": [
17-
"web-controller"
17+
"album-controller"
1818
],
1919
"operationId": "getAllAlbums",
2020
"responses": {
@@ -80,10 +80,12 @@
8080
"properties": {
8181
"_embedded": {
8282
"type": "object",
83-
"additionalProperties": {
84-
"type": "array",
85-
"items": {
86-
"$ref": "#/components/schemas/Album"
83+
"properties": {
84+
"albums": {
85+
"type": "array",
86+
"items": {
87+
"$ref": "#/components/schemas/Album"
88+
}
8789
}
8890
}
8991
},
@@ -100,4 +102,4 @@
100102
}
101103
}
102104
}
103-
}
105+
}

0 commit comments

Comments
 (0)