
io.smallrye.openapi.runtime.scanner.OpenApiAnnotationScanner Maven / Gradle / Ivy
/**
* Copyright 2018 Red Hat, Inc, and individual contributors.
*
* 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
*
* http://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.smallrye.openapi.runtime.scanner;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.ws.rs.core.Application;
import org.eclipse.microprofile.openapi.annotations.enums.Explode;
import org.eclipse.microprofile.openapi.models.Components;
import org.eclipse.microprofile.openapi.models.ExternalDocumentation;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.eclipse.microprofile.openapi.models.Operation;
import org.eclipse.microprofile.openapi.models.PathItem;
import org.eclipse.microprofile.openapi.models.PathItem.HttpMethod;
import org.eclipse.microprofile.openapi.models.Paths;
import org.eclipse.microprofile.openapi.models.callbacks.Callback;
import org.eclipse.microprofile.openapi.models.examples.Example;
import org.eclipse.microprofile.openapi.models.headers.Header;
import org.eclipse.microprofile.openapi.models.info.Contact;
import org.eclipse.microprofile.openapi.models.info.Info;
import org.eclipse.microprofile.openapi.models.info.License;
import org.eclipse.microprofile.openapi.models.links.Link;
import org.eclipse.microprofile.openapi.models.media.Content;
import org.eclipse.microprofile.openapi.models.media.Encoding;
import org.eclipse.microprofile.openapi.models.media.MediaType;
import org.eclipse.microprofile.openapi.models.media.Schema;
import org.eclipse.microprofile.openapi.models.media.Schema.SchemaType;
import org.eclipse.microprofile.openapi.models.parameters.Parameter;
import org.eclipse.microprofile.openapi.models.parameters.RequestBody;
import org.eclipse.microprofile.openapi.models.responses.APIResponse;
import org.eclipse.microprofile.openapi.models.responses.APIResponses;
import org.eclipse.microprofile.openapi.models.security.OAuthFlow;
import org.eclipse.microprofile.openapi.models.security.OAuthFlows;
import org.eclipse.microprofile.openapi.models.security.Scopes;
import org.eclipse.microprofile.openapi.models.security.SecurityRequirement;
import org.eclipse.microprofile.openapi.models.security.SecurityScheme;
import org.eclipse.microprofile.openapi.models.servers.Server;
import org.eclipse.microprofile.openapi.models.servers.ServerVariable;
import org.eclipse.microprofile.openapi.models.servers.ServerVariables;
import org.eclipse.microprofile.openapi.models.tags.Tag;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.smallrye.openapi.api.OpenApiConfig;
import io.smallrye.openapi.api.OpenApiConstants;
import io.smallrye.openapi.api.models.ComponentsImpl;
import io.smallrye.openapi.api.models.ExternalDocumentationImpl;
import io.smallrye.openapi.api.models.OpenAPIImpl;
import io.smallrye.openapi.api.models.OperationImpl;
import io.smallrye.openapi.api.models.PathItemImpl;
import io.smallrye.openapi.api.models.PathsImpl;
import io.smallrye.openapi.api.models.callbacks.CallbackImpl;
import io.smallrye.openapi.api.models.examples.ExampleImpl;
import io.smallrye.openapi.api.models.headers.HeaderImpl;
import io.smallrye.openapi.api.models.info.ContactImpl;
import io.smallrye.openapi.api.models.info.InfoImpl;
import io.smallrye.openapi.api.models.info.LicenseImpl;
import io.smallrye.openapi.api.models.links.LinkImpl;
import io.smallrye.openapi.api.models.media.ContentImpl;
import io.smallrye.openapi.api.models.media.EncodingImpl;
import io.smallrye.openapi.api.models.media.MediaTypeImpl;
import io.smallrye.openapi.api.models.media.SchemaImpl;
import io.smallrye.openapi.api.models.parameters.ParameterImpl;
import io.smallrye.openapi.api.models.parameters.RequestBodyImpl;
import io.smallrye.openapi.api.models.responses.APIResponseImpl;
import io.smallrye.openapi.api.models.responses.APIResponsesImpl;
import io.smallrye.openapi.api.models.security.OAuthFlowImpl;
import io.smallrye.openapi.api.models.security.OAuthFlowsImpl;
import io.smallrye.openapi.api.models.security.ScopesImpl;
import io.smallrye.openapi.api.models.security.SecurityRequirementImpl;
import io.smallrye.openapi.api.models.security.SecuritySchemeImpl;
import io.smallrye.openapi.api.models.servers.ServerImpl;
import io.smallrye.openapi.api.models.servers.ServerVariableImpl;
import io.smallrye.openapi.api.models.servers.ServerVariablesImpl;
import io.smallrye.openapi.api.models.tags.TagImpl;
import io.smallrye.openapi.api.util.MergeUtil;
import io.smallrye.openapi.runtime.scanner.ParameterProcessor.ResourceParameters;
import io.smallrye.openapi.runtime.util.JandexUtil;
import io.smallrye.openapi.runtime.util.JandexUtil.RefType;
import io.smallrye.openapi.runtime.util.ModelUtil;
import io.smallrye.openapi.runtime.util.SchemaFactory;
import io.smallrye.openapi.runtime.util.TypeUtil;
/**
* Scans a deployment (using the archive and jandex annotation index) for JAX-RS and
* OpenAPI annotations. These annotations, if found, are used to generate a valid
* OpenAPI model. For reference, see:
*
* https://github.com/eclipse/microprofile-open-api/blob/master/spec/src/main/asciidoc/microprofile-openapi-spec.adoc#annotations
*
* @author [email protected]
*/
public class OpenApiAnnotationScanner {
private static final Logger LOG = Logger.getLogger(OpenApiAnnotationScanner.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private final OpenApiConfig config;
private final IndexView index;
private String currentAppPath = "";
private String[] currentConsumes;
private String[] currentProduces;
private String currentSecurityScheme;
private List currentFlows;
private String[] resourceRolesAllowed;
private List extensions;
/**
* Constructor.
*
* @param config OpenApiConfig instance
* @param index IndexView of deployment
*/
public OpenApiAnnotationScanner(OpenApiConfig config, IndexView index) {
this(config, index, Collections.emptyList());
}
/**
* Constructor.
*
* @param config OpenApiConfig instance
* @param index IndexView of deployment
* @param extensions A set of extensions to scanning
*/
public OpenApiAnnotationScanner(OpenApiConfig config, IndexView index, List extensions) {
this.config = config;
if (index instanceof FilteredIndexView) {
this.index = index;
} else {
this.index = new FilteredIndexView(index, config);
}
this.extensions = extensions;
}
/**
* Scan the deployment for relevant annotations. Returns an OpenAPI data model that was
* built from those found annotations.
*
* @return OpenAPIImpl generated from scanning annotations
*/
public OpenAPIImpl scan() {
LOG.debug("Scanning deployment for OpenAPI and JAX-RS Annotations.");
// Initialize a new OAI document. Even if nothing is found, this will be returned.
OpenAPIImpl oai = new OpenAPIImpl();
oai.setOpenapi(OpenApiConstants.OPEN_API_VERSION);
// Creating a new instance of a registry which will be set on the thread context.
SchemaRegistry schemaRegistry = SchemaRegistry.newInstance(config, oai, index);
// Register custom schemas if available
getCustomSchemaRegistry().registerCustomSchemas(schemaRegistry);
// Get all jax-rs applications and convert them to OAI models (and merge them into a single one)
Collection applications = this.index
.getAllKnownSubclasses(DotName.createSimple(Application.class.getName()));
for (ClassInfo classInfo : applications) {
oai = MergeUtil.merge(oai, jaxRsApplicationToOpenApi(classInfo));
}
boolean tagsDefined = oai.getTags() != null && !oai.getTags().isEmpty();
// this can be a useful extension point to set/override the application path
for (AnnotationScannerExtension extension : extensions) {
extension.processJaxRsApplications(this, applications);
}
// TODO find all OpenAPIDefinition annotations at the package level
checkSecurityScheme(oai);
// Now find all jax-rs endpoints
Collection resourceClasses = JandexUtil.getJaxRsResourceClasses(this.index);
for (ClassInfo resourceClass : resourceClasses) {
processJaxRsResourceClass(oai, resourceClass, null);
}
if (oai != null) {
// Sort the tags unless the application has defined the order in OpenAPIDefinition annotation(s)
if (!tagsDefined && oai.getTags() != null) {
oai.setTags(oai.getTags()
.stream()
.sorted(Comparator.comparing(Tag::getName))
.collect(Collectors.toList()));
}
// Now that all paths have been created, sort them (we don't have a better way to organize them).
Paths paths = oai.getPaths();
if (paths != null) {
Paths sortedPaths = new PathsImpl();
TreeSet sortedKeys = new TreeSet<>(paths.keySet());
for (String pathKey : sortedKeys) {
PathItem pathItem = paths.getPathItem(pathKey);
sortedPaths.addPathItem(pathKey, pathItem);
}
sortedPaths.setExtensions(paths.getExtensions());
oai.setPaths(sortedPaths);
}
}
return oai;
}
/**
* If there is a single security scheme defined by the @OpenAPIDefinition
* annotations and the scheme is OAuth2 or OpenIdConnect, any of the flows
* where no scopes have yet been provided are eligible to have scopes
* filled by @DeclareRoles
/@RolesAllowed
annotations.
*
* @param oai the current OpenAPI result
*/
void checkSecurityScheme(OpenAPI oai) {
if (oai.getComponents() == null) {
return;
}
Map schemes = oai.getComponents().getSecuritySchemes();
if (schemes != null && schemes.size() == 1) {
Map.Entry scheme = schemes.entrySet().iterator().next();
SecurityScheme.Type schemeType = scheme.getValue().getType();
if (schemeType != null) {
switch (schemeType) {
case OAUTH2:
case OPENIDCONNECT:
saveSecurityScheme(scheme.getKey(), scheme.getValue());
break;
default:
break;
}
}
}
}
/**
* Saves the name of the SecurityScheme and references to any flows
* that did not have scopes defined by the application via a component
* defined in @OpenAPIDefinition
annotations. The saved
* flows may have scopes added by values discovered in @RolesAllowed
* annotations during scanning.
*
* @param scheme the scheme to save for further role processing.
*/
void saveSecurityScheme(String schemeName, SecurityScheme scheme) {
this.currentSecurityScheme = schemeName;
this.currentFlows = new ArrayList<>();
OAuthFlows flows = scheme.getFlows();
saveFlow(flows.getAuthorizationCode());
saveFlow(flows.getClientCredentials());
saveFlow(flows.getImplicit());
saveFlow(flows.getPassword());
}
/**
* Saves an {@link OAuthFlow} object in the list of flows for further processing.
* Only saved if no scopes were defined by the application using annotations.
*
* @param flow
*/
void saveFlow(OAuthFlow flow) {
if (flow != null && flow.getScopes() == null) {
currentFlows.add(flow);
}
}
/**
* Processes a JAX-RS {@link Application} and creates an {@link OpenAPI} model. Performs
* annotation scanning and other processing. Returns a model unique to that single JAX-RS
* app.
*
* @param applicationClass
*/
private OpenAPIImpl jaxRsApplicationToOpenApi(ClassInfo applicationClass) {
OpenAPIImpl oai = new OpenAPIImpl();
oai.setOpenapi(OpenApiConstants.OPEN_API_VERSION);
// Get the @ApplicationPath info and save it for later (also support @Path which seems nonstandard but common).
////////////////////////////////////////
AnnotationInstance appPathAnno = JandexUtil.getClassAnnotation(applicationClass,
OpenApiConstants.DOTNAME_APPLICATION_PATH);
if (appPathAnno == null) {
appPathAnno = JandexUtil.getClassAnnotation(applicationClass, OpenApiConstants.DOTNAME_PATH);
}
// TODO: Add support for Application selection when there are more than one
if (appPathAnno != null) {
this.currentAppPath = appPathAnno.value().asString();
} else {
this.currentAppPath = "/";
}
// Get the @OpenAPIDefinition annotation and process it.
////////////////////////////////////////
AnnotationInstance openApiDefAnno = JandexUtil.getClassAnnotation(applicationClass,
OpenApiConstants.DOTNAME_OPEN_API_DEFINITION);
if (openApiDefAnno != null) {
processDefinition(oai, openApiDefAnno);
}
// Process @SecurityScheme annotations
////////////////////////////////////////
List securitySchemeAnnotations = JandexUtil.getRepeatableAnnotation(applicationClass,
OpenApiConstants.DOTNAME_SECURITY_SCHEME, OpenApiConstants.DOTNAME_SECURITY_SCHEMES);
for (AnnotationInstance annotation : securitySchemeAnnotations) {
String name = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_SECURITY_SCHEME_NAME);
if (name == null && JandexUtil.isRef(annotation)) {
name = JandexUtil.nameFromRef(annotation);
}
if (name != null) {
SecurityScheme securityScheme = readSecurityScheme(annotation);
Components components = ModelUtil.components(oai);
components.addSecurityScheme(name, securityScheme);
}
}
// Process @Server annotations
///////////////////////////////////
List serverAnnotations = JandexUtil.getRepeatableAnnotation(applicationClass,
OpenApiConstants.DOTNAME_SERVER, OpenApiConstants.DOTNAME_SERVERS);
for (AnnotationInstance annotation : serverAnnotations) {
Server server = readServer(annotation);
oai.addServer(server);
}
return oai;
}
/**
* Processing a single JAX-RS resource class (annotated with @Path).
*
* @param openApi
* @param resourceClass
* @param locatorPathParameters
*/
private void processJaxRsResourceClass(OpenAPIImpl openApi, ClassInfo resourceClass,
List locatorPathParameters) {
LOG.debug("Processing a JAX-RS resource class: " + resourceClass.simpleName());
// Process @SecurityScheme annotations
////////////////////////////////////////
List securitySchemeAnnotations = JandexUtil.getRepeatableAnnotation(resourceClass,
OpenApiConstants.DOTNAME_SECURITY_SCHEME, OpenApiConstants.DOTNAME_SECURITY_SCHEMES);
for (AnnotationInstance annotation : securitySchemeAnnotations) {
String name = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_SECURITY_SCHEME_NAME);
if (name == null && JandexUtil.isRef(annotation)) {
name = JandexUtil.nameFromRef(annotation);
}
if (name != null) {
SecurityScheme securityScheme = readSecurityScheme(annotation);
Components components = ModelUtil.components(openApi);
components.addSecurityScheme(name, securityScheme);
}
}
// Process tags (both declarations and references)
////////////////////////////////////////
Set tagRefs = processTags(openApi, resourceClass, false);
addScopes(TypeUtil.getAnnotationValue(resourceClass, OpenApiConstants.DOTNAME_DECLARE_ROLES));
resourceRolesAllowed = TypeUtil.getAnnotationValue(resourceClass, OpenApiConstants.DOTNAME_ROLES_ALLOWED);
addScopes(resourceRolesAllowed);
// Now find and process the operation methods
////////////////////////////////////////
for (MethodInfo methodInfo : getResourceMethods(resourceClass)) {
final AtomicInteger resourceCount = new AtomicInteger(0);
OpenApiConstants.DOTNAME_JAXRS_HTTP_METHODS
.stream()
.filter(methodInfo::hasAnnotation)
.map(DotName::withoutPackagePrefix)
.map(HttpMethod::valueOf)
.forEach(httpMethod -> {
resourceCount.incrementAndGet();
processJaxRsMethod(openApi, resourceClass, methodInfo, httpMethod, tagRefs, locatorPathParameters);
});
if (resourceCount.get() == 0 && methodInfo.hasAnnotation(OpenApiConstants.DOTNAME_PATH)) {
processJaxRsSubResource(openApi, locatorPathParameters, resourceClass, methodInfo);
}
}
}
/**
* Adds the array of roles as scopes to each of the OAuth2 flows stored previously.
* The flows are those declared by the application in components/securitySchemes
* using annotations where the scopes were not defined. The description of the scope
* will be set to the role name plus the string " role".
*
* @param roles array of roles from either @DeclareRoles
or
* @RolesAllowed
*/
void addScopes(String[] roles) {
if (roles == null || this.currentFlows == null) {
return;
}
this.currentFlows.forEach(flow -> {
if (flow.getScopes() == null) {
flow.setScopes(new ScopesImpl());
}
Arrays.stream(roles).forEach(role -> flow.getScopes().addScope(role, role + " role"));
});
}
/**
* Extracts all methods from the provided class and its ancestors that are known to the instance's index
*
* @param resource
* @return all methods from the provided class and its ancestors
*/
List getResourceMethods(ClassInfo resource) {
Type resourceType = Type.create(resource.name(), Type.Kind.CLASS);
Map chain = JandexUtil.inheritanceChain(index, resource, resourceType);
List methods = new ArrayList<>();
for (ClassInfo classInfo : chain.keySet()) {
methods.addAll(classInfo.methods());
classInfo.interfaceTypes()
.stream()
.map(iface -> index.getClassByName(TypeUtil.getName(iface)))
.filter(Objects::nonNull)
.flatMap(iface -> iface.methods().stream())
.forEach(methods::add);
}
return methods;
}
/**
* Scans a sub-resource locator method's return type as a resource class. The list of locator path parameters
* will be expanded with any parameters that apply to the resource sub-locator method (both path and operation
* parameters).
*
* @param openApi current OAI result
* @param locatorPathParameters the parent resource's list of path parameters, may be null
* @param resourceClass the JAX-RS resource class being processed. May be a sub-class of the class which declares method
* @param method sub-resource locator JAX-RS method
*/
private void processJaxRsSubResource(OpenAPIImpl openApi, List locatorPathParameters, ClassInfo resourceClass,
MethodInfo method) {
final Type methodReturnType = method.returnType();
if (Type.Kind.VOID.equals(methodReturnType.kind())) {
// Can sub-resource locators return a CompletionStage?
return;
}
ClassInfo subResourceClass = index.getClassByName(methodReturnType.name());
if (subResourceClass != null) {
final String originalAppPath = this.currentAppPath;
ResourceParameters params = ParameterProcessor.process(index, resourceClass, method, this::readParameter,
extensions);
this.currentAppPath = makePath(this.currentAppPath, params.getOperationPath());
/*
* Combine parameters passed previously with all of those from the current resource class and
* method that apply to this Path. The full list will be used as PATH-LEVEL parameters for
* sub-resource methods deeper in the scan.
*/
processJaxRsResourceClass(openApi, subResourceClass,
mergeNullableLists(locatorPathParameters,
params.getPathItemParameters(),
params.getOperationParameters()));
this.currentAppPath = originalAppPath;
}
}
/**
* Process a single JAX-RS method to produce an OpenAPI Operation.
*
* @param openApi
* @param resourceClass
* @param method
* @param methodType
* @param resourceTags
* @param locatorPathParameters
*/
private void processJaxRsMethod(OpenAPIImpl openApi, ClassInfo resourceClass, MethodInfo method,
HttpMethod methodType, Set resourceTags,
List locatorPathParameters) {
LOG.debugf("Processing jax-rs method: {0}", method.toString());
final Operation operation;
// Process any @Operation annotation
/////////////////////////////////////////
if (method.hasAnnotation(OpenApiConstants.DOTNAME_OPERATION)) {
AnnotationInstance operationAnno = method.annotation(OpenApiConstants.DOTNAME_OPERATION);
// If the operation is marked as hidden, just bail here because we don't want it as part of the model.
if (operationAnno.value(OpenApiConstants.PROP_HIDDEN) != null
&& operationAnno.value(OpenApiConstants.PROP_HIDDEN).asBoolean()) {
return;
}
operation = new OperationImpl();
// Otherwise, set various bits of meta-data from the values in the @Operation annotation
operation.setSummary(JandexUtil.stringValue(operationAnno, OpenApiConstants.PROP_SUMMARY));
operation.setDescription(JandexUtil.stringValue(operationAnno, OpenApiConstants.PROP_DESCRIPTION));
operation.setOperationId(JandexUtil.stringValue(operationAnno, OpenApiConstants.PROP_OPERATION_ID));
operation.setDeprecated(JandexUtil.booleanValue(operationAnno, OpenApiConstants.PROP_DEPRECATED));
} else {
operation = new OperationImpl();
}
PathItem pathItem = new PathItemImpl();
// Figure out the current @Produces and @Consumes (if any)
currentConsumes = getMediaTypes(method, OpenApiConstants.DOTNAME_CONSUMES);
currentProduces = getMediaTypes(method, OpenApiConstants.DOTNAME_PRODUCES);
// Process tags - @Tag and @Tags annotations combines with the resource tags we've already found (passed in)
/////////////////////////////////////////
Set tags = processTags(openApi, method, true);
if (tags == null) {
if (!resourceTags.isEmpty()) {
operation.setTags(new ArrayList<>(resourceTags));
}
} else if (!tags.isEmpty()) {
operation.setTags(new ArrayList<>(tags));
}
// Process @Parameter annotations
/////////////////////////////////////////
ResourceParameters params = ParameterProcessor.process(index, resourceClass, method, this::readParameter, extensions);
operation.setParameters(params.getOperationParameters());
pathItem.setParameters(mergeNullableLists(locatorPathParameters, params.getPathItemParameters()));
// Process any @RequestBody annotation
/////////////////////////////////////////
// note: the @RequestBody annotation can be found on a method argument *or* on the method
RequestBody requestBody = null;
List requestBodyAnnotations = JandexUtil.getRepeatableAnnotation(method,
OpenApiConstants.DOTNAME_REQUEST_BODY, null);
for (AnnotationInstance annotation : requestBodyAnnotations) {
requestBody = readRequestBody(annotation);
Content formBodyContent = params.getFormBodyContent();
if (formBodyContent != null) {
// If form parameters were present, overlay RequestBody onto the generated form content
requestBody.setContent((Content) MergeUtil.mergeMaps(formBodyContent, requestBody.getContent()));
}
// TODO if the method argument type is Request, don't generate a Schema!
Type requestBodyType = null;
if (annotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
requestBodyType = JandexUtil.getMethodParameterType(method,
annotation.target().asMethodParameter().position());
} else if (annotation.target().kind() == AnnotationTarget.Kind.METHOD) {
requestBodyType = JandexUtil.getRequestBodyParameterClassType(method, extensions);
}
// Only generate the request body schema if the @RequestBody is not a reference and no schema is yet specified
if (requestBodyType != null && requestBody.getRef() == null) {
if (!ModelUtil.requestBodyHasSchema(requestBody)) {
Schema schema = SchemaFactory.typeToSchema(index, requestBodyType, extensions);
if (schema != null) {
ModelUtil.setRequestBodySchema(requestBody, schema, currentConsumes);
}
}
if (requestBody.getRequired() == null && TypeUtil.isOptional(requestBodyType)) {
requestBody.setRequired(Boolean.FALSE);
}
}
}
// If the request body is null, figure it out from the parameters. Only if the
// method declares that it @Consumes data
if ((requestBody == null || (requestBody.getContent() == null && requestBody.getRef() == null))
&& currentConsumes != null) {
if (params.getFormBodySchema() != null) {
if (requestBody == null) {
requestBody = new RequestBodyImpl();
}
Schema schema = params.getFormBodySchema();
ModelUtil.setRequestBodySchema(requestBody, schema, currentConsumes);
} else {
Type requestBodyType = JandexUtil.getRequestBodyParameterClassType(method, extensions);
if (requestBodyType != null) {
Schema schema = null;
if (OpenApiConstants.DOTNAME_RESTEASY_MULTIPART_INPUTS.contains(requestBodyType.name())) {
schema = new SchemaImpl();
schema.setType(SchemaType.OBJECT);
} else {
schema = SchemaFactory.typeToSchema(index, requestBodyType, extensions);
}
if (requestBody == null) {
requestBody = new RequestBodyImpl();
}
if (schema != null) {
ModelUtil.setRequestBodySchema(requestBody, schema, currentConsumes);
}
if (requestBody.getRequired() == null && TypeUtil.isOptional(requestBodyType)) {
requestBody.setRequired(Boolean.FALSE);
}
}
}
}
if (requestBody != null) {
operation.setRequestBody(requestBody);
}
// Process @APIResponse annotations
/////////////////////////////////////////
APIResponses responses = null;
List apiResponseAnnotations = JandexUtil.getRepeatableAnnotation(method,
OpenApiConstants.DOTNAME_API_RESPONSE, OpenApiConstants.DOTNAME_API_RESPONSES);
for (AnnotationInstance annotation : apiResponseAnnotations) {
String responseCode = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_RESPONSE_CODE);
if (responseCode == null) {
responseCode = APIResponses.DEFAULT;
}
APIResponse response = readResponse(annotation);
responses = ModelUtil.responses(operation);
responses.addAPIResponse(responseCode, response);
}
/*
* If there is no response from annotations, try to create one from the method return value.
* Do not generate a response if the app has used an empty @ApiResponses annotation. This
* provides a way for the application to indicate that responses will be supplied some other
* way (i.e. static file).
*/
AnnotationInstance apiResponses = method.annotation(OpenApiConstants.DOTNAME_API_RESPONSES);
if (apiResponses == null || !JandexUtil.isEmpty(apiResponses)) {
createResponseFromJaxRsMethod(method, operation);
}
// Process @SecurityRequirement annotations
///////////////////////////////////////////
List securityRequirementAnnotations = JandexUtil.getRepeatableAnnotation(method,
OpenApiConstants.DOTNAME_SECURITY_REQUIREMENT, OpenApiConstants.DOTNAME_SECURITY_REQUIREMENTS);
securityRequirementAnnotations.addAll(
JandexUtil.getRepeatableAnnotation(resourceClass, OpenApiConstants.DOTNAME_SECURITY_REQUIREMENT,
OpenApiConstants.DOTNAME_SECURITY_REQUIREMENTS));
for (AnnotationInstance annotation : securityRequirementAnnotations) {
SecurityRequirement requirement = readSecurityRequirement(annotation);
if (requirement != null) {
operation.addSecurityRequirement(requirement);
}
}
// Process @Callback annotations
/////////////////////////////////////////
List callbackAnnotations = JandexUtil.getRepeatableAnnotation(method,
OpenApiConstants.DOTNAME_CALLBACK, OpenApiConstants.DOTNAME_CALLBACKS);
Map callbacks = new LinkedHashMap<>();
for (AnnotationInstance annotation : callbackAnnotations) {
String name = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(annotation)) {
name = JandexUtil.nameFromRef(annotation);
}
if (name != null) {
callbacks.put(name, readCallback(annotation));
}
if (!callbacks.isEmpty()) {
operation.setCallbacks(callbacks);
}
}
// Process @Server annotations
///////////////////////////////////
List serverAnnotations = JandexUtil.getRepeatableAnnotation(method,
OpenApiConstants.DOTNAME_SERVER, OpenApiConstants.DOTNAME_SERVERS);
if (serverAnnotations.isEmpty()) {
serverAnnotations.addAll(JandexUtil.getRepeatableAnnotation(method.declaringClass(),
OpenApiConstants.DOTNAME_SERVER, OpenApiConstants.DOTNAME_SERVERS));
}
for (AnnotationInstance annotation : serverAnnotations) {
Server server = readServer(annotation);
operation.addServer(server);
}
// Process @Extension annotations
///////////////////////////////////
List extensionAnnotations = JandexUtil.getRepeatableAnnotation(method,
OpenApiConstants.DOTNAME_EXTENSION, OpenApiConstants.DOTNAME_EXTENSIONS);
if (extensionAnnotations.isEmpty()) {
extensionAnnotations.addAll(JandexUtil.getRepeatableAnnotation(method.declaringClass(),
OpenApiConstants.DOTNAME_EXTENSION, OpenApiConstants.DOTNAME_EXTENSIONS));
}
for (AnnotationInstance annotation : extensionAnnotations) {
String name = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_NAME);
String value = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_VALUE);
boolean parseValue = JandexUtil.booleanValueWithDefault(annotation, OpenApiConstants.PROP_PARSE_VALUE);
Object parsedValue = value;
if (parseValue) {
parsedValue = parseExtensionValue(value);
}
operation.addExtension(name, parsedValue);
}
processSecurityRoles(method, operation);
// Now set the operation on the PathItem as appropriate based on the Http method type
///////////////////////////////////////////
switch (methodType) {
case DELETE:
pathItem.setDELETE(operation);
break;
case GET:
pathItem.setGET(operation);
break;
case HEAD:
pathItem.setHEAD(operation);
break;
case OPTIONS:
pathItem.setOPTIONS(operation);
break;
case PATCH:
pathItem.setPATCH(operation);
break;
case POST:
pathItem.setPOST(operation);
break;
case PUT:
pathItem.setPUT(operation);
break;
case TRACE:
pathItem.setTRACE(operation);
break;
default:
break;
}
// Figure out the path for the operation. This is a combination of the App, Resource, and Method @Path annotations
String path = makePath(this.currentAppPath, params.getOperationPath());
// Get or create a PathItem to hold the operation
PathItem existingPath = ModelUtil.paths(openApi).getPathItem(path);
if (existingPath == null) {
ModelUtil.paths(openApi).addPathItem(path, pathItem);
} else {
// Changes applied to 'existingPath', no need to re-assign or add to OAI.
MergeUtil.mergeObjects(existingPath, pathItem);
}
}
/**
* Processes any {@link org.eclipse.microprofile.openapi.annotations.tags.Tag} or
* {@link org.eclipse.microprofile.openapi.annotations.tags.Tags} annotations present on
* the annotation target and adds them to the OpenAPI model. The set of tag names found
* (with iteration order preserved) is returned.
*
* @param openApi OpenAPI model
* @param target a MethodInfo or ClassInfo to read for tag annotations
* @param nullWhenMissing determines if an empty set or a null value is returned when no annotations are found.
* @return the set of tag names found
*/
Set processTags(OpenAPIImpl openApi, AnnotationTarget target, boolean nullWhenMissing) {
if (!TypeUtil.hasAnnotation(target, OpenApiConstants.DOTNAME_TAG) &&
!TypeUtil.hasAnnotation(target, OpenApiConstants.DOTNAME_TAGS)) {
return nullWhenMissing ? null : Collections.emptySet();
}
Set tags = new LinkedHashSet<>();
List tagAnnos = JandexUtil.getRepeatableAnnotation(target,
OpenApiConstants.DOTNAME_TAG,
OpenApiConstants.DOTNAME_TAGS);
for (AnnotationInstance ta : tagAnnos) {
if (JandexUtil.isRef(ta)) {
tags.add(JandexUtil.value(ta, OpenApiConstants.PROP_REF));
} else {
Tag tag = readTag(ta);
if (tag.getName() != null) {
ModelUtil.addTag(openApi, tag);
tags.add(tag.getName());
}
}
}
String[] refs = TypeUtil.getAnnotationValue(target, OpenApiConstants.DOTNAME_TAGS, OpenApiConstants.PROP_REFS);
if (refs != null) {
Arrays.stream(refs).forEach(tags::add);
}
return tags;
}
static String[] getMediaTypes(MethodInfo resourceMethod, DotName annotationName) {
AnnotationInstance annotation = resourceMethod.annotation(annotationName);
if (annotation == null) {
annotation = JandexUtil.getClassAnnotation(resourceMethod.declaringClass(), annotationName);
}
if (annotation != null) {
AnnotationValue annotationValue = annotation.value();
if (annotationValue != null) {
return annotationValue.asStringArray();
}
return OpenApiConstants.DEFAULT_MEDIA_TYPES.get();
}
return null;
}
/**
* Called when a jax-rs method's APIResponse annotations have all been processed but
* no response was actually created for the operation. This method will create a response
* from the method information and add it to the given operation. It will try to do this
* by examining the method's return value and the type of operation (GET, PUT, POST, DELETE).
*
* If there is a return value of some kind (a non-void return type) then the response code
* is assumed to be 200.
*
* If there not a return value (void return type) then either a 201 or 204 is returned,
* depending on the type of request.
*
* TODO generate responses for each checked exception?
*
* @param method
* @param operation
*/
private void createResponseFromJaxRsMethod(MethodInfo method, Operation operation) {
Type returnType = method.returnType();
APIResponse response = null;
String code = "200";
String description = "OK";
if (returnType.kind() == Type.Kind.VOID) {
boolean asyncResponse = method.parameters()
.stream()
.map(Type::name)
.anyMatch(OpenApiConstants.DOTNAME_ASYNC_RESPONSE::equals);
if (method.hasAnnotation(OpenApiConstants.DOTNAME_POST)) {
code = "201";
description = "Created";
} else if (!asyncResponse) {
code = "204";
description = "No Content";
}
if (generateResponse(code, operation)) {
response = new APIResponseImpl().description(description);
}
} else if (generateResponse(code, operation)) {
response = new APIResponseImpl().description(description);
/*
* Only generate content if not already supplied in annotations and the
* method does not return an opaque JAX-RS Response
*/
if (!returnType.name().equals(OpenApiConstants.DOTNAME_RESPONSE) &&
(ModelUtil.responses(operation).getAPIResponse(code) == null ||
ModelUtil.responses(operation).getAPIResponse(code).getContent() == null)) {
Schema schema;
if (OpenApiConstants.DOTNAME_RESTEASY_MULTIPART_OUTPUTS.contains(returnType.name())) {
schema = new SchemaImpl();
schema.setType(SchemaType.OBJECT);
} else {
schema = SchemaFactory.typeToSchema(index, returnType, extensions);
}
ContentImpl content = new ContentImpl();
String[] produces = this.currentProduces;
if (produces == null || produces.length == 0) {
produces = OpenApiConstants.DEFAULT_MEDIA_TYPES.get();
}
if (schema != null && schema.getNullable() == null && TypeUtil.isOptional(returnType)) {
schema.setNullable(Boolean.TRUE);
}
for (String producesType : produces) {
MediaType mt = new MediaTypeImpl();
mt.setSchema(schema);
content.addMediaType(producesType, mt);
}
response.setContent(content);
}
}
if (response != null) {
APIResponses responses = ModelUtil.responses(operation);
if (responses.hasAPIResponse(code)) {
APIResponse responseFromAnnotations = responses.getAPIResponse(code);
responses.removeAPIResponse(code);
// Overlay the information from the annotations (2nd arg) onto the generated details (1st)
response = MergeUtil.mergeObjects(response, responseFromAnnotations);
}
responses.addAPIResponse(code, response);
}
}
/**
* Determine if the default response information should be generated.
* It should be done when no responses have been declared or if the default
* response already exists and is missing information (e.g. content).
*
* @param status the status determined to be the generated default
* @param operation current operation
* @return true if a default response should be generated, otherwise false.
*/
private boolean generateResponse(String status, Operation operation) {
APIResponses responses = operation.getResponses();
return responses == null || responses.getAPIResponse(status) != null;
}
/**
* Add method-level or resource-level RolesAllowed
values as
* scopes to the current operation.
*
*
* - If a
DenyAll
annotation is present (and a method-level
* RolesAllowed
is not), the roles allowed will be set to an
* empty array.
*
* - If none of a
PermitAll
, a DenyAll
, and a
* RolesAllowed
annotation is present at the method-level, the
* roles allowed will be set to the resource's RolesAllowed
.
*
* @param method the current JAX-RS method
* @param operation the OpenAPI Operation
*/
void processSecurityRoles(MethodInfo method, Operation operation) {
if (this.currentSecurityScheme != null) {
String[] rolesAllowed = TypeUtil.getAnnotationValue(method, OpenApiConstants.DOTNAME_ROLES_ALLOWED);
if (rolesAllowed != null) {
addScopes(rolesAllowed);
addRolesAllowed(operation, rolesAllowed);
} else if (this.resourceRolesAllowed != null) {
boolean denyAll = TypeUtil.getAnnotation(method, OpenApiConstants.DOTNAME_DENY_ALL) != null;
boolean permitAll = TypeUtil.getAnnotation(method, OpenApiConstants.DOTNAME_PERMIT_ALL) != null;
if (denyAll) {
addRolesAllowed(operation, new String[0]);
} else if (!permitAll) {
addRolesAllowed(operation, this.resourceRolesAllowed);
}
}
}
}
/**
* Add an array of roles to the operation's security requirements.
*
* If no security requirements yet exists, one is created with the name of the
* single OAUTH/OPENIDCONNECT previously defined in the OpenAPI's Components
* section.
*
* Otherwise, the roles are added to only a single existing requirement
* where the name of the requirement's scheme matches the name of the
* single OAUTH/OPENIDCONNECT previously defined in the OpenAPI's Components
* section.
*
* @param operation the OpenAPI Operation
* @param roles a list of JAX-RS roles to use as scopes
*/
void addRolesAllowed(Operation operation, String[] roles) {
List requirements = operation.getSecurity();
if (requirements == null) {
SecurityRequirement requirement = new SecurityRequirementImpl();
requirement.addScheme(currentSecurityScheme, new ArrayList<>(Arrays.asList(roles)));
operation.setSecurity(new ArrayList<>(Arrays.asList(requirement)));
} else if (requirements.size() == 1) {
SecurityRequirement requirement = requirements.get(0);
if (requirement.hasScheme(currentSecurityScheme)) {
// The name of the declared requirement must match the scheme's name
List scopes = requirement.getScheme(currentSecurityScheme);
for (String role : roles) {
if (!scopes.contains(role)) {
scopes.add(role);
}
}
}
}
}
/**
* Make a path out of a number of path segments.
*
* @param segments String paths
* @return Path built from the segments
*/
protected static String makePath(String... segments) {
StringBuilder builder = new StringBuilder();
for (String segment : segments) {
if (segment.startsWith("/")) {
segment = segment.substring(1);
}
if (segment.endsWith("/")) {
segment = segment.substring(0, segment.length() - 1);
}
if (segment.isEmpty()) {
continue;
}
builder.append("/");
builder.append(segment);
}
String rval = builder.toString();
if (rval.isEmpty()) {
return "/";
}
return rval;
}
/**
* Reads a OpenAPIDefinition annotation.
*
* @param openApi OpenAPIImpl
* @param definitionAnno AnnotationInstance
*/
protected void processDefinition(OpenAPIImpl openApi, AnnotationInstance definitionAnno) {
LOG.debug("Processing an @OpenAPIDefinition annotation.");
openApi.setInfo(readInfo(definitionAnno.value(OpenApiConstants.PROP_INFO)));
openApi.setTags(readTags(definitionAnno.value(OpenApiConstants.PROP_TAGS)));
openApi.setServers(readServers(definitionAnno.value(OpenApiConstants.PROP_SERVERS)));
openApi.setSecurity(readSecurity(definitionAnno.value(OpenApiConstants.PROP_SECURITY)));
openApi.setExternalDocs(readExternalDocs(definitionAnno.value(OpenApiConstants.PROP_EXTERNAL_DOCS)));
openApi.setComponents(readComponents(definitionAnno.value(OpenApiConstants.PROP_COMPONENTS)));
}
/**
* Reads an Info annotation.
*
* @param infoAnno
*/
private Info readInfo(AnnotationValue infoAnno) {
if (infoAnno == null) {
return null;
}
LOG.debug("Processing an @Info annotation.");
AnnotationInstance nested = infoAnno.asNested();
InfoImpl info = new InfoImpl();
info.setTitle(JandexUtil.stringValue(nested, OpenApiConstants.PROP_TITLE));
info.setDescription(JandexUtil.stringValue(nested, OpenApiConstants.PROP_DESCRIPTION));
info.setTermsOfService(JandexUtil.stringValue(nested, OpenApiConstants.PROP_TERMS_OF_SERVICE));
info.setContact(readContact(nested.value(OpenApiConstants.PROP_CONTACT)));
info.setLicense(readLicense(nested.value(OpenApiConstants.PROP_LICENSE)));
info.setVersion(JandexUtil.stringValue(nested, OpenApiConstants.PROP_VERSION));
return info;
}
/**
* Reads an Contact annotation.
*
* @param contactAnno
*/
private Contact readContact(AnnotationValue contactAnno) {
if (contactAnno == null) {
return null;
}
LOG.debug("Processing an @Contact annotation.");
AnnotationInstance nested = contactAnno.asNested();
ContactImpl contact = new ContactImpl();
contact.setName(JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME));
contact.setUrl(JandexUtil.stringValue(nested, OpenApiConstants.PROP_URL));
contact.setEmail(JandexUtil.stringValue(nested, OpenApiConstants.PROP_EMAIL));
return contact;
}
/**
* Reads an License annotation.
*
* @param licenseAnno
*/
private License readLicense(AnnotationValue licenseAnno) {
if (licenseAnno == null) {
return null;
}
LOG.debug("Processing an @License annotation.");
AnnotationInstance nested = licenseAnno.asNested();
LicenseImpl license = new LicenseImpl();
license.setName(JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME));
license.setUrl(JandexUtil.stringValue(nested, OpenApiConstants.PROP_URL));
return license;
}
/**
* Reads any Tag annotations. The annotation
* value is an array of Tag annotations.
*
* @param tagAnnos
*/
private List readTags(AnnotationValue tagAnnos) {
if (tagAnnos == null) {
return null;
}
LOG.debug("Processing an array of @Tag annotations.");
AnnotationInstance[] nestedArray = tagAnnos.asNestedArray();
List tags = new ArrayList<>();
for (AnnotationInstance tagAnno : nestedArray) {
if (!JandexUtil.isRef(tagAnno)) {
tags.add(readTag(tagAnno));
}
}
return tags;
}
/**
* Reads a single Tag annotation.
*
* @param tagAnno tag annotation, must not be null
*/
private Tag readTag(AnnotationInstance tagAnno) {
Objects.requireNonNull(tagAnno, "Tag annotation must not be null");
LOG.debug("Processing a single @Tag annotation.");
TagImpl tag = new TagImpl();
tag.setName(JandexUtil.stringValue(tagAnno, OpenApiConstants.PROP_NAME));
tag.setDescription(JandexUtil.stringValue(tagAnno, OpenApiConstants.PROP_DESCRIPTION));
tag.setExternalDocs(readExternalDocs(tagAnno.value(OpenApiConstants.PROP_EXTERNAL_DOCS)));
return tag;
}
/**
* Reads any Server annotations. The annotation value is an array of Server annotations.
*
* @param serverAnnos
*/
private List readServers(AnnotationValue serverAnnos) {
if (serverAnnos == null) {
return null;
}
LOG.debug("Processing an array of @Server annotations.");
AnnotationInstance[] nestedArray = serverAnnos.asNestedArray();
List servers = new ArrayList<>();
for (AnnotationInstance serverAnno : nestedArray) {
servers.add(readServer(serverAnno));
}
return servers;
}
/**
* Reads a single Server annotation.
*
* @param serverAnno
*/
private Server readServer(AnnotationValue value) {
if (value == null) {
return null;
}
return readServer(value.asNested());
}
/**
* Reads a single Server annotation.
*
* @param serverAnno
*/
private Server readServer(AnnotationInstance serverAnno) {
if (serverAnno == null) {
return null;
}
LOG.debug("Processing a single @Server annotation.");
ServerImpl server = new ServerImpl();
server.setUrl(JandexUtil.stringValue(serverAnno, OpenApiConstants.PROP_URL));
server.setDescription(JandexUtil.stringValue(serverAnno, OpenApiConstants.PROP_DESCRIPTION));
server.setVariables(readServerVariables(serverAnno.value(OpenApiConstants.PROP_VARIABLES)));
return server;
}
/**
* Reads an array of ServerVariable annotations, returning a new {@link ServerVariables} model. The
* annotation value is an array of ServerVariable annotations.
*
* @param value
* @return
*/
private ServerVariables readServerVariables(AnnotationValue serverVariableAnnos) {
if (serverVariableAnnos == null) {
return null;
}
LOG.debug("Processing an array of @ServerVariable annotations.");
AnnotationInstance[] nestedArray = serverVariableAnnos.asNestedArray();
ServerVariables variables = new ServerVariablesImpl();
for (AnnotationInstance serverVariableAnno : nestedArray) {
String name = JandexUtil.stringValue(serverVariableAnno, OpenApiConstants.PROP_NAME);
if (name != null) {
variables.addServerVariable(name, readServerVariable(serverVariableAnno));
}
}
return variables;
}
/**
* Reads a single ServerVariable annotation.
*
* @param serverVariableAnno
*/
private ServerVariable readServerVariable(AnnotationInstance serverVariableAnno) {
if (serverVariableAnno == null) {
return null;
}
LOG.debug("Processing a single @ServerVariable annotation.");
ServerVariable variable = new ServerVariableImpl();
variable.setDescription(JandexUtil.stringValue(serverVariableAnno, OpenApiConstants.PROP_DESCRIPTION));
variable.setEnumeration(JandexUtil.stringListValue(serverVariableAnno, OpenApiConstants.PROP_ENUMERATION));
variable.setDefaultValue(JandexUtil.stringValue(serverVariableAnno, OpenApiConstants.PROP_DEFAULT_VALUE));
return variable;
}
/**
* Reads any SecurityRequirement annotations. The annotation value is an array of
* SecurityRequirement annotations.
*
* @param value
*/
private List readSecurity(AnnotationValue securityRequirementAnnos) {
if (securityRequirementAnnos == null) {
return null;
}
LOG.debug("Processing an array of @SecurityRequirement annotations.");
AnnotationInstance[] nestedArray = securityRequirementAnnos.asNestedArray();
List requirements = new ArrayList<>();
for (AnnotationInstance requirementAnno : nestedArray) {
SecurityRequirement requirement = readSecurityRequirement(requirementAnno);
if (requirement != null) {
requirements.add(requirement);
}
}
return requirements;
}
/**
* Reads a single SecurityRequirement annotation.
*
* @param annotation
*/
private SecurityRequirement readSecurityRequirement(AnnotationInstance annotation) {
String name = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_NAME);
if (name != null) {
List scopes = JandexUtil.stringListValue(annotation, OpenApiConstants.PROP_SCOPES);
SecurityRequirement requirement = new SecurityRequirementImpl();
if (scopes == null) {
requirement.addScheme(name);
} else {
requirement.addScheme(name, scopes);
}
return requirement;
}
return null;
}
/**
* Reads an ExternalDocumentation annotation.
*
* @param externalDocAnno
*/
private ExternalDocumentation readExternalDocs(AnnotationValue externalDocAnno) {
if (externalDocAnno == null) {
return null;
}
LOG.debug("Processing an @ExternalDocumentation annotation.");
AnnotationInstance nested = externalDocAnno.asNested();
ExternalDocumentation externalDoc = new ExternalDocumentationImpl();
externalDoc.setDescription(JandexUtil.stringValue(nested, OpenApiConstants.PROP_DESCRIPTION));
externalDoc.setUrl(JandexUtil.stringValue(nested, OpenApiConstants.PROP_URL));
return externalDoc;
}
/**
* Reads any Components annotations.
*
* @param componentsAnno
*/
private Components readComponents(AnnotationValue componentsAnno) {
if (componentsAnno == null) {
return null;
}
LOG.debug("Processing an @Components annotation.");
AnnotationInstance nested = componentsAnno.asNested();
Components components = new ComponentsImpl();
// TODO for EVERY item below, handle the case where the annotation is ref-only. then strip the ref path and use the final segment as the name
components.setCallbacks(readCallbacks(nested.value(OpenApiConstants.PROP_CALLBACKS)));
components.setExamples(readExamples(nested.value(OpenApiConstants.PROP_EXAMPLES)));
components.setHeaders(readHeaders(nested.value(OpenApiConstants.PROP_HEADERS)));
components.setLinks(readLinks(nested.value(OpenApiConstants.PROP_LINKS)));
components.setParameters(readParameters(nested.value(OpenApiConstants.PROP_PARAMETERS)));
components.setRequestBodies(readRequestBodies(nested.value(OpenApiConstants.PROP_REQUEST_BODIES)));
components.setResponses(readResponses(nested.value(OpenApiConstants.PROP_RESPONSES)));
components.setSchemas(readSchemas(nested.value(OpenApiConstants.PROP_SCHEMAS)));
components.setSecuritySchemes(readSecuritySchemes(nested.value(OpenApiConstants.PROP_SECURITY_SCHEMES)));
return components;
}
/**
* Reads a map of Callback annotations.
*
* @param value
*/
private Map readCallbacks(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @Callback annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
if (name != null) {
map.put(name, readCallback(nested));
}
}
return map;
}
/**
* Reads a Callback annotation into a model.
*
* @param annotation
*/
private Callback readCallback(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @Callback annotation.");
Callback callback = new CallbackImpl();
callback.setRef(JandexUtil.refValue(annotation, RefType.Callback));
String expression = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_CALLBACK_URL_EXPRESSION);
callback.addPathItem(expression, readCallbackOperations(annotation.value(OpenApiConstants.PROP_OPERATIONS)));
return callback;
}
/**
* Reads the CallbackOperation annotations as a PathItem. The annotation value
* in this case is an array of CallbackOperation annotations.
*
* @param value
*/
private PathItem readCallbackOperations(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing an array of @CallbackOperation annotations.");
AnnotationInstance[] nestedArray = value.asNestedArray();
PathItem pathItem = new PathItemImpl();
for (AnnotationInstance operationAnno : nestedArray) {
String method = JandexUtil.stringValue(operationAnno, OpenApiConstants.PROP_METHOD);
Operation operation = readCallbackOperation(operationAnno);
if (method == null) {
continue;
}
try {
PropertyDescriptor descriptor = new PropertyDescriptor(method.toUpperCase(), pathItem.getClass());
Method mutator = descriptor.getWriteMethod();
mutator.invoke(pathItem, operation);
} catch (Exception e) {
LOG.error("Error reading a CallbackOperation annotation.", e);
}
}
return pathItem;
}
/**
* Reads a single CallbackOperation annotation.
*
* @param operationAnno
* @return
*/
private Operation readCallbackOperation(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @CallbackOperation annotation.");
Operation operation = new OperationImpl();
operation.setSummary(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_SUMMARY));
operation.setDescription(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_DESCRIPTION));
operation.setExternalDocs(readExternalDocs(annotation.value(OpenApiConstants.PROP_EXTERNAL_DOCS)));
operation.setParameters(readCallbackOperationParameters(annotation.value(OpenApiConstants.PROP_PARAMETERS)));
operation.setRequestBody(readRequestBody(annotation.value(OpenApiConstants.PROP_REQUEST_BODY)));
operation.setResponses(readCallbackOperationResponses(annotation.value(OpenApiConstants.PROP_RESPONSES)));
operation.setSecurity(readSecurity(annotation.value(OpenApiConstants.PROP_SECURITY)));
operation.setExtensions(readExtensions(annotation.value(OpenApiConstants.PROP_EXTENSIONS)));
return operation;
}
/**
* Reads an array of Parameter annotations into a list.
*
* @param value
*/
private List readCallbackOperationParameters(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a list of @Parameter annotations.");
List parameters = new ArrayList<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
ParameterImpl parameter = readParameter(nested);
if (parameter != null && !parameter.isHidden()) {
parameters.add(parameter);
}
}
return parameters;
}
/**
* Reads an array of APIResponse annotations into an {@link APIResponses} model.
*
* @param value
*/
private APIResponses readCallbackOperationResponses(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a list of @APIResponse annotations into an APIResponses model.");
APIResponses responses = new APIResponsesImpl();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String responseCode = JandexUtil.stringValue(nested, OpenApiConstants.PROP_RESPONSE_CODE);
if (responseCode != null) {
responses.addAPIResponse(responseCode, readResponse(nested));
}
}
return responses;
}
/**
* Reads a map of Example annotations.
*
* @param value
*/
private Map readExamples(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @ExampleObject annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
if (name != null) {
map.put(name, readExample(nested));
}
}
return map;
}
/**
* Reads a Example annotation into a model.
*
* @param annotation
*/
private Example readExample(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @ExampleObject annotation.");
Example example = new ExampleImpl();
example.setSummary(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_SUMMARY));
example.setDescription(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_DESCRIPTION));
example.setValue(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_VALUE));
example.setExternalValue(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_EXTERNAL_VALUE));
example.setRef(JandexUtil.refValue(annotation, RefType.Example));
return example;
}
/**
* Reads a map of Header annotations.
*
* @param value
*/
private Map readHeaders(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @Header annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
if (name != null) {
map.put(name, readHeader(nested));
}
}
return map;
}
/**
* Reads a Header annotation into a model.
*
* @param annotation
*/
private Header readHeader(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @Header annotation.");
Header header = new HeaderImpl();
header.setDescription(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_DESCRIPTION));
header.setSchema(SchemaFactory.readSchema(index, annotation.value(OpenApiConstants.PROP_SCHEMA)));
header.setRequired(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_REQUIRED));
header.setDeprecated(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_DEPRECATED));
header.setAllowEmptyValue(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_ALLOW_EMPTY_VALUE));
header.setRef(JandexUtil.refValue(annotation, RefType.Header));
return header;
}
/**
* Reads a map of Link annotations.
*
* @param value
*/
private Map readLinks(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @Link annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
if (name != null) {
map.put(name, readLink(nested));
}
}
return map;
}
/**
* Reads a Link annotation into a model.
*
* @param annotation
*/
private Link readLink(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @Link annotation.");
Link link = new LinkImpl();
link.setOperationRef(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_OPERATION_REF));
link.setOperationId(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_OPERATION_ID));
link.setParameters(readLinkParameters(annotation.value(OpenApiConstants.PROP_PARAMETERS)));
link.setDescription(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_DESCRIPTION));
link.setRequestBody(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_REQUEST_BODY));
link.setServer(readServer(annotation.value(OpenApiConstants.PROP_SERVER)));
link.setRef(JandexUtil.refValue(annotation, RefType.Link));
return link;
}
/**
* Reads an array of LinkParameter annotations into a map.
*
* @param value
*/
private Map readLinkParameters(AnnotationValue value) {
if (value == null) {
return null;
}
AnnotationInstance[] nestedArray = value.asNestedArray();
Map linkParams = new LinkedHashMap<>();
for (AnnotationInstance annotation : nestedArray) {
String name = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_NAME);
if (name != null) {
String expression = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_EXPRESSION);
linkParams.put(name, expression);
}
}
return linkParams;
}
/**
* Reads a map of Parameter annotations.
*
* @param value
*/
private Map readParameters(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @Parameter annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
if (name != null) {
ParameterImpl parameter = readParameter(nested);
if (parameter != null && !parameter.isHidden()) {
map.put(name, parameter);
}
}
}
return map;
}
/**
* Reads a Parameter annotation into a model.
*
* @param annotation
*/
private ParameterImpl readParameter(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @Link annotation.");
ParameterImpl parameter = new ParameterImpl();
parameter.setName(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_NAME));
parameter.setIn(JandexUtil.enumValue(annotation, OpenApiConstants.PROP_IN,
org.eclipse.microprofile.openapi.models.parameters.Parameter.In.class));
// Params can be hidden. Skip if that's the case.
Boolean isHidden = JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_HIDDEN);
if (Boolean.TRUE.equals(isHidden)) {
parameter.setHidden(true);
return parameter;
}
parameter.setDescription(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_DESCRIPTION));
parameter.setRequired(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_REQUIRED));
parameter.setDeprecated(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_DEPRECATED));
parameter.setAllowEmptyValue(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_ALLOW_EMPTY_VALUE));
parameter.setStyle(JandexUtil.enumValue(annotation, OpenApiConstants.PROP_STYLE,
org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.class));
parameter.setExplode(readExplode(JandexUtil.enumValue(annotation, OpenApiConstants.PROP_EXPLODE,
org.eclipse.microprofile.openapi.annotations.enums.Explode.class)));
parameter.setAllowReserved(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_ALLOW_RESERVED));
parameter.setSchema(SchemaFactory.readSchema(index, annotation.value(OpenApiConstants.PROP_SCHEMA)));
parameter.setContent(readContent(annotation.value(OpenApiConstants.PROP_CONTENT), ContentDirection.Parameter));
parameter.setExamples(readExamples(annotation.value(OpenApiConstants.PROP_EXAMPLES)));
parameter.setExample(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_EXAMPLE));
parameter.setRef(JandexUtil.refValue(annotation, RefType.Parameter));
return parameter;
}
/**
* Converts from an Explode enum to a true/false/null.
*
* @param enumValue
*/
private Boolean readExplode(Explode enumValue) {
if (enumValue == Explode.TRUE) {
return Boolean.TRUE;
}
if (enumValue == Explode.FALSE) {
return Boolean.FALSE;
}
return null;
}
/**
* Reads a single Content annotation into a model. The value in this case is an array of
* Content annotations.
*
* @param value
*/
private Content readContent(AnnotationValue value, ContentDirection direction) {
if (value == null) {
return null;
}
LOG.debug("Processing a single @Content annotation.");
Content content = new ContentImpl();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String contentType = JandexUtil.stringValue(nested, OpenApiConstants.PROP_MEDIA_TYPE);
MediaType mediaTypeModel = readMediaType(nested);
if (contentType == null) {
// If the content type is not provided in the @Content annotation, then
// we assume it applies to all the jax-rs method's @Consumes or @Produces
String[] mimeTypes = {};
if (direction == ContentDirection.Input && currentConsumes != null) {
mimeTypes = currentConsumes;
}
if (direction == ContentDirection.Output && currentProduces != null) {
mimeTypes = currentProduces;
}
if (direction == ContentDirection.Parameter) {
mimeTypes = OpenApiConstants.DEFAULT_MEDIA_TYPES.get();
}
for (String mimeType : mimeTypes) {
content.addMediaType(mimeType, mediaTypeModel);
}
} else {
content.addMediaType(contentType, mediaTypeModel);
}
}
return content;
}
/**
* Reads a single Content annotation into a {@link MediaType} model.
*
* @param nested
*/
private MediaType readMediaType(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @Content annotation as a MediaType.");
MediaType mediaType = new MediaTypeImpl();
mediaType.setExamples(readExamples(annotation.value(OpenApiConstants.PROP_EXAMPLES)));
mediaType.setExample(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_EXAMPLE));
mediaType.setSchema(SchemaFactory.readSchema(index, annotation.value(OpenApiConstants.PROP_SCHEMA)));
mediaType.setEncoding(readEncodings(annotation.value(OpenApiConstants.PROP_ENCODING)));
return mediaType;
}
/**
* Reads an array of Encoding annotations as a Map.
*
* @param value
*/
private Map readEncodings(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @Encoding annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance annotation : nestedArray) {
String name = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_NAME);
if (name != null) {
map.put(name, readEncoding(annotation));
}
}
return map;
}
/**
* Reads a single Encoding annotation into a model.
*
* @param annotation
*/
private Encoding readEncoding(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @Encoding annotation.");
Encoding encoding = new EncodingImpl();
encoding.setContentType(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_CONTENT_TYPE));
encoding.setStyle(JandexUtil.enumValue(annotation, OpenApiConstants.PROP_STYLE,
org.eclipse.microprofile.openapi.models.media.Encoding.Style.class));
encoding.setExplode(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_EXPLODE));
encoding.setAllowReserved(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_ALLOW_RESERVED));
encoding.setHeaders(readHeaders(annotation.value(OpenApiConstants.PROP_HEADERS)));
return encoding;
}
/**
* Reads a map of RequestBody annotations.
*
* @param value
*/
private Map readRequestBodies(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @RequestBody annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
if (name != null) {
map.put(name, readRequestBody(nested));
}
}
return map;
}
/**
* Reads a RequestBody annotation into a model.
*
* @param value
*/
private RequestBody readRequestBody(AnnotationValue value) {
if (value == null) {
return null;
}
return readRequestBody(value.asNested());
}
/**
* Reads a RequestBody annotation into a model.
*
* @param annotation
*/
private RequestBody readRequestBody(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @RequestBody annotation.");
RequestBody requestBody = new RequestBodyImpl();
requestBody.setDescription(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_DESCRIPTION));
requestBody.setContent(readContent(annotation.value(OpenApiConstants.PROP_CONTENT), ContentDirection.Input));
requestBody.setRequired(JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_REQUIRED));
requestBody.setRef(JandexUtil.refValue(annotation, RefType.RequestBody));
return requestBody;
}
/**
* Reads a map of APIResponse annotations.
*
* @param value
*/
private Map readResponses(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @APIResponse annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
if (name != null) {
map.put(name, readResponse(nested));
}
}
return map;
}
/**
* Reads a APIResponse annotation into a model.
*
* @param annotation
*/
private APIResponse readResponse(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @Response annotation.");
APIResponse response = new APIResponseImpl();
response.setDescription(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_DESCRIPTION));
response.setHeaders(readHeaders(annotation.value(OpenApiConstants.PROP_HEADERS)));
response.setLinks(readLinks(annotation.value(OpenApiConstants.PROP_LINKS)));
response.setContent(readContent(annotation.value(OpenApiConstants.PROP_CONTENT), ContentDirection.Output));
response.setRef(JandexUtil.refValue(annotation, RefType.Response));
return response;
}
/**
* Reads a map of Schema annotations.
*
* @param value
*/
private Map readSchemas(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @Schema annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
/*
* The name is REQUIRED when the schema is defined within
* {@link org.eclipse.microprofile.openapi.annotations.Components}.
*/
if (name != null) {
map.put(name, SchemaFactory.readSchema(index, nested));
} /*-
//For consideration - be more lenient and attempt to use the name from the implementation's @Schema?
else {
if (JandexUtil.isSimpleClassSchema(nested)) {
Schema schema = SchemaFactory.readClassSchema(index, nested.value(OpenApiConstants.PROP_IMPLEMENTATION), false);
if (schema instanceof SchemaImpl) {
name = ((SchemaImpl) schema).getName();
if (name != null) {
map.put(name, schema);
}
}
}
}*/
}
return map;
}
/**
* Reads a map of SecurityScheme annotations.
*
* @param value
*/
private Map readSecuritySchemes(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a map of @SecurityScheme annotations.");
Map map = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_SECURITY_SCHEME_NAME);
if (name == null && JandexUtil.isRef(nested)) {
name = JandexUtil.nameFromRef(nested);
}
if (name != null) {
map.put(name, readSecurityScheme(nested));
}
}
return map;
}
/**
* Reads a SecurityScheme annotation into a model.
*
* @param annotation
*/
private SecurityScheme readSecurityScheme(AnnotationInstance annotation) {
if (annotation == null) {
return null;
}
LOG.debug("Processing a single @SecurityScheme annotation.");
SecurityScheme securityScheme = new SecuritySchemeImpl();
securityScheme.setType(JandexUtil.enumValue(annotation, OpenApiConstants.PROP_TYPE,
org.eclipse.microprofile.openapi.models.security.SecurityScheme.Type.class));
securityScheme.setDescription(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_DESCRIPTION));
securityScheme.setName(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_API_KEY_NAME));
securityScheme.setIn(JandexUtil.enumValue(annotation, OpenApiConstants.PROP_IN,
org.eclipse.microprofile.openapi.models.security.SecurityScheme.In.class));
securityScheme.setScheme(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_SCHEME));
securityScheme.setBearerFormat(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_BEARER_FORMAT));
securityScheme.setFlows(readOAuthFlows(annotation.value(OpenApiConstants.PROP_FLOWS)));
securityScheme.setOpenIdConnectUrl(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_OPEN_ID_CONNECT_URL));
securityScheme.setRef(JandexUtil.refValue(annotation, RefType.SecurityScheme));
return securityScheme;
}
/**
* Reads an OAuthFlows annotation into a model.
*
* @param value
*/
private OAuthFlows readOAuthFlows(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a single @OAuthFlows annotation.");
AnnotationInstance annotation = value.asNested();
OAuthFlows flows = new OAuthFlowsImpl();
flows.setImplicit(readOAuthFlow(annotation.value(OpenApiConstants.PROP_IMPLICIT)));
flows.setPassword(readOAuthFlow(annotation.value(OpenApiConstants.PROP_PASSWORD)));
flows.setClientCredentials(readOAuthFlow(annotation.value(OpenApiConstants.PROP_CLIENT_CREDENTIALS)));
flows.setAuthorizationCode(readOAuthFlow(annotation.value(OpenApiConstants.PROP_AUTHORIZATION_CODE)));
return flows;
}
/**
* Reads a single OAuthFlow annotation into a model.
*
* @param value
*/
private OAuthFlow readOAuthFlow(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a single @OAuthFlow annotation.");
AnnotationInstance annotation = value.asNested();
OAuthFlow flow = new OAuthFlowImpl();
flow.setAuthorizationUrl(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_AUTHORIZATION_URL));
flow.setTokenUrl(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_TOKEN_URL));
flow.setRefreshUrl(JandexUtil.stringValue(annotation, OpenApiConstants.PROP_REFRESH_URL));
flow.setScopes(readOAuthScopes(annotation.value(OpenApiConstants.PROP_SCOPES)));
return flow;
}
/**
* Reads an array of OAuthScope annotations into a Scopes model.
*
* @param value
*/
private Scopes readOAuthScopes(AnnotationValue value) {
if (value == null) {
return null;
}
LOG.debug("Processing a list of @OAuthScope annotations.");
AnnotationInstance[] nestedArray = value.asNestedArray();
Scopes scopes = new ScopesImpl();
for (AnnotationInstance nested : nestedArray) {
String name = JandexUtil.stringValue(nested, OpenApiConstants.PROP_NAME);
if (name != null) {
String description = JandexUtil.stringValue(nested, OpenApiConstants.PROP_DESCRIPTION);
scopes.addScope(name, description);
}
}
return scopes;
}
/**
* Reads an array of Extension annotations. The AnnotationValue in this case is
* an array of Extension annotations. These must be read and converted into a Map.
*
* @param value
*/
private Map readExtensions(AnnotationValue value) {
if (value == null) {
return null;
}
Map extensions = new LinkedHashMap<>();
AnnotationInstance[] nestedArray = value.asNestedArray();
for (AnnotationInstance annotation : nestedArray) {
String extName = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_NAME);
String extValue = JandexUtil.stringValue(annotation, OpenApiConstants.PROP_VALUE);
extensions.put(extName, extValue);
}
return extensions;
}
/**
* Parses an extension value. The value may be:
*
* - JSON object - starts with {
* - JSON array - starts with [
* - number
* - boolean
* - string
*
* @param value
*/
private Object parseExtensionValue(String value) {
if (value == null) {
return null;
}
if ("true".equals(value)) {
return Boolean.TRUE;
}
if ("false".equals(value)) {
return Boolean.FALSE;
}
if (value.trim().startsWith("{")) {
try {
return MAPPER.readTree(value.trim());
} catch (Exception e) {
// TODO log the error
}
}
if (value.trim().startsWith("[")) {
try {
return MAPPER.readTree(value.trim());
} catch (Exception e) {
// TODO log the error
}
}
if (Character.isDigit(value.charAt(0)) || value.charAt(0) == '-' || value.charAt(0) == '+') {
try {
return Integer.parseInt(value);
} catch (Exception e) {
}
try {
return Float.parseFloat(value);
} catch (Exception e) {
}
try {
return Double.parseDouble(value);
} catch (Exception e) {
}
}
return value;
}
private CustomSchemaRegistry getCustomSchemaRegistry() {
if (config == null || config.customSchemaRegistryClass() == null) {
// Provide default implementation that does nothing
return (type) -> {
};
} else {
try {
return (CustomSchemaRegistry) Class.forName(config.customSchemaRegistryClass(), true, getContextClassLoader())
.newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
throw new RuntimeException("Failed to create instance of custom schema registry: "
+ config.customSchemaRegistryClass(), ex);
}
}
}
private static ClassLoader getContextClassLoader() {
if (System.getSecurityManager() == null) {
return Thread.currentThread().getContextClassLoader();
}
return AccessController
.doPrivileged((PrivilegedAction) () -> Thread.currentThread().getContextClassLoader());
}
/**
* Simple enum to indicate whether an @Content annotation being processed is
* an input or an output.
*
* @author [email protected]
*/
private static enum ContentDirection {
Input,
Output,
Parameter
}
public void setCurrentAppPath(String path) {
this.currentAppPath = path;
}
/**
* Combines the lists passed into a new list, excluding any null lists given.
* If the resulting list is empty, return null. This method is marked with
* {@code @SafeVarargs} because the elements of the lists handled generically
* and the input/output types match.
*
* @param element type of the list
* @param lists one or more lists to combine
* @return the combined/merged lists or null if the resulting merged list is empty
*/
@SafeVarargs
private static List mergeNullableLists(List... lists) {
List result = (List) Arrays.stream(lists)
.filter(Objects::nonNull)
.flatMap(List::stream)
.collect(Collectors.toList());
return result.isEmpty() ? null : result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy