All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springdoc.api.AbstractOpenApiResource Maven / Gradle / Ivy

The newest version!
/*
 *
 *  *
 *  *  *
 *  *  *  * Copyright 2019-2022 the original author or authors.
 *  *  *  *
 *  *  *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  *  *  * you may not use this file except in compliance with the License.
 *  *  *  * You may obtain a copy of the License at
 *  *  *  *
 *  *  *  *      https://www.apache.org/licenses/LICENSE-2.0
 *  *  *  *
 *  *  *  * Unless required by applicable law or agreed to in writing, software
 *  *  *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  *  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  *  * See the License for the specific language governing permissions and
 *  *  *  * limitations under the License.
 *  *  *
 *  *
 *
 */

package org.springdoc.api;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature;
import io.swagger.v3.core.filter.SpecFilter;
import io.swagger.v3.core.util.ReflectionUtils;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.callbacks.Callback;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.PathItem.HttpMethod;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.SpecVersion;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.servers.Server;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.AbstractRequestService;
import org.springdoc.core.GenericParameterService;
import org.springdoc.core.GenericResponseService;
import org.springdoc.core.MethodAttributes;
import org.springdoc.core.OpenAPIService;
import org.springdoc.core.OperationService;
import org.springdoc.core.SpringDocConfigProperties;
import org.springdoc.core.SpringDocConfigProperties.ApiDocs.OpenApiVersion;
import org.springdoc.core.SpringDocConfigProperties.GroupConfig;
import org.springdoc.core.SpringDocProviders;
import org.springdoc.core.SpringDocUtils;
import org.springdoc.core.annotations.RouterOperations;
import org.springdoc.core.customizers.DataRestRouterOperationCustomizer;
import org.springdoc.core.customizers.OpenApiLocaleCustomizer;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springdoc.core.customizers.RouterOperationCustomizer;
import org.springdoc.core.customizers.SpringDocCustomizers;
import org.springdoc.core.fn.AbstractRouterFunctionVisitor;
import org.springdoc.core.fn.RouterFunctionData;
import org.springdoc.core.fn.RouterOperation;
import org.springdoc.core.providers.ActuatorProvider;
import org.springdoc.core.providers.CloudFunctionProvider;
import org.springdoc.core.providers.JavadocProvider;
import org.springdoc.core.providers.ObjectMapperProvider;

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;

import static org.springdoc.core.Constants.ACTUATOR_DEFAULT_GROUP;
import static org.springdoc.core.Constants.DOT;
import static org.springdoc.core.Constants.OPERATION_ATTRIBUTE;
import static org.springdoc.core.Constants.SPRING_MVC_SERVLET_PATH;
import static org.springdoc.core.converters.SchemaPropertyDeprecatingConverter.isDeprecated;
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;

/**
 * The type Abstract open api resource.
 * @author bnasslahsen
 * @author kevinraddatz
 * @author hyeonisism
 */
public abstract class AbstractOpenApiResource extends SpecFilter {

	/**
	 * The constant LOGGER.
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOpenApiResource.class);

	/**
	 * The constant ADDITIONAL_REST_CONTROLLERS.
	 */
	private static final Set> ADDITIONAL_REST_CONTROLLERS = new CopyOnWriteArraySet<>();

	/**
	 * The constant HIDDEN_REST_CONTROLLERS.
	 */
	private static final Set> HIDDEN_REST_CONTROLLERS = new CopyOnWriteArraySet<>();

	/**
	 * The constant MODEL_AND_VIEW_CLASS.
	 */
	private static Class modelAndViewClass;

	/**
	 * The Spring doc config properties.
	 */
	protected final SpringDocConfigProperties springDocConfigProperties;

	/**
	 * The Group name.
	 */
	protected final String groupName;

	/**
	 * The Spring doc providers.
	 */
	protected final SpringDocProviders springDocProviders;

	/**
	 * The open api builder object factory.
	 */
	private final ObjectFactory openAPIBuilderObjectFactory;

	/**
	 * The Request builder.
	 */
	private final AbstractRequestService requestBuilder;

	/**
	 * The Response builder.
	 */
	private final GenericResponseService responseBuilder;

	/**
	 * The Operation parser.
	 */
	private final OperationService operationParser;

	/**
	 * The Ant path matcher.
	 */
	private final AntPathMatcher antPathMatcher = new AntPathMatcher();


	/**
	 * The Open api builder.
	 */
	protected OpenAPIService openAPIService;

	/**
	 * The Spring doc customizers.
	 */
	protected final SpringDocCustomizers springDocCustomizers;

	/**
	 * The Reentrant lock.
	 */
	private final Lock reentrantLock = new ReentrantLock();

	/**
	 * Instantiates a new Abstract open api resource.
	 *
	 * @param groupName the group name
	 * @param openAPIBuilderObjectFactory the open api builder object factory
	 * @param requestBuilder the request builder
	 * @param responseBuilder the response builder
	 * @param operationParser the operation parser
	 * @param springDocConfigProperties the spring doc config properties
	 * @param springDocProviders the spring doc providers
	 * @param springDocCustomizers the spring doc customizers
	 */
	protected AbstractOpenApiResource(String groupName, ObjectFactory openAPIBuilderObjectFactory,
			AbstractRequestService requestBuilder,
			GenericResponseService responseBuilder, OperationService operationParser,
			SpringDocConfigProperties springDocConfigProperties,
			SpringDocProviders springDocProviders, SpringDocCustomizers springDocCustomizers) {
		super();
		this.groupName = Objects.requireNonNull(groupName, "groupName");
		this.openAPIBuilderObjectFactory = openAPIBuilderObjectFactory;
		this.openAPIService = openAPIBuilderObjectFactory.getObject();
		this.requestBuilder = requestBuilder;
		this.responseBuilder = responseBuilder;
		this.operationParser = operationParser;
		this.springDocProviders = springDocProviders;
		this.springDocConfigProperties = springDocConfigProperties;
		this.springDocCustomizers=springDocCustomizers;
		if (springDocConfigProperties.isPreLoadingEnabled()) {
			if (CollectionUtils.isEmpty(springDocConfigProperties.getPreLoadingLocales())) {
				Executors.newSingleThreadExecutor().execute(this::getOpenApi);
			} else {
				for (String locale : springDocConfigProperties.getPreLoadingLocales()) {
					Executors.newSingleThreadExecutor().execute(() -> this.getOpenApi(Locale.forLanguageTag(locale)));
				}
			}
		}	}

	/**
	 * Add rest controllers.
	 *
	 * @param classes the classes
	 */
	public static void addRestControllers(Class... classes) {
		ADDITIONAL_REST_CONTROLLERS.addAll(Arrays.asList(classes));
	}

	/**
	 * Add hidden rest controllers.
	 *
	 * @param classes the classes
	 */
	public static void addHiddenRestControllers(Class... classes) {
		HIDDEN_REST_CONTROLLERS.addAll(Arrays.asList(classes));
	}

	/**
	 * Add hidden rest controllers.
	 *
	 * @param classes the classes
	 */
	public static void addHiddenRestControllers(String... classes) {
		Set> hiddenClasses = new HashSet<>();
		for (String aClass : classes) {
			try {
				hiddenClasses.add(Class.forName(aClass));
			}
			catch (ClassNotFoundException e) {
				LOGGER.warn("The following class doesn't exist and cannot be hidden: {}", aClass);
			}
		}
		HIDDEN_REST_CONTROLLERS.addAll(hiddenClasses);
	}

	/**
	 * Contains response body boolean.
	 *
	 * @param handlerMethod the handler method
	 * @return the boolean
	 */
	public static boolean containsResponseBody(HandlerMethod handlerMethod) {
		ResponseBody responseBodyAnnotation = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), ResponseBody.class);
		if (responseBodyAnnotation == null)
			responseBodyAnnotation = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), ResponseBody.class);
		return responseBodyAnnotation != null;
	}

	/**
	 * Is hidden rest controllers boolean.
	 *
	 * @param rawClass the raw class
	 * @return the boolean
	 */
	public static boolean isHiddenRestControllers(Class rawClass) {
		return HIDDEN_REST_CONTROLLERS.stream().anyMatch(clazz -> clazz.isAssignableFrom(rawClass));
	}

	/**
	 * Sets model and view class.
	 *
	 * @param modelAndViewClass the model and view class
	 */
	public static void setModelAndViewClass(Class modelAndViewClass) {
		AbstractOpenApiResource.modelAndViewClass = modelAndViewClass;
	}

	/**
	 * Gets open api.
	 */
	private void getOpenApi() {
		this.getOpenApi(Locale.getDefault());
	}

	/**
	 * Gets open api.
	 * @param locale the locale
	 * @return the open api
	 */
	protected OpenAPI getOpenApi(Locale locale) {
		this.reentrantLock.lock();
		try {
			final OpenAPI openAPI;
			final Locale finalLocale = locale == null ? Locale.getDefault() : locale;
			if (openAPIService.getCachedOpenAPI(finalLocale) == null || springDocConfigProperties.isCacheDisabled()) {
				Instant start = Instant.now();
				openAPI = openAPIService.build(finalLocale);
				Map mappingsMap = openAPIService.getMappingsMap().entrySet().stream()
						.filter(controller -> (AnnotationUtils.findAnnotation(controller.getValue().getClass(),
								Hidden.class) == null))
						.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getClass()))
						.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));

				Map findControllerAdvice = openAPIService.getControllerAdviceMap();
				if (OpenApiVersion.OPENAPI_3_1 == springDocConfigProperties.getApiDocs().getVersion()){
					openAPI.openapi(OpenApiVersion.OPENAPI_3_1.getVersion());
					openAPI.specVersion(SpecVersion.V31);
				}
				if (springDocConfigProperties.isDefaultOverrideWithGenericResponse()) {
					if (!CollectionUtils.isEmpty(mappingsMap))
						findControllerAdvice.putAll(mappingsMap);
					responseBuilder.buildGenericResponse(openAPI.getComponents(), findControllerAdvice, finalLocale);
				}
				getPaths(mappingsMap, finalLocale, openAPI);

				Optional cloudFunctionProviderOptional = springDocProviders.getSpringCloudFunctionProvider();
				cloudFunctionProviderOptional.ifPresent(cloudFunctionProvider -> {
							List routerOperationList = cloudFunctionProvider.getRouterOperations(openAPI);
							if (!CollectionUtils.isEmpty(routerOperationList))
								this.calculatePath(routerOperationList, locale, openAPI);
						}
				);
				if (!CollectionUtils.isEmpty(openAPI.getServers()))
					openAPIService.setServersPresent(true);
				else
					openAPIService.setServersPresent(false);
				openAPIService.updateServers(openAPI);

				if (springDocConfigProperties.isRemoveBrokenReferenceDefinitions())
					this.removeBrokenReferenceDefinitions(openAPI);

				// run the optional customisers
				List servers = openAPI.getServers();
				List serversCopy = null;
				try {
					serversCopy = springDocProviders.jsonMapper()
							.readValue(springDocProviders.jsonMapper().writeValueAsString(servers), new TypeReference>() {});
				}
				catch (JsonProcessingException e) {
					LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage());
				}

				openAPIService.getContext().getBeansOfType(OpenApiLocaleCustomizer.class).values().forEach(openApiLocaleCustomizer -> openApiLocaleCustomizer.customise(openAPI, finalLocale));
				springDocCustomizers.getOpenApiCustomizers().ifPresent(apiCustomisers -> apiCustomisers.forEach(openApiCustomiser -> openApiCustomiser.customise(openAPI)));
				if (!CollectionUtils.isEmpty(openAPI.getServers()) && !openAPI.getServers().equals(serversCopy))
					openAPIService.setServersPresent(true);

				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.getCachedOpenAPI(finalLocale);
				openAPIService.updateServers(openAPI);
			}

			return openAPI;
		}
		finally {
			this.reentrantLock.unlock();
		}
	}

	/**
	 * Gets paths.
	 *
	 * @param findRestControllers the find rest controllers
	 * @param locale the locale
	 * @param openAPI the open api
	 */
	protected abstract void getPaths(Map 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, OpenAPI openAPI) {

		routerOperation = customizeRouterOperation(routerOperation, handlerMethod);

		String operationPath = routerOperation.getPath();
		Set requestMethods = new TreeSet<>(Arrays.asList(routerOperation.getMethods()));
		io.swagger.v3.oas.annotations.Operation apiOperation = routerOperation.getOperation();
		String[] methodConsumes = routerOperation.getConsumes();
		String[] methodProduces = routerOperation.getProduces();
		String[] headers = routerOperation.getHeaders();
		Map queryParams = routerOperation.getQueryParams();

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

		Map operationMap = null;
		if (paths.containsKey(operationPath)) {
			PathItem pathItem = paths.get(operationPath);
			operationMap = pathItem.readOperationsMap();
		}

		JavadocProvider javadocProvider = operationParser.getJavadocProvider();

		for (RequestMethod requestMethod : requestMethods) {
			Operation existingOperation = getExistingOperation(operationMap, requestMethod);
			Method method = handlerMethod.getMethod();
			// skip hidden operations
			if (operationParser.isHidden(method))
				continue;

			RequestMapping reqMappingClass = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(),
					RequestMapping.class);

			MethodAttributes methodAttributes = new MethodAttributes(springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType(), methodConsumes, methodProduces, headers, locale);
			methodAttributes.setMethodOverloaded(existingOperation != null);
			//Use the javadoc return if present
			if (javadocProvider != null) {
				methodAttributes.setJavadocReturn(javadocProvider.getMethodJavadocReturn(handlerMethod.getMethod()));
			}

			if (reqMappingClass != null) {
				methodAttributes.setClassConsumes(reqMappingClass.consumes());
				methodAttributes.setClassProduces(reqMappingClass.produces());
			}

			methodAttributes.calculateHeadersForClass(method.getDeclaringClass());
			methodAttributes.calculateConsumesProduces(method);

			Operation operation = (existingOperation != null) ? existingOperation : new Operation();

			if (isDeprecated(method))
				operation.setDeprecated(true);

			// Add documentation from operation annotation
			if (apiOperation == null || StringUtils.isBlank(apiOperation.operationId()))
				apiOperation = AnnotatedElementUtils.findMergedAnnotation(method,
						io.swagger.v3.oas.annotations.Operation.class);

			calculateJsonView(apiOperation, methodAttributes, method);
			if (apiOperation != null)
				openAPI = operationParser.parse(apiOperation, operation, openAPI, methodAttributes);
			fillParametersList(operation, queryParams, methodAttributes);

			// compute tags
			operation = openAPIService.buildTags(handlerMethod, operation, openAPI, locale);

			io.swagger.v3.oas.annotations.parameters.RequestBody requestBodyDoc = AnnotatedElementUtils.findMergedAnnotation(method,
					io.swagger.v3.oas.annotations.parameters.RequestBody.class);

			// RequestBody in Operation
			requestBuilder.getRequestBodyBuilder()
					.buildRequestBodyFromDoc(requestBodyDoc, methodAttributes, components,
							methodAttributes.getJsonViewAnnotationForRequestBody(), locale)
					.ifPresent(operation::setRequestBody);
			// requests
			operation = requestBuilder.build(handlerMethod, requestMethod, operation, methodAttributes, openAPI);

			// responses
			ApiResponses apiResponses = responseBuilder.build(components, handlerMethod, operation, methodAttributes);
			operation.setResponses(apiResponses);

			// get javadoc method description
			if (javadocProvider != null) {
				String description = javadocProvider.getMethodJavadocDescription(handlerMethod.getMethod());
				String summary = javadocProvider.getFirstSentence(description);
				boolean emptyOverrideDescription = StringUtils.isEmpty(operation.getDescription());
				boolean emptyOverrideSummary = StringUtils.isEmpty(operation.getSummary());
				if (!StringUtils.isEmpty(description) && emptyOverrideDescription) {
					operation.setDescription(description);
				}
				// if there is a previously set description
				// but no summary then it is intentional
				// we keep it as is
				if (!StringUtils.isEmpty(summary) && emptyOverrideSummary && emptyOverrideDescription) {
					operation.setSummary(javadocProvider.getFirstSentence(description));
				}
			}

			Set apiCallbacks = AnnotatedElementUtils.findMergedRepeatableAnnotations(method, io.swagger.v3.oas.annotations.callbacks.Callback.class);

			// callbacks
			buildCallbacks(openAPI, methodAttributes, operation, apiCallbacks);

			// allow for customisation
			operation = customizeOperation(operation, handlerMethod);

			PathItem pathItemObject = buildPathItem(requestMethod, operation, operationPath, paths);
			paths.addPathItem(operationPath, pathItemObject);
		}
	}

	/**
	 * Build callbacks.
	 *
	 * @param openAPI the open api
	 * @param methodAttributes the method attributes
	 * @param operation the operation
	 * @param apiCallbacks the api callbacks
	 */
	private void buildCallbacks(OpenAPI openAPI, MethodAttributes methodAttributes, Operation operation, Set apiCallbacks) {
		if (!CollectionUtils.isEmpty(apiCallbacks))
			operationParser.buildCallbacks(apiCallbacks, openAPI, methodAttributes)
					.ifPresent(operation::setCallbacks);
	}

	/**
	 * Calculate path.
	 *
	 * @param routerOperationList the router operation list
	 * @param locale the locale
	 * @param openAPI the open api
	 */
	protected void calculatePath(List routerOperationList, Locale locale, OpenAPI openAPI) {
		ApplicationContext applicationContext = openAPIService.getContext();
		if (!CollectionUtils.isEmpty(routerOperationList)) {
			Collections.sort(routerOperationList);
			for (RouterOperation routerOperation : routerOperationList) {
				if (routerOperation.getBeanClass() != null && !Void.class.equals(routerOperation.getBeanClass())) {
					Object handlerBean = applicationContext.getBean(routerOperation.getBeanClass());
					HandlerMethod handlerMethod = null;

					if (StringUtils.isNotBlank(routerOperation.getBeanMethod())) {
						try {
							if (ArrayUtils.isEmpty(routerOperation.getParameterTypes())) {
								Method[] declaredMethods = AopUtils.getTargetClass(handlerBean).getDeclaredMethods();
								Optional methodOptional = Arrays.stream(declaredMethods)
										.filter(method -> routerOperation.getBeanMethod().equals(method.getName()) && method.getParameters().length == 0)
										.findAny();
								if (!methodOptional.isPresent())
									methodOptional = Arrays.stream(declaredMethods)
											.filter(method1 -> routerOperation.getBeanMethod().equals(method1.getName()))
											.findAny();
								if (methodOptional.isPresent())
									handlerMethod = new HandlerMethod(handlerBean, methodOptional.get());
							}
							else
								handlerMethod = new HandlerMethod(handlerBean, routerOperation.getBeanMethod(), routerOperation.getParameterTypes());
						}
						catch (NoSuchMethodException e) {
							LOGGER.error(e.getMessage());
						}
						if (handlerMethod != null && isFilterCondition(handlerMethod, routerOperation.getPath(), routerOperation.getProduces(), routerOperation.getConsumes(), routerOperation.getHeaders()))
							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, openAPI);
				}
				else if (routerOperation.getOperationModel() != null && StringUtils.isNotBlank(routerOperation.getOperationModel().getOperationId()) && isFilterCondition(routerOperation.getPath(), routerOperation.getProduces(), routerOperation.getConsumes(), routerOperation.getHeaders())) {
					calculatePath(routerOperation, locale, openAPI);
				}
			}
		}
	}

	/**
	 * Calculate path.
	 *
	 * @param routerOperation the router operation
	 * @param locale the locale
	 */
	protected void calculatePath(RouterOperation routerOperation, Locale locale, OpenAPI openAPI) {
		routerOperation = customizeDataRestRouterOperation(routerOperation);

		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 queryParams = routerOperation.getQueryParams();

		Paths paths = openAPI.getPaths();
		Map operationMap = null;
		if (paths.containsKey(operationPath)) {
			PathItem pathItem = paths.get(operationPath);
			operationMap = pathItem.readOperationsMap();
		}
		for (RequestMethod requestMethod : routerOperation.getMethods()) {
			Operation existingOperation = getExistingOperation(operationMap, requestMethod);
			MethodAttributes methodAttributes = new MethodAttributes(springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType(), methodConsumes, methodProduces, headers, locale);
			methodAttributes.setMethodOverloaded(existingOperation != null);
			Operation operation = getOperation(routerOperation, existingOperation);
			if (apiOperation != null)
				openAPI = operationParser.parse(apiOperation, operation, openAPI, methodAttributes);

			String operationId = operationParser.getOperationId(operation.getOperationId(), openAPI);
			operation.setOperationId(operationId);

			fillParametersList(operation, queryParams, methodAttributes);
			if (!CollectionUtils.isEmpty(operation.getParameters()))
				operation.getParameters().stream()
						.filter(parameter -> StringUtils.isEmpty(parameter.get$ref()))
						.forEach(parameter -> {
									if (parameter.getSchema() == null)
										parameter.setSchema(new StringSchema());
									if (parameter.getIn() == null)
										parameter.setIn(ParameterIn.QUERY.toString());
								}
						);
			PathItem pathItemObject = buildPathItem(requestMethod, operation, operationPath, paths);
			paths.addPathItem(operationPath, pathItemObject);
		}
	}

	/**
	 * Calculate path.
	 *
	 * @param handlerMethod the handler method
	 * @param operationPath the operation path
	 * @param requestMethods the request methods
	 * @param consumes the consumes
	 * @param produces the produces
	 * @param headers the headers
	 * @param params the params
	 * @param locale the locale
	 * @param openAPI the open api
	 */
	protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
			Set requestMethods, String[] consumes, String[] produces, String[] headers, String[] params, Locale locale, OpenAPI openAPI) {
		this.calculatePath(handlerMethod,
				new RouterOperation(operationPath, requestMethods.toArray(new RequestMethod[requestMethods.size()]), consumes, produces, headers, params),
				locale, openAPI);
	}

	/**
	 * Gets router function paths.
	 *
	 * @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, 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, openAPI);
		}
		else {
			List routerOperationList = new ArrayList<>();
			ApplicationContext applicationContext = openAPIService.getContext();
			RouterOperations routerOperations = applicationContext.findAnnotationOnBean(beanName, RouterOperations.class);
			if (routerOperations == null) {
				org.springdoc.core.annotations.RouterOperation routerOperation = applicationContext.findAnnotationOnBean(beanName, org.springdoc.core.annotations.RouterOperation.class);
				if (routerOperation != null)
					routerOperationList.add(routerOperation);
			}
			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, openAPI);
			else {
				List operationList = routerOperationList.stream().map(RouterOperation::new).collect(Collectors.toList());
				mergeRouters(routerFunctionVisitor.getRouterFunctionDatas(), operationList);
				calculatePath(operationList, locale, openAPI);
			}
		}
	}

	/**
	 * Is filter condition boolean.
	 *
	 * @param handlerMethod the handler method
	 * @param operationPath the operation path
	 * @param produces the produces
	 * @param consumes the consumes
	 * @param headers the headers
	 * @return the boolean
	 */
	protected boolean isFilterCondition(HandlerMethod handlerMethod, String operationPath, String[] produces, String[] consumes, String[] headers) {
		return isMethodToFilter(handlerMethod)
				&& isPackageToScan(handlerMethod.getBeanType().getPackage())
				&& isFilterCondition(operationPath, produces, consumes, headers);
	}

	/**
	 * Is target method suitable for inclusion in current documentation/
	 *
	 * @param handlerMethod the method to check
	 * @return whether the method should be included in the current OpenAPI definition
	 */
	protected boolean isMethodToFilter(HandlerMethod handlerMethod) {
		return this.springDocCustomizers.getMethodFilters()
				.map(Collection::stream)
				.map(stream -> stream.allMatch(m -> m.isMethodToInclude(handlerMethod.getMethod())))
				.orElse(true);
	}

	/**
	 * Is condition to match boolean.
	 *
	 * @param existingConditions the existing conditions
	 * @param conditionType the condition type
	 * @return the boolean
	 */
	protected boolean isConditionToMatch(String[] existingConditions, ConditionType conditionType) {
		List conditionsToMatch = getConditionsToMatch(conditionType);
		if (CollectionUtils.isEmpty(conditionsToMatch)) {
			Optional optionalGroupConfig = springDocConfigProperties.getGroupConfigs().stream().filter(groupConfig -> this.groupName.equals(groupConfig.getGroup())).findAny();
			if (optionalGroupConfig.isPresent())
				conditionsToMatch = getConditionsToMatch(conditionType, optionalGroupConfig.get());
		}
		return CollectionUtils.isEmpty(conditionsToMatch)
				|| (!ArrayUtils.isEmpty(existingConditions) && conditionsToMatch.size() == existingConditions.length && conditionsToMatch.containsAll(Arrays.asList(existingConditions)));
	}

	/**
	 * Is package to scan boolean.
	 *
	 * @param aPackage the a package
	 * @return the boolean
	 */
	protected boolean isPackageToScan(Package aPackage) {
		if (aPackage == null)
			return true;
		final String packageName = aPackage.getName();
		List packagesToScan = springDocConfigProperties.getPackagesToScan();
		List packagesToExclude = springDocConfigProperties.getPackagesToExclude();
		if (CollectionUtils.isEmpty(packagesToScan)) {
			Optional optionalGroupConfig = springDocConfigProperties.getGroupConfigs().stream().filter(groupConfig -> this.groupName.equals(groupConfig.getGroup())).findAny();
			if (optionalGroupConfig.isPresent())
				packagesToScan = optionalGroupConfig.get().getPackagesToScan();
		}
		if (CollectionUtils.isEmpty(packagesToExclude)) {
			Optional optionalGroupConfig = springDocConfigProperties.getGroupConfigs().stream().filter(groupConfig -> this.groupName.equals(groupConfig.getGroup())).findAny();
			if (optionalGroupConfig.isPresent())
				packagesToExclude = optionalGroupConfig.get().getPackagesToExclude();
		}
		boolean include = CollectionUtils.isEmpty(packagesToScan)
				|| packagesToScan.stream().anyMatch(pack -> packageName.equals(pack)
				|| packageName.startsWith(pack + DOT));
		boolean exclude = !CollectionUtils.isEmpty(packagesToExclude)
				&& (packagesToExclude.stream().anyMatch(pack -> packageName.equals(pack)
				|| packageName.startsWith(pack + DOT)));

		return include && !exclude;
	}

	/**
	 * Is path to match boolean.
	 *
	 * @param operationPath the operation path
	 * @return the boolean
	 */
	protected boolean isPathToMatch(String operationPath) {
		List pathsToMatch = springDocConfigProperties.getPathsToMatch();
		List pathsToExclude = springDocConfigProperties.getPathsToExclude();
		if (CollectionUtils.isEmpty(pathsToMatch)) {
			Optional optionalGroupConfig = springDocConfigProperties.getGroupConfigs().stream().filter(groupConfig -> this.groupName.equals(groupConfig.getGroup())).findAny();
			if (optionalGroupConfig.isPresent())
				pathsToMatch = optionalGroupConfig.get().getPathsToMatch();
		}
		if (CollectionUtils.isEmpty(pathsToExclude)) {
			Optional optionalGroupConfig = springDocConfigProperties.getGroupConfigs().stream().filter(groupConfig -> this.groupName.equals(groupConfig.getGroup())).findAny();
			if (optionalGroupConfig.isPresent())
				pathsToExclude = optionalGroupConfig.get().getPathsToExclude();
		}
		boolean include = CollectionUtils.isEmpty(pathsToMatch) || pathsToMatch.stream().anyMatch(pattern -> antPathMatcher.match(pattern, operationPath));
		boolean exclude = !CollectionUtils.isEmpty(pathsToExclude) && pathsToExclude.stream().anyMatch(pattern -> antPathMatcher.match(pattern, operationPath));
		return include && !exclude;
	}

	/**
	 * Decode string.
	 *
	 * @param requestURI the request uri
	 * @return the string
	 */
	protected String decode(String requestURI) {
		try {
			return URLDecoder.decode(requestURI, StandardCharsets.UTF_8.toString());
		}
		catch (UnsupportedEncodingException e) {
			return requestURI;
		}
	}

	/**
	 * Is additional rest controller boolean.
	 *
	 * @param rawClass the raw class
	 * @return the boolean
	 */
	protected boolean isAdditionalRestController(Class rawClass) {
		return ADDITIONAL_REST_CONTROLLERS.stream().anyMatch(clazz -> clazz.isAssignableFrom(rawClass));
	}

	/**
	 * Is rest controller boolean.
	 *
	 * @param restControllers the rest controllers
	 * @param handlerMethod the handler method
	 * @param operationPath the operation path
	 * @return the boolean
	 */
	protected boolean isRestController(Map restControllers, HandlerMethod handlerMethod,
			String operationPath) {
		boolean hasOperationAnnotation = AnnotatedElementUtils.hasAnnotation(handlerMethod.getMethod(), io.swagger.v3.oas.annotations.Operation.class);

		return ((containsResponseBody(handlerMethod) || hasOperationAnnotation) && restControllers.containsKey(handlerMethod.getBean().toString()) || isAdditionalRestController(handlerMethod.getBeanType()))
				&& operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
				&& (springDocConfigProperties.isModelAndViewAllowed() || modelAndViewClass == null || !modelAndViewClass.isAssignableFrom(handlerMethod.getMethod().getReturnType()));
	}

	/**
	 * Gets default allowed http methods.
	 *
	 * @return the default allowed http methods
	 */
	protected Set getDefaultAllowedHttpMethods() {
		RequestMethod[] allowedRequestMethods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.OPTIONS, RequestMethod.HEAD };
		return new HashSet<>(Arrays.asList(allowedRequestMethods));
	}

	/**
	 * Customise operation operation.
	 *
	 * @param operation the operation
	 * @param handlerMethod the handler method
	 * @return the operation
	 */
	protected Operation customizeOperation(Operation operation, HandlerMethod handlerMethod) {
		Optional> optionalOperationCustomizers = springDocCustomizers.getOperationCustomizers();
		if (optionalOperationCustomizers.isPresent()) {
			List operationCustomizerList = optionalOperationCustomizers.get();
			for (OperationCustomizer operationCustomizer : operationCustomizerList)
				operation = operationCustomizer.customize(operation, handlerMethod);
		}
		return operation;
	}

	/**
	 * Customise router operation
	 *
	 * @param routerOperation the router operation
	 * @param handlerMethod the handler method
	 * @return the router operation
	 */
	protected RouterOperation customizeRouterOperation(RouterOperation routerOperation, HandlerMethod handlerMethod) {
		Optional> optionalRouterOperationCustomizers = springDocCustomizers.getRouterOperationCustomizers();
		if (optionalRouterOperationCustomizers.isPresent()) {
			List routerOperationCustomizerList = optionalRouterOperationCustomizers.get();
			for (RouterOperationCustomizer routerOperationCustomizer : routerOperationCustomizerList) {
				routerOperation = routerOperationCustomizer.customize(routerOperation, handlerMethod);
			}
		}
		return routerOperation;
	}

	/**
	 * Merge routers.
	 *
	 * @param routerFunctionDatas the router function datas
	 * @param routerOperationList the router operation list
	 */
	protected void mergeRouters(List routerFunctionDatas, List routerOperationList) {
		for (RouterOperation routerOperation : routerOperationList) {
			if (StringUtils.isNotBlank(routerOperation.getPath())) {
				// PATH
				List routerFunctionDataList = routerFunctionDatas.stream()
						.filter(routerFunctionData1 -> routerFunctionData1.getPath().equals(routerOperation.getPath()))
						.collect(Collectors.toList());
				if (routerFunctionDataList.size() == 1)
					fillRouterOperation(routerFunctionDataList.get(0), routerOperation);
				else if (routerFunctionDataList.size() > 1 && ArrayUtils.isNotEmpty(routerOperation.getMethods())) {
					// PATH + METHOD
					routerFunctionDataList = routerFunctionDatas.stream()
							.filter(routerFunctionData1 -> routerFunctionData1.getPath().equals(routerOperation.getPath())
									&& isEqualMethods(routerOperation.getMethods(), routerFunctionData1.getMethods()))
							.collect(Collectors.toList());
					if (routerFunctionDataList.size() == 1)
						fillRouterOperation(routerFunctionDataList.get(0), routerOperation);
					else if (routerFunctionDataList.size() > 1 && ArrayUtils.isNotEmpty(routerOperation.getProduces())) {
						// PATH + METHOD + PRODUCES
						routerFunctionDataList = routerFunctionDatas.stream()
								.filter(routerFunctionData1 -> routerFunctionData1.getPath().equals(routerOperation.getPath())
										&& isEqualMethods(routerOperation.getMethods(), routerFunctionData1.getMethods())
										&& isEqualArrays(routerFunctionData1.getProduces(), routerOperation.getProduces()))
								.collect(Collectors.toList());
						if (routerFunctionDataList.size() == 1)
							fillRouterOperation(routerFunctionDataList.get(0), routerOperation);
						else if (routerFunctionDataList.size() > 1 && ArrayUtils.isNotEmpty(routerOperation.getConsumes())) {
							// PATH + METHOD + PRODUCES + CONSUMES
							routerFunctionDataList = routerFunctionDatas.stream()
									.filter(routerFunctionData1 -> routerFunctionData1.getPath().equals(routerOperation.getPath())
											&& isEqualMethods(routerOperation.getMethods(), routerFunctionData1.getMethods())
											&& isEqualArrays(routerFunctionData1.getProduces(), routerOperation.getProduces())
											&& isEqualArrays(routerFunctionData1.getConsumes(), routerOperation.getConsumes()))
									.collect(Collectors.toList());
							if (routerFunctionDataList.size() == 1)
								fillRouterOperation(routerFunctionDataList.get(0), routerOperation);
						}
					}
					else if (routerFunctionDataList.size() > 1 && ArrayUtils.isNotEmpty(routerOperation.getConsumes())) {
						// PATH + METHOD + CONSUMES
						routerFunctionDataList = routerFunctionDatas.stream()
								.filter(routerFunctionData1 -> routerFunctionData1.getPath().equals(routerOperation.getPath())
										&& isEqualMethods(routerOperation.getMethods(), routerFunctionData1.getMethods())
										&& isEqualArrays(routerFunctionData1.getConsumes(), routerOperation.getConsumes()))
								.collect(Collectors.toList());
						if (routerFunctionDataList.size() == 1)
							fillRouterOperation(routerFunctionDataList.get(0), routerOperation);
					}
				}
				else if (routerFunctionDataList.size() > 1 && ArrayUtils.isNotEmpty(routerOperation.getProduces())) {
					// PATH + PRODUCES
					routerFunctionDataList = routerFunctionDatas.stream()
							.filter(routerFunctionData1 -> routerFunctionData1.getPath().equals(routerOperation.getPath())
									&& isEqualArrays(routerFunctionData1.getProduces(), routerOperation.getProduces()))
							.collect(Collectors.toList());
					if (routerFunctionDataList.size() == 1)
						fillRouterOperation(routerFunctionDataList.get(0), routerOperation);
					else if (routerFunctionDataList.size() > 1 && ArrayUtils.isNotEmpty(routerOperation.getConsumes())) {
						// PATH + PRODUCES + CONSUMES
						routerFunctionDataList = routerFunctionDatas.stream()
								.filter(routerFunctionData1 -> routerFunctionData1.getPath().equals(routerOperation.getPath())
										&& isEqualMethods(routerOperation.getMethods(), routerFunctionData1.getMethods())
										&& isEqualArrays(routerFunctionData1.getConsumes(), routerOperation.getConsumes())
										&& isEqualArrays(routerFunctionData1.getProduces(), routerOperation.getProduces()))
								.collect(Collectors.toList());
						if (routerFunctionDataList.size() == 1)
							fillRouterOperation(routerFunctionDataList.get(0), routerOperation);
					}
				}
				else if (routerFunctionDataList.size() > 1 && ArrayUtils.isNotEmpty(routerOperation.getConsumes())) {
					// PATH + CONSUMES
					routerFunctionDataList = routerFunctionDatas.stream()
							.filter(routerFunctionData1 -> routerFunctionData1.getPath().equals(routerOperation.getPath())
									&& isEqualArrays(routerFunctionData1.getConsumes(), routerOperation.getConsumes()))
							.collect(Collectors.toList());
					if (routerFunctionDataList.size() == 1)
						fillRouterOperation(routerFunctionDataList.get(0), routerOperation);
				}
			}
		}
	}

	/**
	 * Calculate json view.
	 *
	 * @param apiOperation the api operation
	 * @param methodAttributes the method attributes
	 * @param method the method
	 */
	private void calculateJsonView(io.swagger.v3.oas.annotations.Operation apiOperation,
			MethodAttributes methodAttributes, Method method) {
		JsonView jsonViewAnnotation;
		JsonView jsonViewAnnotationForRequestBody;
		if (apiOperation != null && apiOperation.ignoreJsonView()) {
			jsonViewAnnotation = null;
			jsonViewAnnotationForRequestBody = null;
		}
		else {
			jsonViewAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, JsonView.class);
			/*
			 * If one and only one exists, use the @JsonView annotation from the method
			 * parameter annotated with @RequestBody. Otherwise fall back to the @JsonView
			 * annotation for the method itself.
			 */
			jsonViewAnnotationForRequestBody = (JsonView) Arrays.stream(ReflectionUtils.getParameterAnnotations(method))
					.filter(arr -> Arrays.stream(arr)
							.anyMatch(annotation -> (annotation.annotationType()
									.equals(io.swagger.v3.oas.annotations.parameters.RequestBody.class) || annotation.annotationType().equals(RequestBody.class))))
					.flatMap(Arrays::stream).filter(annotation -> annotation.annotationType().equals(JsonView.class))
					.reduce((a, b) -> null).orElse(jsonViewAnnotation);
		}
		methodAttributes.setJsonViewAnnotation(jsonViewAnnotation);
		methodAttributes.setJsonViewAnnotationForRequestBody(jsonViewAnnotationForRequestBody);
	}

	/**
	 * Is equal arrays boolean.
	 *
	 * @param array1 the array 1
	 * @param array2 the array 2
	 * @return the boolean
	 */
	private boolean isEqualArrays(String[] array1, String[] array2) {
		Arrays.sort(array1);
		Arrays.sort(array2);
		return Arrays.equals(array1, array2);
	}

	/**
	 * Is equal methods boolean.
	 *
	 * @param requestMethods1 the request methods 1
	 * @param requestMethods2 the request methods 2
	 * @return the boolean
	 */
	private boolean isEqualMethods(RequestMethod[] requestMethods1, RequestMethod[] requestMethods2) {
		Arrays.sort(requestMethods1);
		Arrays.sort(requestMethods2);
		return Arrays.equals(requestMethods1, requestMethods2);
	}

	/**
	 * Fill parameters list.
	 *
	 * @param operation the operation
	 * @param queryParams the query params
	 * @param methodAttributes the method attributes
	 */
	private void fillParametersList(Operation operation, Map queryParams, MethodAttributes methodAttributes) {
		List parametersList = operation.getParameters();
		if (parametersList == null)
			parametersList = new ArrayList<>();
		Collection headersMap = AbstractRequestService.getHeaders(methodAttributes, new LinkedHashMap<>());
		headersMap.forEach(parameter -> {
			Optional existingParam;
			if (!CollectionUtils.isEmpty(operation.getParameters())){
				existingParam = operation.getParameters().stream().filter(p -> parameter.getName().equals(p.getName())).findAny();
				if (!existingParam.isPresent())
					operation.addParametersItem(parameter);
			}
		});

		if (!CollectionUtils.isEmpty(queryParams)) {
			for (Map.Entry entry : queryParams.entrySet()) {
				io.swagger.v3.oas.models.parameters.Parameter parameter = new io.swagger.v3.oas.models.parameters.Parameter();
				parameter.setName(entry.getKey());
				parameter.setSchema(new StringSchema()._default(entry.getValue()));
				parameter.setRequired(true);
				parameter.setIn(ParameterIn.QUERY.toString());
				GenericParameterService.mergeParameter(parametersList, parameter);
			}
			operation.setParameters(parametersList);
		}
	}

	/**
	 * Fill router operation.
	 *
	 * @param routerFunctionData the router function data
	 * @param routerOperation the router operation
	 */
	private void fillRouterOperation(RouterFunctionData routerFunctionData, RouterOperation routerOperation) {
		if (ArrayUtils.isEmpty(routerOperation.getConsumes()))
			routerOperation.setConsumes(routerFunctionData.getConsumes());
		if (ArrayUtils.isEmpty(routerOperation.getProduces()))
			routerOperation.setProduces(routerFunctionData.getProduces());
		if (ArrayUtils.isEmpty(routerOperation.getHeaders()))
			routerOperation.setHeaders(routerFunctionData.getHeaders());
		if (ArrayUtils.isEmpty(routerOperation.getMethods()))
			routerOperation.setMethods(routerFunctionData.getMethods());
		if (CollectionUtils.isEmpty(routerOperation.getQueryParams()))
			routerOperation.setQueryParams(routerFunctionData.getQueryParams());
	}

	/**
	 * Build path item path item.
	 *
	 * @param requestMethod the request method
	 * @param operation the operation
	 * @param operationPath the operation path
	 * @param paths the paths
	 * @return the path item
	 */
	private PathItem buildPathItem(RequestMethod requestMethod, Operation operation, String operationPath,
			Paths paths) {
		PathItem pathItemObject;
		if(operation!=null && !CollectionUtils.isEmpty(operation.getParameters())){
			Iterator paramIt = operation.getParameters().iterator();
			while (paramIt.hasNext()){
				Parameter parameter = paramIt.next();
				if(ParameterIn.PATH.toString().equals(parameter.getIn())){
					// check it's present in the path
					String name = parameter.getName();
					if(!StringUtils.containsAny(operationPath, "{" + name + "}", "{*" + name + "}"))
						paramIt.remove();
				}
			}
		}
		if (paths.containsKey(operationPath))
			pathItemObject = paths.get(operationPath);
		else
			pathItemObject = new PathItem();

		switch (requestMethod) {
			case POST:
				pathItemObject.post(operation);
				break;
			case GET:
				pathItemObject.get(operation);
				break;
			case DELETE:
				pathItemObject.delete(operation);
				break;
			case PUT:
				pathItemObject.put(operation);
				break;
			case PATCH:
				pathItemObject.patch(operation);
				break;
			case TRACE:
				pathItemObject.trace(operation);
				break;
			case HEAD:
				pathItemObject.head(operation);
				break;
			case OPTIONS:
				pathItemObject.options(operation);
				break;
			default:
				// Do nothing here
				break;
		}
		return pathItemObject;
	}

	/**
	 * Gets existing operation.
	 *
	 * @param operationMap the operation map
	 * @param requestMethod the request method
	 * @return the existing operation
	 */
	private Operation getExistingOperation(Map operationMap, RequestMethod requestMethod) {
		Operation existingOperation = null;
		if (!CollectionUtils.isEmpty(operationMap)) {
			// Get existing operation definition
			switch (requestMethod) {
				case GET:
					existingOperation = operationMap.get(HttpMethod.GET);
					break;
				case POST:
					existingOperation = operationMap.get(HttpMethod.POST);
					break;
				case PUT:
					existingOperation = operationMap.get(HttpMethod.PUT);
					break;
				case DELETE:
					existingOperation = operationMap.get(HttpMethod.DELETE);
					break;
				case PATCH:
					existingOperation = operationMap.get(HttpMethod.PATCH);
					break;
				case HEAD:
					existingOperation = operationMap.get(HttpMethod.HEAD);
					break;
				case OPTIONS:
					existingOperation = operationMap.get(HttpMethod.OPTIONS);
					break;
				default:
					// Do nothing here
					break;
			}
		}
		return existingOperation;
	}

	/**
	 * Gets operation.
	 *
	 * @param routerOperation the router operation
	 * @param existingOperation the existing operation
	 * @return the operation
	 */
	private Operation getOperation(RouterOperation routerOperation, Operation existingOperation) {
		Operation operationModel = routerOperation.getOperationModel();
		Operation operation;
		if (existingOperation != null && operationModel == null) {
			operation = existingOperation;
		}
		else if (existingOperation == null && operationModel != null) {
			operation = operationModel;
		}
		else if (existingOperation != null) {
			operation = operationParser.mergeOperation(existingOperation, operationModel);
		}
		else {
			operation = new Operation();
		}
		return operation;
	}

	/**
	 * Init open api builder.
	 * @param locale the locale
	 */
	protected void initOpenAPIBuilder(Locale locale) {
		locale = locale == null ? Locale.getDefault() : locale;
		if (openAPIService.getCachedOpenAPI(locale) != null && springDocConfigProperties.isCacheDisabled()) {
			openAPIService = openAPIBuilderObjectFactory.getObject();
		}
	}

	/**
	 * Write yaml value string.
	 *
	 * @param openAPI the open api
	 * @return the string
	 * @throws JsonProcessingException the json processing exception
	 */
	protected byte[] writeYamlValue(OpenAPI openAPI) throws JsonProcessingException {
		String result;
		ObjectMapper objectMapper = springDocProviders.yamlMapper();
		if (springDocConfigProperties.isWriterWithOrderByKeys())
			ObjectMapperProvider.sortOutput(objectMapper, springDocConfigProperties);
		YAMLFactory factory = (YAMLFactory) objectMapper.getFactory();
		factory.configure(Feature.USE_NATIVE_TYPE_ID, false);
		if (!springDocConfigProperties.isWriterWithDefaultPrettyPrinter())
			result = objectMapper.writerFor(OpenAPI.class).writeValueAsString(openAPI);
		else
			result = objectMapper.writerWithDefaultPrettyPrinter().forType(OpenAPI.class).writeValueAsString(openAPI);
		return result.getBytes(StandardCharsets.UTF_8);
	}

	/**
	 * Gets actuator uri.
	 *
	 * @param scheme the scheme
	 * @param host the host
	 * @return the actuator uri
	 */
	protected URI getActuatorURI(String scheme, String host) {
		final Optional actuatorProviderOptional = springDocProviders.getActuatorProvider();
		URI uri = null;
		if (actuatorProviderOptional.isPresent()) {
			ActuatorProvider actuatorProvider = actuatorProviderOptional.get();
			int port;
			String path;
			if (ACTUATOR_DEFAULT_GROUP.equals(this.groupName)) {
				port = actuatorProvider.getActuatorPort();
				path = actuatorProvider.getActuatorPath();
			}
			else {
				port = actuatorProvider.getApplicationPort();
				path = actuatorProvider.getContextPath();
				String mvcServletPath = this.openAPIService.getContext().getBean(Environment.class).getProperty(SPRING_MVC_SERVLET_PATH);
				if (SpringDocUtils.isValidPath(mvcServletPath))
					path = path + mvcServletPath;
			}
			try {
				uri = new URI(StringUtils.defaultIfEmpty(scheme, "http"), null, StringUtils.defaultIfEmpty(host, "localhost"), port, path, null, null);
			}
			catch (URISyntaxException e) {
				LOGGER.error("Unable to parse the URL: scheme {}, host {}, port {}, path {}", scheme, host, port, path);
			}
		}
		return uri;
	}

	/**
	 * Is actuator rest controller boolean.
	 *
	 * @param operationPath the operation path
	 * @param handlerMethod the handler method
	 * @return the boolean
	 */
	protected boolean isActuatorRestController(String operationPath, HandlerMethod handlerMethod) {
		Optional actuatorProviderOptional = springDocProviders.getActuatorProvider();
		boolean isActuatorRestController = false;
		if (actuatorProviderOptional.isPresent())
			isActuatorRestController = actuatorProviderOptional.get().isRestController(operationPath, handlerMethod);
		return springDocConfigProperties.isShowActuator() && isActuatorRestController && (modelAndViewClass == null || !modelAndViewClass.isAssignableFrom(handlerMethod.getMethod().getReturnType()));
	}

	/**
	 * Write json value string.
	 *
	 * @param openAPI the open api
	 * @return the string
	 * @throws JsonProcessingException the json processing exception
	 */
	protected byte[] writeJsonValue(OpenAPI openAPI) throws JsonProcessingException {
		String result;
		ObjectMapper objectMapper = springDocProviders.jsonMapper();
		if (springDocConfigProperties.isWriterWithOrderByKeys())
			ObjectMapperProvider.sortOutput(objectMapper, springDocConfigProperties);
		if (!springDocConfigProperties.isWriterWithDefaultPrettyPrinter())
			result = objectMapper.writerFor(OpenAPI.class).writeValueAsString(openAPI);
		else
			result = objectMapper.writerWithDefaultPrettyPrinter().forType(OpenAPI.class).writeValueAsString(openAPI);
		return result.getBytes(StandardCharsets.UTF_8);
	}

	/**
	 * Gets conditions to match.
	 *
	 * @param conditionType the condition type
	 * @param groupConfigs the group configs
	 * @return the conditions to match
	 */
	private List getConditionsToMatch(ConditionType conditionType, GroupConfig... groupConfigs) {
		List conditionsToMatch = null;
		GroupConfig groupConfig = null;
		if (ArrayUtils.isNotEmpty(groupConfigs))
			groupConfig = groupConfigs[0];
		switch (conditionType) {
			case HEADERS:
				conditionsToMatch = (groupConfig != null) ? groupConfig.getHeadersToMatch() : springDocConfigProperties.getHeadersToMatch();
				break;
			case PRODUCES:
				conditionsToMatch = (groupConfig != null) ? groupConfig.getProducesToMatch() : springDocConfigProperties.getProducesToMatch();
				break;
			case CONSUMES:
				conditionsToMatch = (groupConfig != null) ? groupConfig.getConsumesToMatch() : springDocConfigProperties.getConsumesToMatch();
				break;
			default:
				break;
		}
		return conditionsToMatch;
	}

	/**
	 * Is filter condition boolean.
	 *
	 * @param operationPath the operation path
	 * @param produces the produces
	 * @param consumes the consumes
	 * @param headers the headers
	 * @return the boolean
	 */
	private boolean isFilterCondition(String operationPath, String[] produces, String[] consumes, String[] headers) {
		return isPathToMatch(operationPath)
				&& isConditionToMatch(produces, ConditionType.PRODUCES)
				&& isConditionToMatch(consumes, ConditionType.CONSUMES)
				&& isConditionToMatch(headers, ConditionType.HEADERS);
	}

	/**
	 * The enum Condition type.
	 */
	enum ConditionType {
		/**
		 *Produces condition type.
		 */
		PRODUCES,
		/**
		 *Consumes condition type.
		 */
		CONSUMES,
		/**
		 *Headers condition type.
		 */
		HEADERS
	}

	/**
	 * Customize data rest router operation router operation.
	 *
	 * @param routerOperation the router operation
	 * @return the router operation
	 */
	private RouterOperation customizeDataRestRouterOperation(RouterOperation routerOperation) {
		Optional> optionalDataRestRouterOperationCustomizers = springDocCustomizers.getDataRestRouterOperationCustomizers();
		if (optionalDataRestRouterOperationCustomizers.isPresent()) {
			List dataRestRouterOperationCustomizerList = optionalDataRestRouterOperationCustomizers.get();
			for (DataRestRouterOperationCustomizer dataRestRouterOperationCustomizer : dataRestRouterOperationCustomizerList) {
				routerOperation = dataRestRouterOperationCustomizer.customize(routerOperation);
			}
		}
		return routerOperation;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy