Skip to content

Commit

Permalink
Changes report: ConcurrentModificationException when querying /v3/api…
Browse files Browse the repository at this point in the history
…-docs/{group} concurrently for different groups. #1641
  • Loading branch information
bnasslahsen committed May 3, 2022
1 parent 4d7d4d0 commit 425eff2
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 98 deletions.
Expand Up @@ -138,12 +138,12 @@ public abstract class AbstractOpenApiResource extends SpecFilter {
/**
* The constant ADDITIONAL_REST_CONTROLLERS.
*/
private static final List<Class<?>> ADDITIONAL_REST_CONTROLLERS = new ArrayList<>();
private static final List<Class<?>> ADDITIONAL_REST_CONTROLLERS = Collections.synchronizedList(new ArrayList<>());

/**
* The constant HIDDEN_REST_CONTROLLERS.
*/
private static final List<Class<?>> HIDDEN_REST_CONTROLLERS = new ArrayList<>();
private static final List<Class<?>> HIDDEN_REST_CONTROLLERS = Collections.synchronizedList(new ArrayList<>());

/**
* The Open api builder.
Expand Down Expand Up @@ -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<String, Object> mappingsMap = openAPIService.getMappingsMap().entrySet().stream()
.filter(controller -> (AnnotationUtils.findAnnotation(controller.getValue().getClass(),
Hidden.class) == null))
Expand All @@ -319,33 +319,32 @@ protected synchronized OpenAPI getOpenApi(Locale locale) {

Map<String, Object> 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<CloudFunctionProvider> cloudFunctionProviderOptional = springDocProviders.getSpringCloudFunctionProvider();
cloudFunctionProviderOptional.ifPresent(cloudFunctionProvider -> {
List<RouterOperation> routerOperationList = cloudFunctionProvider.getRouterOperations(openApi);
List<RouterOperation> 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<Server> servers = openApi.getServers();
List<Server> servers = openAPI.getServers();
List<Server> serversCopy = null;
try {
serversCopy = springDocProviders.jsonMapper()
Expand All @@ -355,40 +354,41 @@ 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;
}

/**
* Gets paths.
*
* @param findRestControllers the find rest controllers
* @param locale the locale
* @param openAPI the open api
*/
protected abstract void getPaths(Map<String, Object> findRestControllers, Locale locale);
protected abstract void getPaths(Map<String, Object> findRestControllers, Locale locale, OpenAPI openAPI);

/**
* Calculate path.
*
* @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<RequestMethod> requestMethods = new HashSet<>(Arrays.asList(routerOperation.getMethods()));
io.swagger.v3.oas.annotations.Operation apiOperation = routerOperation.getOperation();
Expand All @@ -397,7 +397,6 @@ protected void calculatePath(HandlerMethod handlerMethod, RouterOperation router
String[] headers = routerOperation.getHeaders();
Map<String, String> queryParams = routerOperation.getQueryParams();

OpenAPI openAPI = openAPIService.getCalculatedOpenAPI();
Components components = openAPI.getComponents();
Paths paths = openAPI.getPaths();

Expand Down Expand Up @@ -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<RouterOperation> routerOperationList, Locale locale) {
protected void calculatePath(List<RouterOperation> routerOperationList, Locale locale, OpenAPI openAPI) {
ApplicationContext applicationContext = openAPIService.getContext();
if (!CollectionUtils.isEmpty(routerOperationList)) {
Collections.sort(routerOperationList);
Expand Down Expand Up @@ -547,14 +547,14 @@ protected void calculatePath(List<RouterOperation> 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);
}
}
}
Expand All @@ -566,15 +566,14 @@ 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();
String[] methodProduces = routerOperation.getProduces();
String[] headers = routerOperation.getHeaders();
Map<String, String> queryParams = routerOperation.getQueryParams();

OpenAPI openAPI = openAPIService.getCalculatedOpenAPI();
Paths paths = openAPI.getPaths();
Map<HttpMethod, Operation> operationMap = null;
if (paths.containsKey(operationPath)) {
Expand Down Expand Up @@ -620,8 +619,8 @@ protected void calculatePath(RouterOperation routerOperation, Locale locale) {
* @param locale the locale
*/
protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
Set<RequestMethod> 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<RequestMethod> 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);
}

/**
Expand All @@ -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<RouterOperation> operationList = routerFunctionVisitor.getRouterFunctionDatas().stream().map(RouterOperation::new).collect(Collectors.toList());
calculatePath(operationList, locale);
calculatePath(operationList, locale, openAPI);
}
else {
List<org.springdoc.core.annotations.RouterOperation> routerOperationList = new ArrayList<>();
Expand All @@ -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<RouterOperation> operationList = routerOperationList.stream().map(RouterOperation::new).collect(Collectors.toList());
mergeRouters(routerFunctionVisitor.getRouterFunctionDatas(), operationList);
calculatePath(operationList, locale);
calculatePath(operationList, locale, openAPI);
}
}
}
Expand Down
Expand Up @@ -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;
Expand All @@ -42,17 +43,17 @@ public class ConverterUtils {
/**
* The constant RESULT_WRAPPERS_TO_IGNORE.
*/
private static final List<Class<?>> RESULT_WRAPPERS_TO_IGNORE = new ArrayList<>();
private static final List<Class<?>> RESULT_WRAPPERS_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());

/**
* The constant RESPONSE_TYPES_TO_IGNORE.
*/
private static final List<Class<?>> RESPONSE_TYPES_TO_IGNORE = new ArrayList<>();
private static final List<Class<?>> RESPONSE_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());

/**
* The constant FLUX_WRAPPERS_TO_IGNORE.
*/
private static final List<Class<?>> FLUX_WRAPPERS_TO_IGNORE = new ArrayList<>();
private static final List<Class<?>> FLUX_WRAPPERS_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());

static {
RESULT_WRAPPERS_TO_IGNORE.add(Callable.class);
Expand Down
Expand Up @@ -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;
Expand All @@ -47,7 +48,7 @@ public class SchemaPropertyDeprecatingConverter implements ModelConverter {
/**
* The constant DEPRECATED_ANNOTATIONS.
*/
private static final List<Class<? extends Annotation>> DEPRECATED_ANNOTATIONS = new ArrayList<>();
private static final List<Class<? extends Annotation>> DEPRECATED_ANNOTATIONS = Collections.synchronizedList(new ArrayList<>());

static {
DEPRECATED_ANNOTATIONS.add(Deprecated.class);
Expand Down
Expand Up @@ -476,7 +476,7 @@ public GroupedOpenApi build() {
public GroupedOpenApi addAllOpenApiCustomizer(Collection<? extends OpenApiCustomizer> openApiCustomizerCollection) {
List<OpenApiCustomizer> result = new ArrayList<>();
result.addAll(openApiCustomizerCollection);
result.addAll(openApiCustomizerCollection);
result.addAll(openApiCustomizers);
openApiCustomizers = result;
return this;
}
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -104,7 +105,7 @@ public abstract class AbstractRequestService {
/**
* The constant PARAM_TYPES_TO_IGNORE.
*/
private static final List<Class<?>> PARAM_TYPES_TO_IGNORE = new ArrayList<>();
private static final List<Class<?>> PARAM_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());

/**
* The constant ANNOTATIONS_FOR_REQUIRED.
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -83,7 +84,7 @@ public class GenericParameterService {
/**
* The constant FILE_TYPES.
*/
private static final List<Class<?>> FILE_TYPES = new ArrayList<>();
private static final List<Class<?>> FILE_TYPES = Collections.synchronizedList(new ArrayList<>());

/**
* The Optional delegating method parameter customizer.
Expand Down
Expand Up @@ -248,7 +248,9 @@ public void buildGenericResponse(Components components, Map<String, Object> find
apiResponses.forEach(controllerAdviceInfoApiResponseMap::put);
}
}
controllerAdviceInfos.add(controllerAdviceInfo);
synchronized (this) {
controllerAdviceInfos.add(controllerAdviceInfo);
}
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -640,9 +643,9 @@ else if (returnType instanceof ParameterizedType) {
* @param beanType the bean type
* @return the generic map response
*/
private Map<String, ApiResponse> getGenericMapResponse(Class<?> beanType) {
private synchronized Map<String, ApiResponse> 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);
}
Expand Down

0 comments on commit 425eff2

Please sign in to comment.