Skip to content

Commit 407281b

Browse files
author
bnasslahsen
committed
Wrong hateoas relation. Fixes springdoc#566
1 parent c7513a3 commit 407281b

File tree

67 files changed

+2645
-94
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2645
-94
lines changed

pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<module>springdoc-openapi-data-rest</module>
6262
<module>springdoc-openapi-security</module>
6363
<module>springdoc-openapi-groovy</module>
64+
<module>springdoc-openapi-hateoas</module>
6465
</modules>
6566

6667
<properties>

springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@
6767
import org.springdoc.core.annotations.RouterOperations;
6868
import org.springdoc.core.customizers.OpenApiCustomiser;
6969
import org.springdoc.core.customizers.OperationCustomizer;
70-
import org.springdoc.core.models.RouterFunctionData;
71-
import org.springdoc.core.models.RouterOperation;
72-
import org.springdoc.core.visitor.AbstractRouterFunctionVisitor;
70+
import org.springdoc.core.fn.AbstractRouterFunctionVisitor;
71+
import org.springdoc.core.fn.RouterFunctionData;
72+
import org.springdoc.core.fn.RouterOperation;
7373

7474
import org.springframework.context.ApplicationContext;
7575
import org.springframework.core.annotation.AnnotatedElementUtils;
@@ -346,9 +346,9 @@ protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVis
346346
else
347347
routerOperationList.addAll(Arrays.asList(routerOperations.value()));
348348
if (routerOperationList.size() == 1)
349-
calculatePath(routerOperationList.stream().map(routerOperation -> new org.springdoc.core.models.RouterOperation(routerOperation, routerFunctionVisitor.getRouterFunctionDatas().get(0))).collect(Collectors.toList()));
349+
calculatePath(routerOperationList.stream().map(routerOperation -> new RouterOperation(routerOperation, routerFunctionVisitor.getRouterFunctionDatas().get(0))).collect(Collectors.toList()));
350350
else {
351-
List<org.springdoc.core.models.RouterOperation> operationList = routerOperationList.stream().map(org.springdoc.core.models.RouterOperation::new).collect(Collectors.toList());
351+
List<RouterOperation> operationList = routerOperationList.stream().map(RouterOperation::new).collect(Collectors.toList());
352352
mergeRouters(routerFunctionVisitor.getRouterFunctionDatas(), operationList);
353353
calculatePath(operationList);
354354
}
@@ -426,8 +426,8 @@ protected Operation customiseOperation(Operation operation, HandlerMethod handle
426426
return operation;
427427
}
428428

429-
protected void mergeRouters(List<RouterFunctionData> routerFunctionDatas, List<org.springdoc.core.models.RouterOperation> routerOperationList) {
430-
for (org.springdoc.core.models.RouterOperation routerOperation : routerOperationList) {
429+
protected void mergeRouters(List<RouterFunctionData> routerFunctionDatas, List<RouterOperation> routerOperationList) {
430+
for (RouterOperation routerOperation : routerOperationList) {
431431
if (StringUtils.isNotBlank(routerOperation.getPath())) {
432432
// PATH
433433
List<RouterFunctionData> routerFunctionDataList = routerFunctionDatas.stream()
@@ -565,7 +565,7 @@ private void fillParametersList(Operation operation, Map<String, String> queryPa
565565
}
566566
}
567567

568-
private void fillRouterOperation(RouterFunctionData routerFunctionData, org.springdoc.core.models.RouterOperation routerOperation) {
568+
private void fillRouterOperation(RouterFunctionData routerFunctionData, RouterOperation routerOperation) {
569569
if (ArrayUtils.isEmpty(routerOperation.getConsumes()))
570570
routerOperation.setConsumes(routerFunctionData.getConsumes());
571571
if (ArrayUtils.isEmpty(routerOperation.getProduces()))

springdoc-openapi-common/src/main/java/org/springdoc/core/converters/MonetaryAmount.java renamed to springdoc-openapi-common/src/main/java/org/springdoc/core/converters/models/MonetaryAmount.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
*/
2020

21-
package org.springdoc.core.converters;
21+
package org.springdoc.core.converters.models;
2222

2323
import java.math.BigDecimal;
2424

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/converters/Pageable.java renamed to springdoc-openapi-common/src/main/java/org/springdoc/core/converters/models/Pageable.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
*/
2020

21-
package org.springdoc.data.rest.converters;
21+
package org.springdoc.core.converters.models;
2222

2323
import java.util.List;
2424
import java.util.Objects;
+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
*/
2020

21-
package org.springdoc.data.rest.converters;
21+
package org.springdoc.core.converters.models;
2222

2323
import java.lang.annotation.ElementType;
2424
import java.lang.annotation.Retention;
+1-3
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@
1818
*
1919
*/
2020

21-
package org.springdoc.core.visitor;
21+
package org.springdoc.core.fn;
2222

2323
import java.util.ArrayList;
2424
import java.util.List;
2525
import java.util.Set;
2626

27-
import org.springdoc.core.models.RouterFunctionData;
28-
2927
import org.springframework.http.HttpHeaders;
3028
import org.springframework.http.HttpMethod;
3129

springdoc-openapi-common/src/main/java/org/springdoc/core/models/RouterFunctionData.java renamed to springdoc-openapi-common/src/main/java/org/springdoc/core/fn/RouterFunctionData.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
*/
2020

21-
package org.springdoc.core.models;
21+
package org.springdoc.core.fn;
2222

2323
import java.util.ArrayList;
2424
import java.util.HashMap;

springdoc-openapi-common/src/main/java/org/springdoc/core/models/RouterOperation.java renamed to springdoc-openapi-common/src/main/java/org/springdoc/core/fn/RouterOperation.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
*/
2020

21-
package org.springdoc.core.models;
21+
package org.springdoc.core.fn;
2222

2323
import java.util.Map;
2424

springdoc-openapi-data-rest/pom.xml

+5-15
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,32 @@
99

1010
<artifactId>springdoc-openapi-data-rest</artifactId>
1111
<dependencies>
12-
<!-- springdoc-common -->
12+
<!-- springdoc-openapi-hateoas -->
1313
<dependency>
1414
<groupId>org.springdoc</groupId>
15-
<artifactId>springdoc-openapi-common</artifactId>
15+
<artifactId>springdoc-openapi-hateoas</artifactId>
1616
<version>${project.version}</version>
1717
</dependency>
1818
<dependency>
1919
<groupId>org.springframework.data</groupId>
2020
<artifactId>spring-data-rest-core</artifactId>
2121
</dependency>
22-
<dependency>
23-
<groupId>org.hibernate.validator</groupId>
24-
<artifactId>hibernate-validator</artifactId>
25-
<scope>test</scope>
26-
</dependency>
2722
<dependency>
2823
<groupId>com.querydsl</groupId>
2924
<artifactId>querydsl-core</artifactId>
3025
<optional>true</optional>
3126
</dependency>
3227
<dependency>
33-
<groupId>jakarta.annotation</groupId>
34-
<artifactId>jakarta.annotation-api</artifactId>
35-
<optional>true</optional>
28+
<groupId>org.hibernate.validator</groupId>
29+
<artifactId>hibernate-validator</artifactId>
30+
<scope>test</scope>
3631
</dependency>
3732
<dependency>
3833
<groupId>org.springdoc</groupId>
3934
<artifactId>springdoc-openapi-webmvc-core</artifactId>
4035
<version>${project.version}</version>
4136
<scope>test</scope>
4237
</dependency>
43-
<dependency>
44-
<groupId>org.springframework.boot</groupId>
45-
<artifactId>spring-boot-starter-hateoas</artifactId>
46-
<scope>test</scope>
47-
</dependency>
4838
<dependency>
4939
<groupId>org.springframework.boot</groupId>
5040
<artifactId>spring-boot-starter-data-rest</artifactId>

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,21 @@
2525
import javax.annotation.PostConstruct;
2626

2727
import io.swagger.v3.core.util.Json;
28+
import org.springdoc.hateoas.HateoasHalProvider;
2829

2930
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
3031
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
3132

32-
public class HalProvider {
33+
public class DataRestHalProvider extends HateoasHalProvider {
3334

3435
private Optional<RepositoryRestConfiguration> repositoryRestConfigurationOptional;
3536

36-
public HalProvider(Optional<RepositoryRestConfiguration> repositoryRestConfigurationOptional) {
37+
public DataRestHalProvider(Optional<RepositoryRestConfiguration> repositoryRestConfigurationOptional) {
3738
this.repositoryRestConfigurationOptional = repositoryRestConfigurationOptional;
3839
}
3940

4041
@PostConstruct
41-
private void init() {
42+
protected void init() {
4243
if (!isHalEnabled())
4344
return;
4445
if (!Jackson2HalModule.isAlreadyRegisteredIn(Json.mapper()))

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

+5-52
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,8 @@
2222

2323
import java.util.Optional;
2424

25-
import com.fasterxml.jackson.core.JsonGenerator;
26-
import com.fasterxml.jackson.databind.SerializerProvider;
2725
import com.querydsl.core.types.Predicate;
28-
import io.swagger.v3.core.converter.AnnotatedType;
29-
import io.swagger.v3.core.converter.ModelConverters;
30-
import io.swagger.v3.core.converter.ResolvedSchema;
31-
import io.swagger.v3.core.util.AnnotationsUtils;
32-
import io.swagger.v3.core.util.Json;
33-
import io.swagger.v3.oas.models.media.MapSchema;
34-
import io.swagger.v3.oas.models.media.ObjectSchema;
35-
import io.swagger.v3.oas.models.media.StringSchema;
36-
import org.springdoc.core.customizers.OpenApiCustomiser;
37-
import org.springdoc.data.rest.converters.CollectionModelContentConverter;
38-
import org.springdoc.data.rest.converters.Pageable;
39-
import org.springdoc.data.rest.converters.RepresentationModelLinksOASMixin;
26+
import org.springdoc.core.converters.models.Pageable;
4027
import org.springdoc.data.rest.customisers.QuerydslPredicateOperationCustomizer;
4128

4229
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -45,12 +32,9 @@
4532
import org.springframework.context.annotation.Bean;
4633
import org.springframework.context.annotation.Configuration;
4734
import org.springframework.context.annotation.Lazy;
35+
import org.springframework.context.annotation.Primary;
4836
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
4937
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
50-
import org.springframework.hateoas.Link;
51-
import org.springframework.hateoas.Links;
52-
import org.springframework.hateoas.RepresentationModel;
53-
import org.springframework.hateoas.server.LinkRelationProvider;
5438

5539
import static org.springdoc.core.Constants.SPRINGDOC_ENABLED;
5640
import static org.springdoc.core.SpringDocUtils.getConfig;
@@ -66,43 +50,12 @@ public class SpringDocDataRestConfiguration {
6650

6751
@Bean
6852
@ConditionalOnMissingBean
53+
@Primary
6954
@Lazy(false)
70-
HalProvider halProvider(Optional<RepositoryRestConfiguration> repositoryRestConfiguration) {
71-
return new HalProvider(repositoryRestConfiguration);
55+
DataRestHalProvider halProvider(Optional<RepositoryRestConfiguration> repositoryRestConfiguration) {
56+
return new DataRestHalProvider(repositoryRestConfiguration);
7257
}
7358

74-
@Bean
75-
@ConditionalOnMissingBean
76-
@Lazy(false)
77-
CollectionModelContentConverter collectionModelContentConverter(HalProvider halProvider, LinkRelationProvider linkRelationProvider) {
78-
return halProvider.isHalEnabled() ? new CollectionModelContentConverter(linkRelationProvider) : null;
79-
}
80-
81-
/**
82-
* Registers an OpenApiCustomiser and a jackson mixin to ensure the definition of `Links` matches the serialized
83-
* output. This is done because the customer serializer converts the data to a map before serializing it.
84-
*
85-
* @see org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)
86-
*/
87-
@Bean
88-
@ConditionalOnMissingBean
89-
@Lazy(false)
90-
OpenApiCustomiser linksSchemaCustomiser(HalProvider halProvider) {
91-
if (!halProvider.isHalEnabled()) {
92-
return openApi -> {
93-
};
94-
}
95-
Json.mapper().addMixIn(RepresentationModel.class, RepresentationModelLinksOASMixin.class);
96-
97-
ResolvedSchema resolvedLinkSchema = ModelConverters.getInstance()
98-
.resolveAsResolvedSchema(new AnnotatedType(Link.class));
99-
100-
return openApi -> openApi
101-
.schema("Link", resolvedLinkSchema.schema)
102-
.schema("Links", new MapSchema()
103-
.additionalProperties(new StringSchema())
104-
.additionalProperties(new ObjectSchema().$ref(AnnotationsUtils.COMPONENTS_REF +"Link")));
105-
}
10659

10760
@ConditionalOnClass(value = { QuerydslBindingsFactory.class })
10861
class QuerydslProvider {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
package test.org.springdoc.api.app3;
2020

21-
import org.springdoc.data.rest.converters.PageableAsQueryParam;
21+
import org.springdoc.core.converters.models.PageableAsQueryParam;
2222

2323
import org.springframework.http.MediaType;
2424
import org.springframework.web.bind.annotation.GetMapping;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 test.org.springdoc.api.app9;
20+
21+
import java.lang.reflect.Field;
22+
import java.util.HashMap;
23+
import java.util.Iterator;
24+
import java.util.Map;
25+
26+
import com.fasterxml.jackson.databind.ObjectMapper;
27+
import com.fasterxml.jackson.databind.introspect.SimpleMixInResolver;
28+
import com.fasterxml.jackson.databind.type.ClassKey;
29+
import io.swagger.v3.core.util.Json;
30+
import org.apache.commons.lang3.reflect.FieldUtils;
31+
import org.junit.jupiter.api.AfterEach;
32+
import org.junit.jupiter.api.BeforeEach;
33+
import test.org.springdoc.api.AbstractSpringDocTest;
34+
35+
import org.springframework.boot.autoconfigure.SpringBootApplication;
36+
import org.springframework.test.context.TestPropertySource;
37+
38+
@TestPropertySource(properties = "spring.hateoas.use-hal-as-default-json-media-type= false")
39+
public class SpringDocApp9Test extends AbstractSpringDocTest {
40+
41+
@SpringBootApplication
42+
static class SpringDocTestApp {}
43+
44+
private Map<ClassKey, Class<?>> springMixins = new HashMap<>();
45+
46+
@BeforeEach
47+
void init() throws IllegalAccessException {
48+
Field convertersField2 = FieldUtils.getDeclaredField(ObjectMapper.class, "_mixIns", true);
49+
SimpleMixInResolver _mixIns = (SimpleMixInResolver) convertersField2.get(Json.mapper());
50+
Field convertersField3 = FieldUtils.getDeclaredField(SimpleMixInResolver.class, "_localMixIns", true);
51+
Map<ClassKey, Class<?>> _localMixIns = (Map<ClassKey, Class<?>>) convertersField3.get(_mixIns);
52+
Iterator<Map.Entry<ClassKey, Class<?>>> it = _localMixIns.entrySet().iterator();
53+
while (it.hasNext()) {
54+
Map.Entry<ClassKey, Class<?>> entry = it.next();
55+
if (entry.getKey().toString().startsWith("org.springframework")) {
56+
springMixins.put(entry.getKey(), entry.getValue());
57+
it.remove();
58+
}
59+
}
60+
61+
}
62+
63+
@AfterEach
64+
private void clean() throws IllegalAccessException {
65+
Field convertersField2 = FieldUtils.getDeclaredField(ObjectMapper.class, "_mixIns", true);
66+
SimpleMixInResolver _mixIns = (SimpleMixInResolver) convertersField2.get(Json.mapper());
67+
Field convertersField3 = FieldUtils.getDeclaredField(SimpleMixInResolver.class, "_localMixIns", true);
68+
Map<ClassKey, Class<?>> _localMixIns = (Map<ClassKey, Class<?>>) convertersField3.get(_mixIns);
69+
_localMixIns.putAll(springMixins);
70+
}
71+
72+
}

0 commit comments

Comments
 (0)