org.apache.camel.openapi.RestOpenApiReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of camel-openapi-java Show documentation
Show all versions of camel-openapi-java Show documentation
Rest-dsl support for using openapi doc
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.camel.openapi;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.swagger.v3.core.filter.AbstractSpecFilter;
import io.swagger.v3.core.filter.SpecFilter;
import io.swagger.v3.core.model.ApiDescription;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.BinarySchema;
import io.swagger.v3.oas.models.media.ByteArraySchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.DateSchema;
import io.swagger.v3.oas.models.media.DateTimeSchema;
import io.swagger.v3.oas.models.media.FileSchema;
import io.swagger.v3.oas.models.media.IntegerSchema;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.NumberSchema;
import io.swagger.v3.oas.models.media.PasswordSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.Parameter.StyleEnum;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.OAuthFlow;
import io.swagger.v3.oas.models.security.OAuthFlows;
import io.swagger.v3.oas.models.security.Scopes;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.tags.Tag;
import org.apache.camel.CamelContext;
import org.apache.camel.model.rest.ApiKeyDefinition;
import org.apache.camel.model.rest.BasicAuthDefinition;
import org.apache.camel.model.rest.BearerTokenDefinition;
import org.apache.camel.model.rest.CollectionFormat;
import org.apache.camel.model.rest.MutualTLSDefinition;
import org.apache.camel.model.rest.OAuth2Definition;
import org.apache.camel.model.rest.OpenIdConnectDefinition;
import org.apache.camel.model.rest.ParamDefinition;
import org.apache.camel.model.rest.ResponseHeaderDefinition;
import org.apache.camel.model.rest.ResponseMessageDefinition;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.RestPropertyDefinition;
import org.apache.camel.model.rest.RestSecuritiesDefinition;
import org.apache.camel.model.rest.RestSecurityDefinition;
import org.apache.camel.model.rest.SecurityDefinition;
import org.apache.camel.model.rest.VerbDefinition;
import org.apache.camel.spi.ClassResolver;
import org.apache.camel.spi.NodeIdFactory;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.ObjectHelper;
import org.apache.camel.util.FileUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.invoke.MethodHandles.publicLookup;
/**
* A Camel REST-DSL openApi reader that parse the rest-dsl into a openApi model representation.
*
* This reader supports the OpenApi Specification 2.0 and 3.0
*/
public class RestOpenApiReader {
public static final String OAS20_SCHEMA_DEFINITION_PREFIX = "#/definitions/";
public static final String OAS30_SCHEMA_DEFINITION_PREFIX = "#/components/schemas/";
private static final Logger LOG = LoggerFactory.getLogger(RestOpenApiReader.class);
// Types that are not allowed in references.
private static final Set NO_REFERENCE_TYPE_NAMES = new HashSet<>(
Arrays.asList(
"byte", "char", "short", "int", "java.lang.Integer", "long", "java.lang.Long", "float", "java.lang.Float",
"double", "java.lang.Double", "string", "java.lang.String", "boolean", "java.lang.Boolean",
"file", "java.io.File"));
private static String getValue(CamelContext camelContext, String text) {
return camelContext.resolvePropertyPlaceholders(text);
}
private static List getValue(CamelContext camelContext, List list) {
if (list == null) {
return null;
}
List answer = new ArrayList<>();
for (String line : list) {
answer.add(camelContext.resolvePropertyPlaceholders(line));
}
return answer;
}
/**
* Read the REST-DSL definition's and parse that as a OpenApi model representation
*
* @param camelContext the camel context
* @param rests the rest-dsl
* @param config the openApi configuration
* @param classResolver class resolver to use @return the openApi model
* @throws ClassNotFoundException is thrown if error loading class
*/
public OpenAPI read(
CamelContext camelContext, List rests, BeanConfig config,
String camelContextId, ClassResolver classResolver)
throws ClassNotFoundException {
OpenAPI openApi = new OpenAPI();
for (RestDefinition rest : rests) {
Boolean disabled = CamelContextHelper.parseBoolean(camelContext, rest.getDisabled());
if (disabled == null || !disabled) {
parse(camelContext, openApi, rest, camelContextId, classResolver, config);
}
}
openApi = shortenClassNames(openApi);
/*
* Fixes the problem of not generating the "paths" section when no rest route is defined.
* A schema with no paths is considered invalid.
*/
if (openApi.getPaths() == null) {
openApi.setPaths(new Paths());
}
/*
* Fixes the problem of generating duplicated tags which is invalid per the specification
*/
if (openApi.getTags() != null) {
openApi.setTags(new ArrayList<>(
openApi.getTags()
.stream()
.collect(Collectors.toMap(
Tag::getName,
Function.identity(),
(prev, current) -> prev,
LinkedHashMap::new))
.values()));
}
// configure before returning
openApi = config.configure(openApi);
checkCompatOpenApi2(openApi, config);
return openApi;
}
private void checkCompatOpenApi2(OpenAPI openApi, BeanConfig config) {
if (!config.isOpenApi3()) {
// Verify that the OpenAPI 3 model can be downgraded to OpenApi 2
OpenAPI3to2 converter = new OpenAPI3to2();
converter.convertOpenAPI3to2(openApi);
}
}
private void parse(
CamelContext camelContext, OpenAPI openApi, RestDefinition rest, String camelContextId,
ClassResolver classResolver, BeanConfig config)
throws ClassNotFoundException {
// only include enabled verbs
List filter = new ArrayList<>();
for (VerbDefinition verb : rest.getVerbs()) {
Boolean disabled = CamelContextHelper.parseBoolean(camelContext, verb.getDisabled());
if (disabled == null || !disabled) {
filter.add(verb);
}
}
List verbs = new ArrayList<>(filter);
// must sort the verbs by uri so we group them together when an uri has multiple operations
verbs.sort(new VerbOrdering(camelContext));
// we need to group the operations within the same tag, so use the path as default if not configured
// Multi tag support for a comma delimeted tag
String[] pathAsTags = null != rest.getTag()
? getValue(camelContext, rest.getTag()).split(",")
: null != rest.getPath()
? new String[] { getValue(camelContext, rest.getPath()) }
: new String[0];
parseOas30(openApi, rest, pathAsTags);
// gather all types in use
Set types = new LinkedHashSet<>();
for (VerbDefinition verb : verbs) {
// check if the Verb Definition must be excluded from documentation
String apiDocs;
if (verb.getApiDocs() != null) {
apiDocs = verb.getApiDocs();
} else {
// fallback to option on rest
apiDocs = rest.getApiDocs();
}
if (apiDocs != null && !Boolean.parseBoolean(apiDocs)) {
continue;
}
String type = verb.getType();
if (org.apache.camel.util.ObjectHelper.isNotEmpty(type)) {
if (type.endsWith("[]")) {
type = type.substring(0, type.length() - 2);
}
types.add(type);
}
type = verb.getOutType();
if (org.apache.camel.util.ObjectHelper.isNotEmpty(type)) {
if (type.endsWith("[]")) {
type = type.substring(0, type.length() - 2);
}
types.add(type);
}
// there can also be types in response messages
if (verb.getResponseMsgs() != null) {
for (ResponseMessageDefinition def : verb.getResponseMsgs()) {
type = def.getResponseModel();
if (org.apache.camel.util.ObjectHelper.isNotEmpty(type)) {
if (type.endsWith("[]")) {
type = type.substring(0, type.length() - 2);
}
types.add(type);
}
}
}
}
// use annotation scanner to find models (annotated classes)
for (String type : types) {
Class> clazz = classResolver.resolveMandatoryClass(type);
appendModels(clazz, openApi);
}
doParseVerbs(camelContext, openApi, rest, camelContextId, verbs, pathAsTags, config);
// setup root security node if necessary
List securityRequirements = rest.getSecurityRequirements();
securityRequirements.forEach(requirement -> {
SecurityRequirement oasRequirement = new SecurityRequirement();
List scopes;
if (requirement.getScopes() == null || requirement.getScopes().trim().isEmpty()) {
scopes = Collections.emptyList();
} else {
scopes = Arrays.asList(requirement.getScopes().trim().split("\\s*,\\s*"));
}
oasRequirement.addList(requirement.getKey(), scopes);
openApi.addSecurityItem(oasRequirement);
});
}
private void parseOas30(OpenAPI openApi, RestDefinition rest, String[] pathAsTags) {
String summary = rest.getDescriptionText();
for (String tag : pathAsTags) {
// add rest as tag
openApi.addTagsItem(new Tag().name(tag).description(summary));
}
// setup security definitions
RestSecuritiesDefinition sd = rest.getSecurityDefinitions();
if (sd != null && !sd.getSecurityDefinitions().isEmpty() && openApi.getComponents() == null) {
openApi.setComponents(new Components());
}
if (sd != null) {
for (RestSecurityDefinition def : sd.getSecurityDefinitions()) {
if (def instanceof BasicAuthDefinition) {
SecurityScheme auth = new SecurityScheme().type(SecurityScheme.Type.HTTP)
.scheme("basic").description(def.getDescription());
openApi.getComponents().addSecuritySchemes(def.getKey(), auth);
} else if (def instanceof BearerTokenDefinition) {
SecurityScheme auth = new SecurityScheme().type(SecurityScheme.Type.HTTP)
.scheme("bearer").description(def.getDescription())
.bearerFormat(((BearerTokenDefinition) def).getFormat());
openApi.getComponents().addSecuritySchemes(def.getKey(), auth);
} else if (def instanceof ApiKeyDefinition) {
ApiKeyDefinition rs = (ApiKeyDefinition) def;
SecurityScheme auth = new SecurityScheme().type(SecurityScheme.Type.APIKEY)
.name(rs.getName()).description(def.getDescription());
if (rs.getInHeader() != null && Boolean.parseBoolean(rs.getInHeader())) {
auth.setIn(SecurityScheme.In.HEADER);
} else if (rs.getInQuery() != null && Boolean.parseBoolean(rs.getInQuery())) {
auth.setIn(SecurityScheme.In.QUERY);
} else if (rs.getInCookie() != null && Boolean.parseBoolean(rs.getInCookie())) {
auth.setIn(SecurityScheme.In.COOKIE);
} else {
throw new IllegalStateException("No API Key location specified.");
}
openApi.getComponents().addSecuritySchemes(def.getKey(), auth);
} else if (def instanceof OAuth2Definition) {
OAuth2Definition rs = (OAuth2Definition) def;
SecurityScheme auth = new SecurityScheme().type(SecurityScheme.Type.OAUTH2)
.description(def.getDescription());
String flow = rs.getFlow();
if (flow == null) {
flow = inferOauthFlow(rs);
}
OAuthFlows oauthFlows = new OAuthFlows();
auth.setFlows(oauthFlows);
OAuthFlow oauthFlow = new OAuthFlow();
switch (flow) {
case "authorizationCode":
case "accessCode":
oauthFlows.setAuthorizationCode(oauthFlow);
break;
case "implicit":
oauthFlows.setImplicit(oauthFlow);
break;
case "clientCredentials":
case "application":
oauthFlows.setClientCredentials(oauthFlow);
break;
case "password":
oauthFlows.setPassword(oauthFlow);
break;
default:
throw new IllegalStateException("Invalid OAuth flow '" + flow + "' specified");
}
oauthFlow.setAuthorizationUrl(rs.getAuthorizationUrl());
oauthFlow.setTokenUrl(rs.getTokenUrl());
oauthFlow.setRefreshUrl(rs.getRefreshUrl());
if (!rs.getScopes().isEmpty()) {
oauthFlow.setScopes(new Scopes());
for (RestPropertyDefinition scope : rs.getScopes()) {
oauthFlow.getScopes().addString(scope.getKey(), scope.getValue());
}
}
openApi.getComponents().addSecuritySchemes(def.getKey(), auth);
} else if (def instanceof MutualTLSDefinition) {
SecurityScheme auth = new SecurityScheme().type(SecurityScheme.Type.MUTUALTLS)
.description(def.getDescription());
openApi.getComponents().addSecuritySchemes(def.getKey(), auth);
} else if (def instanceof OpenIdConnectDefinition) {
SecurityScheme auth = new SecurityScheme().type(SecurityScheme.Type.OPENIDCONNECT)
.description(def.getDescription());
auth.setOpenIdConnectUrl(((OpenIdConnectDefinition) def).getUrl());
openApi.getComponents().addSecuritySchemes(def.getKey(), auth);
}
}
}
}
private String buildBasePath(CamelContext camelContext, RestDefinition rest) {
// used during gathering of apis
String basePath = FileUtil.stripLeadingSeparator(getValue(camelContext, rest.getPath()));
// must start with leading slash
if (basePath != null && !basePath.startsWith("/")) {
basePath = "/" + basePath;
}
return basePath;
}
private void doParseVerbs(
CamelContext camelContext, OpenAPI openApi, RestDefinition rest, String camelContextId,
List verbs, String[] pathAsTags, BeanConfig config) {
String basePath = buildBasePath(camelContext, rest);
for (VerbDefinition verb : verbs) {
// check if the Verb Definition must be excluded from documentation
String apiDocs;
if (verb.getApiDocs() != null) {
apiDocs = getValue(camelContext, verb.getApiDocs());
} else {
// fallback to option on rest
apiDocs = getValue(camelContext, rest.getApiDocs());
}
if (apiDocs != null && !Boolean.parseBoolean(apiDocs)) {
continue;
}
// the method must be in lower case
String method = verb.asVerb().toLowerCase(Locale.US);
// operation path is a key
String opPath = OpenApiHelper.buildUrl(basePath, getValue(camelContext, verb.getPath()));
if (openApi.getPaths() == null) {
openApi.paths(new Paths());
}
PathItem path = openApi.getPaths().get(opPath);
if (path == null) {
path = new PathItem(); //openApi.paths.createPathItem(opPath);
}
Operation op = new Operation(); //path.createOperation(method);
for (String tag : pathAsTags) {
// group in the same tag
op.addTagsItem(tag);
}
// favour ids from verb, rest, route
final String operationId;
if (verb.getId() != null) {
operationId = getValue(camelContext, verb.getId());
} else if (rest.getId() != null) {
operationId = getValue(camelContext, rest.getId());
} else {
verb.idOrCreate(camelContext.getCamelContextExtension().getContextPlugin(NodeIdFactory.class));
operationId = verb.getId();
}
op.setOperationId(operationId);
// add id as vendor extensions
op.addExtension("x-camelContextId", camelContextId);
path.operation(PathItem.HttpMethod.valueOf(method.toUpperCase()), op);
String consumes = getValue(camelContext, verb.getConsumes() != null ? verb.getConsumes() : rest.getConsumes());
if (consumes == null) {
consumes = config.defaultConsumes;
}
String produces = getValue(camelContext, verb.getProduces() != null ? verb.getProduces() : rest.getProduces());
if (produces == null) {
produces = config.defaultProduces;
}
doParseVerb(camelContext, openApi, verb, op, consumes, produces);
// enrich with configured response messages from the rest-dsl
doParseResponseMessages(camelContext, openApi, verb, op, produces);
// add path
openApi.getPaths().addPathItem(opPath, path);
}
}
private void doParseVerb(
CamelContext camelContext, OpenAPI openApi, VerbDefinition verb, Operation op, String consumes,
String produces) {
if (verb.getDescriptionText() != null) {
op.setSummary(getValue(camelContext, verb.getDescriptionText()));
}
if ("true".equals(verb.getDeprecated())) {
op.setDeprecated(Boolean.TRUE);
}
// security
for (SecurityDefinition sd : verb.getSecurity()) {
List scopes = new ArrayList<>();
if (sd.getScopes() != null) {
for (String scope : ObjectHelper.createIterable(getValue(camelContext, sd.getScopes()))) {
scopes.add(scope);
}
}
SecurityRequirement securityRequirement = new SecurityRequirement(); //op.createSecurityRequirement();
securityRequirement.addList(getValue(camelContext, sd.getKey()), scopes);
op.addSecurityItem(securityRequirement);
}
for (ParamDefinition param : verb.getParams()) {
Parameter parameter = new Parameter().in(param.getType().name());
if (parameter != null) {
parameter.setName(getValue(camelContext, param.getName()));
if (org.apache.camel.util.ObjectHelper.isNotEmpty(param.getDescription())) {
parameter.setDescription(getValue(camelContext, param.getDescription()));
}
parameter.setRequired(param.getRequired());
// set type on parameter
if (!"body".equals(parameter.getIn())) {
Schema schema = new Schema<>();
final boolean isArray = getValue(camelContext, param.getDataType()).equalsIgnoreCase("array");
final List allowableValues = getValue(camelContext, param.getAllowableValuesAsStringList());
final boolean hasAllowableValues = allowableValues != null && !allowableValues.isEmpty();
if (param.getDataType() != null) {
parameter.setSchema(schema);
schema.setType(getValue(camelContext, param.getDataType()));
if (param.getDataFormat() != null) {
schema.setFormat(getValue(camelContext, param.getDataFormat()));
}
if (isArray) {
String arrayType = getValue(camelContext, param.getArrayType());
if (arrayType != null) {
if (arrayType.equalsIgnoreCase("string")) {
defineSchemas(parameter, allowableValues, String.class);
}
if (arrayType.equalsIgnoreCase("int") || arrayType.equalsIgnoreCase("integer")) {
defineSchemas(parameter, allowableValues, Integer.class);
}
if (arrayType.equalsIgnoreCase("long")) {
defineSchemas(parameter, allowableValues, Long.class);
}
if (arrayType.equalsIgnoreCase("float")) {
defineSchemas(parameter, allowableValues, Float.class);
}
if (arrayType.equalsIgnoreCase("double")) {
defineSchemas(parameter, allowableValues, Double.class);
}
if (arrayType.equalsIgnoreCase("boolean")) {
defineSchemas(parameter, allowableValues, Boolean.class);
}
if (arrayType.equalsIgnoreCase("byte")) {
defineSchemas(parameter, allowableValues, ByteArraySchema.class);
}
if (arrayType.equalsIgnoreCase("binary")) {
defineSchemas(parameter, allowableValues, BinarySchema.class);
}
if (arrayType.equalsIgnoreCase("date")) {
defineSchemas(parameter, allowableValues, DateSchema.class);
}
if (arrayType.equalsIgnoreCase("date-time")) {
defineSchemas(parameter, allowableValues, DateTimeSchema.class);
}
if (arrayType.equalsIgnoreCase("password")) {
defineSchemas(parameter, allowableValues, PasswordSchema.class);
}
}
}
}
if (param.getCollectionFormat() != null) {
parameter.setStyle(convertToOpenApiStyle(getValue(camelContext, param.getCollectionFormat().name())));
}
if (hasAllowableValues && !isArray) {
schema.setEnum(allowableValues);
}
// set default value on parameter
if (org.apache.camel.util.ObjectHelper.isNotEmpty(param.getDefaultValue())) {
schema.setDefault(getValue(camelContext, param.getDefaultValue()));
}
// add examples
if (param.getExamples() != null && !param.getExamples().isEmpty()) {
// Examples can be added with a key or a single one with no key
for (RestPropertyDefinition example : param.getExamples()) {
if (example.getKey().isEmpty()) {
if (parameter.getExample() != null) {
LOG.warn("The parameter already has an example with no key!");
}
parameter.setExample(example.getValue());
} else {
parameter.addExample(example.getKey(), new Example().value(example.getValue()));
}
}
}
op.addParametersItem(parameter);
}
// In OpenAPI 3x, body or form parameters are replaced by requestBody
if (parameter.getIn().equals("body")) {
RequestBody reqBody = new RequestBody().content(new Content());
reqBody.setRequired(param.getRequired());
reqBody.setDescription(getValue(camelContext, param.getDescription()));
op.setRequestBody(reqBody);
String type = getValue(camelContext, param.getDataType() != null ? param.getDataType() : verb.getType());
Schema> bodySchema = null;
if (type != null) {
if (type.endsWith("[]")) {
type = type.substring(0, type.length() - 2);
// Schema arrayModel = (Oas30Schema) bp.createSchema();
bodySchema = modelTypeAsProperty(type, openApi);
} else {
String ref = modelTypeAsRef(type, openApi);
if (ref != null) {
bodySchema = new Schema().$ref(OAS30_SCHEMA_DEFINITION_PREFIX + ref);
} else {
bodySchema = modelTypeAsProperty(type, openApi);
}
}
}
if (consumes != null) {
String[] parts = consumes.split(",");
for (String part : parts) {
MediaType mediaType = new MediaType().schema(bodySchema);
if (param.getExamples() != null) {
for (RestPropertyDefinition example : param.getExamples()) {
if (part.equals(example.getKey())) {
mediaType.setExample(example.getValue());
}
// TODO: Check for non-matched or empty key
}
}
reqBody.getContent().addMediaType(part, mediaType);
}
}
}
// op.addParameter(parameter);
}
}
// clear parameters if its empty
if (op.getParameters() != null && op.getParameters().isEmpty()) {
// op.parameters.clear();
op.setParameters(null); // Is this necessary?
}
// if we have an out type then set that as response message
if (verb.getOutType() != null) {
if (op.getResponses() == null) {
op.setResponses(new ApiResponses());
}
String[] parts;
if (produces != null) {
parts = produces.split(",");
for (String produce : parts) {
ApiResponse response = new ApiResponse().description("Output type"); // ??
Content responseContent = new Content();
MediaType contentType = new MediaType();
responseContent.addMediaType(produce, contentType);
Schema> model = modelTypeAsProperty(getValue(camelContext, verb.getOutType()), openApi);
contentType.setSchema(model);
response.setContent(responseContent);
// response.description = "Output type";
// op.responses.addResponse("200", response);
op.getResponses().addApiResponse("200", response);
}
}
}
}
private StyleEnum convertToOpenApiStyle(String value) {
//Should be a Collection Format name
switch (CollectionFormat.valueOf(value)) {
case csv:
return StyleEnum.FORM;
case ssv:
case tsv:
return StyleEnum.SPACEDELIMITED;
case pipes:
return StyleEnum.PIPEDELIMITED;
case multi:
return StyleEnum.DEEPOBJECT;
default:
return null;
}
}
private static void defineSchemas(
final Parameter serializableParameter,
final List allowableValues,
final Class> type) {
Schema parameterSchema = serializableParameter.getSchema();
if (allowableValues != null && !allowableValues.isEmpty()) {
if (String.class.equals(type)) {
parameterSchema.setEnum(allowableValues);
} else {
convertAndSetItemsEnum(parameterSchema, allowableValues, type);
}
} else if (Objects.equals(parameterSchema.getType(), "array")) {
Schema> itemsSchema;
if (Integer.class.equals(type)) {
itemsSchema = new IntegerSchema();
} else if (Long.class.equals(type)) {
itemsSchema = new IntegerSchema().format("int64");
} else if (Float.class.equals(type)) {
itemsSchema = new NumberSchema().format("float");
} else if (Double.class.equals(type)) {
itemsSchema = new NumberSchema().format("double");
} else if (ByteArraySchema.class.equals(type)) {
itemsSchema = new ByteArraySchema();
} else if (BinarySchema.class.equals(type)) {
itemsSchema = new BinarySchema();
} else if (Date.class.equals(type)) {
itemsSchema = new DateSchema();
} else if (DateTimeSchema.class.equals(type)) {
itemsSchema = new DateTimeSchema();
} else if (PasswordSchema.class.equals(type)) {
itemsSchema = new PasswordSchema();
} else {
itemsSchema = new StringSchema();
}
parameterSchema.setItems(itemsSchema);
}
}
private static void convertAndSetItemsEnum(
final Schema items, final List allowableValues,
final Class> type) {
try {
final MethodHandle valueOf = publicLookup().findStatic(type, "valueOf",
MethodType.methodType(type, String.class));
final MethodHandle setEnum = publicLookup().bind(items, "setEnum",
MethodType.methodType(void.class, List.class));
final List> values = allowableValues.stream().map(v -> {
try {
return valueOf.invoke(v);
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}).collect(Collectors.toList());
setEnum.invoke(values);
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
private void doParseResponseMessages(
CamelContext camelContext, OpenAPI openApi, VerbDefinition verb, Operation op, String produces) {
if (op.getResponses() == null) {
op.setResponses(new ApiResponses());
}
for (ResponseMessageDefinition msg : verb.getResponseMsgs()) {
doParseResponse(camelContext, openApi, op, produces, msg);
}
// must include an empty noop response if none exists
if (op.getResponses().isEmpty()) {
op.getResponses().setDefault(new ApiResponse());
}
}
private void doParseResponse(
CamelContext camelContext, OpenAPI openApi, Operation op, String produces,
ResponseMessageDefinition msg) {
ApiResponse response = null;
String code = getValue(camelContext, msg.getCode());
response = op.getResponses().get(code);
if (response == null) {
response = new ApiResponse();
op.getResponses().addApiResponse(code, response);
}
if (org.apache.camel.util.ObjectHelper.isNotEmpty(msg.getResponseModel())) {
String[] parts;
if (produces != null) {
Content respContent = new Content();
parts = produces.split(",");
for (String produce : parts) {
Schema model = modelTypeAsProperty(getValue(camelContext, msg.getResponseModel()), openApi);
respContent.addMediaType(produce, new MediaType().schema(model));
}
response.setContent(respContent);
}
}
if (org.apache.camel.util.ObjectHelper.isNotEmpty(msg.getMessage())) {
response.setDescription(getValue(camelContext, msg.getMessage()));
}
// add headers
if (msg.getHeaders() != null) {
for (ResponseHeaderDefinition header : msg.getHeaders()) {
String name = getValue(camelContext, header.getName());
String type = getValue(camelContext, header.getDataType());
String format = getValue(camelContext, header.getDataFormat());
if ("string".equals(type) || "long".equals(type) || "float".equals(type)
|| "double".equals(type) || "boolean".equals(type)) {
setResponseHeader(camelContext, response, header, name, format, type);
} else if ("int".equals(type) || "integer".equals(type)) {
setResponseHeader(camelContext, response, header, name, format, "integer");
} else if ("array".equals(type)) {
Header ap = new Header();
response.addHeaderObject(name, ap);
if (org.apache.camel.util.ObjectHelper.isNotEmpty(header.getDescription())) {
ap.setDescription(getValue(camelContext, header.getDescription()));
}
if (header.getArrayType() != null) {
String arrayType = getValue(camelContext, header.getArrayType());
if (arrayType.equalsIgnoreCase("string")
|| arrayType.equalsIgnoreCase("long")
|| arrayType.equalsIgnoreCase("float")
|| arrayType.equalsIgnoreCase("double")
|| arrayType.equalsIgnoreCase("boolean")) {
setHeaderSchemaOas30(ap, arrayType);
} else if (arrayType.equalsIgnoreCase("int")
|| arrayType.equalsIgnoreCase("integer")) {
setHeaderSchemaOas30(ap, "integer");
}
}
// add example
if (header.getExample() != null) {
ap.addExample("", new Example().value(getValue(camelContext, header.getExample())));
}
}
}
}
// add examples
if (msg.getExamples() != null) {
if (response.getContent() != null) {
for (MediaType mediaType : response.getContent().values()) {
for (RestPropertyDefinition prop : msg.getExamples()) {
mediaType.addExamples(getValue(camelContext, prop.getKey()), new Example()
.value(getValue(camelContext, prop.getValue())));
}
}
}
// if no content, can't add examples!
}
}
private void setHeaderSchemaOas30(Header ap, String arrayType) {
Schema items = new Schema().type(arrayType);
ap.setSchema(items);
}
private void setResponseHeader(
CamelContext camelContext, ApiResponse response, ResponseHeaderDefinition header,
String name, String format, String type) {
Header ip = new Header();
response.addHeaderObject(name, ip);
Schema schema = new Schema().type(type);
ip.setSchema(schema);
if (format != null) {
schema.setFormat(format);
}
ip.setDescription(getValue(camelContext, header.getDescription()));
List values;
if (header.getAllowableValues() != null) {
values = new ArrayList<>();
for (String text : header.getAllowableValuesAsStringList()) {
values.add(getValue(camelContext, text));
}
schema.setEnum(values);
}
// add example
if (header.getExample() != null) {
ip.addExample("", new Example().value(getValue(camelContext, header.getExample())));
}
}
private String modelTypeAsRef(String typeName, OpenAPI openApi) {
boolean array = typeName.endsWith("[]");
if (array) {
typeName = typeName.substring(0, typeName.length() - 2);
}
if (NO_REFERENCE_TYPE_NAMES.contains(typeName)) {
return null;
}
if (openApi.getComponents() != null
&& openApi.getComponents().getSchemas() != null) {
for (Schema model : openApi.getComponents().getSchemas().values()) {
if (typeName.equals(getClassNameExtension(model))) {
return model.getName();
}
}
}
return null;
}
private Object getClassNameExtension(Schema model) {
Object className = null;
if (model.getExtensions() != null) {
Object value = model.getExtensions().get("x-className");
if (value instanceof Map) {
className = ((Map) value).get("format");
}
}
return className;
}
private Schema> modelTypeAsProperty(String typeName, OpenAPI openApi) {
Schema> prop = null;
boolean array = typeName.endsWith("[]");
if (array) {
typeName = typeName.substring(0, typeName.length() - 2);
}
String ref = modelTypeAsRef(typeName, openApi);
if (ref == null) {
// No explicit schema reference so handle primitive types
// special for byte arrays
if (array && ("byte".equals(typeName) || "java.lang.Byte".equals(typeName))) {
// Note built-in ByteArraySchema sets type="string" !
prop = new Schema().type("number").format("byte");
array = false;
} else if ("string".equalsIgnoreCase(typeName) || "java.lang.String".equals(typeName)) {
prop = new StringSchema();
} else if ("int".equals(typeName) || "java.lang.Integer".equals(typeName)) {
prop = new IntegerSchema();
} else if ("long".equals(typeName) || "java.lang.Long".equals(typeName)) {
prop = new IntegerSchema().format("int64");
} else if ("float".equals(typeName) || "java.lang.Float".equals(typeName)) {
prop = new NumberSchema().format("float");
} else if ("double".equals(typeName) || "java.lang.Double".equals(typeName)) {
prop = new NumberSchema().format("double");
} else if ("boolean".equals(typeName) || "java.lang.Boolean".equals(typeName)) {
prop = new NumberSchema().format("boolean");
} else if ("file".equals(typeName) || "java.io.File".equals(typeName)) {
prop = new FileSchema();
} else {
prop = new StringSchema();
}
}
if (array) {
Schema> items = new Schema<>();
if (ref != null) {
items.set$ref(OAS30_SCHEMA_DEFINITION_PREFIX + ref);
}
prop = new ArraySchema().items(items);
} else if (prop == null) {
prop = new Schema<>().$ref(OAS30_SCHEMA_DEFINITION_PREFIX + ref);
}
return prop;
}
/**
* If the class is annotated with openApi annotations its parsed into a OpenApi model representation which is added
* to openApi
*
* @param clazz the class such as pojo with openApi annotation
* @param openApi the openApi model
*/
private void appendModels(Class> clazz, OpenAPI openApi) {
RestModelConverters converters = new RestModelConverters();
List extends Schema>> models = converters.readClass(openApi, clazz);
if (models == null) {
return;
}
if (openApi.getComponents() == null) {
openApi.setComponents(new Components());
}
for (Schema> newSchema : models) {
// favor keeping any existing model that has the vendor extension in the model
boolean addSchema = true;
if (openApi.getComponents().getSchemas() != null) {
Schema> existing = openApi.getComponents().getSchemas().get(newSchema.getName());
if (existing != null) {
// check classname extension
Object oldClassName = getClassNameExtension(existing);
Object newClassName = getClassNameExtension(newSchema);
if (oldClassName != null) {
// a schema with this name and a classname is already in the model
addSchema = false;
LOG.info("Duplicate schema found for with name {}; classname1={}, classname2={}",
newSchema.getName(), oldClassName,
newClassName != null ? newClassName : "none");
}
}
}
if (addSchema) {
openApi.getComponents().addSchemas(newSchema.getName(), newSchema);
}
}
}
/**
* To sort the rest operations
*/
private static class VerbOrdering implements Comparator {
private final CamelContext camelContext;
public VerbOrdering(CamelContext camelContext) {
this.camelContext = camelContext;
}
@Override
public int compare(VerbDefinition a, VerbDefinition b) {
String u1 = "";
if (a.getPath() != null) {
// replace { with _ which comes before a when soring by char
u1 = getValue(camelContext, a.getPath()).replace("{", "_");
}
String u2 = "";
if (b.getPath() != null) {
// replace { with _ which comes before a when soring by char
u2 = getValue(camelContext, b.getPath()).replace("{", "_");
}
int num = u1.compareTo(u2);
if (num == 0) {
// same uri, so use http method as sorting
num = a.asVerb().compareTo(b.asVerb());
}
return num;
}
}
private OpenAPI shortenClassNames(OpenAPI document) {
if (document.getComponents() == null || document.getComponents().getSchemas() == null) {
return document;
}
// Make a mapping from full name to possibly shortened name.
Map names = new HashMap<>();
Stream schemaStream = document.getComponents().getSchemas().keySet().stream();
schemaStream.forEach(key -> {
String s = key.replaceAll("[^a-zA-Z0-9.-_]", "_");
String shortName = s.substring(s.lastIndexOf('.') + 1);
names.put(key, names.containsValue(shortName) ? s : shortName);
});
// Remap the names
for (Map.Entry namePair : names.entrySet()) {
Schema> schema = document.getComponents().getSchemas().get(namePair.getKey());
if (schema != null) {
document.getComponents().getSchemas().remove(namePair.getKey());
document.getComponents().addSchemas(namePair.getValue(), schema);
}
}
AbstractSpecFilter filter = new SchemaFilter(names);
return new SpecFilter().filter(document, filter, null, null, null);
}
private String inferOauthFlow(OAuth2Definition rs) {
String flow;
if (rs.getAuthorizationUrl() != null && rs.getTokenUrl() != null) {
flow = "authorizationCode";
} else if (rs.getTokenUrl() == null && rs.getAuthorizationUrl() != null) {
flow = "implicit";
} else {
throw new IllegalStateException("Error inferring OAuth flow");
}
return flow;
}
private static class SchemaFilter extends AbstractSpecFilter {
private final Map names;
SchemaFilter(Map names) {
this.names = names;
}
@Override
public Optional filterParameter(
Parameter parameter, Operation operation, ApiDescription api,
Map> params, Map cookies, Map> headers) {
if (parameter.getContent() != null) {
processRefsInContent(parameter.getContent(), params, cookies, headers);
}
return Optional.of(parameter);
}
@Override
public Optional filterRequestBody(
RequestBody requestBody, Operation operation, ApiDescription api,
Map> params, Map cookies, Map> headers) {
if (requestBody.getContent() != null) {
processRefsInContent(requestBody.getContent(), params, cookies, headers);
}
return Optional.of(requestBody);
}
@Override
public Optional filterResponse(
ApiResponse response, Operation operation, ApiDescription api,
Map> params, Map cookies, Map> headers) {
if (response.getContent() != null) {
processRefsInContent(response.getContent(), params, cookies, headers);
}
return Optional.of(response);
}
@SuppressWarnings("rawtypes")
@Override
public Optional filterSchema(
Schema schema,
Map> params, Map cookies, Map> headers) {
if (schema.getName() != null) {
schema.setName(fixSchemaReference(schema.getName(), OAS30_SCHEMA_DEFINITION_PREFIX));
}
if (schema.get$ref() != null) {
schema.set$ref(fixSchemaReference(schema.get$ref(), OAS30_SCHEMA_DEFINITION_PREFIX));
}
if (schema.getItems() != null) {
filterSchema(schema.getItems(), params, cookies, headers);
}
if (schema.getProperties() != null) {
for (Schema> propSchema : (Collection) schema.getProperties().values()) {
filterSchema(propSchema, params, cookies, headers);
}
}
if (schema.getAdditionalProperties() != null &&
schema.getAdditionalProperties() instanceof Schema) {
filterSchema((Schema) schema.getAdditionalProperties(), params, cookies, headers);
}
if (schema.getAnyOf() != null) {
List any = schema.getAnyOf();
for (Schema child : any) {
filterSchema(child, params, cookies, headers);
}
}
if (schema.getAllOf() != null) {
List all = schema.getAllOf();
for (Schema child : all) {
filterSchema(child, params, cookies, headers);
}
}
if (schema.getOneOf() != null) {
List oneOf = schema.getOneOf();
for (Schema child : oneOf) {
filterSchema(child, params, cookies, headers);
}
}
return Optional.of(schema);
}
private void processRefsInContent(
Content content, Map> params,
Map cookies, Map> headers) {
for (MediaType media : content.values()) {
if (media.getSchema() != null) {
filterSchema(media.getSchema(), params, cookies, headers);
}
}
}
private String fixSchemaReference(String ref, String prefix) {
if (ref.startsWith(prefix)) {
ref = ref.substring(prefix.length());
}
String name = names.get(ref);
return name == null ? ref : name;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy