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

io.sinistral.proteus.openapi.jaxrs2.Reader Maven / Gradle / Ivy

/**
 * 
 */
package io.sinistral.proteus.openapi.jaxrs2;

/**
 * @author jbauer
 */

import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.sinistral.proteus.server.ServerRequest;
import io.sinistral.proteus.server.ServerResponse;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.converter.ResolvedSchema;
import io.swagger.v3.core.util.*;
import io.swagger.v3.jaxrs2.OperationParser;
import io.swagger.v3.jaxrs2.ReaderListener;
import io.swagger.v3.jaxrs2.ResolvedParameter;
import io.swagger.v3.jaxrs2.SecurityParser;
import io.swagger.v3.jaxrs2.ext.OpenAPIExtension;
import io.swagger.v3.jaxrs2.ext.OpenAPIExtensions;
import io.swagger.v3.jaxrs2.util.ReaderUtils;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.servers.Server;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.callbacks.Callback;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.tags.Tag;
import io.undertow.server.HttpServerExchange;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Application;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class Reader extends io.swagger.v3.jaxrs2.Reader
{
	private static final Logger LOGGER = LoggerFactory.getLogger(Reader.class);

	public static final String DEFAULT_MEDIA_TYPE_VALUE = "*/*";
	public static final String DEFAULT_DESCRIPTION = "default response";

	protected OpenAPIConfiguration config;

	private Application application;
	private OpenAPI openAPI;
	private Components components;
	private Paths paths;
	private Set openApiTags;

	private static final String GET_METHOD = "get";
	private static final String POST_METHOD = "post";
	private static final String PUT_METHOD = "put";
	private static final String DELETE_METHOD = "delete";
	private static final String PATCH_METHOD = "patch";
	private static final String TRACE_METHOD = "trace";
	private static final String HEAD_METHOD = "head";
	private static final String OPTIONS_METHOD = "options";

	public Reader()
	{
// Json.mapper().addMixIn(ServerRequest.class, ServerRequestMixIn.class);

		this.openAPI = new OpenAPI();
		paths = new Paths();
		openApiTags = new LinkedHashSet<>();
		components = new Components();

	}

	public Reader(OpenAPI openAPI)
	{
		this();

		setConfiguration(new SwaggerConfiguration().openAPI(openAPI));
	}

	public Reader(OpenAPIConfiguration openApiConfiguration)
	{
		this();
		setConfiguration(openApiConfiguration);
	}

	public OpenAPI getOpenAPI()
	{
		return openAPI;
	}

	/**
	 * Scans a single class for Swagger annotations - does not invoke
	 * ReaderListeners
	 */
	public OpenAPI read(Class cls)
	{
		return read(cls, resolveApplicationPath(), null, false, null, null, new LinkedHashSet(), new ArrayList(), new HashSet>());
	}

	/**
	 * Scans a set of classes for both ReaderListeners and OpenAPI annotations.
	 * All found listeners will
	 * be instantiated before any of the classes are scanned for OpenAPI
	 * annotations - so they can be invoked
	 * accordingly.
	 * @param classes
	 *            a set of classes to scan
	 * @return the generated OpenAPI definition
	 */
	public OpenAPI read(Set> classes)
	{
		Set> sortedClasses = new TreeSet<>(new Comparator>()
		{
			@Override
			public int compare(Class class1, Class class2)
			{
				if (class1.equals(class2))
				{
					return 0;
				}
				else if (class1.isAssignableFrom(class2))
				{
					return -1;
				}
				else if (class2.isAssignableFrom(class1))
				{
					return 1;
				}
				return class1.getName().compareTo(class2.getName());
			}
		});
		sortedClasses.addAll(classes);

		Map, ReaderListener> listeners = new HashMap<>();

		for (Class cls : sortedClasses)
		{
			if (ReaderListener.class.isAssignableFrom(cls) && !listeners.containsKey(cls))
			{
				try
				{
					listeners.put(cls, (ReaderListener) cls.newInstance());
				} catch (Exception e)
				{
					LOGGER.error("Failed to create ReaderListener", e);
				}
			}
		}

		for (ReaderListener listener : listeners.values())
		{
			try
			{
				listener.beforeScan(this, openAPI);
			} catch (Exception e)
			{
				LOGGER.error("Unexpected error invoking beforeScan listener [" + listener.getClass().getName() + "]", e);
			}
		}

		for (Class cls : sortedClasses)
		{
			read(cls, resolveApplicationPath(), null, false, null, null, new LinkedHashSet(), new ArrayList(), new HashSet>());
		}

		for (ReaderListener listener : listeners.values())
		{
			try
			{
				listener.afterScan(this, openAPI);
			} catch (Exception e)
			{
				LOGGER.error("Unexpected error invoking afterScan listener [" + listener.getClass().getName() + "]", e);
			}
		}
		return openAPI;
	}

	public static OpenAPIConfiguration deepCopy(OpenAPIConfiguration config) {
		if (config == null) {
			return null;
		}
		try {
			return Json.mapper().readValue(Json.pretty(config), SwaggerConfiguration.class);
		} catch (Exception e) {
			LOGGER.error("Exception cloning config: " + e.getMessage(), e);
			return config;
		}
	}

	@Override
	public void setConfiguration(OpenAPIConfiguration openApiConfiguration)
	{
		if (openApiConfiguration != null)
		{
			this.config = deepCopy(openApiConfiguration);
			if (openApiConfiguration.getOpenAPI() != null)
			{
				this.openAPI = this.config.getOpenAPI();
				if (this.openAPI.getComponents() != null)
				{
					this.components = this.openAPI.getComponents();
				}
			}
		}
	}

	public OpenAPI read(Set> classes, Map resources)
	{
		return read(classes);
	}

	protected String resolveApplicationPath()
	{
		if (application != null)
		{
			Class applicationToScan = this.application.getClass();
			ApplicationPath applicationPath;
			// search up in the hierarchy until we find one with the annotation,
			// this is needed because for example Weld proxies will not have the
			// annotation and the right class will be the superClass
			while ((applicationPath = applicationToScan.getAnnotation(ApplicationPath.class)) == null && !applicationToScan.getSuperclass().equals(Application.class))
			{
				applicationToScan = applicationToScan.getSuperclass();
			}

			if (applicationPath != null)
			{
				if (StringUtils.isNotBlank(applicationPath.value()))
				{
					return applicationPath.value();
				}
			}
			// look for inner application, e.g. ResourceConfig
			try
			{
				Application innerApp = application;
				Method m = application.getClass().getMethod("getApplication", null);
				while (m != null)
				{
					Application retrievedApp = (Application) m.invoke(innerApp, null);
					if (retrievedApp == null)
					{
						break;
					}
					if (retrievedApp.getClass().equals(innerApp.getClass()))
					{
						break;
					}
					innerApp = retrievedApp;
					applicationPath = innerApp.getClass().getAnnotation(ApplicationPath.class);
					if (applicationPath != null)
					{
						if (StringUtils.isNotBlank(applicationPath.value()))
						{
							return applicationPath.value();
						}
					}
					m = innerApp.getClass().getMethod("getApplication", null);
				}
			} catch (NoSuchMethodException e)
			{
				// no inner application found
			} catch (Exception e)
			{
				// no inner application found
			}
		}
		return "";
	}

	public OpenAPI read(Class cls,
						String parentPath,
						String parentMethod,
						boolean isSubresource,
						RequestBody parentRequestBody,
						ApiResponses parentResponses,
						Set parentTags,
						List parentParameters,
						Set> scannedResources)
	{

		Hidden hidden = cls.getAnnotation(Hidden.class);
		// class path
		final javax.ws.rs.Path apiPath = ReflectionUtils.getAnnotation(cls, javax.ws.rs.Path.class);

		if (hidden != null)
		{ // || (apiPath == null && !isSubresource)) {
			return openAPI;
		}

		io.swagger.v3.oas.annotations.responses.ApiResponse[] classResponses = ReflectionUtils
				.getRepeatableAnnotationsArray(cls, io.swagger.v3.oas.annotations.responses.ApiResponse.class);

		List apiSecurityScheme = ReflectionUtils
				.getRepeatableAnnotations(cls, io.swagger.v3.oas.annotations.security.SecurityScheme.class);
		List apiSecurityRequirements = ReflectionUtils
				.getRepeatableAnnotations(cls, io.swagger.v3.oas.annotations.security.SecurityRequirement.class);

		ExternalDocumentation apiExternalDocs = ReflectionUtils.getAnnotation(cls, ExternalDocumentation.class);
		io.swagger.v3.oas.annotations.tags.Tag[] apiTags = ReflectionUtils.getRepeatableAnnotationsArray(cls, io.swagger.v3.oas.annotations.tags.Tag.class);
		Server[] apiServers = ReflectionUtils.getRepeatableAnnotationsArray(cls, Server.class);

		Consumes classConsumes = ReflectionUtils.getAnnotation(cls, Consumes.class);
		Produces classProduces = ReflectionUtils.getAnnotation(cls, Produces.class);

		// OpenApiDefinition
		OpenAPIDefinition openAPIDefinition = ReflectionUtils.getAnnotation(cls, OpenAPIDefinition.class);

		if (openAPIDefinition != null)
		{

			// info
			AnnotationsUtils.getInfo(openAPIDefinition.info()).ifPresent(info -> openAPI.setInfo(info));

			// OpenApiDefinition security requirements
			SecurityParser
					.getSecurityRequirements(openAPIDefinition.security())
					.ifPresent(s -> openAPI.setSecurity(s));
			//
			// OpenApiDefinition external docs
			AnnotationsUtils
					.getExternalDocumentation(openAPIDefinition.externalDocs())
					.ifPresent(docs -> openAPI.setExternalDocs(docs));

			// OpenApiDefinition tags
			AnnotationsUtils
					.getTags(openAPIDefinition.tags(), false)
					.ifPresent(tags -> openApiTags.addAll(tags));

			// OpenApiDefinition servers
			AnnotationsUtils.getServers(openAPIDefinition.servers()).ifPresent(servers -> openAPI.setServers(servers));

			// OpenApiDefinition extensions
			if (openAPIDefinition.extensions().length > 0)
			{
				openAPI.setExtensions(AnnotationsUtils
						.getExtensions(openAPIDefinition.extensions()));
			}

		}

		// class security schemes
		if (apiSecurityScheme != null)
		{
			for (io.swagger.v3.oas.annotations.security.SecurityScheme securitySchemeAnnotation : apiSecurityScheme)
			{
				Optional securityScheme = SecurityParser.getSecurityScheme(securitySchemeAnnotation);
				if (securityScheme.isPresent())
				{
					Map securitySchemeMap = new HashMap<>();
					if (StringUtils.isNotBlank(securityScheme.get().key))
					{
						securitySchemeMap.put(securityScheme.get().key, securityScheme.get().securityScheme);
						if (components.getSecuritySchemes() != null && components.getSecuritySchemes().size() != 0)
						{
							components.getSecuritySchemes().putAll(securitySchemeMap);
						}
						else
						{
							components.setSecuritySchemes(securitySchemeMap);
						}
					}
				}
			}
		}

		// class security requirements
		List classSecurityRequirements = new ArrayList<>();
		if (apiSecurityRequirements != null)
		{
			Optional> requirementsObject = SecurityParser.getSecurityRequirements(
																											apiSecurityRequirements.toArray(
																																			new io.swagger.v3.oas.annotations.security.SecurityRequirement[apiSecurityRequirements
																																					.size()]));
			if (requirementsObject.isPresent())
			{
				classSecurityRequirements = requirementsObject.get();
			}
		}

		// class tags, consider only name to add to class operations
		final Set classTags = new LinkedHashSet<>();
		if (apiTags != null)
		{
			AnnotationsUtils
					.getTags(apiTags, false).ifPresent(tags -> tags
							.stream()
							.map(t -> t.getName())
							.forEach(t -> classTags.add(t)));
		}

		// parent tags
		if (isSubresource)
		{
			if (parentTags != null)
			{
				classTags.addAll(parentTags);
			}
		}

		// servers
		final List classServers = new ArrayList<>();
		if (apiServers != null)
		{
			AnnotationsUtils.getServers(apiServers).ifPresent(servers -> classServers.addAll(servers));
		}

		// class external docs
		Optional classExternalDocumentation = AnnotationsUtils.getExternalDocumentation(apiExternalDocs);

		JavaType classType = TypeFactory.defaultInstance().constructType(cls);

		BeanDescription bd = Json.mapper().getSerializationConfig().introspect(classType);

		final List globalParameters = new ArrayList<>();

		// look for constructor-level annotated properties
		globalParameters.addAll(ReaderUtils.collectConstructorParameters(cls, components, classConsumes, null));

		// look for field-level annotated properties
		globalParameters.addAll(ReaderUtils.collectFieldParameters(cls, components, classConsumes, null));

		// iterate class methods
		Method methods[] = cls.getMethods();
		for (Method method : methods)
		{
			if (isOperationHidden(method))
			{
				continue;
			}

			Class[] parameterTypes = Arrays.stream(method.getParameterTypes()).filter(p -> !p.isAssignableFrom(ServerRequest.class)).toArray(Class[]::new);

			AnnotatedMethod annotatedMethod = bd.findMethod(method.getName(), parameterTypes);

			Produces methodProduces = ReflectionUtils.getAnnotation(method, Produces.class);
			Consumes methodConsumes = ReflectionUtils.getAnnotation(method, Consumes.class);

			if (ReflectionUtils.isOverriddenMethod(method, cls))
			{
				continue;
			}

			javax.ws.rs.Path methodPath = ReflectionUtils.getAnnotation(method, javax.ws.rs.Path.class);

			String operationPath = ReaderUtils.getPath(apiPath, methodPath, parentPath, isSubresource);

			// skip if path is the same as parent, e.g. for @ApplicationPath
			// annotated application
			// extending resource config.
			if (ignoreOperationPath(operationPath, parentPath) && !isSubresource)
			{
				continue;
			}

			Map regexMap = new LinkedHashMap<>();
			operationPath = PathUtils.parsePath(operationPath, regexMap);
			if (operationPath != null)
			{
				if (config != null && ReaderUtils.isIgnored(operationPath, config))
				{
					continue;
				}

				final Class subResource = getSubResourceWithJaxRsSubresourceLocatorSpecs(method);

				String httpMethod = ReaderUtils.extractOperationMethod(method, OpenAPIExtensions.chain());
				httpMethod = (httpMethod == null && isSubresource) ? parentMethod : httpMethod;

				if (StringUtils.isBlank(httpMethod) && subResource == null)
				{
					continue;
				}
				else if (StringUtils.isBlank(httpMethod) && subResource != null)
				{
					Type returnType = method.getGenericReturnType();
					if (shouldIgnoreClass(returnType.getTypeName()) && !returnType.equals(subResource))
					{
						continue;
					}
				}

				io.swagger.v3.oas.annotations.Operation apiOperation = ReflectionUtils.getAnnotation(method, io.swagger.v3.oas.annotations.Operation.class);
				JsonView jsonViewAnnotation = ReflectionUtils.getAnnotation(method, JsonView.class);
				if (apiOperation != null && apiOperation.ignoreJsonView())
				{
					jsonViewAnnotation = null;
				}

				Operation operation = parseMethod(
													method,
													globalParameters,
													methodProduces,
													classProduces,
													methodConsumes,
													classConsumes,
													classSecurityRequirements,
													classExternalDocumentation,
													classTags,
													classServers,
													isSubresource,
													parentRequestBody,
													parentResponses,
													jsonViewAnnotation,
													classResponses);
				if (operation != null)
				{
					// LOGGER.debug("operation is not null");

					List operationParameters = new ArrayList<>();
					List formParameters = new ArrayList<>();
					Annotation[][] paramAnnotations = getParameterAnnotations(method);
					if (annotatedMethod == null)
					{ // annotatedMethod not null only when method with 0-2
						// parameters
						Type[] genericParameterTypes = method.getGenericParameterTypes();

						genericParameterTypes = Arrays.stream(genericParameterTypes).filter(t -> !t.getTypeName().contains("ServerRequest")).toArray(Type[]::new);
//
// for( Type t : genericParameterTypes )
// {
// LOGGER.warn("Generic parameter type: " + t);
// }
//
// LOGGER.warn("paramAnnotations length: " + paramAnnotations.length + "
// genericParameterTypes length: " + genericParameterTypes.length);

						for (int i = 0; i < genericParameterTypes.length; i++)
						{
							final Type type = TypeFactory.defaultInstance().constructType(genericParameterTypes[i], cls);
							io.swagger.v3.oas.annotations.Parameter paramAnnotation = AnnotationsUtils
									.getAnnotation(io.swagger.v3.oas.annotations.Parameter.class, paramAnnotations[i]);

							Type paramType = ParameterProcessor.getParameterType(paramAnnotation, true);

							if (paramType == null)
							{
								paramType = type;
							}
							else
							{
								if (!(paramType instanceof Class))
								{
									paramType = type;
								}
							}

							boolean isOptional = isOptionalType(TypeFactory.defaultInstance().constructType(paramType));

							ResolvedParameter resolvedParameter = getParameters(
																				paramType, Arrays.asList(paramAnnotations[i]), operation, classConsumes, methodConsumes,
																				jsonViewAnnotation);

							operationParameters.addAll(resolvedParameter.parameters);

							resolvedParameter.formParameters.stream().forEach(fp -> fp.setRequired(!isOptional));

							formParameters.addAll(resolvedParameter.formParameters);

							if (resolvedParameter.requestBody != null)
							{
								processRequestBody(
													resolvedParameter.requestBody,
													operation,
													methodConsumes,
													classConsumes,
													operationParameters,
													paramAnnotations[i],
													type,
													jsonViewAnnotation);
							}
						}
					}
					else
					{
						for (int i = 0; i < annotatedMethod.getParameterCount(); i++)
						{
							AnnotatedParameter param = annotatedMethod.getParameter(i);

							final Type type = TypeFactory.defaultInstance().constructType(param.getParameterType(), cls);

							io.swagger.v3.oas.annotations.Parameter paramAnnotation = AnnotationsUtils
									.getAnnotation(io.swagger.v3.oas.annotations.Parameter.class, paramAnnotations[i]);
							Type paramType = ParameterProcessor.getParameterType(paramAnnotation, true);

							if (paramType == null)
							{
								paramType = type;
							}
							else
							{
								if (!(paramType instanceof Class))
								{
									paramType = type;
								}
							}

							boolean isOptional = isOptionalType(TypeFactory.defaultInstance().constructType(paramType));

							ResolvedParameter resolvedParameter = getParameters(
																				paramType, Arrays.asList(paramAnnotations[i]), operation, classConsumes, methodConsumes,
																				jsonViewAnnotation);

							operationParameters.addAll(resolvedParameter.parameters);

							resolvedParameter.formParameters.stream().forEach(fp -> fp.setRequired(!isOptional));

							formParameters.addAll(resolvedParameter.formParameters);

							if (resolvedParameter.requestBody != null)
							{
								processRequestBody(
													resolvedParameter.requestBody,
													operation,
													methodConsumes,
													classConsumes,
													operationParameters,
													paramAnnotations[i],
													type,
													jsonViewAnnotation);
							}
						}
					}
					// if we have form parameters, need to merge them into
					// single schema and use as request body..
					if (formParameters.size() > 0)
					{
						Schema mergedSchema = new ObjectSchema();

						boolean isRequired = false;

						for (Parameter formParam : formParameters)
						{
							if (formParam.getRequired() != null && formParam.getRequired())
							{
								isRequired = true;
							}

							mergedSchema.addProperties(formParam.getName(), formParam.getSchema());
						}

						Parameter merged = new Parameter().schema(mergedSchema);

						merged.setRequired(isRequired);

						processRequestBody(
											merged,
											operation,
											methodConsumes,
											classConsumes,
											operationParameters,
											new Annotation[0],
											null,
											jsonViewAnnotation);

					}
					if (operationParameters.size() > 0)
					{
						for (Parameter operationParameter : operationParameters)
						{
							operation.addParametersItem(operationParameter);
						}
					}

					// if subresource, merge parent parameters
					if (parentParameters != null)
					{
						for (Parameter parentParameter : parentParameters)
						{
							operation.addParametersItem(parentParameter);
						}
					}

					if (subResource != null && !scannedResources.contains(subResource))
					{
						scannedResources.add(subResource);
						read(
								subResource, operationPath, httpMethod, true, operation.getRequestBody(), operation.getResponses(), classTags, operation.getParameters(),
								scannedResources);
						// remove the sub resource so that it can visit it later
						// in another path
						// but we have a room for optimization in the future to
						// reuse the scanned result
						// by caching the scanned resources in the reader
						// instance to avoid actual scanning
						// the the resources again
						scannedResources.remove(subResource);
						// don't proceed with root resource operation, as it's
						// handled by subresource
						continue;
					}

					final Iterator chain = OpenAPIExtensions.chain();
					if (chain.hasNext())
					{
						final OpenAPIExtension extension = chain.next();
						extension.decorateOperation(operation, method, chain);
					}

					PathItem pathItemObject;
					if (openAPI.getPaths() != null && openAPI.getPaths().get(operationPath) != null)
					{
						pathItemObject = openAPI.getPaths().get(operationPath);
					}
					else
					{
						pathItemObject = new PathItem();
					}

					if (StringUtils.isBlank(httpMethod))
					{
						continue;
					}
					setPathItemOperation(pathItemObject, httpMethod, operation);

					paths.addPathItem(operationPath, pathItemObject);
					if (openAPI.getPaths() != null)
					{
						this.paths.putAll(openAPI.getPaths());
					}

					openAPI.setPaths(this.paths);

				}
			}
		}

		// if no components object is defined in openApi instance passed by
		// client, set openAPI.components to resolved components (if not empty)
		if (!isEmptyComponents(components) && openAPI.getComponents() == null)
		{
			openAPI.setComponents(components);
		}

		// add tags from class to definition tags
		AnnotationsUtils
				.getTags(apiTags, true).ifPresent(tags -> openApiTags.addAll(tags));

		if (!openApiTags.isEmpty())
		{
			Set tagsSet = new LinkedHashSet<>();
			if (openAPI.getTags() != null)
			{
				for (Tag tag : openAPI.getTags())
				{
					if (tagsSet.stream().noneMatch(t -> t.getName().equals(tag.getName())))
					{
						tagsSet.add(tag);
					}
				}
			}
			for (Tag tag : openApiTags)
			{
				if (tagsSet.stream().noneMatch(t -> t.getName().equals(tag.getName())))
				{
					tagsSet.add(tag);
				}
			}
			openAPI.setTags(new ArrayList<>(tagsSet));
		}

		return openAPI;
	}

	public boolean isOptionalType(JavaType propType)
	{
		return Arrays.asList("com.google.common.base.Optional", "java.util.Optional")
				.contains(propType.getRawClass().getCanonicalName());
	}

	public static Annotation[][] getParameterAnnotations(Method method)
	{

		Annotation[][] methodAnnotations = method.getParameterAnnotations();

// LOGGER.warn("methodAnnotations length at start: " +
// methodAnnotations.length);

		java.lang.reflect.Parameter[] params = method.getParameters();

		List filteredParameterIndices = new ArrayList<>();

		for (int i = 0; i < params.length; i++)
		{
			Annotation[] paramAnnotations = methodAnnotations[i];

			if (!params[i].getType().isAssignableFrom(ServerRequest.class) && !params[i].getType().getName().startsWith("io.undertow"))
			{
				// String annotationStrings =
				// Arrays.stream(paramAnnotations).map(a ->
				// a.annotationType().getName()).collect(Collectors.joining("
				// "));

// LOGGER.debug("\nparameter: " + params[i] + " | name: " + params[i].getName()
// + " type: " + params[i].getType() + " -> " + annotationStrings);

				if (paramAnnotations.length == 0)
				{
					final String parameterName = params[i].getName();

					// LOGGER.debug("creating query parameter for " +
					// parameterName);

					QueryParam queryParam = new QueryParam()
					{

						@Override
						public String value()
						{
							return parameterName;
						}

						@Override
						public Class annotationType()
						{
							return QueryParam.class;
						}
					};

					methodAnnotations[i] = new Annotation[] { queryParam };
				}
			}
			else
			{
				filteredParameterIndices.add(i);
			}
		}

		ArrayList annotations = Arrays.stream(methodAnnotations).collect(Collectors.toCollection(ArrayList::new));

		for (int index : filteredParameterIndices)
		{
			annotations.remove(index);
		}

		methodAnnotations = annotations.stream().toArray(Annotation[][]::new);

		Method overriddenmethod = ReflectionUtils.getOverriddenMethod(method);

		if (overriddenmethod != null)
		{
			Annotation[][] overriddenAnnotations = overriddenmethod
					.getParameterAnnotations();

			for (int i = 0; i < methodAnnotations.length; i++)
			{
				List types = new ArrayList<>();
				for (int j = 0; j < methodAnnotations[i].length; j++)
				{
					types.add(methodAnnotations[i][j].annotationType());
				}
				for (int j = 0; j < overriddenAnnotations[i].length; j++)
				{
					if (!types.contains(overriddenAnnotations[i][j]
							.annotationType()))
					{
						methodAnnotations[i] = ArrayUtils.add(
																methodAnnotations[i],
																overriddenAnnotations[i][j]);
					}
				}

			}
		}
		return methodAnnotations;
	}

	protected Content processContent(Content content, Schema schema, Consumes methodConsumes, Consumes classConsumes)
	{
		if (content == null)
		{
			content = new Content();
		}
		if (methodConsumes != null)
		{
			for (String value : methodConsumes.value())
			{
				setMediaTypeToContent(schema, content, value);
			}
		}
		else if (classConsumes != null)
		{
			for (String value : classConsumes.value())
			{
				setMediaTypeToContent(schema, content, value);
			}
		}
		else
		{
			setMediaTypeToContent(schema, content, DEFAULT_MEDIA_TYPE_VALUE);
		}
		return content;
	}

	protected void processRequestBody(	Parameter requestBodyParameter, Operation operation,
										Consumes methodConsumes, Consumes classConsumes,
										List operationParameters,
										Annotation[] paramAnnotations, Type type,
										JsonView jsonViewAnnotation)
	{

		boolean isOptional = !(requestBodyParameter.getRequired() != null ? requestBodyParameter.getRequired() : true);

		if (type != null && !isOptional)
		{
			JavaType classType = TypeFactory.defaultInstance().constructType(type);

			if (classType != null)
			{
				isOptional = isOptionalType(classType);
				type = classType;
			}
		}

		io.swagger.v3.oas.annotations.parameters.RequestBody requestBodyAnnotation = getRequestBody(Arrays.asList(paramAnnotations));
		if (requestBodyAnnotation != null)
		{
			Optional optionalRequestBody = OperationParser.getRequestBody(requestBodyAnnotation, classConsumes, methodConsumes, components, jsonViewAnnotation);
			if (optionalRequestBody.isPresent())
			{
				RequestBody requestBody = optionalRequestBody.get();
				if (StringUtils.isBlank(requestBody.get$ref()) &&
						(requestBody.getContent() == null || requestBody.getContent().isEmpty()))
				{
					if (requestBodyParameter.getSchema() != null)
					{
						Content content = processContent(requestBody.getContent(), requestBodyParameter.getSchema(), methodConsumes, classConsumes);
						requestBody.setContent(content);
					}
				}
				else if (StringUtils.isBlank(requestBody.get$ref()) &&
						requestBody.getContent() != null &&
						!requestBody.getContent().isEmpty())
				{
					if (requestBodyParameter.getSchema() != null)
					{
						for (MediaType mediaType : requestBody.getContent().values())
						{
							if (mediaType.getSchema() == null)
							{
								if (requestBodyParameter.getSchema() == null)
								{
									mediaType.setSchema(new Schema());
								}
								else
								{
									mediaType.setSchema(requestBodyParameter.getSchema());
								}
							}
							if (StringUtils.isBlank(mediaType.getSchema().getType()))
							{
								mediaType.getSchema().setType(requestBodyParameter.getSchema().getType());
							}
						}
					}
				}
				requestBody.setRequired(!isOptional);
				operation.setRequestBody(requestBody);
			}
		}
		else
		{
			if (operation.getRequestBody() == null)
			{
				boolean isRequestBodyEmpty = true;
				RequestBody requestBody = new RequestBody();
				if (StringUtils.isNotBlank(requestBodyParameter.get$ref()))
				{
					requestBody.set$ref(requestBodyParameter.get$ref());
					isRequestBodyEmpty = false;
				}
				if (StringUtils.isNotBlank(requestBodyParameter.getDescription()))
				{
					requestBody.setDescription(requestBodyParameter.getDescription());
					isRequestBodyEmpty = false;
				}
				if (Boolean.TRUE.equals(requestBodyParameter.getRequired()))
				{
					requestBody.setRequired(requestBodyParameter.getRequired());
					isRequestBodyEmpty = false;
				}

				if (requestBodyParameter.getSchema() != null)
				{
					Content content = processContent(null, requestBodyParameter.getSchema(), methodConsumes, classConsumes);
					requestBody.setContent(content);
					isRequestBodyEmpty = false;
				}
				if (!isRequestBodyEmpty)
				{
					// requestBody.setExtensions(extensions);
					requestBody.setRequired(!isOptional);
					operation.setRequestBody(requestBody);
				}
			}
		}
	}

	private io.swagger.v3.oas.annotations.parameters.RequestBody getRequestBody(List annotations)
	{
		if (annotations == null)
		{
			return null;
		}
		for (Annotation a : annotations)
		{
			if (a instanceof io.swagger.v3.oas.annotations.parameters.RequestBody)
			{
				return (io.swagger.v3.oas.annotations.parameters.RequestBody) a;
			}
		}
		return null;
	}

	private void setMediaTypeToContent(Schema schema, Content content, String value)
	{
		MediaType mediaTypeObject = new MediaType();
		mediaTypeObject.setSchema(schema);
		content.addMediaType(value, mediaTypeObject);
	}

	public Operation parseMethod(
									Method method,
									List globalParameters,
									JsonView jsonViewAnnotation)
	{
		JavaType classType = TypeFactory.defaultInstance().constructType(method.getDeclaringClass());
		return parseMethod(
							classType.getClass(),
							method,
							globalParameters,
							null,
							null,
							null,
							null,
							new ArrayList<>(),
							Optional.empty(),
							new HashSet<>(),
							new ArrayList<>(),
							false,
							null,
							null,
							jsonViewAnnotation,
							null);
	}

	public Operation parseMethod(
									Method method,
									List globalParameters,
									Produces methodProduces,
									Produces classProduces,
									Consumes methodConsumes,
									Consumes classConsumes,
									List classSecurityRequirements,
									Optional classExternalDocs,
									Set classTags,
									List classServers,
									boolean isSubresource,
									RequestBody parentRequestBody,
									ApiResponses parentResponses,
									JsonView jsonViewAnnotation,
									io.swagger.v3.oas.annotations.responses.ApiResponse[] classResponses)
	{
		JavaType classType = TypeFactory.defaultInstance().constructType(method.getDeclaringClass());
		return parseMethod(
							classType.getClass(),
							method,
							globalParameters,
							methodProduces,
							classProduces,
							methodConsumes,
							classConsumes,
							classSecurityRequirements,
							classExternalDocs,
							classTags,
							classServers,
							isSubresource,
							parentRequestBody,
							parentResponses,
							jsonViewAnnotation,
							classResponses);
	}

	private Operation parseMethod(
									Class cls,
									Method method,
									List globalParameters,
									Produces methodProduces,
									Produces classProduces,
									Consumes methodConsumes,
									Consumes classConsumes,
									List classSecurityRequirements,
									Optional classExternalDocs,
									Set classTags,
									List classServers,
									boolean isSubresource,
									RequestBody parentRequestBody,
									ApiResponses parentResponses,
									JsonView jsonViewAnnotation,
									io.swagger.v3.oas.annotations.responses.ApiResponse[] classResponses)
	{

		if (Arrays.stream(method.getParameters()).filter(p -> p.getType().isAssignableFrom(HttpServerExchange.class)).count() > 0L)
		{
			return null;
		}

		Operation operation = new Operation();

		io.swagger.v3.oas.annotations.Operation apiOperation = ReflectionUtils.getAnnotation(method, io.swagger.v3.oas.annotations.Operation.class);

		List apiSecurity = ReflectionUtils
				.getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.security.SecurityRequirement.class);

		List apiCallbacks = ReflectionUtils
				.getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.callbacks.Callback.class);

		List apiServers = ReflectionUtils.getRepeatableAnnotations(method, Server.class);

		List apiTags = ReflectionUtils.getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class);

		List apiParameters = ReflectionUtils.getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.Parameter.class);

		List apiResponses = ReflectionUtils
				.getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.responses.ApiResponse.class);
		io.swagger.v3.oas.annotations.parameters.RequestBody apiRequestBody = ReflectionUtils.getAnnotation(method, io.swagger.v3.oas.annotations.parameters.RequestBody.class);

		ExternalDocumentation apiExternalDocumentation = ReflectionUtils.getAnnotation(method, ExternalDocumentation.class);

		// callbacks
		Map callbacks = new LinkedHashMap<>();

		if (apiCallbacks != null)
		{
			for (io.swagger.v3.oas.annotations.callbacks.Callback methodCallback : apiCallbacks)
			{
				Map currentCallbacks = getCallbacks(methodCallback, methodProduces, classProduces, methodConsumes, classConsumes, jsonViewAnnotation);
				callbacks.putAll(currentCallbacks);
			}
		}
		if (callbacks.size() > 0)
		{
			operation.setCallbacks(callbacks);
		}

		// security
		classSecurityRequirements.forEach(operation::addSecurityItem);
		if (apiSecurity != null)
		{
			Optional> requirementsObject = SecurityParser.getSecurityRequirements(apiSecurity.toArray(
																																new io.swagger.v3.oas.annotations.security.SecurityRequirement[apiSecurity
																																		.size()]));
			if (requirementsObject.isPresent())
			{
				requirementsObject.get().stream()
						.filter(r -> operation.getSecurity() == null || !operation.getSecurity().contains(r))
						.forEach(operation::addSecurityItem);
			}
		}

		// servers
		if (classServers != null)
		{
			classServers.forEach(operation::addServersItem);
		}

		if (apiServers != null)
		{
			AnnotationsUtils.getServers(apiServers.toArray(new Server[apiServers.size()])).ifPresent(servers -> servers.forEach(operation::addServersItem));
		}

		// external docs
		AnnotationsUtils.getExternalDocumentation(apiExternalDocumentation).ifPresent(operation::setExternalDocs);

		// method tags
		if (apiTags != null)
		{
			apiTags.stream()
					.filter(t -> operation.getTags() == null || (operation.getTags() != null && !operation.getTags().contains(t.name())))
					.map(t -> t.name())
					.forEach(operation::addTagsItem);
			AnnotationsUtils.getTags(apiTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[apiTags.size()]), true).ifPresent(tags -> openApiTags.addAll(tags));
		}

		// parameters
		if (globalParameters != null)
		{
			for (Parameter globalParameter : globalParameters)
			{
				operation.addParametersItem(globalParameter);
			}
		}
		if (apiParameters != null)
		{

			getParametersListFromAnnotation(
											apiParameters.toArray(new io.swagger.v3.oas.annotations.Parameter[apiParameters.size()]),
											classConsumes,
											methodConsumes,
											operation,
											jsonViewAnnotation).ifPresent(p -> p.forEach(operation::addParametersItem));
		}

		// RequestBody in Method
		if (apiRequestBody != null && operation.getRequestBody() == null)
		{

			OperationParser.getRequestBody(apiRequestBody, classConsumes, methodConsumes, components, jsonViewAnnotation).ifPresent(
																																	operation::setRequestBody);
		}

		// operation id
		if (StringUtils.isBlank(operation.getOperationId()))
		{
			operation.setOperationId(getOperationId(method.getName()));
		}

		// classResponses
		if (classResponses != null && classResponses.length > 0)
		{

			OperationParser.getApiResponses(
											classResponses,
											classProduces,
											methodProduces,
											components,
											jsonViewAnnotation)
					.ifPresent(responses ->
					{
						if (operation.getResponses() == null)
						{
							operation.setResponses(responses);
						}
						else
						{
							responses.forEach(operation.getResponses()::addApiResponse);
						}
					});
		}

		if (apiOperation != null)
		{
			setOperationObjectFromApiOperationAnnotation(operation, apiOperation, methodProduces, classProduces, methodConsumes, classConsumes, jsonViewAnnotation);
		}

		// apiResponses
		if (apiResponses != null && apiResponses.size() > 0)
		{
			OperationParser.getApiResponses(
											apiResponses.toArray(new io.swagger.v3.oas.annotations.responses.ApiResponse[apiResponses.size()]),
											classProduces,
											methodProduces,
											components,
											jsonViewAnnotation)
					.ifPresent(responses ->
					{
						if (operation.getResponses() == null)
						{
							operation.setResponses(responses);
						}
						else
						{
							responses.forEach(operation.getResponses()::addApiResponse);
						}
					});
		}

		// class tags after tags defined as field of @Operation
		if (classTags != null)
		{
			classTags.stream()
					.filter(t -> operation.getTags() == null || (operation.getTags() != null && !operation.getTags().contains(t)))
					.forEach(operation::addTagsItem);
		}

		// external docs of class if not defined in annotation of method or as
		// field of Operation annotation
		if (operation.getExternalDocs() == null)
		{
			classExternalDocs.ifPresent(operation::setExternalDocs);
		}

		// if subresource, merge parent requestBody
		if (isSubresource && parentRequestBody != null)
		{
			if (operation.getRequestBody() == null)
			{
				operation.requestBody(parentRequestBody);
			}
			else
			{
				Content content = operation.getRequestBody().getContent();
				if (content == null)
				{
					content = parentRequestBody.getContent();
					operation.getRequestBody().setContent(content);
				}
				else if (parentRequestBody.getContent() != null)
				{
					for (String parentMediaType : parentRequestBody.getContent().keySet())
					{
						if (content.get(parentMediaType) == null)
						{
							content.addMediaType(parentMediaType, parentRequestBody.getContent().get(parentMediaType));
						}
					}
				}
			}
		}

		// handle return type, add as response in case.
		Type returnType = method.getGenericReturnType();
		final Class subResource = getSubResourceWithJaxRsSubresourceLocatorSpecs(method);

		if (!shouldIgnoreClass(returnType.getTypeName()) && !returnType.equals(subResource))
		{
			LOGGER.debug("processing class " + returnType + " " + returnType.getTypeName());

			JavaType classType = TypeFactory.defaultInstance().constructType(returnType);

			if (classType != null && classType.getRawClass() != null)
			{
				if (classType.getRawClass().isAssignableFrom(ServerResponse.class))
				{
					if (classType.containedType(0) != null)
					{
						returnType = classType.containedType(0);
					}
				}
				else if (classType.getRawClass().isAssignableFrom(CompletableFuture.class))
				{
					Class futureCls = classType.containedType(0).getRawClass();

					if (futureCls.isAssignableFrom(ServerResponse.class))
					{
						final JavaType futureType = TypeFactory.defaultInstance().constructType(classType.containedType(0));
						returnType = futureType.containedType(0);
					}
					else
					{
						returnType = classType.containedType(0);
					}
				}
			}

			ResolvedSchema resolvedSchema = ModelConverters.getInstance()
					.resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation));

			if (resolvedSchema.schema != null)
			{
				Schema returnTypeSchema = resolvedSchema.schema;
				Content content = new Content();
				MediaType mediaType = new MediaType().schema(returnTypeSchema);
				AnnotationsUtils.applyTypes(classProduces == null ? new String[0] : classProduces.value(),
											methodProduces == null ? new String[0] : methodProduces.value(), content, mediaType);
				if (operation.getResponses() == null)
				{
					operation.responses(
										new ApiResponses()._default(
																	new ApiResponse().description(DEFAULT_DESCRIPTION)
																			.content(content)));
				}
				if (operation.getResponses().getDefault() != null &&
						StringUtils.isBlank(operation.getResponses().getDefault().get$ref()))
				{
					if (operation.getResponses().getDefault().getContent() == null)
					{
						operation.getResponses().getDefault().content(content);
					}
					else
					{
						for (String key : operation.getResponses().getDefault().getContent().keySet())
						{
							if (operation.getResponses().getDefault().getContent().get(key).getSchema() == null)
							{
								operation.getResponses().getDefault().getContent().get(key).setSchema(returnTypeSchema);
							}
						}
					}
				}
				Map schemaMap = resolvedSchema.referencedSchemas;
				if (schemaMap != null)
				{
					schemaMap.forEach((key, schema) -> components.addSchemas(key, schema));
				}

			}
		}
		if (operation.getResponses() == null || operation.getResponses().isEmpty())
		{
			LOGGER.debug("responses are null or empty");

			// Content content = new Content();
			// MediaType mediaType = new MediaType();
			// AnnotationsUtils.applyTypes(classProduces == null ? new String[0]
			// : classProduces.value(),
			// methodProduces == null ? new String[0] : methodProduces.value(),
			// content, mediaType);

			ApiResponse apiResponseObject = new ApiResponse().description(DEFAULT_DESCRIPTION);// .content(content);
			operation.setResponses(new ApiResponses()._default(apiResponseObject));
		}

		return operation;
	}

	private boolean shouldIgnoreClass(String className)
	{
		if (StringUtils.isBlank(className))
		{
			return true;
		}
		boolean ignore = false;
		ignore = ignore || className.startsWith("javax.ws.rs.");
		ignore = ignore || className.equalsIgnoreCase("void");
		ignore = ignore || className.startsWith("io.undertow");
		ignore = ignore || className.startsWith("java.lang.Void");
		return ignore;
	}

	private Map getCallbacks(
												io.swagger.v3.oas.annotations.callbacks.Callback apiCallback,
												Produces methodProduces,
												Produces classProduces,
												Consumes methodConsumes,
												Consumes classConsumes,
												JsonView jsonViewAnnotation)
	{
		Map callbackMap = new HashMap<>();
		if (apiCallback == null)
		{
			return callbackMap;
		}

		Callback callbackObject = new Callback();
		if (StringUtils.isNotBlank(apiCallback.ref()))
		{
			callbackObject.set$ref(apiCallback.ref());
			callbackMap.put(apiCallback.name(), callbackObject);
			return callbackMap;
		}
		PathItem pathItemObject = new PathItem();
		for (io.swagger.v3.oas.annotations.Operation callbackOperation : apiCallback.operation())
		{
			Operation callbackNewOperation = new Operation();
			setOperationObjectFromApiOperationAnnotation(
															callbackNewOperation,
															callbackOperation,
															methodProduces,
															classProduces,
															methodConsumes,
															classConsumes,
															jsonViewAnnotation);
			setPathItemOperation(pathItemObject, callbackOperation.method(), callbackNewOperation);
		}

		callbackObject.addPathItem(apiCallback.callbackUrlExpression(), pathItemObject);
		callbackMap.put(apiCallback.name(), callbackObject);

		return callbackMap;
	}

	private void setPathItemOperation(PathItem pathItemObject, String method, Operation operation)
	{
		switch (method)
		{
			case POST_METHOD:
				pathItemObject.post(operation);
				break;
			case GET_METHOD:
				pathItemObject.get(operation);
				break;
			case DELETE_METHOD:
				pathItemObject.delete(operation);
				break;
			case PUT_METHOD:
				pathItemObject.put(operation);
				break;
			case PATCH_METHOD:
				pathItemObject.patch(operation);
				break;
			case TRACE_METHOD:
				pathItemObject.trace(operation);
				break;
			case HEAD_METHOD:
				pathItemObject.head(operation);
				break;
			case OPTIONS_METHOD:
				pathItemObject.options(operation);
				break;
			default:
				// Do nothing here
				break;
		}
	}

	private void setOperationObjectFromApiOperationAnnotation(
																Operation operation,
																io.swagger.v3.oas.annotations.Operation apiOperation,
																Produces methodProduces,
																Produces classProduces,
																Consumes methodConsumes,
																Consumes classConsumes,
																JsonView jsonViewAnnotation)
	{
		if (StringUtils.isNotBlank(apiOperation.summary()))
		{
			operation.setSummary(apiOperation.summary());
		}
		if (StringUtils.isNotBlank(apiOperation.description()))
		{
			operation.setDescription(apiOperation.description());
		}
		if (StringUtils.isNotBlank(apiOperation.operationId()))
		{
			operation.setOperationId(getOperationId(apiOperation.operationId()));
		}
		if (apiOperation.deprecated())
		{
			operation.setDeprecated(apiOperation.deprecated());
		}

		ReaderUtils.getStringListFromStringArray(apiOperation.tags()).ifPresent(tags ->
		{
			tags.stream()
					.filter(t -> operation.getTags() == null || (operation.getTags() != null && !operation.getTags().contains(t)))
					.forEach(operation::addTagsItem);
		});

		if (operation.getExternalDocs() == null)
		{ // if not set in root annotation
			AnnotationsUtils.getExternalDocumentation(apiOperation.externalDocs()).ifPresent(operation::setExternalDocs);
		}

		OperationParser.getApiResponses(apiOperation.responses(), classProduces, methodProduces, components, jsonViewAnnotation).ifPresent(responses ->
		{
			if (operation.getResponses() == null)
			{
				operation.setResponses(responses);
			}
			else
			{
				responses.forEach(operation.getResponses()::addApiResponse);
			}
		});
		AnnotationsUtils.getServers(apiOperation.servers()).ifPresent(servers -> servers.forEach(operation::addServersItem));

		getParametersListFromAnnotation(
										apiOperation.parameters(),
										classConsumes,
										methodConsumes,
										operation,
										jsonViewAnnotation).ifPresent(p -> p.forEach(operation::addParametersItem));

		// security
		Optional> requirementsObject = SecurityParser.getSecurityRequirements(apiOperation.security());
		if (requirementsObject.isPresent())
		{
			requirementsObject.get().stream()
					.filter(r -> operation.getSecurity() == null || !operation.getSecurity().contains(r))
					.forEach(operation::addSecurityItem);
		}

		// RequestBody in Operation
		if (apiOperation != null && apiOperation.requestBody() != null && operation.getRequestBody() == null)
		{
			OperationParser.getRequestBody(apiOperation.requestBody(), classConsumes, methodConsumes, components, jsonViewAnnotation).ifPresent(
																																				requestBodyObject -> operation
																																						.setRequestBody(
																																										requestBodyObject));
		}

		// Extensions in Operation
		if (apiOperation.extensions().length > 0)
		{
			Map extensions = AnnotationsUtils.getExtensions(apiOperation.extensions());
			if (extensions != null)
			{
				for (String ext : extensions.keySet())
				{
					operation.addExtension(ext, extensions.get(ext));
				}
			}
		}
	}

	protected String getOperationId(String operationId)
	{
		boolean operationIdUsed = existOperationId(operationId);
		String operationIdToFind = null;
		int counter = 0;
		while (operationIdUsed)
		{
			operationIdToFind = String.format("%s_%d", operationId, ++counter);
			operationIdUsed = existOperationId(operationIdToFind);
		}
		if (operationIdToFind != null)
		{
			operationId = operationIdToFind;
		}
		return operationId;
	}

	private boolean existOperationId(String operationId)
	{
		if (openAPI == null)
		{
			return false;
		}
		if (openAPI.getPaths() == null || openAPI.getPaths().isEmpty())
		{
			return false;
		}
		for (PathItem path : openAPI.getPaths().values())
		{
			String pathOperationId = extractOperationIdFromPathItem(path);
			if (operationId.equalsIgnoreCase(pathOperationId))
			{
				return true;
			}

		}
		return false;
	}

	protected Optional> getParametersListFromAnnotation(io.swagger.v3.oas.annotations.Parameter[] parameters, Consumes classConsumes, Consumes methodConsumes,
																		Operation operation, JsonView jsonViewAnnotation)
	{
		if (parameters == null)
		{
			return Optional.empty();
		}
		List parametersObject = new ArrayList<>();
		for (io.swagger.v3.oas.annotations.Parameter parameter : parameters)
		{

			ResolvedParameter resolvedParameter = getParameters(
																ParameterProcessor.getParameterType(parameter), Collections.singletonList(parameter), operation, classConsumes,
																methodConsumes, jsonViewAnnotation);
			parametersObject.addAll(resolvedParameter.parameters);
		}
		if (parametersObject.size() == 0)
		{
			return Optional.empty();
		}
		return Optional.of(parametersObject);
	}

	protected ResolvedParameter getParameters(	Type type, List annotations, Operation operation, Consumes classConsumes,
												Consumes methodConsumes, JsonView jsonViewAnnotation)
	{

		final Iterator chain = OpenAPIExtensions.chain();
		if (!chain.hasNext())
		{
			return new ResolvedParameter();
		}
		LOGGER.debug("getParameters for {}", type);
		Set typesToSkip = new HashSet<>();
		final OpenAPIExtension extension = chain.next();
		LOGGER.debug("trying extension {}", extension);

		final ResolvedParameter extractParametersResult = extension
				.extractParameters(annotations, type, typesToSkip, components, classConsumes, methodConsumes, true, jsonViewAnnotation, chain);
		return extractParametersResult;
	}

	private String extractOperationIdFromPathItem(PathItem path)
	{
		if (path.getGet() != null)
		{
			return path.getGet().getOperationId();
		}
		else if (path.getPost() != null)
		{
			return path.getPost().getOperationId();
		}
		else if (path.getPut() != null)
		{
			return path.getPut().getOperationId();
		}
		else if (path.getDelete() != null)
		{
			return path.getDelete().getOperationId();
		}
		else if (path.getOptions() != null)
		{
			return path.getOptions().getOperationId();
		}
		else if (path.getHead() != null)
		{
			return path.getHead().getOperationId();
		}
		else if (path.getPatch() != null)
		{
			return path.getPatch().getOperationId();
		}
		return "";
	}

	private boolean isEmptyComponents(Components components)
	{
		if (components == null)
		{
			return true;
		}
		if (components.getSchemas() != null && components.getSchemas().size() > 0)
		{
			return false;
		}
		if (components.getSecuritySchemes() != null && components.getSecuritySchemes().size() > 0)
		{
			return false;
		}
		if (components.getCallbacks() != null && components.getCallbacks().size() > 0)
		{
			return false;
		}
		if (components.getExamples() != null && components.getExamples().size() > 0)
		{
			return false;
		}
		if (components.getExtensions() != null && components.getExtensions().size() > 0)
		{
			return false;
		}
		if (components.getHeaders() != null && components.getHeaders().size() > 0)
		{
			return false;
		}
		if (components.getLinks() != null && components.getLinks().size() > 0)
		{
			return false;
		}
		if (components.getParameters() != null && components.getParameters().size() > 0)
		{
			return false;
		}
		if (components.getRequestBodies() != null && components.getRequestBodies().size() > 0)
		{
			return false;
		}
		if (components.getResponses() != null && components.getResponses().size() > 0)
		{
			return false;
		}

		return true;
	}

	protected boolean isOperationHidden(Method method)
	{
		io.swagger.v3.oas.annotations.Operation apiOperation = ReflectionUtils.getAnnotation(method, io.swagger.v3.oas.annotations.Operation.class);
		if (apiOperation != null && apiOperation.hidden())
		{
			return true;
		}
		Hidden hidden = method.getAnnotation(Hidden.class);
		if (hidden != null)
		{
			return true;
		}
		if (config != null && !Boolean.TRUE.equals(config.isReadAllResources()) && apiOperation == null)
		{
			return true;
		}
		return false;
	}

	public void setApplication(Application application)
	{
		this.application = application;
	}

	protected boolean ignoreOperationPath(String path, String parentPath)
	{

		if (StringUtils.isBlank(path) && StringUtils.isBlank(parentPath))
		{
			return true;
		}
		else if (StringUtils.isNotBlank(path) && StringUtils.isBlank(parentPath))
		{
			return false;
		}
		else if (StringUtils.isBlank(path) && StringUtils.isNotBlank(parentPath))
		{
			return false;
		}
		if (parentPath != null && !"".equals(parentPath) && !"/".equals(parentPath))
		{
			if (!parentPath.startsWith("/"))
			{
				parentPath = "/" + parentPath;
			}
			if (parentPath.endsWith("/"))
			{
				parentPath = parentPath.substring(0, parentPath.length() - 1);
			}
		}
		if (path != null && !"".equals(path) && !"/".equals(path))
		{
			if (!path.startsWith("/"))
			{
				path = "/" + path;
			}
			if (path.endsWith("/"))
			{
				path = path.substring(0, path.length() - 1);
			}
		}
		if (path.equals(parentPath))
		{
			return true;
		}
		return false;
	}

	protected Class getSubResourceWithJaxRsSubresourceLocatorSpecs(Method method)
	{
		final Class rawType = method.getReturnType();
		final Class type;
		if (Class.class.equals(rawType))
		{
			type = getClassArgument(method.getGenericReturnType());
			if (type == null)
			{
				return null;
			}
		}
		else
		{
			type = rawType;
		}

		if (method.getAnnotation(javax.ws.rs.Path.class) != null)
		{
			if (ReaderUtils.extractOperationMethod(method, null) == null)
			{
				return type;
			}
		}
		return null;
	}

	private static Class getClassArgument(Type cls)
	{
		if (cls instanceof ParameterizedType)
		{
			final ParameterizedType parameterized = (ParameterizedType) cls;
			final Type[] args = parameterized.getActualTypeArguments();
			if (args.length != 1)
			{
				LOGGER.error("Unexpected class definition: {}", cls);
				return null;
			}
			final Type first = args[0];
			if (first instanceof Class)
			{
				return (Class) first;
			}
			else
			{
				return null;
			}
		}
		else
		{
			LOGGER.error("Unknown class definition: {}", cls);
			return null;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy