Skip to content

Commit 1224f54

Browse files
committed
Merge branch 'christophejan-ease-group-declaration-with-actuators'
2 parents bd4dabf + c4dfd04 commit 1224f54

File tree

14 files changed

+1977
-109
lines changed

14 files changed

+1977
-109
lines changed

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@
5252
import org.springdoc.core.customizers.DataRestDelegatingMethodParameterCustomizer;
5353
import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer;
5454
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
55+
import org.springdoc.core.customizers.GlobalOperationCustomizer;
5556
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
56-
import org.springdoc.core.customizers.OpenApiCustomiser;
57-
import org.springdoc.core.customizers.OperationCustomizer;
5857
import org.springdoc.core.customizers.PropertyCustomizer;
5958
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
6059
import org.springdoc.core.providers.ActuatorProvider;
@@ -462,7 +461,7 @@ static BeanFactoryPostProcessor springdocBeanFactoryPostProcessor3(List<GroupedO
462461
@Bean
463462
@Lazy(false)
464463
@ConditionalOnManagementPort(ManagementPortType.SAME)
465-
OperationCustomizer actuatorCustomizer() {
464+
GlobalOperationCustomizer actuatorCustomizer() {
466465
return new ActuatorOperationCustomizer();
467466
}
468467

@@ -475,7 +474,7 @@ OperationCustomizer actuatorCustomizer() {
475474
@Bean
476475
@Lazy(false)
477476
@ConditionalOnManagementPort(ManagementPortType.SAME)
478-
OpenApiCustomiser actuatorOpenApiCustomiser(WebEndpointProperties webEndpointProperties) {
477+
GlobalOpenApiCustomizer actuatorOpenApiCustomiser(WebEndpointProperties webEndpointProperties) {
479478
return new ActuatorOpenApiCustomizer(webEndpointProperties);
480479
}
481480

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
7777
GroupedOpenApi actuatorGroup = GroupedOpenApi.builder().group(ACTUATOR_DEFAULT_GROUP)
7878
.pathsToMatch(webEndpointProperties.getBasePath() + ALL_PATTERN)
7979
.pathsToExclude(webEndpointProperties.getBasePath() + HEALTH_PATTERN)
80-
.addOperationCustomizer(actuatorCustomizer)
81-
.addOpenApiCustomiser(actuatorOpenApiCustomiser)
8280
.build();
8381
// Add the actuator group
8482
newGroups.add(actuatorGroup);

springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOpenApiCustomizer.java

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@
2222

2323
package org.springdoc.core.customizers;
2424

25+
import java.util.Comparator;
26+
import java.util.HashSet;
2527
import java.util.List;
28+
import java.util.Map.Entry;
2629
import java.util.Optional;
30+
import java.util.Set;
2731
import java.util.regex.Matcher;
2832
import java.util.regex.Pattern;
33+
import java.util.stream.Stream;
2934

3035
import io.swagger.v3.oas.models.OpenAPI;
3136
import io.swagger.v3.oas.models.PathItem;
37+
import io.swagger.v3.oas.models.Paths;
3238
import io.swagger.v3.oas.models.media.StringSchema;
3339
import io.swagger.v3.oas.models.parameters.Parameter;
3440
import io.swagger.v3.oas.models.parameters.PathParameter;
@@ -42,7 +48,7 @@
4248
* The type Actuator open api customiser.
4349
* @author bnasslahsen
4450
*/
45-
public class ActuatorOpenApiCustomizer implements OpenApiCustomiser {
51+
public class ActuatorOpenApiCustomizer implements GlobalOpenApiCustomizer {
4652

4753
/**
4854
* The Path pathern.
@@ -63,26 +69,54 @@ public ActuatorOpenApiCustomizer(WebEndpointProperties webEndpointProperties) {
6369
this.webEndpointProperties = webEndpointProperties;
6470
}
6571

72+
private Stream<Entry<String, PathItem>> actuatorPathEntryStream(OpenAPI openApi, String relativeSubPath) {
73+
String pathPrefix = webEndpointProperties.getBasePath() + Optional.ofNullable(relativeSubPath).orElse("");
74+
return Optional.ofNullable(openApi.getPaths())
75+
.map(Paths::entrySet)
76+
.map(Set::stream)
77+
.map(s -> s.filter(entry -> entry.getKey().startsWith(pathPrefix)))
78+
.orElse(Stream.empty());
79+
}
80+
81+
private void handleActuatorPathParam(OpenAPI openApi) {
82+
actuatorPathEntryStream(openApi, DEFAULT_PATH_SEPARATOR).forEach(stringPathItemEntry -> {
83+
String path = stringPathItemEntry.getKey();
84+
Matcher matcher = pathPathern.matcher(path);
85+
while (matcher.find()) {
86+
String pathParam = matcher.group(1);
87+
PathItem pathItem = stringPathItemEntry.getValue();
88+
pathItem.readOperations().forEach(operation -> {
89+
List<Parameter> existingParameters = operation.getParameters();
90+
Optional<Parameter> existingParam = Optional.empty();
91+
if (!CollectionUtils.isEmpty(existingParameters))
92+
existingParam = existingParameters.stream().filter(p -> pathParam.equals(p.getName())).findAny();
93+
if (!existingParam.isPresent())
94+
operation.addParametersItem(new PathParameter().name(pathParam).schema(new StringSchema()));
95+
});
96+
}
97+
});
98+
}
99+
100+
private void handleActuatorOperationIdUniqueness(OpenAPI openApi) {
101+
Set<String> usedOperationIds = new HashSet<>();
102+
actuatorPathEntryStream(openApi, null)
103+
.sorted(Comparator.comparing(Entry::getKey))
104+
.forEachOrdered(stringPathItemEntry -> {
105+
stringPathItemEntry.getValue().readOperations().forEach(operation -> {
106+
String initialOperationId = operation.getOperationId();
107+
String uniqueOperationId = operation.getOperationId();
108+
int counter = 1;
109+
while (!usedOperationIds.add(uniqueOperationId)) {
110+
uniqueOperationId = initialOperationId + "_" + ++counter;
111+
}
112+
operation.setOperationId(uniqueOperationId);
113+
});
114+
});
115+
}
116+
66117
@Override
67118
public void customise(OpenAPI openApi) {
68-
if (!CollectionUtils.isEmpty(openApi.getPaths()))
69-
openApi.getPaths().entrySet().stream()
70-
.filter(stringPathItemEntry -> stringPathItemEntry.getKey().startsWith(webEndpointProperties.getBasePath() + DEFAULT_PATH_SEPARATOR))
71-
.forEach(stringPathItemEntry -> {
72-
String path = stringPathItemEntry.getKey();
73-
Matcher matcher = pathPathern.matcher(path);
74-
while (matcher.find()) {
75-
String pathParam = matcher.group(1);
76-
PathItem pathItem = stringPathItemEntry.getValue();
77-
pathItem.readOperations().forEach(operation -> {
78-
List<Parameter> existingParameters = operation.getParameters();
79-
Optional<Parameter> existingParam = Optional.empty();
80-
if (!CollectionUtils.isEmpty(existingParameters))
81-
existingParam = existingParameters.stream().filter(p -> pathParam.equals(p.getName())).findAny();
82-
if (!existingParam.isPresent())
83-
operation.addParametersItem(new PathParameter().name(pathParam).schema(new StringSchema()));
84-
});
85-
}
86-
});
119+
handleActuatorPathParam(openApi);
120+
handleActuatorOperationIdUniqueness(openApi);
87121
}
88122
}

springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import java.lang.reflect.Field;
2626
import java.lang.reflect.Parameter;
27-
import java.util.HashMap;
2827
import java.util.regex.Matcher;
2928
import java.util.regex.Pattern;
3029

@@ -45,19 +44,13 @@
4544
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
4645
import org.springframework.web.method.HandlerMethod;
4746

48-
import static org.apache.commons.lang3.math.NumberUtils.INTEGER_ONE;
4947
import static org.springdoc.core.providers.ActuatorProvider.getTag;
5048

5149
/**
5250
* The type Actuator operation customizer.
5351
* @author bnasslahsen
5452
*/
55-
public class ActuatorOperationCustomizer implements OperationCustomizer {
56-
57-
/**
58-
* The Method count.
59-
*/
60-
private final HashMap<String, Integer> methodCountMap = new HashMap<>();
53+
public class ActuatorOperationCustomizer implements GlobalOperationCustomizer {
6154

6255
/**
6356
* The constant OPERATION.
@@ -113,14 +106,6 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) {
113106
while (matcher.find()) {
114107
operationId = matcher.group(1);
115108
}
116-
if (methodCountMap.containsKey(operationId)) {
117-
Integer methodCount = methodCountMap.get(operationId) + 1;
118-
methodCountMap.put(operationId, methodCount);
119-
operationId = operationId + "_" + methodCount;
120-
}
121-
else
122-
methodCountMap.put(operationId, INTEGER_ONE);
123-
124109
if (!summary.contains("$"))
125110
operation.setSummary(summary);
126111
operation.setOperationId(operationId);
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
*
3+
* *
4+
* * * Copyright 2019-2020 the original author or authors.
5+
* * *
6+
* * * Licensed under the Apache License, Version 2.0 (the "License");
7+
* * * you may not use this file except in compliance with the License.
8+
* * * You may obtain a copy of the License at
9+
* * *
10+
* * * https://www.apache.org/licenses/LICENSE-2.0
11+
* * *
12+
* * * Unless required by applicable law or agreed to in writing, software
13+
* * * distributed under the License is distributed on an "AS IS" BASIS,
14+
* * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* * * See the License for the specific language governing permissions and
16+
* * * limitations under the License.
17+
* *
18+
*
19+
*/
20+
21+
package test.org.springdoc.api.app186;
22+
23+
import static org.springdoc.core.Constants.ALL_PATTERN;
24+
25+
import org.junit.jupiter.api.Test;
26+
import org.springdoc.core.Constants;
27+
import org.springdoc.core.GroupedOpenApi;
28+
import org.springdoc.core.customizers.OpenApiCustomiser;
29+
import org.springdoc.core.customizers.OperationCustomizer;
30+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
31+
import org.springframework.boot.autoconfigure.SpringBootApplication;
32+
import org.springframework.boot.test.context.SpringBootTest;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.ComponentScan;
35+
import org.springframework.test.context.TestPropertySource;
36+
37+
import test.org.springdoc.api.AbstractCommonTest;
38+
39+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
40+
@TestPropertySource(properties={ "springdoc.show-actuator=true",
41+
"springdoc.group-configs[0].group=group-actuator-as-properties",
42+
"springdoc.group-configs[0].paths-to-match=${management.endpoints.web.base-path:/actuator}/**",
43+
"management.endpoints.enabled-by-default=true",
44+
"management.endpoints.web.exposure.include=*",
45+
"management.endpoints.web.exposure.exclude=functions, shutdown"})
46+
public class SpringDocApp186Test extends AbstractCommonTest {
47+
48+
@SpringBootApplication
49+
@ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app186" })
50+
static class SpringDocTestApp {
51+
52+
@Bean
53+
public GroupedOpenApi asCodeCheckBackwardsCompatibility(OpenApiCustomiser actuatorOpenApiCustomiser,
54+
OperationCustomizer actuatorCustomizer, WebEndpointProperties endpointProperties) {
55+
return GroupedOpenApi.builder()
56+
.group("group-actuator-as-code-check-backwards-compatibility")
57+
.pathsToMatch(endpointProperties.getBasePath()+ ALL_PATTERN)
58+
.addOpenApiCustomiser(actuatorOpenApiCustomiser)
59+
.addOperationCustomizer(actuatorCustomizer)
60+
.build();
61+
}
62+
63+
@Bean
64+
public GroupedOpenApi asCode(WebEndpointProperties endpointProperties) {
65+
return GroupedOpenApi.builder()
66+
.group("group-actuator-as-code")
67+
.pathsToMatch(endpointProperties.getBasePath()+ ALL_PATTERN)
68+
.build();
69+
}
70+
}
71+
72+
@Test
73+
public void testApp() throws Exception {
74+
webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL).exchange()
75+
.expectStatus().isOk()
76+
.expectBody().json(getContent("results/app186.json"), true);
77+
}
78+
79+
@Test
80+
public void testGroupActuatorAsCodeCheckBackwardsCompatibility() throws Exception {
81+
webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code-check-backwards-compatibility").exchange()
82+
.expectStatus().isOk()
83+
.expectBody().json(getContent("results/app186.json"), true);
84+
}
85+
86+
@Test
87+
public void testGroupActuatorAsCode() throws Exception {
88+
webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code").exchange()
89+
.expectStatus().isOk()
90+
.expectBody().json(getContent("results/app186.json"), true);
91+
}
92+
93+
@Test
94+
public void testGroupActuatorAsProperties() throws Exception {
95+
webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-properties").exchange()
96+
.expectStatus().isOk()
97+
.expectBody().json(getContent("results/app186.json"), true);
98+
}
99+
100+
}

0 commit comments

Comments
 (0)