Skip to content

Commit 2e9756a

Browse files
author
bnasslahsen
committed
Merge branch 'devnied-actuator-description'
2 parents afe890a + 98213a9 commit 2e9756a

File tree

7 files changed

+136
-51
lines changed

7 files changed

+136
-51
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public final class Constants {
2525
public static final String SPRINGDOC_PACKAGES_TO_SCAN = "${springdoc.packages-to-scan:#{null}}";
2626
public static final String SPRINGDOC_PATHS_TO_MATCH = "${springdoc.paths-to-match:#{null}}";
2727
public static final String SPRINGDOC_ACTUATOR_TAG = "Actuator";
28+
public static final String SPRINGDOC_ACTUATOR_DESCRIPTION = "Monitor and interact";
29+
public static final String SPRINGDOC_ACTUATOR_DOC_URL = "https://docs.spring.io/spring-boot/docs/current/actuator-api/html/";
30+
public static final String SPRINGDOC_ACTUATOR_DOC_DESCRIPTION = "Spring Boot Actuator Web API Documentation";
2831
public static final String DEFAULT_WEB_JARS_PREFIX_URL = "/webjars";
2932
public static final String WEB_JARS_PREFIX_URL = "${springdoc.webjars.prefix:#{T(org.springdoc.core.Constants).DEFAULT_WEB_JARS_PREFIX_URL}}";
3033
public static final String SWAGGER_UI_URL = "/swagger-ui/index.html";

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

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,20 @@
2727
import org.springframework.web.bind.annotation.RestController;
2828
import org.springframework.web.method.HandlerMethod;
2929

30-
import java.util.*;
30+
import java.util.ArrayList;
31+
import java.util.HashMap;
32+
import java.util.HashSet;
33+
import java.util.List;
34+
import java.util.Locale;
35+
import java.util.Map;
36+
import java.util.Optional;
37+
import java.util.Set;
3138
import java.util.stream.Collectors;
3239
import java.util.stream.Stream;
3340

34-
import static org.springdoc.core.Constants.*;
41+
import static org.springdoc.core.Constants.DEFAULT_SERVER_DESCRIPTION;
42+
import static org.springdoc.core.Constants.DEFAULT_TITLE;
43+
import static org.springdoc.core.Constants.DEFAULT_VERSION;
3544

3645
public class OpenAPIBuilder {
3746

@@ -40,7 +49,7 @@ public class OpenAPIBuilder {
4049
private boolean isServersPresent = false;
4150
private final ApplicationContext context;
4251
private final SecurityParser securityParser;
43-
private final Map<HandlerMethod, String> springdocTags = new HashMap<>();
52+
private final Map<HandlerMethod, io.swagger.v3.oas.models.tags.Tag> springdocTags = new HashMap<>();
4453
private String serverBaseUrl;
4554
private final Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider;
4655

@@ -130,8 +139,13 @@ public Operation buildTags(HandlerMethod handlerMethod, Operation operation, Ope
130139
allTags.addAll(classTags);
131140
}
132141

133-
if (springdocTags.containsKey(handlerMethod))
134-
tagsStr.add(springdocTags.get(handlerMethod));
142+
if (springdocTags.containsKey(handlerMethod)) {
143+
io.swagger.v3.oas.models.tags.Tag tag = springdocTags.get(handlerMethod);
144+
tagsStr.add(tag.getName());
145+
if(openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
146+
openAPI.addTagsItem(tag);
147+
}
148+
}
135149

136150
Optional<Set<io.swagger.v3.oas.models.tags.Tag>> tags = AnnotationsUtils
137151
.getTags(allTags.toArray(new Tag[0]), true);
@@ -300,8 +314,8 @@ private List<io.swagger.v3.oas.annotations.security.SecurityScheme> getSecurityS
300314
return apiSecurityScheme;
301315
}
302316

303-
public void addTag(Set<HandlerMethod> handlerMethods, String tagName) {
304-
handlerMethods.forEach(handlerMethod -> springdocTags.put(handlerMethod, tagName));
317+
public void addTag(Set<HandlerMethod> handlerMethods, io.swagger.v3.oas.models.tags.Tag tag) {
318+
handlerMethods.forEach(handlerMethod -> springdocTags.put(handlerMethod, tag));
305319
}
306320

307321
public Map<String, Object> getRestControllersMap() {
Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package org.springdoc.api;
22

3+
import io.swagger.v3.oas.models.ExternalDocumentation;
4+
import io.swagger.v3.oas.models.tags.Tag;
5+
import org.springdoc.core.Constants;
36
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
7+
import org.springframework.util.AntPathMatcher;
8+
import org.springframework.web.method.HandlerMethod;
9+
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
10+
11+
import java.util.Map;
12+
413

514
public class ActuatorProvider {
615

@@ -10,8 +19,24 @@ public ActuatorProvider(WebMvcEndpointHandlerMapping webMvcEndpointHandlerMappin
1019
this.webMvcEndpointHandlerMapping = webMvcEndpointHandlerMapping;
1120
}
1221

13-
public WebMvcEndpointHandlerMapping getWebMvcHandlerMapping() {
14-
return webMvcEndpointHandlerMapping;
22+
public Map<RequestMappingInfo, HandlerMethod> getMethods() {
23+
return webMvcEndpointHandlerMapping.getHandlerMethods();
24+
}
25+
26+
public Tag getTag() {
27+
Tag actuatorTag = new Tag();
28+
actuatorTag.setName(Constants.SPRINGDOC_ACTUATOR_TAG);
29+
actuatorTag.setDescription(Constants.SPRINGDOC_ACTUATOR_DESCRIPTION);
30+
actuatorTag.setExternalDocs(
31+
new ExternalDocumentation()
32+
.url(Constants.SPRINGDOC_ACTUATOR_DOC_URL)
33+
.description(Constants.SPRINGDOC_ACTUATOR_DOC_DESCRIPTION)
34+
);
35+
return actuatorTag;
36+
}
37+
38+
public boolean isRestController(Map<String, Object> restControllers, HandlerMethod handlerMethod, String operationPath) {
39+
return operationPath.startsWith(AntPathMatcher.DEFAULT_PATH_SEPARATOR);
1540
}
1641

1742
}

springdoc-openapi-webmvc-core/src/main/java/org/springdoc/api/MultipleOpenApiResource.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
44
import io.swagger.v3.oas.annotations.Operation;
5-
import org.springdoc.core.*;
5+
import org.springdoc.core.AbstractRequestBuilder;
6+
import org.springdoc.core.AbstractResponseBuilder;
7+
import org.springdoc.core.GroupedOpenApi;
8+
import org.springdoc.core.OpenAPIBuilder;
9+
import org.springdoc.core.OperationBuilder;
610
import org.springframework.beans.factory.InitializingBean;
711
import org.springframework.beans.factory.ObjectFactory;
812
import org.springframework.beans.factory.annotation.Value;
@@ -18,7 +22,10 @@
1822
import java.util.Optional;
1923
import java.util.stream.Collectors;
2024

21-
import static org.springdoc.core.Constants.*;
25+
import static org.springdoc.core.Constants.API_DOCS_URL;
26+
import static org.springdoc.core.Constants.APPLICATION_OPENAPI_YAML;
27+
import static org.springdoc.core.Constants.DEFAULT_API_DOCS_URL_YAML;
28+
import static org.springdoc.core.Constants.SPRINGDOC_CACHE_DISABLED_VALUE;
2229
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
2330

2431
@RestController
@@ -32,8 +39,6 @@ public class MultipleOpenApiResource implements InitializingBean {
3239
private final RequestMappingInfoHandlerMapping requestMappingHandlerMapping;
3340
private final Optional<ActuatorProvider> servletContextProvider;
3441
private Map<String, OpenApiResource> groupedOpenApiResources;
35-
@Value(SPRINGDOC_SHOW_ACTUATOR_VALUE)
36-
private boolean showActuator;
3742
@Value(SPRINGDOC_CACHE_DISABLED_VALUE)
3843
private boolean cacheDisabled;
3944

@@ -63,7 +68,6 @@ public void afterPropertiesSet() throws Exception {
6368
requestMappingHandlerMapping,
6469
servletContextProvider,
6570
Optional.of(item.getOpenApiCustomisers()), item.getPathsToMatch(), item.getPackagesToScan(),
66-
showActuator,
6771
cacheDisabled
6872
)
6973
));
@@ -93,4 +97,4 @@ private OpenApiResource getOpenApiResourceOrThrow(String group) {
9397
}
9498
return openApiResource;
9599
}
96-
}
100+
}

springdoc-openapi-webmvc-core/src/main/java/org/springdoc/api/OpenApiResource.java

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
import io.swagger.v3.oas.annotations.Hidden;
99
import io.swagger.v3.oas.annotations.Operation;
1010
import io.swagger.v3.oas.models.OpenAPI;
11-
import org.springdoc.core.*;
11+
import org.springdoc.core.AbstractRequestBuilder;
12+
import org.springdoc.core.AbstractResponseBuilder;
13+
import org.springdoc.core.OpenAPIBuilder;
14+
import org.springdoc.core.OperationBuilder;
15+
import org.springdoc.core.SecurityOAuth2Provider;
1216
import org.springdoc.core.customizers.OpenApiCustomiser;
1317
import org.springframework.beans.factory.annotation.Value;
1418
import org.springframework.core.annotation.AnnotationUtils;
@@ -23,9 +27,16 @@
2327
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
2428

2529
import javax.servlet.http.HttpServletRequest;
26-
import java.util.*;
27-
28-
import static org.springdoc.core.Constants.*;
30+
import java.util.HashSet;
31+
import java.util.LinkedHashMap;
32+
import java.util.List;
33+
import java.util.Map;
34+
import java.util.Optional;
35+
import java.util.Set;
36+
37+
import static org.springdoc.core.Constants.API_DOCS_URL;
38+
import static org.springdoc.core.Constants.APPLICATION_OPENAPI_YAML;
39+
import static org.springdoc.core.Constants.DEFAULT_API_DOCS_URL_YAML;
2940
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
3041

3142
@RestController
@@ -35,9 +46,6 @@ public class OpenApiResource extends AbstractOpenApiResource {
3546

3647
private final Optional<ActuatorProvider> servletContextProvider;
3748

38-
@Value(SPRINGDOC_SHOW_ACTUATOR_VALUE)
39-
private boolean showActuator;
40-
4149
public OpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder,
4250
AbstractResponseBuilder responseBuilder, OperationBuilder operationParser,
4351
RequestMappingInfoHandlerMapping requestMappingHandlerMapping, Optional<ActuatorProvider> servletContextProvider,
@@ -51,11 +59,10 @@ public OpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder req
5159
AbstractResponseBuilder responseBuilder, OperationBuilder operationParser,
5260
RequestMappingInfoHandlerMapping requestMappingHandlerMapping, Optional<ActuatorProvider> servletContextProvider,
5361
Optional<List<OpenApiCustomiser>> openApiCustomisers, List<String> pathsToMatch, List<String> packagesToScan,
54-
boolean showActuator,boolean cacheDisabled) {
55-
super(openAPIBuilder, requestBuilder, responseBuilder, operationParser, openApiCustomisers, pathsToMatch, packagesToScan,cacheDisabled);
62+
boolean cacheDisabled) {
63+
super(openAPIBuilder, requestBuilder, responseBuilder, operationParser, openApiCustomisers, pathsToMatch, packagesToScan, cacheDisabled);
5664
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
5765
this.servletContextProvider = servletContextProvider;
58-
this.showActuator = showActuator;
5966
}
6067

6168
@Operation(hidden = true)
@@ -79,23 +86,23 @@ public String openapiYaml(HttpServletRequest request, @Value(DEFAULT_API_DOCS_UR
7986
@Override
8087
protected void getPaths(Map<String, Object> restControllers) {
8188
Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
82-
calculatePath(restControllers, map);
83-
if (showActuator && servletContextProvider.isPresent()) {
84-
map = servletContextProvider.get().getWebMvcHandlerMapping().getHandlerMethods();
85-
Set<HandlerMethod> handlerMethods = new HashSet<>(map.values());
86-
this.openAPIBuilder.addTag(handlerMethods, SPRINGDOC_ACTUATOR_TAG);
87-
calculatePath(restControllers, map);
89+
calculatePath(restControllers, map, Optional.empty());
90+
91+
if (servletContextProvider.isPresent()){
92+
map = servletContextProvider.get().getMethods();
93+
this.openAPIBuilder.addTag(new HashSet<>(map.values()), servletContextProvider.get().getTag());
94+
calculatePath(restControllers, map, servletContextProvider);
8895
}
8996
Optional<SecurityOAuth2Provider> securityOAuth2ProviderOptional = openAPIBuilder.getSpringSecurityOAuth2Provider();
9097
if (securityOAuth2ProviderOptional.isPresent()) {
9198
SecurityOAuth2Provider securityOAuth2Provider = securityOAuth2ProviderOptional.get();
9299
Map<RequestMappingInfo, HandlerMethod> mapOauth = securityOAuth2Provider.getHandlerMethods();
93100
Map<String, Object> requestMappingMapSec = securityOAuth2Provider.getFrameworkEndpoints();
94-
calculatePath(requestMappingMapSec, mapOauth);
101+
calculatePath(requestMappingMapSec, mapOauth, Optional.empty());
95102
}
96103
}
97104

98-
private void calculatePath(Map<String, Object> restControllers, Map<RequestMappingInfo, HandlerMethod> map) {
105+
private void calculatePath(Map<String, Object> restControllers, Map<RequestMappingInfo, HandlerMethod> map, Optional<ActuatorProvider> actuatorProvider) {
99106
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
100107
RequestMappingInfo requestMappingInfo = entry.getKey();
101108
HandlerMethod handlerMethod = entry.getValue();
@@ -104,7 +111,10 @@ private void calculatePath(Map<String, Object> restControllers, Map<RequestMappi
104111
Map<String, String> regexMap = new LinkedHashMap<>();
105112
for (String pattern : patterns) {
106113
String operationPath = PathUtils.parsePath(pattern, regexMap);
107-
if (isRestController(restControllers, handlerMethod, operationPath) && isPackageToScan(handlerMethod.getBeanType().getPackage().getName()) && isPathToMatch(operationPath)) {
114+
if ( ((actuatorProvider.isPresent() && actuatorProvider.get().isRestController(restControllers, handlerMethod, operationPath))
115+
|| isRestController(restControllers, handlerMethod, operationPath))
116+
&& isPackageToScan(handlerMethod.getBeanType().getPackage().getName())
117+
&& isPathToMatch(operationPath)) {
108118
Set<RequestMethod> requestMethods = requestMappingInfo.getMethodsCondition().getMethods();
109119
calculatePath(openAPIBuilder, handlerMethod, operationPath, requestMethods);
110120
}
@@ -114,21 +124,13 @@ private void calculatePath(Map<String, Object> restControllers, Map<RequestMappi
114124

115125
private boolean isRestController(Map<String, Object> restControllers, HandlerMethod handlerMethod,
116126
String operationPath) {
117-
boolean result;
118-
if (showActuator)
119-
result = operationPath.startsWith(DEFAULT_PATH_SEPARATOR);
120-
else {
121-
ResponseBody responseBodyAnnotation = ReflectionUtils.getAnnotation(handlerMethod.getBeanType(),
122-
ResponseBody.class);
123-
124-
if (responseBodyAnnotation == null)
125-
responseBodyAnnotation = ReflectionUtils.getAnnotation(handlerMethod.getMethod(), ResponseBody.class);
126-
result = operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
127-
&& (restControllers.containsKey(handlerMethod.getBean().toString())
128-
|| (responseBodyAnnotation != null && AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Hidden.class) == null));
129-
}
127+
ResponseBody responseBodyAnnotation = ReflectionUtils.getAnnotation(handlerMethod.getBeanType(), ResponseBody.class);
130128

131-
return result;
129+
if (responseBodyAnnotation == null)
130+
responseBodyAnnotation = ReflectionUtils.getAnnotation(handlerMethod.getMethod(), ResponseBody.class);
131+
return operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
132+
&& (restControllers.containsKey(handlerMethod.getBean().toString())
133+
|| (responseBodyAnnotation != null && AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Hidden.class) == null));
132134
}
133135

134136
private void calculateServerUrl(HttpServletRequest request, String apiDocsUrl) {

springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/SpringDocWebMvcConfiguration.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.springdoc.core;
22

3+
import io.swagger.v3.oas.models.Operation;
34
import org.springdoc.api.ActuatorProvider;
45
import org.springdoc.api.OpenApiResource;
56
import org.springdoc.core.customizers.OpenApiCustomiser;
@@ -13,6 +14,7 @@
1314
import org.springframework.context.annotation.Bean;
1415
import org.springframework.context.annotation.Configuration;
1516
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
17+
import org.springframework.web.method.HandlerMethod;
1618
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
1719

1820
import java.util.List;
@@ -67,6 +69,22 @@ public ActuatorProvider actuatorProvider(WebMvcEndpointHandlerMapping webMvcEndp
6769
return new ActuatorProvider(webMvcEndpointHandlerMapping);
6870
}
6971

70-
}
72+
@Bean
73+
public OperationCustomizer ActuatorCustomizer(ActuatorProvider actuatorProvider){
74+
return new OperationCustomizer() {
75+
76+
private int methodCount;
7177

78+
@Override
79+
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
80+
if(operation.getTags() != null && operation.getTags().contains(actuatorProvider.getTag().getName())) {
81+
operation.setSummary(handlerMethod.toString());
82+
operation.setOperationId(operation.getOperationId()+"_"+methodCount++);
83+
}
84+
return operation;
85+
}
86+
};
87+
}
88+
89+
}
7290
}

springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app68/SpringDocApp68Test.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package test.org.springdoc.api.app68;
22

3+
import org.hamcrest.Matchers;
34
import org.junit.Test;
45
import org.junit.runner.RunWith;
56
import org.springdoc.core.Constants;
@@ -10,10 +11,14 @@
1011
import org.springframework.test.context.junit4.SpringRunner;
1112
import org.springframework.test.web.servlet.MockMvc;
1213

14+
import static org.hamcrest.Matchers.contains;
1315
import static org.hamcrest.Matchers.containsString;
16+
import static org.hamcrest.Matchers.hasSize;
1417
import static org.hamcrest.Matchers.is;
1518
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
16-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
19+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
20+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
21+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
1722
import static test.org.springdoc.api.app68.FileUtils.getContent;
1823

1924
@RunWith(SpringRunner.class)
@@ -62,6 +67,20 @@ public void testApp4() throws Exception {
6267
@Test
6368
public void testActuator() throws Exception {
6469
mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)).andExpect(status().isOk())
65-
.andExpect(jsonPath("$.openapi", is("3.0.1"))).andExpect(jsonPath("$.paths./actuator/info.get.operationId", containsString("handle"))).andExpect(jsonPath("$.paths./actuator/health.get.operationId", containsString("handle")));
70+
.andExpect(jsonPath("$.openapi", is("3.0.1")))
71+
.andExpect(jsonPath("$.paths./actuator/info.get.operationId", containsString("handle_")))
72+
.andExpect(jsonPath("$.paths./actuator/info.get.summary", Matchers.is("Actuator web endpoint 'info'")))
73+
.andExpect(jsonPath("$.paths./actuator/health.get.operationId", containsString("handle_")));
74+
}
75+
76+
@Test
77+
public void testActuatorDescription() throws Exception {
78+
mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)).andExpect(status().isOk())
79+
.andExpect(jsonPath("$.openapi", is("3.0.1")))
80+
.andExpect(jsonPath("$.tags", hasSize(4)))
81+
.andExpect(jsonPath("$.tags[?(@.name == '"+Constants.SPRINGDOC_ACTUATOR_TAG+"')].name", contains(Constants.SPRINGDOC_ACTUATOR_TAG)))
82+
.andExpect(jsonPath("$.tags[?(@.name == '"+Constants.SPRINGDOC_ACTUATOR_TAG+"')].description", contains(Constants.SPRINGDOC_ACTUATOR_DESCRIPTION)))
83+
.andExpect(jsonPath("$.tags[?(@.name == '"+Constants.SPRINGDOC_ACTUATOR_TAG+"')].externalDocs.description", contains(Constants.SPRINGDOC_ACTUATOR_DOC_DESCRIPTION)))
84+
.andExpect(jsonPath("$.tags[?(@.name == '"+Constants.SPRINGDOC_ACTUATOR_TAG+"')].externalDocs.url", contains(Constants.SPRINGDOC_ACTUATOR_DOC_URL)));
6685
}
6786
}

0 commit comments

Comments
 (0)