Skip to content

Commit 1b64ec7

Browse files
committed
Switch from statefull to stateless actuator customizers
- Actuator operation ids should be unique across each OpenAPI definition not across all OpenAPI definitions - Statefull cutomizer beans seems odd
1 parent fd63c28 commit 1b64ec7

File tree

12 files changed

+152
-181
lines changed

12 files changed

+152
-181
lines changed

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

Lines changed: 53 additions & 19 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;
@@ -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: 0 additions & 15 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,7 +44,6 @@
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
/**
@@ -54,11 +52,6 @@
5452
*/
5553
public class ActuatorOperationCustomizer implements GlobalOperationCustomizer {
5654

57-
/**
58-
* The Method count.
59-
*/
60-
private final HashMap<String, Integer> methodCountMap = new HashMap<>();
61-
6255
/**
6356
* The constant OPERATION.
6457
*/
@@ -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);

springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app186/SpringDocApp186Test.java

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,7 @@
2222

2323
import static org.springdoc.core.Constants.ALL_PATTERN;
2424

25-
import org.json.JSONException;
2625
import org.junit.jupiter.api.Test;
27-
import org.skyscreamer.jsonassert.Customization;
28-
import org.skyscreamer.jsonassert.JSONAssert;
29-
import org.skyscreamer.jsonassert.JSONCompareMode;
30-
import org.skyscreamer.jsonassert.ValueMatcher;
31-
import org.skyscreamer.jsonassert.comparator.CustomComparator;
32-
import org.skyscreamer.jsonassert.comparator.JSONComparator;
3326
import org.springdoc.core.Constants;
3427
import org.springdoc.core.GroupedOpenApi;
3528
import org.springdoc.core.customizers.OpenApiCustomiser;
@@ -52,16 +45,6 @@
5245
"management.endpoints.web.exposure.exclude=functions, shutdown"})
5346
public class SpringDocApp186Test extends AbstractCommonTest {
5447

55-
private static final JSONComparator STRICT_IGNORING_OPERATION_ID = new CustomComparator(JSONCompareMode.STRICT,
56-
Customization.customization(
57-
"paths.*.*.operationId"
58-
, new ValueMatcher<Object>() {
59-
@Override
60-
public boolean equal(Object o1, Object o2) {
61-
return true;
62-
}
63-
}));
64-
6548
@SpringBootApplication
6649
@ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app186" })
6750
static class SpringDocTestApp {
@@ -86,40 +69,32 @@ public GroupedOpenApi asCode(WebEndpointProperties endpointProperties) {
8669
}
8770
}
8871

89-
private void assertBodyApp186(String content) {
90-
try {
91-
JSONAssert.assertEquals(getContent("results/app186.json"), content, STRICT_IGNORING_OPERATION_ID);
92-
} catch (JSONException e) {
93-
throw new RuntimeException(e);
94-
}
95-
}
96-
9772
@Test
9873
public void testApp() throws Exception {
9974
webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL).exchange()
10075
.expectStatus().isOk()
101-
.expectBody(String.class).value(this::assertBodyApp186);
76+
.expectBody().json(getContent("results/app186.json"), true);
10277
}
10378

10479
@Test
10580
public void testGroupActuatorAsCodeCheckBackwardsCompatibility() throws Exception {
10681
webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code-check-backwards-compatibility").exchange()
10782
.expectStatus().isOk()
108-
.expectBody(String.class).value(this::assertBodyApp186);
83+
.expectBody().json(getContent("results/app186.json"), true);
10984
}
11085

11186
@Test
11287
public void testGroupActuatorAsCode() throws Exception {
11388
webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code").exchange()
11489
.expectStatus().isOk()
115-
.expectBody(String.class).value(this::assertBodyApp186);
90+
.expectBody().json(getContent("results/app186.json"), true);
11691
}
11792

11893
@Test
11994
public void testGroupActuatorAsProperties() throws Exception {
12095
webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-properties").exchange()
12196
.expectStatus().isOk()
122-
.expectBody(String.class).value(this::assertBodyApp186);
97+
.expectBody().json(getContent("results/app186.json"), true);
12398
}
12499

125100
}

springdoc-openapi-webflux-core/src/test/resources/results/app146-1.json

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"tags": [
2727
"Actuator"
2828
],
29-
"operationId": "handle_1_6",
29+
"operationId": "handle_5",
3030
"parameters": [
3131
{
3232
"name": "name",
@@ -147,7 +147,7 @@
147147
"tags": [
148148
"Actuator"
149149
],
150-
"operationId": "handle_1_2",
150+
"operationId": "handle_1",
151151
"responses": {
152152
"200": {
153153
"description": "OK",
@@ -177,7 +177,7 @@
177177
"tags": [
178178
"Actuator"
179179
],
180-
"operationId": "handle_1_3",
180+
"operationId": "handle_2",
181181
"parameters": [
182182
{
183183
"name": "requiredMetricName",
@@ -217,7 +217,7 @@
217217
"tags": [
218218
"Actuator"
219219
],
220-
"operationId": "handle_1_4",
220+
"operationId": "handle_3",
221221
"responses": {
222222
"200": {
223223
"description": "OK",
@@ -247,7 +247,7 @@
247247
"tags": [
248248
"Actuator"
249249
],
250-
"operationId": "handle_1_5",
250+
"operationId": "handle_4",
251251
"responses": {
252252
"200": {
253253
"description": "OK",
@@ -277,7 +277,7 @@
277277
"tags": [
278278
"Actuator"
279279
],
280-
"operationId": "handle_1_7",
280+
"operationId": "handle_6",
281281
"responses": {
282282
"200": {
283283
"description": "OK",
@@ -307,7 +307,7 @@
307307
"tags": [
308308
"Actuator"
309309
],
310-
"operationId": "handle_1_8",
310+
"operationId": "handle_7",
311311
"responses": {
312312
"200": {
313313
"description": "OK",
@@ -337,7 +337,7 @@
337337
"tags": [
338338
"Actuator"
339339
],
340-
"operationId": "handle_1_9",
340+
"operationId": "handle_8",
341341
"responses": {
342342
"200": {
343343
"description": "OK",
@@ -357,7 +357,7 @@
357357
"tags": [
358358
"Actuator"
359359
],
360-
"operationId": "handle_1_10",
360+
"operationId": "handle_9",
361361
"responses": {
362362
"200": {
363363
"description": "OK",
@@ -387,7 +387,7 @@
387387
"tags": [
388388
"Actuator"
389389
],
390-
"operationId": "handle_1_11",
390+
"operationId": "handle_10",
391391
"parameters": [
392392
{
393393
"name": "toMatch",
@@ -427,7 +427,7 @@
427427
"tags": [
428428
"Actuator"
429429
],
430-
"operationId": "handle_1_12",
430+
"operationId": "handle_11",
431431
"responses": {
432432
"200": {
433433
"description": "OK",
@@ -457,7 +457,7 @@
457457
"tags": [
458458
"Actuator"
459459
],
460-
"operationId": "handle_1_13",
460+
"operationId": "handle_12",
461461
"parameters": [
462462
{
463463
"name": "prefix",
@@ -497,7 +497,7 @@
497497
"tags": [
498498
"Actuator"
499499
],
500-
"operationId": "handle_1_14",
500+
"operationId": "handle_13",
501501
"responses": {
502502
"200": {
503503
"description": "OK",
@@ -527,7 +527,7 @@
527527
"tags": [
528528
"Actuator"
529529
],
530-
"operationId": "handle_1_15",
530+
"operationId": "handle_14",
531531
"responses": {
532532
"200": {
533533
"description": "OK",
@@ -557,7 +557,7 @@
557557
"tags": [
558558
"Actuator"
559559
],
560-
"operationId": "handle_1_16",
560+
"operationId": "handle_15",
561561
"parameters": [
562562
{
563563
"name": "cache",
@@ -595,7 +595,7 @@
595595
"tags": [
596596
"Actuator"
597597
],
598-
"operationId": "handle_1_20",
598+
"operationId": "handle_19",
599599
"parameters": [
600600
{
601601
"name": "cache",
@@ -635,7 +635,7 @@
635635
"tags": [
636636
"Actuator"
637637
],
638-
"operationId": "handle_1_17",
638+
"operationId": "handle_16",
639639
"responses": {
640640
"200": {
641641
"description": "OK",
@@ -663,7 +663,7 @@
663663
"tags": [
664664
"Actuator"
665665
],
666-
"operationId": "handle_1_19",
666+
"operationId": "handle_18",
667667
"responses": {
668668
"200": {
669669
"description": "OK",
@@ -683,7 +683,7 @@
683683
"tags": [
684684
"Actuator"
685685
],
686-
"operationId": "handle_1_18",
686+
"operationId": "handle_17",
687687
"responses": {
688688
"200": {
689689
"description": "OK",

0 commit comments

Comments
 (0)