Skip to content

Commit 5d68d93

Browse files
committed
ConcurrentModificationException when querying /v3/api-docs/{group} concurrently for different groups. Fixes #1641.
1 parent 9ebd61c commit 5d68d93

File tree

4 files changed

+15
-39
lines changed

4 files changed

+15
-39
lines changed

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

+4-8
Original file line numberDiff line numberDiff line change
@@ -308,16 +308,14 @@ protected synchronized OpenAPI getOpenApi(Locale locale) {
308308
final Locale finalLocale = locale == null ? Locale.getDefault() : locale;
309309
if (openAPIService.getCachedOpenAPI(finalLocale) == null || springDocConfigProperties.isCacheDisabled()) {
310310
Instant start = Instant.now();
311-
openAPIService.build(finalLocale);
311+
openAPI = openAPIService.build(finalLocale);
312312
Map<String, Object> mappingsMap = openAPIService.getMappingsMap().entrySet().stream()
313313
.filter(controller -> (AnnotationUtils.findAnnotation(controller.getValue().getClass(),
314314
Hidden.class) == null))
315315
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getClass()))
316316
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
317317

318318
Map<String, Object> findControllerAdvice = openAPIService.getControllerAdviceMap();
319-
// calculate generic responses
320-
openAPI = openAPIService.getCalculatedOpenAPI();
321319
if (OpenApiVersion.OPENAPI_3_1 == springDocConfigProperties.getApiDocs().getVersion())
322320
openAPI.openapi(OpenApiVersion.OPENAPI_3_1.getVersion());
323321
if (springDocConfigProperties.isDefaultOverrideWithGenericResponse()) {
@@ -359,7 +357,6 @@ protected synchronized OpenAPI getOpenApi(Locale locale) {
359357
openAPIService.setServersPresent(true);
360358

361359
openAPIService.setCachedOpenAPI(openAPI, finalLocale);
362-
openAPIService.resetCalculatedOpenAPI();
363360

364361
LOGGER.info("Init duration for springdoc-openapi is: {} ms",
365362
Duration.between(start, Instant.now()).toMillis());
@@ -552,10 +549,10 @@ protected void calculatePath(List<RouterOperation> routerOperationList, Locale l
552549
}
553550
}
554551
else if (routerOperation.getOperation() != null && StringUtils.isNotBlank(routerOperation.getOperation().operationId()) && isFilterCondition(routerOperation.getPath(), routerOperation.getProduces(), routerOperation.getConsumes(), routerOperation.getHeaders())) {
555-
calculatePath(routerOperation, locale);
552+
calculatePath(routerOperation, locale, openAPI);
556553
}
557554
else if (routerOperation.getOperationModel() != null && StringUtils.isNotBlank(routerOperation.getOperationModel().getOperationId()) && isFilterCondition(routerOperation.getPath(), routerOperation.getProduces(), routerOperation.getConsumes(), routerOperation.getHeaders())) {
558-
calculatePath(routerOperation, locale);
555+
calculatePath(routerOperation, locale, openAPI);
559556
}
560557
}
561558
}
@@ -567,15 +564,14 @@ else if (routerOperation.getOperationModel() != null && StringUtils.isNotBlank(r
567564
* @param routerOperation the router operation
568565
* @param locale the locale
569566
*/
570-
protected void calculatePath(RouterOperation routerOperation, Locale locale) {
567+
protected void calculatePath(RouterOperation routerOperation, Locale locale, OpenAPI openAPI) {
571568
String operationPath = routerOperation.getPath();
572569
io.swagger.v3.oas.annotations.Operation apiOperation = routerOperation.getOperation();
573570
String[] methodConsumes = routerOperation.getConsumes();
574571
String[] methodProduces = routerOperation.getProduces();
575572
String[] headers = routerOperation.getHeaders();
576573
Map<String, String> queryParams = routerOperation.getQueryParams();
577574

578-
OpenAPI openAPI = openAPIService.getCalculatedOpenAPI();
579575
Paths paths = openAPI.getPaths();
580576
Map<HttpMethod, Operation> operationMap = null;
581577
if (paths.containsKey(operationPath)) {

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

+8-27
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,6 @@ public class OpenAPIService implements ApplicationContextAware {
138138
*/
139139
private final Map<String, OpenAPI> cachedOpenAPI = new HashMap<>();
140140

141-
/**
142-
* The Calculated open api.
143-
*/
144-
private OpenAPI calculatedOpenAPI;
145-
146141
/**
147142
* The Is servers present.
148143
*/
@@ -241,18 +236,18 @@ public static String splitCamelCase(String str) {
241236
* Build.
242237
* @param locale the locale
243238
*/
244-
public void build(Locale locale) {
239+
public OpenAPI build(Locale locale) {
245240
Optional<OpenAPIDefinition> apiDef = getOpenAPIDefinition();
246-
241+
OpenAPI calculatedOpenAPI = null;
247242
if (openAPI == null) {
248-
this.calculatedOpenAPI = new OpenAPI();
249-
this.calculatedOpenAPI.setComponents(new Components());
250-
this.calculatedOpenAPI.setPaths(new Paths());
243+
calculatedOpenAPI = new OpenAPI();
244+
calculatedOpenAPI.setComponents(new Components());
245+
calculatedOpenAPI.setPaths(new Paths());
251246
}
252247
else {
253248
try {
254249
ObjectMapper objectMapper = new ObjectMapper();
255-
this.calculatedOpenAPI = objectMapper.readValue(objectMapper.writeValueAsString(openAPI), OpenAPI.class );
250+
calculatedOpenAPI = objectMapper.readValue(objectMapper.writeValueAsString(openAPI), OpenAPI.class );
256251
}
257252
catch (JsonProcessingException e) {
258253
LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage());
@@ -277,6 +272,8 @@ else if (calculatedOpenAPI.getInfo() == null) {
277272
// add security schemes
278273
this.calculateSecuritySchemes(calculatedOpenAPI.getComponents(), locale);
279274
openApiBuilderCustomisers.ifPresent(customisers -> customisers.forEach(customiser -> customiser.customise(this)));
275+
276+
return calculatedOpenAPI;
280277
}
281278

282279
/**
@@ -722,22 +719,6 @@ public void setCachedOpenAPI(OpenAPI cachedOpenAPI, Locale locale) {
722719
this.cachedOpenAPI.put(locale.toLanguageTag(), cachedOpenAPI);
723720
}
724721

725-
/**
726-
* Gets calculated open api.
727-
*
728-
* @return the calculated open api
729-
*/
730-
public OpenAPI getCalculatedOpenAPI() {
731-
return calculatedOpenAPI;
732-
}
733-
734-
/**
735-
* Reset calculated open api.
736-
*/
737-
public void resetCalculatedOpenAPI() {
738-
this.calculatedOpenAPI = null;
739-
}
740-
741722
/**
742723
* Gets context.
743724
*

springdoc-openapi-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ public void setUp() {
118118
ReflectionTestUtils.setField(openAPIService, "cachedOpenAPI", new HashMap<>());
119119
ReflectionTestUtils.setField(openAPIService, "serverBaseUrlCustomizers", Optional.empty());
120120

121-
when(openAPIService.getCalculatedOpenAPI()).thenReturn(openAPI);
122121
when(openAPIService.getContext()).thenReturn(context);
123122

124123
when(openAPIBuilderObjectFactory.getObject()).thenReturn(openAPIService);
@@ -162,7 +161,7 @@ void calculatePathFromRouterOperation() {
162161
routerOperation.setOperationModel(operation);
163162
routerOperation.setPath(PATH);
164163

165-
resource.calculatePath(routerOperation, Locale.getDefault());
164+
resource.calculatePath(routerOperation, Locale.getDefault(), this.openAPI);
166165

167166
final List<Parameter> parameters = resource.getOpenApi(Locale.getDefault()).getPaths().get(PATH).getGet().getParameters();
168167
assertThat(parameters.size(), is(3));

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ protected void getPaths(Map<String, Object> restControllers, Locale locale, Open
161161

162162
Optional<RepositoryRestResourceProvider> repositoryRestResourceProviderOptional = springDocProviders.getRepositoryRestResourceProvider();
163163
repositoryRestResourceProviderOptional.ifPresent(restResourceProvider -> {
164-
List<RouterOperation> operationList = restResourceProvider.getRouterOperations(openAPIService.getCalculatedOpenAPI(), locale);
164+
List<RouterOperation> operationList = restResourceProvider.getRouterOperations(openAPI, locale);
165165
calculatePath(operationList, locale, openAPI);
166-
restResourceProvider.customize(openAPIService.getCalculatedOpenAPI());
166+
restResourceProvider.customize(openAPI);
167167
Map<RequestMappingInfo, HandlerMethod> mapDataRest = restResourceProvider.getHandlerMethods();
168168
Map<String, Object> requestMappingMap = restResourceProvider.getBasePathAwareControllerEndpoints();
169169
Class[] additionalRestClasses = requestMappingMap.values().stream().map(AopUtils::getTargetClass).toArray(Class[]::new);

0 commit comments

Comments
 (0)