Java tutorial
/* * Copyright 2017 Okta * * 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 com.okta.swagger.codegen; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.codegen.CodegenModel; import io.swagger.codegen.CodegenOperation; import io.swagger.codegen.CodegenParameter; import io.swagger.codegen.CodegenProperty; import io.swagger.codegen.CodegenType; import io.swagger.codegen.languages.AbstractJavaCodegen; import io.swagger.models.HttpMethod; import io.swagger.models.Model; import io.swagger.models.ModelImpl; import io.swagger.models.Operation; import io.swagger.models.Path; import io.swagger.models.RefModel; import io.swagger.models.Response; import io.swagger.models.Swagger; import io.swagger.models.parameters.BodyParameter; import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.Property; import io.swagger.models.properties.RefProperty; import io.swagger.parser.SwaggerException; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Collectors; public abstract class AbstractOktaJavaClientCodegen extends AbstractJavaCodegen { private final String codeGenName; public static final String API_FILE_KEY = "apiFile"; static final String X_OPENAPI_V3_SCHEMA_REF = "x-openapi-v3-schema-ref"; @SuppressWarnings("hiding") private final Logger log = LoggerFactory.getLogger(AbstractOktaJavaClientCodegen.class); protected Map<String, String> modelTagMap = new HashMap<>(); protected Set<String> enumList = new HashSet<>(); protected Map<String, Discriminator> discriminatorMap = new HashMap<>(); protected Map<String, String> reverseDiscriminatorMap = new HashMap<>(); protected Set<String> topLevelResources = new HashSet<>(); protected Map<String, Object> rawSwaggerConfig; public AbstractOktaJavaClientCodegen(String codeGenName, String relativeTemplateDir, String modelPackage) { super(); this.codeGenName = codeGenName; this.dateLibrary = "legacy"; outputFolder = "generated-code" + File.separator + codeGenName; embeddedTemplateDir = templateDir = relativeTemplateDir; artifactId = "not_used"; this.modelPackage = modelPackage; // TODO: these are hard coded for now, calling Maven Plugin does NOT set the packages correctly. invokerPackage = "com.okta.sdk.invoker"; apiPackage = "com.okta.sdk.client"; apiTemplateFiles.clear(); modelTemplateFiles.clear(); } @Override public void preprocessSwagger(Swagger swagger) { // make sure we have the apiFile location String apiFile = (String) additionalProperties.get(API_FILE_KEY); if (apiFile == null || apiFile.isEmpty()) { throw new SwaggerException("'additionalProperties." + API_FILE_KEY + " property is required. This must be " + "set to the same file that Swagger is using."); } try (Reader reader = new InputStreamReader(new FileInputStream(apiFile), StandardCharsets.UTF_8.toString())) { rawSwaggerConfig = new Yaml().loadAs(reader, Map.class); } catch (IOException e) { throw new IllegalStateException("Failed to parse apiFile: " + apiFile, e); } vendorExtensions.put("basePath", swagger.getBasePath()); super.preprocessSwagger(swagger); tagEnums(swagger); buildTopLevelResourceList(swagger); addListModels(swagger); buildModelTagMap(swagger); removeListAfterAndLimit(swagger); moveOperationsToSingleClient(swagger); handleOktaLinkedOperations(swagger); buildDiscriminationMap(swagger); } /** * Figure out which models are top level models (directly returned from a endpoint). * @param swagger The instance of swagger. */ protected void buildTopLevelResourceList(Swagger swagger) { Set<String> resources = new HashSet<>(); // Loop through all of the operations looking for the models that are used as the response and body params swagger.getPaths().forEach((pathName, path) -> path.getOperations().forEach(operation -> { // find all body params operation.getParameters().forEach(parameter -> { if (parameter instanceof BodyParameter) { resources.add(((RefModel) ((BodyParameter) parameter).getSchema()).getSimpleRef()); } }); // response objects are a more complicated, start with filter for only the 200 responses operation.getResponses().entrySet().stream().filter(entry -> "200".equals(entry.getKey())) .forEach(entry -> { // this schema could be a ref or an array property containing a ref (or null) Property rawSchema = entry.getValue().getSchema(); if (rawSchema != null) { RefProperty refProperty; // detect array properties if (rawSchema instanceof ArrayProperty) { Property innerProp = ((ArrayProperty) rawSchema).getItems(); if (innerProp instanceof RefProperty) { refProperty = (RefProperty) innerProp; } else { // invalid swagger config file throw new SwaggerException("Expected 'schema.items.$ref' to exist."); } } else if (rawSchema instanceof RefProperty) { // non array, standard ref property typically in the format of '#/Definitions/MyModel' refProperty = (RefProperty) rawSchema; } else { throw new SwaggerException( "Expected 'schema' to be of type 'ArrayProperty' or 'RefProperty'."); } // get the simple name 'MyModel' instead of '#/Definitions/MyModel' resources.add(refProperty.getSimpleRef()); } }); })); // find any children of these resources swagger.getDefinitions().forEach((name, model) -> { String parent = (String) model.getVendorExtensions().get("x-okta-parent"); if (parent != null) { parent = parent.replaceAll(".*/", ""); if (resources.contains(parent)) { resources.add(parent); } } }); // mark each model with a 'top-level' vendorExtension resources.stream().map(resourceName -> swagger.getDefinitions().get(resourceName)).forEach(model -> { model.getVendorExtensions().put("top-level", true); }); this.topLevelResources = resources; } protected void buildDiscriminationMap(Swagger swagger) { swagger.getDefinitions().forEach((name, model) -> { ObjectNode discriminatorMapExtention = (ObjectNode) model.getVendorExtensions() .get("x-openapi-v3-discriminator"); if (discriminatorMapExtention != null) { String propertyName = discriminatorMapExtention.get("propertyName").asText(); ObjectNode mapping = (ObjectNode) discriminatorMapExtention.get("mapping"); ObjectMapper mapper = new ObjectMapper(); Map<String, String> result = mapper.convertValue(mapping, Map.class); result = result.entrySet().stream().collect(Collectors .toMap(e -> e.getValue().substring(e.getValue().lastIndexOf('/') + 1), e -> e.getKey())); result.forEach((key, value) -> { reverseDiscriminatorMap.put(key, name); }); discriminatorMap.put(name, new Discriminator(name, propertyName, result)); } }); } protected void tagEnums(Swagger swagger) { swagger.getDefinitions().forEach((name, model) -> { if (model instanceof ModelImpl && ((ModelImpl) model).getEnum() != null) { enumList.add(name); } }); } protected void buildModelTagMap(Swagger swagger) { swagger.getDefinitions().forEach((key, definition) -> { Object tags = definition.getVendorExtensions().get("x-okta-tags"); if (tags != null) { // if tags is NOT null, then assume it is an array if (tags instanceof List) { if (!((List) tags).isEmpty()) { String packageName = tagToPackageName(((List) tags).get(0).toString()); addToModelTagMap(key, packageName); definition.getVendorExtensions().put("x-okta-package", packageName); } } else { throw new SwaggerException("Model: " + key + " contains 'x-okta-tags' that is NOT a List."); } } }); } protected void addToModelTagMap(String modelName, String packageName) { modelTagMap.put(modelName, packageName); } protected String tagToPackageName(String tag) { return tag.replaceAll("(.)(\\p{Upper})", "$1.$2").toLowerCase(Locale.ENGLISH); } public void removeListAfterAndLimit(Swagger swagger) { swagger.getPaths() .forEach((pathName, path) -> path.getOperations() .forEach(operation -> operation.getParameters().removeIf(param -> !param.getRequired() && ("limit".equals(param.getName()) || "after".equals(param.getName()))))); } private void addAllIfNotNull(List<ObjectNode> destList, List<ObjectNode> srcList) { if (srcList != null) { destList.addAll(srcList); } } private void handleOktaLinkedOperations(Swagger swagger) { // we want to move any operations defined by the 'x-okta-operations' or 'x-okta-crud' vendor extension to the model Map<String, Model> modelMap = swagger.getDefinitions().entrySet().stream() .filter(e -> e.getValue().getVendorExtensions().containsKey("x-okta-operations") || e.getValue().getVendorExtensions().containsKey("x-okta-crud")) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); modelMap.forEach((k, model) -> { List<ObjectNode> linkNodes = new ArrayList<>(); addAllIfNotNull(linkNodes, (List<ObjectNode>) model.getVendorExtensions().get("x-okta-operations")); addAllIfNotNull(linkNodes, (List<ObjectNode>) model.getVendorExtensions().get("x-okta-crud")); Map<String, CodegenOperation> operationMap = new HashMap<>(); linkNodes.forEach(n -> { String operationId = n.get("operationId").textValue(); // find the swagger path operation swagger.getPaths().forEach((pathName, path) -> { Optional<Map.Entry<HttpMethod, Operation>> operationEntry = path.getOperationMap().entrySet() .stream().filter(e -> e.getValue().getOperationId().equals(operationId)).findFirst(); if (operationEntry.isPresent()) { Operation operation = operationEntry.get().getValue(); CodegenOperation cgOperation = fromOperation(pathName, operationEntry.get().getKey().name().toLowerCase(), operation, swagger.getDefinitions(), swagger); boolean canLinkMethod = true; JsonNode aliasNode = n.get("alias"); if (aliasNode != null) { String alias = aliasNode.textValue(); cgOperation.vendorExtensions.put("alias", alias); if ("update".equals(alias)) { model.getVendorExtensions().put("saveable", true); } else if ("delete".equals(alias)) { model.getVendorExtensions().put("deletable", true); cgOperation.vendorExtensions.put("selfDelete", true); } else if ("read".equals(alias) || "create".equals(alias)) { canLinkMethod = false; } } // we do NOT link read or create methods, those need to be on the parent object if (canLinkMethod) { // now any params that match the models we need to use the model value directly // for example if the path contained {id} we would call getId() instead Map<String, String> argMap = createArgMap(n); List<CodegenParameter> cgOtherPathParamList = new ArrayList<>(); List<CodegenParameter> cgParamAllList = new ArrayList<>(); List<CodegenParameter> cgParamModelList = new ArrayList<>(); cgOperation.pathParams.forEach(param -> { if (argMap.containsKey(param.paramName)) { String paramName = argMap.get(param.paramName); cgParamModelList.add(param); if (model.getProperties() != null) { CodegenProperty cgProperty = fromProperty(paramName, model.getProperties().get(paramName)); param.vendorExtensions.put("fromModel", cgProperty); } else { System.err.println("Model '" + model.getTitle() + "' has no properties"); } } else { cgOtherPathParamList.add(param); } }); // remove the body param if the body is the object itself for (Iterator<CodegenParameter> iter = cgOperation.bodyParams.iterator(); iter .hasNext();) { CodegenParameter bodyParam = iter.next(); if (argMap.containsKey(bodyParam.paramName)) { cgOperation.vendorExtensions.put("bodyIsSelf", true); iter.remove(); } } // do not add the parrent path params to the list (they will be parsed from the href) SortedSet<String> pathParents = parentPathParams(n); cgOtherPathParamList.forEach(param -> { if (!pathParents.contains(param.paramName)) { cgParamAllList.add(param); } }); if (!pathParents.isEmpty()) { cgOperation.vendorExtensions.put("hasPathParents", true); cgOperation.vendorExtensions.put("pathParents", pathParents); } cgParamAllList.addAll(cgOperation.queryParams); cgParamAllList.addAll(cgOperation.bodyParams); // set all params to have more cgParamAllList.forEach(param -> param.hasMore = true); // then grab the last one and mark it as the last if (!cgParamAllList.isEmpty()) { CodegenParameter param = cgParamAllList.get(cgParamAllList.size() - 1); param.hasMore = false; } cgOperation.vendorExtensions.put("allParams", cgParamAllList); cgOperation.vendorExtensions.put("fromModelPathParams", cgParamModelList); addOptionalExtension(cgOperation, cgParamAllList); operationMap.put(cgOperation.operationId, cgOperation); // mark the operation as moved so we do NOT add it to the client operation.getVendorExtensions().put("moved", true); } } }); }); model.getVendorExtensions().put("operations", operationMap.values()); }); } private Map<String, String> createArgMap(ObjectNode n) { Map<String, String> argMap = new LinkedHashMap<>(); ArrayNode argNodeList = (ArrayNode) n.get("arguments"); if (argNodeList != null) { for (Iterator argNodeIter = argNodeList.iterator(); argNodeIter.hasNext();) { JsonNode argNode = (JsonNode) argNodeIter.next(); if (argNode.has("src")) { String src = argNode.get("src").textValue(); String dest = argNode.get("dest").textValue(); if (src != null) { argMap.put(dest, src); // reverse lookup } } if (argNode.has("self")) { String dest = argNode.get("dest").textValue(); argMap.put(dest, "this"); // reverse lookup } } } return argMap; } private SortedSet<String> parentPathParams(ObjectNode n) { SortedSet<String> result = new TreeSet<>(); ArrayNode argNodeList = (ArrayNode) n.get("arguments"); if (argNodeList != null) { for (JsonNode argNode : argNodeList) { if (argNode.has("parentSrc")) { String src = argNode.get("parentSrc").textValue(); String dest = argNode.get("dest").textValue(); if (src != null) { result.add(dest); } } } } return result; } private void moveOperationsToSingleClient(Swagger swagger) { swagger.getPaths().values().forEach(path -> path.getOperations() .forEach(operation -> operation.setTags(Collections.singletonList("client")))); } @Override public String apiFileFolder() { return outputFolder + "/" + apiPackage().replace('.', File.separatorChar); } @Override public String modelFileFolder() { return outputFolder + "/" + modelPackage().replace('.', File.separatorChar); } @Override public CodegenType getTag() { return CodegenType.CLIENT; } @Override public String getName() { return codeGenName; } @Override public String getHelp() { return "Generates a Java client library."; } @Override public String toModelFilename(String name) { if (modelTagMap.containsKey(name)) { String tag = modelTagMap.get(name); return tag.replaceAll("\\.", "/") + "/" + super.toModelFilename(name); } return super.toModelFilename(name); } @Override public String toModelImport(String name) { if (modelTagMap.containsKey(name)) { return modelPackage() + "." + modelTagMap.get(name) + "." + name; } return super.toModelImport(name); } @Override public CodegenModel fromModel(String name, Model model, Map<String, Model> allDefinitions) { CodegenModel codegenModel = super.fromModel(name, model, allDefinitions); // super add these imports, and we don't want that dependency codegenModel.imports.remove("ApiModel"); if (model.getVendorExtensions().containsKey("x-baseType")) { String baseType = (String) model.getVendorExtensions().get("x-baseType"); codegenModel.vendorExtensions.put("baseType", toModelName(baseType)); codegenModel.imports.add(toModelName(baseType)); } Collection<CodegenOperation> operations = (Collection<CodegenOperation>) codegenModel.vendorExtensions .get("operations"); if (operations != null) { operations.forEach(op -> { if (op.returnType != null) { codegenModel.imports.add(op.returnType); } if (op.allParams != null) { op.allParams.stream().filter(param -> needToImport(param.dataType)) .forEach(param -> codegenModel.imports.add(param.dataType)); } }); } // force alias == false (likely only relevant for Lists, but something changed in swagger 2.2.3 to require this) codegenModel.isAlias = false; String parent = (String) model.getVendorExtensions().get("x-okta-parent"); if (StringUtils.isNotEmpty(parent)) { codegenModel.parent = toApiName(parent.substring(parent.lastIndexOf("/"))); // figure out the resourceClass if this model has a parent String discriminatorRoot = getRootDiscriminator(name); if (discriminatorRoot != null) { model.getVendorExtensions().put("discriminatorRoot", discriminatorRoot); } } // We use '$ref' attributes with siblings, which isn't valid JSON schema (or swagger), so we need process // additional attributes from the raw schema Map<String, Object> modelDef = getRawSwaggerDefinition(name); codegenModel.vars.forEach(codegenProperty -> { Map<String, Object> rawPropertyMap = getRawSwaggerProperty(modelDef, codegenProperty.baseName); codegenProperty.isReadOnly = Boolean.TRUE.equals(rawPropertyMap.get("readOnly")); }); return codegenModel; } private String getRootDiscriminator(String name) { String result = reverseDiscriminatorMap.get(name); if (result != null) { String parentResult = getRootDiscriminator(result); if (parentResult != null) { result = parentResult; } } return result; } @Override public Map<String, Object> postProcessOperations(Map<String, Object> objs) { Map<String, Object> resultMap = super.postProcessOperations(objs); List<Map<String, String>> imports = (List<Map<String, String>>) objs.get("imports"); Map<String, Object> operations = (Map<String, Object>) objs.get("operations"); List<CodegenOperation> codegenOperations = (List<CodegenOperation>) operations.get("operation"); // find all of the list return values Set<String> importsToAdd = new HashSet<>(); codegenOperations.stream().filter(cgOp -> cgOp.returnType != null) .filter(cgOp -> cgOp.returnType.matches(".+List$")) .forEach(cgOp -> importsToAdd.add(toModelImport(cgOp.returnType))); // the params might have imports too codegenOperations.stream().filter(cgOp -> cgOp.allParams != null) .forEach(cgOp -> cgOp.allParams.stream().filter(cgParam -> cgParam.isEnum) .forEach(cgParam -> importsToAdd.add(toModelImport(cgParam.dataType)))); // add each one as an import importsToAdd.forEach(className -> { Map<String, String> listImport = new LinkedHashMap<>(); listImport.put("import", className); imports.add(listImport); }); return resultMap; } @Override public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { super.postProcessModelProperty(model, property); if (!BooleanUtils.toBoolean(model.isEnum)) { String datatype = property.datatype; if (datatype != null && datatype.matches(".+List$") && needToImport(datatype)) { model.imports.add(datatype); } String type = property.complexType; if (type == null) { type = property.baseType; } if (needToImport(type)) { model.imports.add(type); } // super add these imports, and we don't want that dependency model.imports.remove("ApiModelProperty"); model.imports.remove("ApiModel"); //final String lib = getLibrary(); //Needed imports for Jackson based libraries if (additionalProperties.containsKey("jackson")) { model.imports.add("JsonProperty"); } if (additionalProperties.containsKey("gson")) { model.imports.add("SerializedName"); } } else { // enum class //Needed imports for Jackson's JsonCreator if (additionalProperties.containsKey("jackson")) { model.imports.add("JsonCreator"); } } } @Override public Map<String, Object> postProcessModelsEnum(Map<String, Object> objs) { objs = super.postProcessModelsEnum(objs); //Needed import for Gson based libraries if (additionalProperties.containsKey("gson")) { List<Map<String, String>> imports = (List<Map<String, String>>) objs.get("imports"); List<Object> models = (List<Object>) objs.get("models"); for (Object _mo : models) { Map<String, Object> mo = (Map<String, Object>) _mo; CodegenModel cm = (CodegenModel) mo.get("model"); // for enum model if (Boolean.TRUE.equals(cm.isEnum) && cm.allowableValues != null) { cm.imports.add(importMapping.get("SerializedName")); Map<String, String> item = new HashMap<String, String>(); item.put("import", importMapping.get("SerializedName")); imports.add(item); } } } return objs; } @Override public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, Map<String, Model> definitions, Swagger swagger) { CodegenOperation co = super.fromOperation(path, httpMethod, operation, definitions, swagger); // scan params for X_OPENAPI_V3_SCHEMA_REF, and _correct_ the param co.allParams.forEach(param -> { if (param.vendorExtensions.containsKey(X_OPENAPI_V3_SCHEMA_REF)) { String enumDef = param.vendorExtensions.get(X_OPENAPI_V3_SCHEMA_REF).toString().replaceFirst(".*/", ""); param.isEnum = true; param.enumName = enumDef; param.dataType = enumDef; } }); // mark the operation as having optional params, so we can take advantage of it in the template addOptionalExtension(co, co.allParams); // if the body and the return type are the same mark the body param co.bodyParams.forEach(bodyParam -> { if (bodyParam.dataType.equals(co.returnType)) { co.vendorExtensions.put("updateBody", true); } }); return co; } private void addOptionalExtension(CodegenOperation co, List<CodegenParameter> params) { if (params.parallelStream().anyMatch(param -> !param.required)) { co.vendorExtensions.put("hasOptional", true); List<CodegenParameter> nonOptionalParams = params.stream().filter(param -> param.required) .map(CodegenParameter::copy).collect(Collectors.toList()); if (!nonOptionalParams.isEmpty()) { CodegenParameter param = nonOptionalParams.get(nonOptionalParams.size() - 1); param.hasMore = false; co.vendorExtensions.put("nonOptionalParams", nonOptionalParams); } // remove the noOptionalParams if we have trimmed down the list. if (co.vendorExtensions.get("nonOptionalParams") != null && nonOptionalParams.isEmpty()) { co.vendorExtensions.remove("nonOptionalParams"); } // remove th body parameter if it was optional if (co.bodyParam != null && !co.bodyParam.required) { co.vendorExtensions.put("optionalBody", true); } } } @Override public String toVarName(String name) { String originalResult = super.toVarName(name); if (originalResult.contains("oauth")) { originalResult = originalResult.replaceAll("oauth", "oAuth"); } return originalResult; } @Override public String toApiName(String name) { if (name.length() == 0) { return "Client"; } name = sanitizeName(name); return camelize(name); } private Property getArrayPropertyFromOperation(Operation operation) { if (operation != null && operation.getResponses() != null) { Response response = operation.getResponses().get("200"); if (response != null) { return response.getSchema(); } } return null; } public void addListModels(Swagger swagger) { Map<String, Model> listModels = new LinkedHashMap<>(); // lists in paths for (Path path : swagger.getPaths().values()) { List<Property> properties = new ArrayList<>(); properties.add(getArrayPropertyFromOperation(path.getGet())); properties.add(getArrayPropertyFromOperation(path.getPost())); properties.add(getArrayPropertyFromOperation(path.getPatch())); properties.add(getArrayPropertyFromOperation(path.getPut())); listModels.putAll(processListsFromProperties(properties, null, swagger)); } swagger.getDefinitions().entrySet().stream().filter(entry -> topLevelResources.contains(entry.getKey())) .forEach(entry -> { Model model = entry.getValue(); if (model != null && model.getProperties() != null) { listModels .putAll(processListsFromProperties(model.getProperties().values(), model, swagger)); } }); listModels.forEach(swagger::addDefinition); } private Map<String, Model> processListsFromProperties(Collection<Property> properties, Model baseModel, Swagger swagger) { Map<String, Model> result = new LinkedHashMap<>(); for (Property p : properties) { if (p != null && "array".equals(p.getType())) { ArrayProperty arrayProperty = (ArrayProperty) p; if (arrayProperty.getItems() instanceof RefProperty) { RefProperty ref = (RefProperty) arrayProperty.getItems(); String baseName = ref.getSimpleRef(); // Do not generate List wrappers for primitives (or strings) if (!languageSpecificPrimitives.contains(baseName) && topLevelResources.contains(baseName)) { String modelName = baseName + "List"; ModelImpl model = new ModelImpl(); model.setName(modelName); model.setAllowEmptyValue(false); model.setDescription("Collection List for " + baseName); if (baseModel == null) { baseModel = swagger.getDefinitions().get(baseName); } // only add the tags from the base model if (baseModel.getVendorExtensions().containsKey("x-okta-tags")) { model.setVendorExtension("x-okta-tags", baseModel.getVendorExtensions().get("x-okta-tags")); } model.setVendorExtension("x-isResourceList", true); model.setVendorExtension("x-baseType", baseName); model.setType(modelName); result.put(modelName, model); } } } } return result; } @Override public String getTypeDeclaration(Property p) { if ("password".equals(p.getFormat())) { return "char[]"; } if (p instanceof ArrayProperty) { ArrayProperty ap = (ArrayProperty) p; Property inner = ap.getItems(); if (inner == null) { // mimic super behavior log.warn("{} (array property) does not have a proper inner type defined", ap.getName()); return null; } String type = super.getTypeDeclaration(inner); if (!languageSpecificPrimitives.contains(type) && topLevelResources.contains(type)) { return type + "List"; } } return super.getTypeDeclaration(p); } private Map<String, Object> castToMap(Object object) { return (Map<String, Object>) object; } protected Map<String, Object> getRawSwaggerDefinition(String name) { return castToMap(castToMap(rawSwaggerConfig.get("definitions")).get(name)); } protected Map<String, Object> getRawSwaggerProperty(Map<String, Object> definition, String propertyName) { return castToMap(castToMap(definition.get("properties")).get(propertyName)); } }