Skip to content

Commit 79c6168

Browse files
committed
Improve actuator documentation
1 parent 583847d commit 79c6168

File tree

6 files changed

+108
-48
lines changed

6 files changed

+108
-48
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
@@ -17,6 +17,9 @@ public final class Constants {
1717
public static final String SPRINGDOC_PACKAGES_TO_SCAN = "${springdoc.packagesToScan:#{null}}";
1818
public static final String SPRINGDOC_PATHS_TO_MATCH = "${springdoc.pathsToMatch:#{null}}";
1919
public static final String SPRINGDOC_ACTUATOR_TAG = "Actuator";
20+
public static final String SPRINGDOC_ACTUATOR_DESCRIPTION = "Monitor and interact";
21+
public static final String SPRINGDOC_ACTUATOR_DOC_URL = "https://docs.spring.io/spring-boot/docs/current/actuator-api/html/";
22+
public static final String SPRINGDOC_ACTUATOR_DOC_DESCRIPTION = "Spring Boot Actuator Web API Documentation";
2023
public static final String DEFAULT_WEB_JARS_PREFIX_URL = "/webjars";
2124
public static final String WEB_JARS_PREFIX_URL = "${springdoc.webjars.prefix:#{T(org.springdoc.core.Constants).DEFAULT_WEB_JARS_PREFIX_URL}}";
2225
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,19 +27,28 @@
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

3847
private static final Logger LOGGER = LoggerFactory.getLogger(OpenAPIBuilder.class);
3948
private final OpenAPI openAPI;
4049
private final ApplicationContext context;
4150
private final SecurityParser securityParser;
42-
private final Map<HandlerMethod, String> springdocTags = new HashMap<>();
51+
private final Map<HandlerMethod, io.swagger.v3.oas.models.tags.Tag> springdocTags = new HashMap<>();
4352
private String serverBaseUrl;
4453

4554
@SuppressWarnings("WeakerAccess")
@@ -123,8 +132,13 @@ public Operation buildTags(HandlerMethod handlerMethod, Operation operation, Ope
123132
allTags.addAll(classTags);
124133
}
125134

126-
if (springdocTags.containsKey(handlerMethod))
127-
tagsStr.add(springdocTags.get(handlerMethod));
135+
if (springdocTags.containsKey(handlerMethod)) {
136+
io.swagger.v3.oas.models.tags.Tag tag = springdocTags.get(handlerMethod);
137+
tagsStr.add(tag.getName());
138+
if(openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
139+
openAPI.addTagsItem(tag);
140+
}
141+
}
128142

129143
Optional<Set<io.swagger.v3.oas.models.tags.Tag>> tags = AnnotationsUtils
130144
.getTags(allTags.toArray(new Tag[0]), true);
@@ -281,8 +295,8 @@ private List<io.swagger.v3.oas.annotations.security.SecurityScheme> getSecurityS
281295
return apiSecurityScheme;
282296
}
283297

284-
public void addTag(Set<HandlerMethod> handlerMethods, String tagName) {
285-
handlerMethods.forEach(handlerMethod -> springdocTags.put(handlerMethod, tagName));
298+
public void addTag(Set<HandlerMethod> handlerMethods, io.swagger.v3.oas.models.tags.Tag tag) {
299+
handlerMethods.forEach(handlerMethod -> springdocTags.put(handlerMethod, tag));
286300
}
287301

288302
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 & 7 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,9 @@
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;
2228
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
2329

2430
@RestController
@@ -32,8 +38,6 @@ public class MultipleOpenApiResource implements InitializingBean {
3238
private final RequestMappingInfoHandlerMapping requestMappingHandlerMapping;
3339
private final Optional<ActuatorProvider> servletContextProvider;
3440
private Map<String, OpenApiResource> groupedOpenApiResources;
35-
@Value(SPRINGDOC_SHOW_ACTUATOR_VALUE)
36-
private boolean showActuator;
3741

3842
public MultipleOpenApiResource(List<GroupedOpenApi> groupedOpenApis,
3943
ObjectFactory<OpenAPIBuilder> defaultOpenAPIBuilder, AbstractRequestBuilder requestBuilder,
@@ -60,8 +64,7 @@ public void afterPropertiesSet() throws Exception {
6064
operationParser,
6165
requestMappingHandlerMapping,
6266
servletContextProvider,
63-
Optional.of(item.getOpenApiCustomisers()), item.getPathsToMatch(), item.getPackagesToScan(),
64-
showActuator
67+
Optional.of(item.getOpenApiCustomisers()), item.getPathsToMatch(), item.getPackagesToScan()
6568
)
6669
));
6770
}
@@ -90,4 +93,4 @@ private OpenApiResource getOpenApiResourceOrThrow(String group) {
9093
}
9194
return openApiResource;
9295
}
93-
}
96+
}

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

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,16 @@
2626
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
2727

2828
import javax.servlet.http.HttpServletRequest;
29-
import java.util.*;
30-
31-
import static org.springdoc.core.Constants.*;
29+
import java.util.HashSet;
30+
import java.util.LinkedHashMap;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.Optional;
34+
import java.util.Set;
35+
36+
import static org.springdoc.core.Constants.API_DOCS_URL;
37+
import static org.springdoc.core.Constants.APPLICATION_OPENAPI_YAML;
38+
import static org.springdoc.core.Constants.DEFAULT_API_DOCS_URL_YAML;
3239
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
3340

3441
@RestController
@@ -38,9 +45,6 @@ public class OpenApiResource extends AbstractOpenApiResource {
3845

3946
private final Optional<ActuatorProvider> servletContextProvider;
4047

41-
@Value(SPRINGDOC_SHOW_ACTUATOR_VALUE)
42-
private boolean showActuator;
43-
4448
public OpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder,
4549
AbstractResponseBuilder responseBuilder, OperationBuilder operationParser,
4650
RequestMappingInfoHandlerMapping requestMappingHandlerMapping, Optional<ActuatorProvider> servletContextProvider,
@@ -53,12 +57,10 @@ public OpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder req
5357
public OpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder,
5458
AbstractResponseBuilder responseBuilder, OperationBuilder operationParser,
5559
RequestMappingInfoHandlerMapping requestMappingHandlerMapping, Optional<ActuatorProvider> servletContextProvider,
56-
Optional<List<OpenApiCustomiser>> openApiCustomisers, List<String> pathsToMatch, List<String> packagesToScan,
57-
boolean showActuator) {
60+
Optional<List<OpenApiCustomiser>> openApiCustomisers, List<String> pathsToMatch, List<String> packagesToScan) {
5861
super(openAPIBuilder, requestBuilder, responseBuilder, operationParser, openApiCustomisers, pathsToMatch, packagesToScan);
5962
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
6063
this.servletContextProvider = servletContextProvider;
61-
this.showActuator = showActuator;
6264
}
6365

6466
@Operation(hidden = true)
@@ -82,16 +84,16 @@ public String openapiYaml(HttpServletRequest request, @Value(DEFAULT_API_DOCS_UR
8284
@Override
8385
protected void getPaths(Map<String, Object> restControllers) {
8486
Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
85-
calculatePath(restControllers, map);
86-
if (showActuator && servletContextProvider.isPresent()) {
87-
map = servletContextProvider.get().getWebMvcHandlerMapping().getHandlerMethods();
88-
Set<HandlerMethod> handlerMethods = new HashSet<>(map.values());
89-
this.openAPIBuilder.addTag(handlerMethods, SPRINGDOC_ACTUATOR_TAG);
90-
calculatePath(restControllers, map);
87+
calculatePath(restControllers, map, Optional.empty());
88+
89+
if (servletContextProvider.isPresent()){
90+
map = servletContextProvider.get().getMethods();
91+
this.openAPIBuilder.addTag(new HashSet<>(map.values()), servletContextProvider.get().getTag());
92+
calculatePath(restControllers, map, servletContextProvider);
9193
}
9294
}
9395

94-
private void calculatePath(Map<String, Object> restControllers, Map<RequestMappingInfo, HandlerMethod> map) {
96+
private void calculatePath(Map<String, Object> restControllers, Map<RequestMappingInfo, HandlerMethod> map, Optional<ActuatorProvider> actuatorProvider) {
9597
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
9698
RequestMappingInfo requestMappingInfo = entry.getKey();
9799
HandlerMethod handlerMethod = entry.getValue();
@@ -100,7 +102,10 @@ private void calculatePath(Map<String, Object> restControllers, Map<RequestMappi
100102
Map<String, String> regexMap = new LinkedHashMap<>();
101103
for (String pattern : patterns) {
102104
String operationPath = PathUtils.parsePath(pattern, regexMap);
103-
if (isRestController(restControllers, handlerMethod, operationPath) && isPackageToScan(handlerMethod.getBeanType().getPackage().getName()) && isPathToMatch(operationPath)) {
105+
if ( ((actuatorProvider.isPresent() && actuatorProvider.get().isRestController(restControllers, handlerMethod, operationPath))
106+
|| isRestController(restControllers, handlerMethod, operationPath))
107+
&& isPackageToScan(handlerMethod.getBeanType().getPackage().getName())
108+
&& isPathToMatch(operationPath)) {
104109
Set<RequestMethod> requestMethods = requestMappingInfo.getMethodsCondition().getMethods();
105110
calculatePath(openAPIBuilder, handlerMethod, operationPath, requestMethods);
106111
}
@@ -110,21 +115,13 @@ private void calculatePath(Map<String, Object> restControllers, Map<RequestMappi
110115

111116
private boolean isRestController(Map<String, Object> restControllers, HandlerMethod handlerMethod,
112117
String operationPath) {
113-
boolean result;
114-
if (showActuator)
115-
result = operationPath.startsWith(DEFAULT_PATH_SEPARATOR);
116-
else {
117-
ResponseBody responseBodyAnnotation = ReflectionUtils.getAnnotation(handlerMethod.getBeanType(),
118-
ResponseBody.class);
119-
120-
if (responseBodyAnnotation == null)
121-
responseBodyAnnotation = ReflectionUtils.getAnnotation(handlerMethod.getMethod(), ResponseBody.class);
122-
result = operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
123-
&& (restControllers.containsKey(handlerMethod.getBean().toString())
124-
|| (responseBodyAnnotation != null && AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Hidden.class) == null));
125-
}
118+
ResponseBody responseBodyAnnotation = ReflectionUtils.getAnnotation(handlerMethod.getBeanType(), ResponseBody.class);
126119

127-
return result;
120+
if (responseBodyAnnotation == null)
121+
responseBodyAnnotation = ReflectionUtils.getAnnotation(handlerMethod.getMethod(), ResponseBody.class);
122+
return operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
123+
&& (restControllers.containsKey(handlerMethod.getBean().toString())
124+
|| (responseBodyAnnotation != null && AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Hidden.class) == null));
128125
}
129126

130127
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
}

0 commit comments

Comments
 (0)