From 425eff29b8f8147fc745e6b6b9b211fb451a584c Mon Sep 17 00:00:00 2001 From: bnasslah Date: Tue, 3 May 2022 13:52:36 +0200 Subject: [PATCH] Changes report: ConcurrentModificationException when querying /v3/api-docs/{group} concurrently for different groups. #1641 --- .../api/AbstractOpenApiResource.java | 73 ++++++++++--------- .../core/converters/ConverterUtils.java | 7 +- .../SchemaPropertyDeprecatingConverter.java | 3 +- .../springdoc/core/models/GroupedOpenApi.java | 2 +- .../core/service/AbstractRequestService.java | 3 +- .../core/service/GenericParameterService.java | 3 +- .../core/service/GenericResponseService.java | 11 ++- .../core/service/OpenAPIService.java | 43 +++-------- .../core/utils/SpringDocAnnotationsUtils.java | 3 +- .../api/AbstractOpenApiResourceTest.java | 6 +- .../webflux/api/OpenApiResource.java | 14 ++-- .../springdoc/webmvc/api/OpenApiResource.java | 19 ++--- 12 files changed, 89 insertions(+), 98 deletions(-) diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java index e87e9ef8d..bbfa302bf 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java @@ -138,12 +138,12 @@ public abstract class AbstractOpenApiResource extends SpecFilter { /** * The constant ADDITIONAL_REST_CONTROLLERS. */ - private static final List> ADDITIONAL_REST_CONTROLLERS = new ArrayList<>(); + private static final List> ADDITIONAL_REST_CONTROLLERS = Collections.synchronizedList(new ArrayList<>()); /** * The constant HIDDEN_REST_CONTROLLERS. */ - private static final List> HIDDEN_REST_CONTROLLERS = new ArrayList<>(); + private static final List> HIDDEN_REST_CONTROLLERS = Collections.synchronizedList(new ArrayList<>()); /** * The Open api builder. @@ -306,11 +306,11 @@ public static void addHiddenRestControllers(String... classes) { * @return the open api */ protected synchronized OpenAPI getOpenApi(Locale locale) { - OpenAPI openApi; + OpenAPI openAPI; final Locale finalLocale = locale == null ? Locale.getDefault() : locale; if (openAPIService.getCachedOpenAPI(finalLocale) == null || springDocConfigProperties.isCacheDisabled()) { Instant start = Instant.now(); - openAPIService.build(finalLocale); + openAPI = openAPIService.build(finalLocale); Map mappingsMap = openAPIService.getMappingsMap().entrySet().stream() .filter(controller -> (AnnotationUtils.findAnnotation(controller.getValue().getClass(), Hidden.class) == null)) @@ -319,33 +319,32 @@ protected synchronized OpenAPI getOpenApi(Locale locale) { Map findControllerAdvice = openAPIService.getControllerAdviceMap(); // calculate generic responses - openApi = openAPIService.getCalculatedOpenAPI(); if (OpenApiVersion.OPENAPI_3_1 == springDocConfigProperties.getApiDocs().getVersion()) - openApi.openapi(OpenApiVersion.OPENAPI_3_1.getVersion()); + openAPI.openapi(OpenApiVersion.OPENAPI_3_1.getVersion()); if (springDocConfigProperties.isDefaultOverrideWithGenericResponse()) { if (!CollectionUtils.isEmpty(mappingsMap)) findControllerAdvice.putAll(mappingsMap); - responseBuilder.buildGenericResponse(openApi.getComponents(), findControllerAdvice, finalLocale); + responseBuilder.buildGenericResponse(openAPI.getComponents(), findControllerAdvice, finalLocale); } - getPaths(mappingsMap, finalLocale); + getPaths(mappingsMap, finalLocale, openAPI); Optional cloudFunctionProviderOptional = springDocProviders.getSpringCloudFunctionProvider(); cloudFunctionProviderOptional.ifPresent(cloudFunctionProvider -> { - List routerOperationList = cloudFunctionProvider.getRouterOperations(openApi); + List routerOperationList = cloudFunctionProvider.getRouterOperations(openAPI); if (!CollectionUtils.isEmpty(routerOperationList)) - this.calculatePath(routerOperationList, locale); + this.calculatePath(routerOperationList, locale, openAPI); } ); - if (!CollectionUtils.isEmpty(openApi.getServers())) + if (!CollectionUtils.isEmpty(openAPI.getServers())) openAPIService.setServersPresent(true); - openAPIService.updateServers(openApi); + openAPIService.updateServers(openAPI); if (springDocConfigProperties.isRemoveBrokenReferenceDefinitions()) - this.removeBrokenReferenceDefinitions(openApi); + this.removeBrokenReferenceDefinitions(openAPI); // run the optional customisers - List servers = openApi.getServers(); + List servers = openAPI.getServers(); List serversCopy = null; try { serversCopy = springDocProviders.jsonMapper() @@ -355,22 +354,21 @@ protected synchronized OpenAPI getOpenApi(Locale locale) { LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage()); } - openApiLocaleCustomizers.values().forEach(openApiLocaleCustomizer -> openApiLocaleCustomizer.customise(openApi, finalLocale)); - openApiCustomizers.ifPresent(apiCustomisers -> apiCustomisers.forEach(openApiCustomizer -> openApiCustomizer.customise(openApi))); - if (!CollectionUtils.isEmpty(openApi.getServers()) && !openApi.getServers().equals(serversCopy)) + openApiLocaleCustomizers.values().forEach(openApiLocaleCustomizer -> openApiLocaleCustomizer.customise(openAPI, finalLocale)); + openApiCustomizers.ifPresent(apiCustomisers -> apiCustomisers.forEach(openApiCustomizer -> openApiCustomizer.customise(openAPI))); + if (!CollectionUtils.isEmpty(openAPI.getServers()) && !openAPI.getServers().equals(serversCopy)) openAPIService.setServersPresent(true); - openAPIService.setCachedOpenAPI(openApi, finalLocale); - openAPIService.resetCalculatedOpenAPI(); + openAPIService.setCachedOpenAPI(openAPI, finalLocale); LOGGER.info("Init duration for springdoc-openapi is: {} ms", Duration.between(start, Instant.now()).toMillis()); } else { LOGGER.debug("Fetching openApi document from cache"); - openApi = openAPIService.updateServers(openAPIService.getCachedOpenAPI(finalLocale)); + openAPI = openAPIService.updateServers(openAPIService.getCachedOpenAPI(finalLocale)); } - return openApi; + return openAPI; } /** @@ -378,8 +376,9 @@ protected synchronized OpenAPI getOpenApi(Locale locale) { * * @param findRestControllers the find rest controllers * @param locale the locale + * @param openAPI the open api */ - protected abstract void getPaths(Map findRestControllers, Locale locale); + protected abstract void getPaths(Map findRestControllers, Locale locale, OpenAPI openAPI); /** * Calculate path. @@ -387,8 +386,9 @@ protected synchronized OpenAPI getOpenApi(Locale locale) { * @param handlerMethod the handler method * @param routerOperation the router operation * @param locale the locale + * @param openAPI the open api */ - protected void calculatePath(HandlerMethod handlerMethod, RouterOperation routerOperation, Locale locale) { + protected void calculatePath(HandlerMethod handlerMethod, RouterOperation routerOperation, Locale locale, OpenAPI openAPI) { String operationPath = routerOperation.getPath(); Set requestMethods = new HashSet<>(Arrays.asList(routerOperation.getMethods())); io.swagger.v3.oas.annotations.Operation apiOperation = routerOperation.getOperation(); @@ -397,7 +397,6 @@ protected void calculatePath(HandlerMethod handlerMethod, RouterOperation router String[] headers = routerOperation.getHeaders(); Map queryParams = routerOperation.getQueryParams(); - OpenAPI openAPI = openAPIService.getCalculatedOpenAPI(); Components components = openAPI.getComponents(); Paths paths = openAPI.getPaths(); @@ -516,8 +515,9 @@ private void buildCallbacks(OpenAPI openAPI, MethodAttributes methodAttributes, * * @param routerOperationList the router operation list * @param locale the locale + * @param openAPI the open api */ - protected void calculatePath(List routerOperationList, Locale locale) { + protected void calculatePath(List routerOperationList, Locale locale, OpenAPI openAPI) { ApplicationContext applicationContext = openAPIService.getContext(); if (!CollectionUtils.isEmpty(routerOperationList)) { Collections.sort(routerOperationList); @@ -547,14 +547,14 @@ protected void calculatePath(List routerOperationList, Locale l LOGGER.error(e.getMessage()); } if (handlerMethod != null && isFilterCondition(handlerMethod, routerOperation.getPath(), routerOperation.getProduces(), routerOperation.getConsumes(), routerOperation.getHeaders())) - calculatePath(handlerMethod, routerOperation, locale); + calculatePath(handlerMethod, routerOperation, locale, openAPI); } } else if (routerOperation.getOperation() != null && StringUtils.isNotBlank(routerOperation.getOperation().operationId()) && isFilterCondition(routerOperation.getPath(), routerOperation.getProduces(), routerOperation.getConsumes(), routerOperation.getHeaders())) { - calculatePath(routerOperation, locale); + calculatePath(routerOperation, locale, openAPI); } else if (routerOperation.getOperationModel() != null && StringUtils.isNotBlank(routerOperation.getOperationModel().getOperationId()) && isFilterCondition(routerOperation.getPath(), routerOperation.getProduces(), routerOperation.getConsumes(), routerOperation.getHeaders())) { - calculatePath(routerOperation, locale); + calculatePath(routerOperation, locale, openAPI); } } } @@ -566,7 +566,7 @@ else if (routerOperation.getOperationModel() != null && StringUtils.isNotBlank(r * @param routerOperation the router operation * @param locale the locale */ - protected void calculatePath(RouterOperation routerOperation, Locale locale) { + protected void calculatePath(RouterOperation routerOperation, Locale locale, OpenAPI openAPI ) { String operationPath = routerOperation.getPath(); io.swagger.v3.oas.annotations.Operation apiOperation = routerOperation.getOperation(); String[] methodConsumes = routerOperation.getConsumes(); @@ -574,7 +574,6 @@ protected void calculatePath(RouterOperation routerOperation, Locale locale) { String[] headers = routerOperation.getHeaders(); Map queryParams = routerOperation.getQueryParams(); - OpenAPI openAPI = openAPIService.getCalculatedOpenAPI(); Paths paths = openAPI.getPaths(); Map operationMap = null; if (paths.containsKey(operationPath)) { @@ -620,8 +619,8 @@ protected void calculatePath(RouterOperation routerOperation, Locale locale) { * @param locale the locale */ protected void calculatePath(HandlerMethod handlerMethod, String operationPath, - Set requestMethods,String[] consumes, String[] produces, String[] headers, Locale locale) { - this.calculatePath(handlerMethod, new RouterOperation(operationPath, requestMethods.toArray(new RequestMethod[requestMethods.size()]), consumes, produces, headers), locale); + Set requestMethods,String[] consumes, String[] produces, String[] headers, Locale locale, OpenAPI openAPI) { + this.calculatePath(handlerMethod, new RouterOperation(operationPath, requestMethods.toArray(new RequestMethod[requestMethods.size()]), consumes, produces, headers), locale, openAPI); } /** @@ -630,13 +629,15 @@ protected void calculatePath(HandlerMethod handlerMethod, String operationPath, * @param beanName the bean name * @param routerFunctionVisitor the router function visitor * @param locale the locale + * @param openAPI the open api */ - protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVisitor routerFunctionVisitor, Locale locale) { + protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVisitor routerFunctionVisitor, + Locale locale, OpenAPI openAPI ) { boolean withRouterOperation = routerFunctionVisitor.getRouterFunctionDatas().stream() .anyMatch(routerFunctionData -> routerFunctionData.getAttributes().containsKey(OPERATION_ATTRIBUTE)); if (withRouterOperation) { List operationList = routerFunctionVisitor.getRouterFunctionDatas().stream().map(RouterOperation::new).collect(Collectors.toList()); - calculatePath(operationList, locale); + calculatePath(operationList, locale, openAPI); } else { List routerOperationList = new ArrayList<>(); @@ -650,11 +651,11 @@ protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVis else routerOperationList.addAll(Arrays.asList(routerOperations.value())); if (routerOperationList.size() == 1) - calculatePath(routerOperationList.stream().map(routerOperation -> new RouterOperation(routerOperation, routerFunctionVisitor.getRouterFunctionDatas().get(0))).collect(Collectors.toList()), locale); + calculatePath(routerOperationList.stream().map(routerOperation -> new RouterOperation(routerOperation, routerFunctionVisitor.getRouterFunctionDatas().get(0))).collect(Collectors.toList()), locale, openAPI); else { List operationList = routerOperationList.stream().map(RouterOperation::new).collect(Collectors.toList()); mergeRouters(routerFunctionVisitor.getRouterFunctionDatas(), operationList); - calculatePath(operationList, locale); + calculatePath(operationList, locale, openAPI); } } } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java index 2e294ecba..c6f72b592 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; @@ -42,17 +43,17 @@ public class ConverterUtils { /** * The constant RESULT_WRAPPERS_TO_IGNORE. */ - private static final List> RESULT_WRAPPERS_TO_IGNORE = new ArrayList<>(); + private static final List> RESULT_WRAPPERS_TO_IGNORE = Collections.synchronizedList(new ArrayList<>()); /** * The constant RESPONSE_TYPES_TO_IGNORE. */ - private static final List> RESPONSE_TYPES_TO_IGNORE = new ArrayList<>(); + private static final List> RESPONSE_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>()); /** * The constant FLUX_WRAPPERS_TO_IGNORE. */ - private static final List> FLUX_WRAPPERS_TO_IGNORE = new ArrayList<>(); + private static final List> FLUX_WRAPPERS_TO_IGNORE = Collections.synchronizedList(new ArrayList<>()); static { RESULT_WRAPPERS_TO_IGNORE.add(Callable.class); diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/SchemaPropertyDeprecatingConverter.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/SchemaPropertyDeprecatingConverter.java index 62b869134..76f559194 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/SchemaPropertyDeprecatingConverter.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/SchemaPropertyDeprecatingConverter.java @@ -27,6 +27,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.stream.Stream; @@ -47,7 +48,7 @@ public class SchemaPropertyDeprecatingConverter implements ModelConverter { /** * The constant DEPRECATED_ANNOTATIONS. */ - private static final List> DEPRECATED_ANNOTATIONS = new ArrayList<>(); + private static final List> DEPRECATED_ANNOTATIONS = Collections.synchronizedList(new ArrayList<>()); static { DEPRECATED_ANNOTATIONS.add(Deprecated.class); diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/models/GroupedOpenApi.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/models/GroupedOpenApi.java index fce12c711..2a7be9626 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/models/GroupedOpenApi.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/models/GroupedOpenApi.java @@ -476,7 +476,7 @@ public GroupedOpenApi build() { public GroupedOpenApi addAllOpenApiCustomizer(Collection openApiCustomizerCollection) { List result = new ArrayList<>(); result.addAll(openApiCustomizerCollection); - result.addAll(openApiCustomizerCollection); + result.addAll(openApiCustomizers); openApiCustomizers = result; return this; } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java index 2ce6ac7ef..566cbbd01 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/AbstractRequestService.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -104,7 +105,7 @@ public abstract class AbstractRequestService { /** * The constant PARAM_TYPES_TO_IGNORE. */ - private static final List> PARAM_TYPES_TO_IGNORE = new ArrayList<>(); + private static final List> PARAM_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>()); /** * The constant ANNOTATIONS_FOR_REQUIRED. diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java index b6eaca577..460f30684 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java @@ -30,6 +30,7 @@ import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -83,7 +84,7 @@ public class GenericParameterService { /** * The constant FILE_TYPES. */ - private static final List> FILE_TYPES = new ArrayList<>(); + private static final List> FILE_TYPES = Collections.synchronizedList(new ArrayList<>()); /** * The Optional delegating method parameter customizer. diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericResponseService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericResponseService.java index ef89df8a2..03ee051b5 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericResponseService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericResponseService.java @@ -248,7 +248,9 @@ public void buildGenericResponse(Components components, Map find apiResponses.forEach(controllerAdviceInfoApiResponseMap::put); } } - controllerAdviceInfos.add(controllerAdviceInfo); + synchronized (this) { + controllerAdviceInfos.add(controllerAdviceInfo); + } } } @@ -572,7 +574,8 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent())) exceptions.add(parameter.getType()); } } - } else { + } + else { exceptions.addAll(asList(exceptionHandler.value())); } apiResponse.addExtension(EXTENSION_EXCEPTION_CLASSES, exceptions); @@ -640,9 +643,9 @@ else if (returnType instanceof ParameterizedType) { * @param beanType the bean type * @return the generic map response */ - private Map getGenericMapResponse(Class beanType) { + private synchronized Map getGenericMapResponse(Class beanType) { return controllerAdviceInfos.stream() - .filter(controllerAdviceInfo -> new ControllerAdviceBean(controllerAdviceInfo.getControllerAdvice()).isApplicableToBeanType(beanType)) + .filter(controllerAdviceInfo -> new ControllerAdviceBean(controllerAdviceInfo.getControllerAdvice()).isApplicableToBeanType(beanType)) .map(ControllerAdviceInfo::getApiResponseMap) .collect(LinkedHashMap::new, Map::putAll, Map::putAll); } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OpenAPIService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OpenAPIService.java index ae7df7c1b..aa062409a 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OpenAPIService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/OpenAPIService.java @@ -148,11 +148,6 @@ public class OpenAPIService implements ApplicationContextAware { */ private final Map cachedOpenAPI = new HashMap<>(); - /** - * The Calculated open api. - */ - private OpenAPI calculatedOpenAPI; - /** * The Is servers present. */ @@ -220,7 +215,7 @@ public OpenAPIService(Optional openAPI, SecurityService securityParser, if (!CollectionUtils.isEmpty(this.openAPI.getServers())) this.isServersPresent = true; } - this.propertyResolverUtils=propertyResolverUtils; + this.propertyResolverUtils = propertyResolverUtils; this.securityParser = securityParser; this.springDocConfigProperties = springDocConfigProperties; this.openApiBuilderCustomisers = openApiBuilderCustomizers; @@ -250,19 +245,21 @@ public static String splitCamelCase(String str) { /** * Build. * @param locale the locale + * @return the open api */ - public void build(Locale locale) { + public OpenAPI build(Locale locale) { Optional apiDef = getOpenAPIDefinition(); + OpenAPI calculatedOpenAPI = null; if (openAPI == null) { - this.calculatedOpenAPI = new OpenAPI(); - this.calculatedOpenAPI.setComponents(new Components()); - this.calculatedOpenAPI.setPaths(new Paths()); + calculatedOpenAPI = new OpenAPI(); + calculatedOpenAPI.setComponents(new Components()); + calculatedOpenAPI.setPaths(new Paths()); } else { try { ObjectMapper objectMapper = new ObjectMapper(); - this.calculatedOpenAPI = objectMapper.readValue(objectMapper.writeValueAsString(openAPI), OpenAPI.class ); + calculatedOpenAPI = objectMapper.readValue(objectMapper.writeValueAsString(openAPI), OpenAPI.class); } catch (JsonProcessingException e) { LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage()); @@ -286,7 +283,8 @@ else if (calculatedOpenAPI.getInfo() == null) { // add security schemes this.calculateSecuritySchemes(calculatedOpenAPI.getComponents(), locale); - openApiBuilderCustomisers.ifPresent(customisers -> customisers.forEach(customiser -> customiser.customise(this))); + openApiBuilderCustomisers.ifPresent(customizers -> customizers.forEach(customiser -> customiser.customise(this))); + return calculatedOpenAPI; } /** @@ -360,10 +358,9 @@ public Operation buildTags(HandlerMethod handlerMethod, Operation operation, Ope } if (!CollectionUtils.isEmpty(tagsStr)) { - if(CollectionUtils.isEmpty(operation.getTags())) + if (CollectionUtils.isEmpty(operation.getTags())) operation.setTags(new ArrayList<>(tagsStr)); - else - { + else { Set operationTagsSet = new HashSet<>(operation.getTags()); operationTagsSet.addAll(tagsStr); operation.getTags().clear(); @@ -821,22 +818,6 @@ public void setCachedOpenAPI(OpenAPI cachedOpenAPI, Locale locale) { this.cachedOpenAPI.put(locale.toLanguageTag(), cachedOpenAPI); } - /** - * Gets calculated open api. - * - * @return the calculated open api - */ - public OpenAPI getCalculatedOpenAPI() { - return calculatedOpenAPI; - } - - /** - * Reset calculated open api. - */ - public void resetCalculatedOpenAPI() { - this.calculatedOpenAPI = null; - } - /** * Gets context. * diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java index 145ab90b6..a6e3edea6 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java @@ -29,6 +29,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -75,7 +76,7 @@ public class SpringDocAnnotationsUtils extends AnnotationsUtils { /** * The constant ANNOTATIOSN_TO_IGNORE. */ - private static final List ANNOTATIONS_TO_IGNORE = new ArrayList<>(); + private static final List ANNOTATIONS_TO_IGNORE = Collections.synchronizedList(new ArrayList<>()); static { ANNOTATIONS_TO_IGNORE.add(Hidden.class); diff --git a/springdoc-openapi-starter-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java b/springdoc-openapi-starter-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java index 12a15289b..deaaed4eb 100644 --- a/springdoc-openapi-starter-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java +++ b/springdoc-openapi-starter-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java @@ -121,7 +121,7 @@ public void setUp() { ReflectionTestUtils.setField(openAPIService, "cachedOpenAPI", new HashMap<>()); ReflectionTestUtils.setField(openAPIService, "serverBaseUrlCustomizers", Optional.empty()); - when(openAPIService.getCalculatedOpenAPI()).thenReturn(openAPI); + when(openAPIService.build(any())).thenReturn(openAPI); when(openAPIService.getContext()).thenReturn(context); when(openAPIBuilderObjectFactory.getObject()).thenReturn(openAPIService); @@ -165,7 +165,7 @@ void calculatePathFromRouterOperation() { routerOperation.setOperationModel(operation); routerOperation.setPath(PATH); - resource.calculatePath(routerOperation, Locale.getDefault()); + resource.calculatePath(routerOperation, Locale.getDefault(), openAPI); final List parameters = resource.getOpenApi(Locale.getDefault()).getPaths().get(PATH).getGet().getParameters(); assertThat(parameters.size(), is(3)); @@ -298,7 +298,7 @@ private static class EmptyPathsOpenApiResource extends AbstractOpenApiResource { } @Override - public void getPaths(Map findRestControllers, Locale locale) { + public void getPaths(Map findRestControllers, Locale locale, OpenAPI openAPI) { } } } \ No newline at end of file diff --git a/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/api/OpenApiResource.java b/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/api/OpenApiResource.java index 0f0528471..f524e2ce7 100644 --- a/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/api/OpenApiResource.java +++ b/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/api/OpenApiResource.java @@ -158,7 +158,7 @@ protected Mono openapiYaml(ServerHttpRequest serverHttpRequest, String a */ @Override @SuppressWarnings("unchecked") - protected void getPaths(Map restControllers, Locale locale) { + protected void getPaths(Map restControllers, Locale locale, OpenAPI openAPI) { Optional springWebProviderOptional = springDocProviders.getSpringWebProvider(); springWebProviderOptional.ifPresent(springWebProvider -> { Map map = springWebProvider.getHandlerMethods(); @@ -168,8 +168,8 @@ protected void getPaths(Map restControllers, Locale locale) { this.openAPIService.addTag(new HashSet<>(actuatorMap.values()), getTag()); map.putAll(actuatorMap); } - calculatePath(restControllers, map, locale); - getWebFluxRouterFunctionPaths(locale); + calculatePath(restControllers, map, locale, openAPI); + getWebFluxRouterFunctionPaths(locale, openAPI); }); } @@ -180,7 +180,7 @@ protected void getPaths(Map restControllers, Locale locale) { * @param map the map * @param locale the locale */ - protected void calculatePath(Map restControllers, Map map, Locale locale) { + protected void calculatePath(Map restControllers, Map map, Locale locale, OpenAPI openAPI) { TreeMap methodTreeMap = new TreeMap<>(byReversedRequestMappingInfos()); methodTreeMap.putAll(map); Optional springWebProviderOptional = springDocProviders.getSpringWebProvider(); @@ -201,7 +201,7 @@ && isFilterCondition(handlerMethod, operationPath, produces, consumes, headers)) // default allowed requestmethods if (requestMethods.isEmpty()) requestMethods = this.getDefaultAllowedHttpMethods(); - calculatePath(handlerMethod, operationPath, requestMethods, consumes, produces , headers, locale); + calculatePath(handlerMethod, operationPath, requestMethods, consumes, produces , headers, locale, openAPI); } } } @@ -221,13 +221,13 @@ private Comparator byReversedRequestMappingInfos() { * Gets web flux router function paths. * @param locale the locale */ - protected void getWebFluxRouterFunctionPaths(Locale locale) { + protected void getWebFluxRouterFunctionPaths(Locale locale, OpenAPI openAPI) { Map routerBeans = Objects.requireNonNull(openAPIService.getContext()).getBeansOfType(RouterFunction.class); for (Map.Entry entry : routerBeans.entrySet()) { RouterFunction routerFunction = entry.getValue(); RouterFunctionVisitor routerFunctionVisitor = new RouterFunctionVisitor(); routerFunction.accept(routerFunctionVisitor); - getRouterFunctionPaths(entry.getKey(), routerFunctionVisitor, locale); + getRouterFunctionPaths(entry.getKey(), routerFunctionVisitor, locale, openAPI); } } diff --git a/springdoc-openapi-starter-webmvc-api/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java b/springdoc-openapi-starter-webmvc-api/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java index 051e4c428..9dfd13d67 100644 --- a/springdoc-openapi-starter-webmvc-api/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java +++ b/springdoc-openapi-starter-webmvc-api/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java @@ -155,16 +155,16 @@ public String openapiYaml(HttpServletRequest request, @Override @SuppressWarnings("unchecked") - protected void getPaths(Map restControllers, Locale locale) { + protected void getPaths(Map restControllers, Locale locale, OpenAPI openAPI) { Optional springWebProviderOptional = springDocProviders.getSpringWebProvider(); springWebProviderOptional.ifPresent(springWebProvider -> { Map map = springWebProvider.getHandlerMethods(); Optional repositoryRestResourceProviderOptional = springDocProviders.getRepositoryRestResourceProvider(); repositoryRestResourceProviderOptional.ifPresent(restResourceProvider -> { - List operationList = restResourceProvider.getRouterOperations(openAPIService.getCalculatedOpenAPI(), locale); - calculatePath(operationList, locale); - restResourceProvider.customize(openAPIService.getCalculatedOpenAPI()); + List operationList = restResourceProvider.getRouterOperations(openAPI, locale); + calculatePath(operationList, locale, openAPI); + restResourceProvider.customize(openAPI); Map mapDataRest = restResourceProvider.getHandlerMethods(); Map requestMappingMap = restResourceProvider.getBasePathAwareControllerEndpoints(); Class[] additionalRestClasses = requestMappingMap.values().stream().map(AopUtils::getTargetClass).toArray(Class[]::new); @@ -178,7 +178,7 @@ protected void getPaths(Map restControllers, Locale locale) { this.openAPIService.addTag(new HashSet<>(actuatorMap.values()), getTag()); map.putAll(actuatorMap); } - calculatePath(restControllers, map, locale); + calculatePath(restControllers, map, locale, openAPI); }); Optional securityOAuth2ProviderOptional = springDocProviders.getSpringSecurityOAuth2Provider(); @@ -188,11 +188,11 @@ protected void getPaths(Map restControllers, Locale locale) { Map requestMappingMapSec = securityOAuth2Provider.getFrameworkEndpoints(); Class[] additionalRestClasses = requestMappingMapSec.values().stream().map(AopUtils::getTargetClass).toArray(Class[]::new); AbstractOpenApiResource.addRestControllers(additionalRestClasses); - calculatePath(requestMappingMapSec, mapOauth, locale); + calculatePath(requestMappingMapSec, mapOauth, locale, openAPI); } springDocProviders.getRouterFunctionProvider().ifPresent(routerFunctions -> routerFunctions.getRouterFunctionPaths() - .ifPresent(routerBeans -> routerBeans.forEach((beanName, routerFunctionVisitor) -> getRouterFunctionPaths(beanName, routerFunctionVisitor, locale)))); + .ifPresent(routerBeans -> routerBeans.forEach((beanName, routerFunctionVisitor) -> getRouterFunctionPaths(beanName, routerFunctionVisitor, locale, openAPI)))); } @@ -202,8 +202,9 @@ protected void getPaths(Map restControllers, Locale locale) { * @param restControllers the rest controllers * @param map the map * @param locale the locale + * @param openAPI the open api */ - protected void calculatePath(Map restControllers, Map map, Locale locale) { + protected void calculatePath(Map restControllers, Map map, Locale locale, OpenAPI openAPI) { TreeMap methodTreeMap = new TreeMap<>(byReversedRequestMappingInfos()); methodTreeMap.putAll(map); Optional springWebProviderOptional = springDocProviders.getSpringWebProvider(); @@ -225,7 +226,7 @@ && isFilterCondition(handlerMethod, operationPath, produces, consumes, headers)) // default allowed requestmethods if (requestMethods.isEmpty()) requestMethods = this.getDefaultAllowedHttpMethods(); - calculatePath(handlerMethod, operationPath, requestMethods, consumes, produces, headers, locale); + calculatePath(handlerMethod, operationPath, requestMethods, consumes, produces, headers, locale, openAPI); } } }