
io.micronaut.openapi.visitor.AbstractOpenApiEndpointVisitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of micronaut-openapi Show documentation
Show all versions of micronaut-openapi Show documentation
Configuration to integrate Micronaut and OpenAPI/Swagger
The newest version!
/*
* Copyright 2017-2020 original 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 io.micronaut.openapi.visitor;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import io.micronaut.annotation.processing.visitor.JavaClassElementExt;
import io.micronaut.context.env.DefaultPropertyPlaceholderResolver;
import io.micronaut.context.env.PropertyPlaceholderResolver;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.beans.BeanMap;
import io.micronaut.core.bind.annotation.Bindable;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.DefaultConversionService;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.value.PropertyResolver;
import io.micronaut.http.HttpMethod;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.http.uri.UriMatchTemplate;
import io.micronaut.http.uri.UriMatchVariable;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.openapi.javadoc.JavadocDescription;
import io.micronaut.openapi.javadoc.JavadocParser;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.callbacks.Callback;
import io.swagger.v3.oas.annotations.enums.Explode;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.tags.Tags;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.*;
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.servers.Server;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.security.Principal;
import java.util.*;
import java.util.stream.Collectors;
/**
* A {@link TypeElementVisitor} the builds the Swagger model from Micronaut controllers at compile time.
*
* @author graemerocher
* @since 1.0
*/
@Experimental
abstract class AbstractOpenApiEndpointVisitor extends AbstractOpenApiVisitor {
protected List classTags;
protected PropertyPlaceholderResolver propertyPlaceholderResolver;
private static boolean isAnnotationPresent(Element element, String className) {
return element.findAnnotation(className).isPresent();
}
private static RequestBody mergeRequestBody(RequestBody rq1, RequestBody rq2) {
if (rq1.getRequired() == null) {
rq1.setRequired(rq2.getRequired());
}
if (rq1.get$ref() == null) {
rq1.set$ref(rq2.get$ref());
}
if (rq1.getDescription() == null) {
rq1.setDescription(rq2.getDescription());
}
if (rq1.getExtensions() == null) {
rq1.setExtensions(rq2.getExtensions());
} else if (rq2.getExtensions() != null) {
rq1.getExtensions().forEach((key, value) -> rq1.getExtensions().putIfAbsent(key, value));
}
if (rq1.getContent() == null) {
rq1.setContent(rq2.getContent());
} else if (rq2.getContent() != null) {
Content c1 = rq1.getContent();
Content c2 = rq2.getContent();
c2.forEach((key, value) -> c1.putIfAbsent(key, value));
}
return rq1;
}
/**
* Executed when a class is encountered that matches the generic.
*
* @param element The element
* @param context The visitor context
*/
public void visitClass(ClassElement element, VisitorContext context) {
if (ignore(element, context)) {
return;
}
incrementVisitedElements(context);
processSecuritySchemes(element, context);
processTags(element, context);
}
private void processTags(ClassElement element, VisitorContext context) {
classTags = readTags(element, context);
List userDefinedClassTags = classTags(element, context);
if (classTags == null || classTags.isEmpty()) {
classTags = userDefinedClassTags == null ? Collections.emptyList() : userDefinedClassTags;
} else if (userDefinedClassTags != null) {
for (io.swagger.v3.oas.models.tags.Tag tag : userDefinedClassTags) {
if (!containsTag(tag.getName(), classTags)) {
classTags.add(tag);
}
}
}
}
private boolean containsTag(String name, List tags) {
return tags.stream().anyMatch(tag -> name.equals(tag.getName()));
}
/**
* Returns the security requirements at method level.
*
* @param element The MethodElement.
* @param context The context.
* @return The security requirements.
*/
protected abstract List methodSecurityRequirements(MethodElement element, VisitorContext context);
/**
* Returns the servers at method level.
*
* @param element The MethodElement.
* @param context The context.
* @return The servers.
*/
protected abstract List methodServers(MethodElement element, VisitorContext context);
/**
* Returns the class tags.
*
* @param element The ClassElement.
* @param context The context.
* @return The class tags.
*/
protected abstract List classTags(ClassElement element, VisitorContext context);
/**
* Returns true if the specified element should not be processed.
*
* @param element The ClassElement.
* @param context The context.
* @return true if the specified element should not be processed.
*/
protected abstract boolean ignore(ClassElement element, VisitorContext context);
/**
* Returns true if the specified element should not be processed.
*
* @param element The ClassElement.
* @param context The context.
* @return true if the specified element should not be processed.
*/
protected abstract boolean ignore(MethodElement element, VisitorContext context);
/**
* Returns the HttpMethod of the element.
*
* @param element The MethodElement.
* @return The HttpMethod of the element.
*/
protected abstract HttpMethod httpMethod(MethodElement element);
/**
* Returns the uri paths of the element.
*
* @param element The MethodElement.
* @return The uri paths of the element.
*/
protected abstract List uriMatchTemplates(MethodElement element);
/**
* Returns the consumes media types.
*
* @param element The MethodElement.
* @return The consumes media types.
*/
protected abstract List consumesMediaTypes(MethodElement element);
/**
* Returns the produces media types.
*
* @param element The MethodElement.
* @return The produces media types.
*/
protected abstract List producesMediaTypes(MethodElement element);
/**
* Returns the description for the element.
*
* @param element The MethodElement.
* @return The description for the element.
*/
protected abstract String description(MethodElement element);
private boolean hasNoBindingAnnotationOrType(ParameterElement parameter) {
return !parameter.isAnnotationPresent(io.swagger.v3.oas.annotations.Parameter.class) &&
!parameter.isAnnotationPresent(io.swagger.v3.oas.annotations.parameters.RequestBody.class) &&
!parameter.isAnnotationPresent(QueryValue.class) &&
!parameter.isAnnotationPresent(PathVariable.class) &&
!parameter.isAnnotationPresent(Body.class) &&
!parameter.isAnnotationPresent(Part.class) &&
!parameter.isAnnotationPresent(CookieValue.class) &&
!parameter.isAnnotationPresent(Header.class) &&
!isResponseType(parameter.getType());
}
/**
* Executed when a method is encountered that matches the generic.
*
* @param element The element
* @param context The visitor context
*/
public void visitMethod(MethodElement element, VisitorContext context) {
if (ignore(element, context)) {
return;
}
HttpMethod httpMethod = httpMethod(element);
if (httpMethod == null) {
return;
}
Iterator matchTemplates = uriMatchTemplates(element).iterator();
if (!matchTemplates.hasNext()) {
return;
}
incrementVisitedElements(context);
UriMatchTemplate matchTemplate = matchTemplates.next();
PathItem pathItem = resolvePathItem(context, matchTemplate);
OpenAPI openAPI = resolveOpenAPI(context);
io.swagger.v3.oas.models.Operation swaggerOperation = readOperation(element, context);
readTags(element, swaggerOperation, classTags == null ? Collections.emptyList() : classTags);
readSecurityRequirements(element, context, swaggerOperation);
readApiResponses(element, context, swaggerOperation);
readServers(element, context, swaggerOperation);
readCallbacks(element, context, swaggerOperation);
JavadocDescription javadocDescription = getMethodDescription(element, swaggerOperation);
swaggerOperation = setOperationOnPathItem(pathItem, swaggerOperation, httpMethod);
if (element.isAnnotationPresent(Deprecated.class)) {
swaggerOperation.setDeprecated(true);
}
readResponse(element, context, openAPI, swaggerOperation, javadocDescription);
boolean permitsRequestBody = HttpMethod.permitsRequestBody(httpMethod);
if (permitsRequestBody) {
Optional requestBody = readSwaggerRequestBody(element, context);
if (requestBody.isPresent()) {
RequestBody currentRequestBody = swaggerOperation.getRequestBody();
if (currentRequestBody != null) {
swaggerOperation.setRequestBody(mergeRequestBody(currentRequestBody, requestBody.get()));
} else {
swaggerOperation.setRequestBody(requestBody.get());
}
}
}
Map pathVariables = pathVariables(matchTemplate);
List consumesMediaTypes = consumesMediaTypes(element);
processParameters(element, context, openAPI, swaggerOperation, javadocDescription, permitsRequestBody, pathVariables, consumesMediaTypes);
if (HttpMethod.requiresRequestBody(httpMethod) && swaggerOperation.getRequestBody() == null) {
processBodyParameters(element, context, openAPI, swaggerOperation, javadocDescription, pathVariables,
consumesMediaTypes);
}
// if we have multiple uris, process them
while (matchTemplates.hasNext()) {
pathItem = resolvePathItem(context, matchTemplates.next());
swaggerOperation = setOperationOnPathItem(pathItem, swaggerOperation, httpMethod);
}
}
private void processParameters(MethodElement element, VisitorContext context, OpenAPI openAPI,
io.swagger.v3.oas.models.Operation swaggerOperation, JavadocDescription javadocDescription,
boolean permitsRequestBody,
Map pathVariables, List consumesMediaTypes) {
List swaggerParameters = swaggerOperation.getParameters();
boolean hasExistingParameters = CollectionUtils.isNotEmpty(swaggerParameters);
if (!hasExistingParameters) {
swaggerParameters = new ArrayList<>();
swaggerOperation.setParameters(swaggerParameters);
}
for (ParameterElement parameter : element.getParameters()) {
processParameter(context, openAPI, swaggerOperation, javadocDescription, permitsRequestBody, pathVariables,
consumesMediaTypes, swaggerParameters, hasExistingParameters, parameter);
}
}
private void processParameter(VisitorContext context, OpenAPI openAPI,
io.swagger.v3.oas.models.Operation swaggerOperation, JavadocDescription javadocDescription,
boolean permitsRequestBody, Map pathVariables, List consumesMediaTypes,
List swaggerParameters, boolean hasExistingParameters, ParameterElement parameter) {
ClassElement parameterType = parameter.getGenericType();
if (ignoreParameter(parameter)) {
return;
}
if (isJavaElement(parameterType, context)) {
parameterType = new JavaClassElementExt(parameterType, context);
}
if (permitsRequestBody && swaggerOperation.getRequestBody() == null) {
readSwaggerRequestBody(parameter, context).ifPresent(swaggerOperation::setRequestBody);
}
if (parameter.isAnnotationPresent(Body.class)) {
processBody(context, openAPI, swaggerOperation, javadocDescription, permitsRequestBody,
consumesMediaTypes, parameter, parameterType);
return;
}
if (hasExistingParameters) {
return;
}
Parameter newParameter = processMethodParameterAnnotation(context, permitsRequestBody, pathVariables,
parameter);
if (newParameter == null) {
return;
}
if (StringUtils.isEmpty(newParameter.getName())) {
newParameter.setName(parameter.getName());
}
if (newParameter.getRequired() == null) {
newParameter.setRequired(!parameter.isAnnotationPresent(Nullable.class));
}
if (javadocDescription != null && StringUtils.isEmpty(newParameter.getDescription())) {
CharSequence desc = javadocDescription.getParameters().get(parameter.getName());
if (desc != null) {
newParameter.setDescription(desc.toString());
}
}
swaggerParameters.add(newParameter);
Schema schema = newParameter.getSchema();
if (schema == null) {
schema = resolveSchema(openAPI, parameter, parameterType, context, consumesMediaTypes);
}
if (schema != null) {
bindSchemaForElement(context, parameter, parameterType, schema);
newParameter.setSchema(schema);
}
}
private void processBodyParameters(MethodElement element, VisitorContext context, OpenAPI openAPI,
io.swagger.v3.oas.models.Operation swaggerOperation, JavadocDescription javadocDescription,
Map pathVariables, List consumesMediaTypes) {
List bodyParameters = Arrays.stream(element.getParameters())
.filter(p -> !pathVariables.containsKey(p.getName()) && !p.isAnnotationPresent(Bindable.class)
&& !p.isAnnotationPresent(JsonIgnore.class) && !p.isAnnotationPresent(Hidden.class)
&& !p.isAnnotationPresent(Header.class) && !p.isAnnotationPresent(QueryValue.class)
&& !isAnnotationPresent(p, "io.micronaut.session.annotation.SessionValue")
&& !p.getValue(io.swagger.v3.oas.annotations.Parameter.class, "in", ParameterIn.class)
.isPresent()
&& !p.getValue(io.swagger.v3.oas.annotations.Parameter.class, "hidden", Boolean.class)
.orElse(false)
&& !isIgnoredParameterType(p.getType()))
.collect(Collectors.toList());
if (bodyParameters.isEmpty()) {
return;
}
RequestBody requestBody = swaggerOperation.getRequestBody();
final Content content;
if (requestBody == null) {
requestBody = new RequestBody();
content = new Content();
requestBody.setContent(content);
requestBody.setRequired(true);
swaggerOperation.setRequestBody(requestBody);
} else {
content = requestBody.getContent();
}
consumesMediaTypes = consumesMediaTypes.isEmpty() ? Collections.singletonList(MediaType.APPLICATION_JSON_TYPE)
: consumesMediaTypes;
consumesMediaTypes.forEach(mediaType -> {
io.swagger.v3.oas.models.media.MediaType mt = new io.swagger.v3.oas.models.media.MediaType();
ObjectSchema schema = new ObjectSchema();
for (ParameterElement parameter : bodyParameters) {
processBodyParameter(context, openAPI, javadocDescription, mediaType, schema, parameter);
}
mt.setSchema(schema);
content.addMediaType(mediaType.toString(), mt);
});
}
private void processBodyParameter(VisitorContext context, OpenAPI openAPI, JavadocDescription javadocDescription,
MediaType mediaType, ObjectSchema schema, ParameterElement parameter) {
Schema propertySchema = resolveSchema(openAPI, parameter, parameter.getType(), context,
Collections.singletonList(mediaType));
if (propertySchema != null) {
Optional description = parameter.getValue(io.swagger.v3.oas.annotations.Parameter.class,
"description", String.class);
if (description.isPresent()) {
propertySchema.setDescription(description.get());
}
processSchemaProperty(context, parameter, parameter.getType(), schema, propertySchema);
propertySchema.setNullable(parameter.isAnnotationPresent(Nullable.class));
if (javadocDescription != null && StringUtils.isEmpty(propertySchema.getDescription())) {
CharSequence doc = javadocDescription.getParameters().get(parameter.getName());
if (doc != null) {
propertySchema.setDescription(doc.toString());
}
}
}
}
private Parameter processMethodParameterAnnotation(VisitorContext context, boolean permitsRequestBody,
Map pathVariables, ParameterElement parameter) {
Parameter newParameter = null;
String parameterName = parameter.getName();
if (!parameter.hasStereotype(Bindable.class) && pathVariables.containsKey(parameterName)) {
UriMatchVariable var = pathVariables.get(parameterName);
newParameter = var.isQuery() ? new QueryParameter() : new PathParameter();
newParameter.setName(parameterName);
final boolean exploded = var.isExploded();
if (exploded) {
newParameter.setExplode(exploded);
}
} else if (parameter.isAnnotationPresent(PathVariable.class)) {
String paramName = parameter.getValue(PathVariable.class, String.class).orElse(parameterName);
UriMatchVariable var = pathVariables.get(paramName);
if (var == null) {
context.fail("Path variable name: '" + paramName + "' not found in path.", parameter);
return null;
}
newParameter = new PathParameter();
newParameter.setName(paramName);
final boolean exploded = var.isExploded();
if (exploded) {
newParameter.setExplode(exploded);
}
} else if (parameter.isAnnotationPresent(Header.class)) {
String headerName = parameter.getValue(Header.class, "name", String.class).orElse(parameter
.getValue(Header.class, String.class).orElseGet(() -> NameUtils.hyphenate(parameterName)));
newParameter = new HeaderParameter();
newParameter.setName(headerName);
} else if (parameter.isAnnotationPresent(CookieValue.class)) {
String cookieName = parameter.getValue(CookieValue.class, String.class).orElse(parameterName);
newParameter = new CookieParameter();
newParameter.setName(cookieName);
} else if (parameter.isAnnotationPresent(QueryValue.class)) {
String queryVar = parameter.getValue(QueryValue.class, String.class).orElse(parameterName);
newParameter = new QueryParameter();
newParameter.setName(queryVar);
} else if (parameter.hasAnnotation("io.micronaut.management.endpoint.annotation.Selector")) {
newParameter = new PathParameter();
newParameter.setName(parameterName);
} else if (!permitsRequestBody && hasNoBindingAnnotationOrType(parameter)) {
// default to QueryValue -
// https://github.com/micronaut-projects/micronaut-openapi/issues/130
newParameter = new QueryParameter();
newParameter.setName(parameterName);
}
if (parameter.isAnnotationPresent(io.swagger.v3.oas.annotations.Parameter.class)) {
AnnotationValue paramAnn = parameter
.findAnnotation(io.swagger.v3.oas.annotations.Parameter.class).orElse(null);
if (paramAnn != null) {
if (paramAnn.get("hidden", Boolean.class, false).booleanValue()) {
// ignore hidden parameters
return null;
}
Map paramValues = toValueMap(paramAnn.getValues(), context);
normalizeEnumValues(paramValues, Collections.singletonMap("in", ParameterIn.class));
if (parameter.isAnnotationPresent(Header.class)) {
paramValues.put("in", ParameterIn.HEADER.toString());
} else if (parameter.isAnnotationPresent(CookieValue.class)) {
paramValues.put("in", ParameterIn.COOKIE.toString());
} else if (parameter.isAnnotationPresent(QueryValue.class)) {
paramValues.put("in", ParameterIn.QUERY.toString());
}
processExplode(paramAnn, paramValues);
JsonNode jsonNode = jsonMapper.valueToTree(paramValues);
if (newParameter == null) {
try {
newParameter = treeToValue(jsonNode, Parameter.class);
} catch (Exception e) {
context.warn("Error reading Swagger Parameter for element [" + parameter + "]: "
+ e.getMessage(), parameter);
}
} else {
try {
Parameter v = treeToValue(jsonNode, Parameter.class);
if (v == null) {
BeanMap target = BeanMap.of(newParameter);
for (CharSequence name : paramValues.keySet()) {
Object o = paramValues.get(name.toString());
try {
target.put(name.toString(), o);
} catch (Exception e) {
// ignore
}
}
} else {
// horrible hack because Swagger
// ParameterDeserializer breaks updating
// existing objects
BeanMap beanMap = BeanMap.of(v);
BeanMap target = BeanMap.of(newParameter);
for (CharSequence name : paramValues.keySet()) {
Object o = beanMap.get(name.toString());
target.put(name.toString(), o);
}
}
} catch (IOException e) {
context.warn("Error reading Swagger Parameter for element [" + parameter + "]: "
+ e.getMessage(), parameter);
}
}
if (newParameter != null) {
final Schema parameterSchema = newParameter.getSchema();
if (paramAnn.contains("schema") && parameterSchema != null) {
final AnnotationValue schemaAnn = paramAnn.get("schema", AnnotationValue.class)
.orElse(null);
if (schemaAnn != null) {
bindSchemaAnnotationValue(context, parameter, parameterSchema, schemaAnn);
}
}
}
}
}
return newParameter;
}
private void processBody(VisitorContext context, OpenAPI openAPI,
io.swagger.v3.oas.models.Operation swaggerOperation, JavadocDescription javadocDescription,
boolean permitsRequestBody, List consumesMediaTypes, ParameterElement parameter,
ClassElement parameterType) {
if (!permitsRequestBody) {
return;
}
RequestBody requestBody = swaggerOperation.getRequestBody();
if (requestBody == null) {
requestBody = new RequestBody();
swaggerOperation.setRequestBody(requestBody);
}
if (requestBody.getDescription() == null && javadocDescription != null) {
CharSequence desc = javadocDescription.getParameters().get(parameter.getName());
if (desc != null) {
requestBody.setDescription(desc.toString());
}
}
if (requestBody.getRequired() == null) {
requestBody.setRequired(
!parameter.isAnnotationPresent(Nullable.class) && !parameterType.isAssignable(Optional.class));
}
final Content content;
if (consumesMediaTypes.isEmpty()) {
content = buildContent(parameter, parameterType,
Collections.singletonList(MediaType.APPLICATION_JSON_TYPE), openAPI, context);
} else {
content = buildContent(parameter, parameterType, consumesMediaTypes, openAPI, context);
}
if (requestBody.getContent() == null) {
requestBody.setContent(content);
} else {
final Content currentContent = requestBody.getContent();
content.forEach((key, value) -> currentContent.putIfAbsent(key, value));
}
}
private void readResponse(MethodElement element, VisitorContext context, OpenAPI openAPI,
io.swagger.v3.oas.models.Operation swaggerOperation, JavadocDescription javadocDescription) {
ApiResponses responses = swaggerOperation.getResponses();
if (responses == null) {
responses = new ApiResponses();
swaggerOperation.setResponses(responses);
ApiResponse okResponse = new ApiResponse();
if (javadocDescription == null) {
okResponse.setDescription(swaggerOperation.getOperationId() + " default response");
} else {
okResponse.setDescription(javadocDescription.getReturnDescription());
}
ClassElement returnType = returnType(element, context);
if (returnType != null) {
List producesMediaTypes = producesMediaTypes(element);
Content content;
if (producesMediaTypes.isEmpty()) {
content = buildContent(element, returnType, Collections.singletonList(MediaType.APPLICATION_JSON_TYPE), openAPI, context);
} else {
content = buildContent(element, returnType, producesMediaTypes, openAPI, context);
}
okResponse.setContent(content);
}
responses.put(ApiResponses.DEFAULT, okResponse);
}
}
private ClassElement returnType(MethodElement element, VisitorContext context) {
ClassElement returnType = isJavaElement(element.getOwningType(), context) ? JavaClassElementExt.getGenericReturnType(element, context)
: element.getGenericReturnType();
if (returnType.isAssignable("io.reactivex.Completable")) {
returnType = null;
} else if (isResponseType(returnType)) {
returnType = returnType.getFirstTypeArgument().orElse(returnType);
} else if (isSingleResponseType(returnType)) {
returnType = returnType.getFirstTypeArgument().get();
returnType = returnType.getFirstTypeArgument().orElse(returnType);
}
if (isJavaElement(returnType, context)) {
returnType = new JavaClassElementExt(returnType, context);
}
return returnType;
}
private Map pathVariables(UriMatchTemplate matchTemplate) {
List pv = matchTemplate.getVariables();
Map pathVariables = new LinkedHashMap<>(pv.size());
for (UriMatchVariable variable : pv) {
pathVariables.put(variable.getName(), variable);
}
return pathVariables;
}
private JavadocDescription getMethodDescription(MethodElement element,
io.swagger.v3.oas.models.Operation swaggerOperation) {
String descr = description(element);
if (StringUtils.isNotEmpty(descr) && StringUtils.isEmpty(swaggerOperation.getDescription())) {
swaggerOperation.setDescription(descr);
swaggerOperation.setSummary(descr);
}
JavadocDescription javadocDescription = element.getDocumentation().map(s -> new JavadocParser().parse(s))
.orElse(null);
if (javadocDescription != null && StringUtils.isEmpty(swaggerOperation.getDescription())) {
swaggerOperation.setDescription(javadocDescription.getMethodDescription());
swaggerOperation.setSummary(javadocDescription.getMethodDescription());
}
return javadocDescription;
}
private io.swagger.v3.oas.models.Operation readOperation(MethodElement element, VisitorContext context) {
final Optional> operationAnnotation = element.findAnnotation(Operation.class);
io.swagger.v3.oas.models.Operation swaggerOperation = operationAnnotation
.flatMap(o -> toValue(o.getValues(), context, io.swagger.v3.oas.models.Operation.class))
.orElse(new io.swagger.v3.oas.models.Operation());
if (StringUtils.isEmpty(swaggerOperation.getOperationId())) {
swaggerOperation.setOperationId(element.getName());
}
return swaggerOperation;
}
private void readSecurityRequirements(MethodElement element, VisitorContext context, io.swagger.v3.oas.models.Operation swaggerOperation) {
for (SecurityRequirement securityItem : methodSecurityRequirements(element, context)) {
swaggerOperation.addSecurityItem(securityItem);
}
}
private void processExplode(AnnotationValue paramAnn, Map paramValues) {
Optional explode = paramAnn.enumValue("explode", Explode.class);
if (explode.isPresent()) {
Explode ex = explode.get();
switch (ex) {
case TRUE:
paramValues.put("explode", Boolean.TRUE);
break;
case FALSE:
paramValues.put("explode", Boolean.FALSE);
break;
case DEFAULT:
default:
String in = (String) paramValues.get("in");
if (in == null || in.isEmpty()) {
in = "DEFAULT";
}
switch (ParameterIn.valueOf(in.toUpperCase(Locale.US))) {
case COOKIE:
case QUERY:
paramValues.put("explode", Boolean.TRUE);
break;
case DEFAULT:
case HEADER:
case PATH:
default:
paramValues.put("explode", Boolean.FALSE);
break;
}
break;
}
}
}
private boolean ignoreParameter(ParameterElement parameter) {
return parameter.isAnnotationPresent(Hidden.class) ||
parameter.isAnnotationPresent(JsonIgnore.class) ||
parameter.getValue(io.swagger.v3.oas.annotations.Parameter.class, "hidden", Boolean.class)
.orElse(false) ||
isAnnotationPresent(parameter, "io.micronaut.session.annotation.SessionValue") ||
isIgnoredParameterType(parameter.getType());
}
private boolean isIgnoredParameterType(ClassElement parameterType) {
return parameterType == null ||
parameterType.isAssignable(Principal.class) ||
parameterType.isAssignable("io.micronaut.session.Session") ||
parameterType.isAssignable("io.micronaut.security.authentication.Authentication") ||
parameterType.isAssignable("kotlin.coroutines.Continuation") ||
parameterType.isAssignable(HttpRequest.class) ||
parameterType.isAssignable("io.micronaut.http.BasicAuth");
}
private boolean isResponseType(ClassElement returnType) {
return returnType.isAssignable(HttpResponse.class) || returnType.isAssignable("org.springframework.http.HttpEntity");
}
private boolean isSingleResponseType(ClassElement returnType) {
return returnType.isAssignable("io.reactivex.Single")
&& returnType.getFirstTypeArgument().isPresent()
&& isResponseType(returnType.getFirstTypeArgument().get());
}
private io.swagger.v3.oas.models.Operation setOperationOnPathItem(PathItem pathItem, io.swagger.v3.oas.models.Operation swaggerOperation, HttpMethod httpMethod) {
io.swagger.v3.oas.models.Operation operation = swaggerOperation;
switch (httpMethod) {
case GET:
if (pathItem.getGet() != null) {
operation = pathItem.getGet();
} else {
pathItem.get(swaggerOperation);
}
break;
case POST:
if (pathItem.getPost() != null) {
operation = pathItem.getPost();
} else {
pathItem.post(swaggerOperation);
}
break;
case PUT:
if (pathItem.getPut() != null) {
operation = pathItem.getPut();
} else {
pathItem.put(swaggerOperation);
}
break;
case PATCH:
if (pathItem.getPatch() != null) {
operation = pathItem.getPatch();
} else {
pathItem.patch(swaggerOperation);
}
break;
case DELETE:
if (pathItem.getDelete() != null) {
operation = pathItem.getDelete();
} else {
pathItem.delete(swaggerOperation);
}
break;
case HEAD:
if (pathItem.getHead() != null) {
operation = pathItem.getHead();
} else {
pathItem.head(swaggerOperation);
}
break;
case OPTIONS:
if (pathItem.getOptions() != null) {
operation = pathItem.getOptions();
} else {
pathItem.options(swaggerOperation);
}
break;
case TRACE:
if (pathItem.getTrace() != null) {
operation = pathItem.getTrace();
} else {
pathItem.trace(swaggerOperation);
}
break;
default:
operation = swaggerOperation;
// unprocessable
}
return operation;
}
private void readApiResponses(MethodElement element, VisitorContext context, io.swagger.v3.oas.models.Operation swaggerOperation) {
List> responseAnnotations = element.getAnnotationValuesByType(io.swagger.v3.oas.annotations.responses.ApiResponse.class);
if (CollectionUtils.isNotEmpty(responseAnnotations)) {
ApiResponses apiResponses = new ApiResponses();
for (AnnotationValue r : responseAnnotations) {
Optional newResponse = toValue(r.getValues(), context, ApiResponse.class);
newResponse.ifPresent(apiResponse ->
apiResponses.put(r.get("responseCode", String.class).orElse("default"), apiResponse));
}
swaggerOperation.setResponses(apiResponses);
}
}
private Optional readSwaggerRequestBody(Element element, VisitorContext context) {
return element.findAnnotation(io.swagger.v3.oas.annotations.parameters.RequestBody.class)
.flatMap(annotation -> toValue(annotation.getValues(), context, RequestBody.class));
}
private void readServers(MethodElement element, VisitorContext context, io.swagger.v3.oas.models.Operation swaggerOperation) {
for (Server server : methodServers(element, context)) {
swaggerOperation.addServersItem(server);
}
}
private void readCallbacks(MethodElement element, VisitorContext context,
io.swagger.v3.oas.models.Operation swaggerOperation) {
List> callbackAnnotations = element.getAnnotationValuesByType(Callback.class);
if (CollectionUtils.isEmpty(callbackAnnotations)) {
return;
}
for (AnnotationValue callbackAnn : callbackAnnotations) {
final Optional name = callbackAnn.get("name", String.class);
if (!name.isPresent()) {
continue;
}
String callbackName = name.get();
final Optional expr = callbackAnn.get("callbackUrlExpression", String.class);
if (expr.isPresent()) {
processUrlCallbackExpression(context, swaggerOperation, callbackAnn, callbackName, expr.get());
} else {
processCallbackReference(context, swaggerOperation, callbackName);
}
}
}
private void processCallbackReference(VisitorContext context, io.swagger.v3.oas.models.Operation swaggerOperation,
String callbackName) {
final Components components = resolveComponents(resolveOpenAPI(context));
final Map callbackComponents = components
.getCallbacks();
if (callbackComponents != null && callbackComponents.containsKey(callbackName)) {
Map callbacks = initCallbacks(
swaggerOperation);
final io.swagger.v3.oas.models.callbacks.Callback callbackRef = new io.swagger.v3.oas.models.callbacks.Callback();
callbackRef.set$ref("#/components/callbacks/" + callbackName);
callbacks.put(callbackName, callbackRef);
}
}
private void processUrlCallbackExpression(VisitorContext context,
io.swagger.v3.oas.models.Operation swaggerOperation, AnnotationValue callbackAnn,
String callbackName, final String callbackUrl) {
final List> operations = callbackAnn.getAnnotations("operation",
Operation.class);
if (CollectionUtils.isEmpty(operations)) {
Map callbacks = initCallbacks(
swaggerOperation);
final io.swagger.v3.oas.models.callbacks.Callback c = new io.swagger.v3.oas.models.callbacks.Callback();
c.addPathItem(callbackUrl, new PathItem());
callbacks.put(callbackName, c);
} else {
final PathItem pathItem = new PathItem();
for (AnnotationValue operation : operations) {
final Optional operationMethod = operation.get("method", HttpMethod.class);
operationMethod.ifPresent(httpMethod -> toValue(operation.getValues(), context,
io.swagger.v3.oas.models.Operation.class)
.ifPresent(op -> setOperationOnPathItem(pathItem, op, httpMethod)));
}
Map callbacks = initCallbacks(
swaggerOperation);
final io.swagger.v3.oas.models.callbacks.Callback c = new io.swagger.v3.oas.models.callbacks.Callback();
c.addPathItem(callbackUrl, pathItem);
callbacks.put(callbackName, c);
}
}
private Map initCallbacks(io.swagger.v3.oas.models.Operation swaggerOperation) {
Map callbacks = swaggerOperation.getCallbacks();
if (callbacks == null) {
callbacks = new LinkedHashMap<>();
swaggerOperation.setCallbacks(callbacks);
}
return callbacks;
}
private void addTagIfNotPresent(String tag, io.swagger.v3.oas.models.Operation swaggerOperation) {
List tags = swaggerOperation.getTags();
if (tags == null || !tags.contains(tag)) {
swaggerOperation.addTagsItem(tag);
}
}
private void readTags(MethodElement element, io.swagger.v3.oas.models.Operation swaggerOperation, List classTags) {
element.getAnnotationValuesByType(Tag.class).forEach(av -> av.get("name", String.class).ifPresent(swaggerOperation::addTagsItem));
// only way to get inherited tags
element.getValues(Tags.class, AnnotationValue.class).forEach((k, v) -> v.get("name", String.class).ifPresent(name -> addTagIfNotPresent((String) name, swaggerOperation)));
classTags.forEach(tag -> addTagIfNotPresent(tag.getName(), swaggerOperation));
}
private List readTags(ClassElement element, VisitorContext context) {
return element.getAnnotationValuesByType(Tag.class).stream()
.map(av -> toValue(av.getValues(), context, io.swagger.v3.oas.models.tags.Tag.class))
.filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
}
private Content buildContent(Element definingElement, ClassElement type, List mediaTypes, OpenAPI openAPI, VisitorContext context) {
Content content = new Content();
mediaTypes.forEach(mediaType -> {
io.swagger.v3.oas.models.media.MediaType mt = new io.swagger.v3.oas.models.media.MediaType();
mt.setSchema(resolveSchema(openAPI, definingElement, type, context, Collections.singletonList(mediaType)));
content.addMediaType(mediaType.toString(), mt);
});
return content;
}
/**
* @return An Instance of {@link PropertyPlaceholderResolver} to resolve placeholders.
*/
PropertyPlaceholderResolver getPropertyPlaceholderResolver() {
if (this.propertyPlaceholderResolver == null) {
this.propertyPlaceholderResolver = new DefaultPropertyPlaceholderResolver(new PropertyResolver() {
@Override
public boolean containsProperty(@Nonnull String name) {
return false;
}
@Override
public boolean containsProperties(@Nonnull String name) {
return false;
}
@Nonnull
@Override
public Optional getProperty(@Nonnull String name, @Nonnull ArgumentConversionContext conversionContext) {
return Optional.empty();
}
}, new DefaultConversionService());
}
return this.propertyPlaceholderResolver;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy