Skip to content

Commit 2d16de0

Browse files
author
bnasslahsen
committed
Merging PR#451 with master: add OpenApiBuilderCustomiser
2 parents 80e3347 + 83e2321 commit 2d16de0

File tree

6 files changed

+253
-17
lines changed

6 files changed

+253
-17
lines changed

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

+2-6
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,7 @@ protected synchronized OpenAPI getOpenApi() {
114114
if (!computeDone || springDocConfigProperties.getCache().isDisabled()) {
115115
Instant start = Instant.now();
116116
openAPIBuilder.build();
117-
Map<String, Object> restControllersMap = openAPIBuilder.getRestControllersMap();
118-
Map<String, Object> requestMappingMap = openAPIBuilder.getRequestMappingMap();
119-
Map<String, Object> controllerMap = openAPIBuilder.getControllersMap();
120-
Map<String, Object> restControllers = Stream.of(restControllersMap, requestMappingMap, controllerMap)
121-
.flatMap(mapEl -> mapEl.entrySet().stream())
117+
Map<String, Object> mappingsMap = openAPIBuilder.getMappingsMap().entrySet().stream()
122118
.filter(controller -> (AnnotationUtils.findAnnotation(controller.getValue().getClass(),
123119
Hidden.class) == null))
124120
.filter(controller -> !isHiddenRestControllers(controller.getValue().getClass()))
@@ -128,7 +124,7 @@ protected synchronized OpenAPI getOpenApi() {
128124
// calculate generic responses
129125
responseBuilder.buildGenericResponse(openAPIBuilder.getComponents(), findControllerAdvice);
130126

131-
getPaths(restControllers);
127+
getPaths(mappingsMap);
132128
openApi = openAPIBuilder.getCalculatedOpenAPI();
133129

134130
// run the optional customisers

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

+19-9
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.slf4j.Logger;
5050
import org.slf4j.LoggerFactory;
5151

52+
import org.springdoc.core.customizers.OpenApiBuilderCustomiser;
5253
import org.springframework.beans.factory.config.BeanDefinition;
5354
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
5455
import org.springframework.context.ApplicationContext;
@@ -81,18 +82,24 @@ public class OpenAPIBuilder {
8182

8283
private final SecurityParser securityParser;
8384

85+
private final Map<String, Object> mappingsMap = new HashMap<>();
86+
8487
private final Map<HandlerMethod, io.swagger.v3.oas.models.tags.Tag> springdocTags = new HashMap<>();
8588

8689
private final Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider;
8790

91+
private final List<OpenApiBuilderCustomiser> openApiBuilderCustomisers;
92+
8893
private boolean isServersPresent;
8994

9095
private String serverBaseUrl;
9196

9297
private final SpringDocConfigProperties springDocConfigProperties;
9398

9499
@SuppressWarnings("WeakerAccess")
95-
OpenAPIBuilder(Optional<OpenAPI> openAPI, ApplicationContext context, SecurityParser securityParser, Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider, SpringDocConfigProperties springDocConfigProperties) {
100+
OpenAPIBuilder(Optional<OpenAPI> openAPI, ApplicationContext context, SecurityParser securityParser,
101+
Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider, SpringDocConfigProperties springDocConfigProperties,
102+
List<OpenApiBuilderCustomiser> openApiBuilderCustomisers) {
96103
if (openAPI.isPresent()) {
97104
this.openAPI = openAPI.get();
98105
if (this.openAPI.getComponents() == null)
@@ -106,6 +113,7 @@ public class OpenAPIBuilder {
106113
this.securityParser = securityParser;
107114
this.springSecurityOAuth2Provider = springSecurityOAuth2Provider;
108115
this.springDocConfigProperties = springDocConfigProperties;
116+
this.openApiBuilderCustomisers = openApiBuilderCustomisers;
109117
}
110118

111119
private static String splitCamelCase(String str) {
@@ -146,12 +154,18 @@ else if (calculatedOpenAPI.getInfo() == null) {
146154
Info infos = new Info().title(DEFAULT_TITLE).version(DEFAULT_VERSION);
147155
calculatedOpenAPI.setInfo(infos);
148156
}
157+
// Set default mappings
158+
this.mappingsMap.putAll(context.getBeansWithAnnotation(RestController.class));
159+
this.mappingsMap.putAll(context.getBeansWithAnnotation(RequestMapping.class));
160+
this.mappingsMap.putAll(context.getBeansWithAnnotation(Controller.class));
161+
149162
// default server value
150163
if (CollectionUtils.isEmpty(calculatedOpenAPI.getServers()) || !isServersPresent) {
151164
this.updateServers(calculatedOpenAPI);
152165
}
153166
// add security schemes
154167
this.calculateSecuritySchemes(calculatedOpenAPI.getComponents());
168+
Optional.ofNullable(this.openApiBuilderCustomisers).ifPresent(customisers -> customisers.forEach(customiser -> customiser.customise(this)));
155169
}
156170

157171
public void updateServers(OpenAPI openAPI) {
@@ -397,16 +411,12 @@ public void addTag(Set<HandlerMethod> handlerMethods, io.swagger.v3.oas.models.t
397411
handlerMethods.forEach(handlerMethod -> springdocTags.put(handlerMethod, tag));
398412
}
399413

400-
public Map<String, Object> getRestControllersMap() {
401-
return context.getBeansWithAnnotation(RestController.class);
402-
}
403-
404-
public Map<String, Object> getRequestMappingMap() {
405-
return context.getBeansWithAnnotation(RequestMapping.class);
414+
public Map<String, Object> getMappingsMap() {
415+
return this.mappingsMap;
406416
}
407417

408-
public Map<String, Object> getControllersMap() {
409-
return context.getBeansWithAnnotation(Controller.class);
418+
public void addMappings(Map<String, Object> mappings) {
419+
this.mappingsMap.putAll(mappings);
410420
}
411421

412422
public Map<String, Object> getControllerAdviceMap() {

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springdoc.core.converters.ModelConverterRegistrar;
3131
import org.springdoc.core.converters.PropertyCustomizingConverter;
3232
import org.springdoc.core.converters.ResponseSupportConverter;
33+
import org.springdoc.core.customizers.OpenApiBuilderCustomiser;
3334
import org.springdoc.core.customizers.PropertyCustomizer;
3435

3536
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@@ -82,8 +83,10 @@ IgnoredParameterAnnotationsDefault ignoredParameterAnnotationsDefault() {
8283
}
8384

8485
@Bean
85-
public OpenAPIBuilder openAPIBuilder(Optional<OpenAPI> openAPI, ApplicationContext context, SecurityParser securityParser, Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider,SpringDocConfigProperties springDocConfigProperties) {
86-
return new OpenAPIBuilder(openAPI, context, securityParser, springSecurityOAuth2Provider,springDocConfigProperties);
86+
public OpenAPIBuilder openAPIBuilder(Optional<OpenAPI> openAPI, ApplicationContext context, SecurityParser securityParser,
87+
Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider, SpringDocConfigProperties springDocConfigProperties,
88+
List<OpenApiBuilderCustomiser> openApiBuilderCustomisers) {
89+
return new OpenAPIBuilder(openAPI, context, securityParser, springSecurityOAuth2Provider,springDocConfigProperties, openApiBuilderCustomisers);
8790
}
8891

8992
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.customizers;
20+
21+
import org.springdoc.core.OpenAPIBuilder;
22+
23+
public interface OpenApiBuilderCustomiser {
24+
void customise(OpenAPIBuilder openApiBuilder);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package test.org.springdoc.api.app94;
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+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.Optional;
24+
25+
import io.swagger.v3.oas.annotations.Operation;
26+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
27+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
28+
import io.swagger.v3.oas.annotations.tags.Tag;
29+
import org.apache.commons.lang3.RandomStringUtils;
30+
import org.springdoc.api.ActuatorProvider;
31+
import org.springdoc.api.OpenApiResource;
32+
import org.springdoc.core.AbstractRequestBuilder;
33+
import org.springdoc.core.GenericResponseBuilder;
34+
import org.springdoc.core.OpenAPIBuilder;
35+
import org.springdoc.core.OperationBuilder;
36+
import org.springdoc.core.SpringDocConfigProperties;
37+
import org.springdoc.core.customizers.OpenApiBuilderCustomiser;
38+
import org.springdoc.core.customizers.OpenApiCustomiser;
39+
import test.org.springdoc.api.AbstractSpringDocTest;
40+
import test.org.springdoc.api.app91.Greeting;
41+
42+
import org.springframework.beans.BeansException;
43+
import org.springframework.beans.factory.annotation.Qualifier;
44+
import org.springframework.boot.autoconfigure.SpringBootApplication;
45+
import org.springframework.context.ApplicationContext;
46+
import org.springframework.context.ApplicationContextAware;
47+
import org.springframework.context.annotation.Bean;
48+
import org.springframework.http.MediaType;
49+
import org.springframework.http.ResponseEntity;
50+
import org.springframework.test.context.TestPropertySource;
51+
import org.springframework.web.bind.annotation.GetMapping;
52+
import org.springframework.web.bind.annotation.RequestMethod;
53+
import org.springframework.web.bind.annotation.ResponseBody;
54+
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
55+
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
56+
57+
import static org.springdoc.core.Constants.DEFAULT_GROUP_NAME;
58+
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
59+
60+
@TestPropertySource(properties = "springdoc.default-produces-media-type=application/json")
61+
public class SpringDocApp94Test extends AbstractSpringDocTest {
62+
63+
@SpringBootApplication
64+
static class SpringDocTestApp implements ApplicationContextAware {
65+
66+
private ApplicationContext applicationContext;
67+
68+
@Bean
69+
public GreetingController greetingController() {
70+
return new GreetingController();
71+
}
72+
73+
@Bean
74+
public OpenApiBuilderCustomiser customOpenAPI() {
75+
return openApiBuilder -> openApiBuilder.addMappings(Collections.singletonMap("greetingController", new GreetingController()));
76+
}
77+
78+
@Bean
79+
public RequestMappingHandlerMapping defaultTestHandlerMapping(GreetingController greetingController) throws NoSuchMethodException {
80+
RequestMappingHandlerMapping result = new RequestMappingHandlerMapping();
81+
RequestMappingInfo requestMappingInfo =
82+
RequestMappingInfo.paths("/test").methods(RequestMethod.GET).produces(MediaType.APPLICATION_JSON_VALUE).build();
83+
84+
result.setApplicationContext(this.applicationContext);
85+
result.registerMapping(requestMappingInfo, "greetingController", GreetingController.class.getDeclaredMethod("sayHello2"));
86+
//result.handlerme
87+
return result;
88+
}
89+
90+
@Bean(name = "mvcOpenApiResource")
91+
public OpenApiResource openApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder, GenericResponseBuilder responseBuilder,
92+
OperationBuilder operationParser,
93+
@Qualifier("defaultTestHandlerMapping") RequestMappingHandlerMapping requestMappingHandlerMapping,
94+
Optional<ActuatorProvider> servletContextProvider, SpringDocConfigProperties springDocConfigProperties,
95+
Optional<List<OpenApiCustomiser>> openApiCustomisers) {
96+
return new OpenApiResource(DEFAULT_GROUP_NAME, openAPIBuilder, requestBuilder, responseBuilder, operationParser, requestMappingHandlerMapping,
97+
servletContextProvider, openApiCustomisers, springDocConfigProperties);
98+
}
99+
100+
@Override
101+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
102+
this.applicationContext = applicationContext;
103+
}
104+
}
105+
106+
@ResponseBody
107+
@Tag(name = "Demo", description = "The Demo API")
108+
public static class GreetingController {
109+
110+
@GetMapping(produces = APPLICATION_JSON_VALUE)
111+
@Operation(summary = "This API will return a random greeting.")
112+
public ResponseEntity<Greeting> sayHello() {
113+
return ResponseEntity.ok(new Greeting(RandomStringUtils.randomAlphanumeric(10)));
114+
}
115+
116+
@GetMapping("/test")
117+
@ApiResponses(value = { @ApiResponse(responseCode = "201", description = "item created"),
118+
@ApiResponse(responseCode = "400", description = "invalid input, object invalid"),
119+
@ApiResponse(responseCode = "409", description = "an existing item already exists") })
120+
public ResponseEntity<Greeting> sayHello2() {
121+
return ResponseEntity.ok(new Greeting(RandomStringUtils.randomAlphanumeric(10)));
122+
}
123+
124+
}
125+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
"tags": [
14+
{
15+
"name": "Demo",
16+
"description": "The Demo API"
17+
}
18+
],
19+
"paths": {
20+
"/test": {
21+
"get": {
22+
"tags": [
23+
"Demo"
24+
],
25+
"operationId": "sayHello2",
26+
"responses": {
27+
"400": {
28+
"description": "invalid input, object invalid",
29+
"content": {
30+
"application/json": {
31+
"schema": {
32+
"$ref": "#/components/schemas/Greeting"
33+
}
34+
}
35+
}
36+
},
37+
"201": {
38+
"description": "item created",
39+
"content": {
40+
"application/json": {
41+
"schema": {
42+
"$ref": "#/components/schemas/Greeting"
43+
}
44+
}
45+
}
46+
},
47+
"409": {
48+
"description": "an existing item already exists",
49+
"content": {
50+
"application/json": {
51+
"schema": {
52+
"$ref": "#/components/schemas/Greeting"
53+
}
54+
}
55+
}
56+
}
57+
}
58+
}
59+
}
60+
},
61+
"components": {
62+
"schemas": {
63+
"Greeting": {
64+
"title": "Greeting",
65+
"type": "object",
66+
"properties": {
67+
"payload": {
68+
"type": "string",
69+
"description": "The greeting value",
70+
"example": "sdfsdfs"
71+
}
72+
},
73+
"description": "An object containing a greeting message"
74+
}
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)