com.ibm.fhir.swagger.generator.FHIRSwaggerGenerator Maven / Gradle / Ivy
/*
* (C) Copyright IBM Corp. 2016, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.fhir.swagger.generator;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.ibm.fhir.core.FHIRMediaType;
import com.ibm.fhir.model.annotation.Required;
import com.ibm.fhir.model.resource.Bundle;
import com.ibm.fhir.model.resource.CapabilityStatement;
import com.ibm.fhir.model.resource.DomainResource;
import com.ibm.fhir.model.resource.OperationOutcome;
import com.ibm.fhir.model.resource.Resource;
import com.ibm.fhir.model.resource.SearchParameter;
import com.ibm.fhir.model.resource.StructureDefinition;
import com.ibm.fhir.model.type.BackboneElement;
import com.ibm.fhir.model.type.Code;
import com.ibm.fhir.model.type.Element;
import com.ibm.fhir.model.type.ElementDefinition;
import com.ibm.fhir.model.type.Extension;
import com.ibm.fhir.model.type.Identifier;
import com.ibm.fhir.model.type.Oid;
import com.ibm.fhir.model.type.Reference;
import com.ibm.fhir.model.type.Uuid;
import com.ibm.fhir.model.util.ModelSupport;
import com.ibm.fhir.model.visitor.AbstractVisitable;
import com.ibm.fhir.openapi.generator.FHIROpenApiGenerator;
import com.ibm.fhir.search.compartment.CompartmentUtil;
import com.ibm.fhir.search.exception.FHIRSearchException;
import com.ibm.fhir.search.util.SearchUtil;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonStructure;
import jakarta.json.JsonWriter;
import jakarta.json.JsonWriterFactory;
import jakarta.json.stream.JsonGenerator;
/**
* Generate Swagger 2.0 from the HL7 FHIR R4 artifacts and the IBM FHIR object model.
*
*
* By default, this class will create a separate Swagger definition for each resource type and compartment,
* with all applicable HTTP interactions enabled.
*
*
* To limit the output to a given set of resources and/or interactions, pass a set of semicolon-delimited
* filter strings of the form {@code ResourceType1(interaction1,interaction2)}.
*
* For example:
*
* Patient(create,update,read,vread,history,search);Contract(create,read,vread,history,search);RiskAssessment(read)
*
*/
public class FHIRSwaggerGenerator {
private static final String OUTDIR = "swagger";
private static final JsonBuilderFactory factory = Json.createBuilderFactory(null);
private static final Map, StructureDefinition> structureDefinitionMap = buildStructureDefinitionMap();
private static boolean includeDeleteOperation = true;
public static final String TYPEPACKAGENAME = "com.ibm.fhir.model.type";
public static final String RESOURCEPACKAGENAME = "com.ibm.fhir.model.resource";
public static final String APPLICATION_FORM = "application/x-www-form-urlencoded";
public static void main(String[] args) throws Exception {
File file = new File(OUTDIR);
if (!file.exists()) {
file.mkdirs();
}
System.out.println("Generating swagger definitions at " + file.getCanonicalPath());
Filter filter = null;
if (args.length == 1) {
filter = createFilter(args[0]);
} else {
filter = createAcceptAllFilter();
}
List classNames = FHIROpenApiGenerator.getClassNames();
for (String resourceClassName : classNames) {
Class> resourceModelClass = Class.forName(FHIROpenApiGenerator.RESOURCEPACKAGENAME + "." + resourceClassName);
if (DomainResource.class.isAssignableFrom(resourceModelClass)
&& DomainResource.class != resourceModelClass) {
if (filter.acceptResourceType(resourceModelClass)) {
JsonObjectBuilder swagger = factory.createObjectBuilder();
swagger.add("swagger", "2.0");
JsonObjectBuilder info = factory.createObjectBuilder();
info.add("title", resourceClassName + " API");
info.add("description", "A simplified version of the HL7 FHIR API for " + resourceClassName + " resources.");
info.add("version", "4.0.1");
swagger.add("info", info);
swagger.add("basePath", "/fhir-server/api/v4");
// Set the hostname in APIConnectAdapter and uncomment this to add "x-ibm-configuration"
// with a default ExecuteInvoke Assembly
APIConnectAdapter.addApiConnectStuff(swagger);
JsonArrayBuilder tags = factory.createArrayBuilder();
JsonObjectBuilder paths = factory.createObjectBuilder();
JsonObjectBuilder definitions = factory.createObjectBuilder();
// generate Resource and DomainResource definitions
generateDefinition(Resource.class, definitions);
generateDefinition(DomainResource.class, definitions);
generatePaths(resourceModelClass, paths, filter);
JsonObjectBuilder tag = factory.createObjectBuilder();
tag.add("name", resourceModelClass.getSimpleName());
tags.add(tag);
generateDefinition(resourceModelClass, definitions);
// for search response
generateDefinition(Bundle.class, definitions);
// for error response
generateDefinition(OperationOutcome.class, definitions);
// generate definition for all inner classes inside the top level resource.
for (String innerClassName : FHIROpenApiGenerator.getAllResourceInnerClasses()) {
String parentClassName = innerClassName.split("\\$")[0];
if (resourceClassName.equals(parentClassName) ||
"DomainResource".equals(parentClassName) ||
"Resource".equals(parentClassName) ||
"Bundle".equals(parentClassName) ||
"OperationOutcome".equals(parentClassName)) {
Class> innerModelClass = Class.forName(RESOURCEPACKAGENAME + "." + innerClassName);
generateDefinition(innerModelClass, definitions);
}
}
// generate definition for all the applicable defined Types.
for (String typeClassName : FHIROpenApiGenerator.getAllTypesList()) {
Class> typeModelClass = Class.forName(TYPEPACKAGENAME + "." + typeClassName);
if (FHIROpenApiGenerator.isApplicableForClass(typeModelClass, resourceModelClass)) {
generateDefinition(typeModelClass, definitions);
}
}
swagger.add("tags", tags);
swagger.add("paths", paths);
JsonObjectBuilder parameters = factory.createObjectBuilder();
generateParameters(parameters, filter);
JsonObject parametersObject = parameters.build();
if (!parametersObject.isEmpty()) {
swagger.add("parameters", parametersObject);
}
swagger.add("definitions", definitions);
Map config = new HashMap();
config.put(JsonGenerator.PRETTY_PRINTING, true);
JsonWriterFactory factory = Json.createWriterFactory(config);
File outFile = new File(OUTDIR + File.separator + resourceClassName + "-swagger.json");
try (JsonWriter writer = factory.createWriter(new FileOutputStream(outFile), StandardCharsets.UTF_8)) {
writer.writeObject(swagger.build());
} catch (Exception e) {
throw new Error(e);
}
}
// generate file for compartment
generateCompartmentSwagger(resourceModelClass, filter);
}
}
generateMetadataSwagger();
generateBatchTransactionSwagger();
}
private static void generateCompartmentSwagger(Class> compartmentModelClass, Filter filter) throws Exception, ClassNotFoundException, Error {
String compartmentClassName = compartmentModelClass.getSimpleName();
List resourceClassNames = getCompartmentClassNames(compartmentClassName);
if (resourceClassNames == null || resourceClassNames.isEmpty()) {
return;
}
JsonArrayBuilder tags = factory.createArrayBuilder();
JsonObjectBuilder paths = factory.createObjectBuilder();
JsonObjectBuilder definitions = factory.createObjectBuilder();
boolean addedContent = false;
// Only include resource types that are accepted by the filter
for (String resourceClassName : resourceClassNames) {
Class> resourceModelClass = Class.forName(FHIROpenApiGenerator.RESOURCEPACKAGENAME + "." + resourceClassName);
if (DomainResource.class.isAssignableFrom(resourceModelClass)
&& DomainResource.class != resourceModelClass
&& filter.acceptResourceType(resourceModelClass)) {
addedContent = true;
generateCompartmentPaths(compartmentModelClass, resourceModelClass, paths, filter);
JsonObjectBuilder tag = factory.createObjectBuilder();
tag.add("name", resourceClassName);
tags.add(tag);
generateDefinition(resourceModelClass, definitions);
// generate definition for all inner classes inside the top level resource.
for (String innerClassName : FHIROpenApiGenerator.getAllResourceInnerClasses()) {
String parentClassName = innerClassName.split("\\$")[0];
if (resourceClassName.equals(parentClassName) ||
"DomainResource".equals(parentClassName) ||
"Resource".equals(parentClassName) ||
"Bundle".equals(parentClassName) ||
"OperationOutcome".equals(parentClassName)) {
Class> innerModelClass = Class.forName(RESOURCEPACKAGENAME + "." + innerClassName);
generateDefinition(innerModelClass, definitions);
}
}
// generate definition for all the applicable defined Types.
for (String typeClassName : FHIROpenApiGenerator.getAllTypesList()) {
Class> typeModelClass = Class.forName(TYPEPACKAGENAME + "." + typeClassName);
if (FHIROpenApiGenerator.isApplicableForClass(typeModelClass, resourceModelClass)) {
generateDefinition(typeModelClass, definitions);
}
}
}
}
if (!addedContent) {
return;
}
JsonObjectBuilder swagger = factory.createObjectBuilder();
swagger.add("swagger", "2.0");
JsonObjectBuilder info = factory.createObjectBuilder();
info.add("title", compartmentClassName + " compartment API");
info.add("description", "A simplified version of the HL7 FHIR API for " + compartmentClassName + " compartment.");
info.add("version", "4.0.1");
swagger.add("info", info);
swagger.add("basePath", "/fhir-server/api/v4");
// Set the hostname in APIConnectAdapter and uncomment this to add "x-ibm-configuration"
// with a default ExecuteInvoke Assembly
APIConnectAdapter.addApiConnectStuff(swagger);
// generate Resource and DomainResource definitions
generateDefinition(Resource.class, definitions);
generateDefinition(DomainResource.class, definitions);
// for search response
generateDefinition(Bundle.class, definitions);
// for error response
generateDefinition(OperationOutcome.class, definitions);
swagger.add("tags", tags);
swagger.add("paths", paths);
JsonObjectBuilder parameters = factory.createObjectBuilder();
generateCompartmentSearchParameters(parameters, filter);
JsonObject parametersObject = parameters.build();
if (!parametersObject.isEmpty()) {
swagger.add("parameters", parametersObject);
}
swagger.add("definitions", definitions);
Map config = new HashMap();
config.put(JsonGenerator.PRETTY_PRINTING, true);
JsonWriterFactory factory = Json.createWriterFactory(config);
File outFile = new File(OUTDIR + File.separator + compartmentClassName + "-compartment-swagger.json");
try (JsonWriter writer = factory.createWriter(new FileOutputStream(outFile), StandardCharsets.UTF_8)) {
writer.writeObject(swagger.build());
} catch (Exception e) {
throw new Error(e);
}
}
private static void generateMetadataSwagger() throws Exception, ClassNotFoundException, Error {
JsonObjectBuilder swagger = factory.createObjectBuilder();
swagger.add("swagger", "2.0");
JsonObjectBuilder info = factory.createObjectBuilder();
info.add("title", "Capabilities");
info.add("description", "The capabilities interaction retrieves the information about a server's capabilities - which portions of the HL7 FHIR specification it supports.");
info.add("version", "4.0.0");
swagger.add("info", info);
swagger.add("basePath", "/fhir-server/api/v4");
JsonObjectBuilder paths = factory.createObjectBuilder();
JsonObjectBuilder definitions = factory.createObjectBuilder();
// FHIR capabilities interaction
JsonObjectBuilder path = factory.createObjectBuilder();
generateMetadataPathItem(path);
paths.add("/metadata", path);
swagger.add("paths", paths);
generateDefinition(CapabilityStatement.class, definitions);
generateDefinition(Resource.class, definitions);
generateDefinition(DomainResource.class, definitions);
// generate definition for all inner classes inside the top level resources.
for (String innerClassName : FHIROpenApiGenerator.getAllResourceInnerClasses()) {
Class> parentClass = Class.forName(RESOURCEPACKAGENAME + "." + innerClassName.split("\\$")[0]);
if (CapabilityStatement.class.equals(parentClass)) {
Class> innerModelClass = Class.forName(RESOURCEPACKAGENAME + "." + innerClassName);
generateDefinition(innerModelClass, definitions);
}
}
// generate definition for all the applicable data types.
for (String typeClassName : FHIROpenApiGenerator.getAllTypesList()) {
Class> typeModelClass = Class.forName(TYPEPACKAGENAME + "." + typeClassName);
if (FHIROpenApiGenerator.isApplicableForClass(typeModelClass, CapabilityStatement.class)) {
generateDefinition(typeModelClass, definitions);
}
}
swagger.add("definitions", definitions);
Map config = new HashMap();
config.put(JsonGenerator.PRETTY_PRINTING, true);
JsonWriterFactory factory = Json.createWriterFactory(config);
File outFile = new File(OUTDIR + File.separator + "metadata-swagger.json");
try (JsonWriter writer = factory.createWriter(new FileOutputStream(outFile), StandardCharsets.UTF_8)) {
writer.writeObject(swagger.build());
} catch (Exception e) {
throw new Error(e);
}
}
private static void generateBatchTransactionSwagger() throws Exception, ClassNotFoundException, Error {
JsonObjectBuilder swagger = factory.createObjectBuilder();
swagger.add("swagger", "2.0");
JsonObjectBuilder info = factory.createObjectBuilder();
info.add("title", "Batch/Transaction");
info.add("description", "The batch and transaction interactions submit a set of actions to perform on a server in a single HTTP request/response.");
info.add("version", "4.0.0");
swagger.add("info", info);
swagger.add("basePath", "/fhir-server/api/v4");
JsonObjectBuilder paths = factory.createObjectBuilder();
JsonObjectBuilder definitions = factory.createObjectBuilder();
// FHIR batch/transaction interaction
JsonObjectBuilder path = factory.createObjectBuilder();
generateBatchPathItem(path);
paths.add("/", path);
swagger.add("paths", paths);
generateDefinition(Bundle.class, definitions);
// generate definition for all inner classes inside the top level resources.
for (String innerClassName : FHIROpenApiGenerator.getAllResourceInnerClasses()) {
Class> parentClass = Class.forName(RESOURCEPACKAGENAME + "." + innerClassName.split("\\$")[0]);
if (Bundle.class.equals(parentClass)) {
Class> innerModelClass = Class.forName(RESOURCEPACKAGENAME + "." + innerClassName);
generateDefinition(innerModelClass, definitions);
}
}
// generate definition for all the defined Types.
for (String typeClassName : FHIROpenApiGenerator.getAllTypesList()) {
Class> typeModelClass = Class.forName(TYPEPACKAGENAME + "." + typeClassName);
generateDefinition(typeModelClass, definitions);
}
swagger.add("definitions", definitions);
Map config = new HashMap();
config.put(JsonGenerator.PRETTY_PRINTING, true);
JsonWriterFactory factory = Json.createWriterFactory(config);
File outFile = new File(OUTDIR + File.separator + "batch-swagger.json");
try (JsonWriter writer = factory.createWriter(new FileOutputStream(outFile), StandardCharsets.UTF_8)) {
writer.writeObject(swagger.build());
} catch (Exception e) {
throw new Error(e);
}
}
private static Map, StructureDefinition> buildStructureDefinitionMap() {
Map, StructureDefinition> structureDefinitionMap = new HashMap, StructureDefinition>();
try {
FHIROpenApiGenerator.populateStructureDefinitionMap(structureDefinitionMap, "profiles-resources.json");
FHIROpenApiGenerator.populateStructureDefinitionMap(structureDefinitionMap, "profiles-types.json");
} catch (Exception e) {
throw new Error(e);
}
return structureDefinitionMap;
}
private static void generateParameters(JsonObjectBuilder parameters, Filter filter) throws Exception {
if (filter.acceptOperation("read") || filter.acceptOperation("vread") || filter.acceptOperation("update")
|| filter.acceptOperation("delete") || filter.acceptOperation("history")) {
JsonObjectBuilder id = factory.createObjectBuilder();
id.add("name", "id");
id.add("description", "logical identifier");
id.add("in", "path");
id.add("required", true);
id.add("type", "string");
parameters.add("idParam", id);
}
if (filter.acceptOperation("vread")) {
JsonObjectBuilder vid = factory.createObjectBuilder();
vid.add("name", "vid");
vid.add("description", "version identifier");
vid.add("in", "path");
vid.add("required", true);
vid.add("type", "string");
parameters.add("vidParam", vid);
}
if (filter.acceptOperation("search")) {
for (Entry entry : SearchUtil.getSearchParameters(Resource.class.getSimpleName()).entrySet()) {
SearchParameter searchParameter = entry.getValue();
JsonObjectBuilder parameter = factory.createObjectBuilder();
String name = entry.getKey();
parameter.add("name", name);
parameter.add("description", searchParameter.getDescription().getValue());
parameter.add("in", "query");
parameter.add("required", false);
parameter.add("type", "string");
parameters.add(name + "Param", parameter);
}
}
}
private static void generateCompartmentSearchParameters(JsonObjectBuilder parameters, Filter filter) throws Exception {
if (filter.acceptOperation("search")) {
JsonObjectBuilder id = factory.createObjectBuilder();
id.add("name", "id");
id.add("description", "logical identifier");
id.add("in", "path");
id.add("required", true);
id.add("type", "string");
parameters.add("idParam", id);
for (Entry entry : SearchUtil.getSearchParameters(Resource.class.getSimpleName()).entrySet()) {
SearchParameter searchParameter = entry.getValue();
JsonObjectBuilder parameter = factory.createObjectBuilder();
String name = searchParameter.getName().getValue();
parameter.add("name", entry.getKey());
parameter.add("description", searchParameter.getDescription().getValue());
parameter.add("in", "query");
parameter.add("required", false);
parameter.add("type", "string");
parameters.add(name + "Param", parameter);
}
}
}
private static void generatePaths(Class> modelClass, JsonObjectBuilder paths, Filter filter) throws Exception {
JsonObjectBuilder path = factory.createObjectBuilder();
// FHIR create operation
if (filter.acceptOperation(modelClass, "create")) {
generateCreatePathItem(modelClass, path);
}
// FHIR search operation
if (filter.acceptOperation(modelClass, "search")) {
generateSearchPathItem(null, modelClass, path);
}
JsonObject pathObject = path.build();
if (!pathObject.isEmpty()) {
paths.add("/" + modelClass.getSimpleName(), pathObject);
}
path = factory.createObjectBuilder();
// FHIR search (via POST) operation
if (filter.acceptOperation(modelClass, "search")) {
generateSearchViaPostPathItem(null, modelClass, path);
}
pathObject = path.build();
if (!pathObject.isEmpty()) {
paths.add("/" + modelClass.getSimpleName() + "/_search", pathObject);
}
path = factory.createObjectBuilder();
// FHIR vread operation
if (filter.acceptOperation(modelClass, "vread")) {
generateVreadPathItem(modelClass, path);
}
pathObject = path.build();
if (!pathObject.isEmpty()) {
paths.add("/" + modelClass.getSimpleName() + "/{id}/_history/{vid}", pathObject);
}
path = factory.createObjectBuilder();
// FHIR read operation
if (filter.acceptOperation(modelClass, "read")) {
generateReadPathItem(modelClass, path);
}
// FHIR update operation
if (filter.acceptOperation(modelClass, "update")) {
generateUpdatePathItem(modelClass, path);
}
// FHIR delete operation
if (filter.acceptOperation(modelClass, "delete") && includeDeleteOperation) {
generateDeletePathItem(modelClass, path);
}
pathObject = path.build();
if (!pathObject.isEmpty()) {
paths.add("/" + modelClass.getSimpleName() + "/{id}", pathObject);
}
// FHIR history operation
path = factory.createObjectBuilder();
if (filter.acceptOperation(modelClass, "history")) {
generateHistoryPathItem(modelClass, path);
}
pathObject = path.build();
if (!pathObject.isEmpty()) {
paths.add("/" + modelClass.getSimpleName() + "/{id}/_history", pathObject);
}
// TODO: add patch
}
private static void generateCompartmentPaths(Class> compartmentModelClass, Class> resourceModelClass, JsonObjectBuilder paths, Filter filter) throws Exception {
JsonObjectBuilder path = factory.createObjectBuilder();
// FHIR search operation
if (filter.acceptOperation(resourceModelClass, "search")) {
generateSearchPathItem(compartmentModelClass, resourceModelClass, path);
}
JsonObject pathObject = path.build();
if (!pathObject.isEmpty()) {
paths.add("/" + compartmentModelClass.getSimpleName() + "/{id}/" + resourceModelClass.getSimpleName(), pathObject);
}
path = factory.createObjectBuilder();
// FHIR search (via POST) operation
if (filter.acceptOperation(resourceModelClass, "search")) {
generateSearchViaPostPathItem(compartmentModelClass, resourceModelClass, path);
}
pathObject = path.build();
if (!pathObject.isEmpty()) {
paths.add("/" + compartmentModelClass.getSimpleName() + "/{id}/" + resourceModelClass.getSimpleName() + "/_search", pathObject);
}
}
private static void generateCreatePathItem(Class> modelClass, JsonObjectBuilder path) {
JsonObjectBuilder post = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add(modelClass.getSimpleName());
post.add("tags", tags);
post.add("summary", "Create" + getArticle(modelClass) + modelClass.getSimpleName() + " resource");
post.add("operationId", "create" + modelClass.getSimpleName());
JsonArrayBuilder consumes = factory.createArrayBuilder();
consumes.add(FHIRMediaType.APPLICATION_FHIR_JSON);
post.add("consumes", consumes);
JsonArrayBuilder parameters = factory.createArrayBuilder();
JsonObjectBuilder bodyParameter = factory.createObjectBuilder();
bodyParameter.add("name", "body");
bodyParameter.add("in", "body");
bodyParameter.add("required", true);
JsonObjectBuilder schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/" + modelClass.getSimpleName());
bodyParameter.add("schema", schema);
parameters.add(bodyParameter);
post.add("parameters", parameters);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "Create " + modelClass.getSimpleName() + " operation successful");
responses.add("201", response);
post.add("responses", responses);
path.add("post", post);
}
private static void generateReadPathItem(Class> modelClass, JsonObjectBuilder path) {
JsonObjectBuilder get = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add(modelClass.getSimpleName());
get.add("tags", tags);
get.add("summary", "Read" + getArticle(modelClass) + modelClass.getSimpleName() + " resource");
get.add("operationId", "read" + modelClass.getSimpleName());
JsonArrayBuilder produces = factory.createArrayBuilder();
produces.add(FHIRMediaType.APPLICATION_FHIR_JSON);
get.add("produces", produces);
JsonArrayBuilder parameters = factory.createArrayBuilder();
JsonObjectBuilder idParamRef = factory.createObjectBuilder();
idParamRef.add("$ref", "#/parameters/idParam");
parameters.add(idParamRef);
get.add("parameters", parameters);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "Read " + modelClass.getSimpleName() + " operation successful");
JsonObjectBuilder schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/" + modelClass.getSimpleName());
response.add("schema", schema);
responses.add("200", response);
get.add("responses", responses);
path.add("get", get);
}
private static void generateVreadPathItem(Class> modelClass, JsonObjectBuilder path) {
JsonObjectBuilder get = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add(modelClass.getSimpleName());
get.add("tags", tags);
get.add("summary",
"Read specific version of" + getArticle(modelClass) + modelClass.getSimpleName() + " resource");
get.add("operationId", "vread" + modelClass.getSimpleName());
JsonArrayBuilder produces = factory.createArrayBuilder();
produces.add(FHIRMediaType.APPLICATION_FHIR_JSON);
get.add("produces", produces);
JsonArrayBuilder parameters = factory.createArrayBuilder();
JsonObjectBuilder idParamRef = factory.createObjectBuilder();
idParamRef.add("$ref", "#/parameters/idParam");
parameters.add(idParamRef);
JsonObjectBuilder vidParamRef = factory.createObjectBuilder();
vidParamRef.add("$ref", "#/parameters/vidParam");
parameters.add(vidParamRef);
get.add("parameters", parameters);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "Versioned read " + modelClass.getSimpleName() + " operation successful");
JsonObjectBuilder schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/" + modelClass.getSimpleName());
response.add("schema", schema);
responses.add("200", response);
get.add("responses", responses);
path.add("get", get);
}
private static void generateUpdatePathItem(Class> modelClass, JsonObjectBuilder path) {
JsonObjectBuilder put = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add(modelClass.getSimpleName());
put.add("tags", tags);
put.add("summary", "Update an existing " + modelClass.getSimpleName() + " resource");
put.add("operationId", "update" + modelClass.getSimpleName());
JsonArrayBuilder consumes = factory.createArrayBuilder();
consumes.add(FHIRMediaType.APPLICATION_FHIR_JSON);
put.add("consumes", consumes);
JsonArrayBuilder parameters = factory.createArrayBuilder();
JsonObjectBuilder idParamRef = factory.createObjectBuilder();
idParamRef.add("$ref", "#/parameters/idParam");
parameters.add(idParamRef);
JsonObjectBuilder bodyParameter = factory.createObjectBuilder();
bodyParameter.add("name", "body");
bodyParameter.add("in", "body");
bodyParameter.add("required", true);
JsonObjectBuilder schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/" + modelClass.getSimpleName());
bodyParameter.add("schema", schema);
parameters.add(bodyParameter);
put.add("parameters", parameters);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response1 = factory.createObjectBuilder();
response1.add("description", "Update " + modelClass.getSimpleName() + " operation successful");
JsonObjectBuilder response2 = factory.createObjectBuilder();
response2.add("description", "Create " + modelClass.getSimpleName()
+ " operation successful (requires 'updateCreateEnabled' configuration option)");
responses.add("200", response1);
responses.add("201", response2);
put.add("responses", responses);
path.add("put", put);
}
private static void generateDeletePathItem(Class> modelClass, JsonObjectBuilder path) {
JsonObjectBuilder delete = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add(modelClass.getSimpleName());
delete.add("tags", tags);
delete.add("summary", "Delete" + getArticle(modelClass) + modelClass.getSimpleName() + " resource");
delete.add("operationId", "delete" + modelClass.getSimpleName());
JsonArrayBuilder parameters = factory.createArrayBuilder();
JsonObjectBuilder idParamRef = factory.createObjectBuilder();
idParamRef.add("$ref", "#/parameters/idParam");
parameters.add(idParamRef);
delete.add("parameters", parameters);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "Delete " + modelClass.getSimpleName() + " operation successful");
responses.add("204", response);
delete.add("responses", responses);
path.add("delete", delete);
}
private static void generateSearchPathItem(Class> compartmentModelClass, Class> modelClass, JsonObjectBuilder path) throws Exception {
JsonObjectBuilder get = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add(modelClass.getSimpleName());
get.add("tags", tags);
if (compartmentModelClass != null) {
get.add("summary", "Search for " + modelClass.getSimpleName() + " resources within " + compartmentModelClass.getSimpleName() + " compartment");
get.add("operationId", "search" + compartmentModelClass.getSimpleName() + "Compartment"+ modelClass.getSimpleName());
} else {
get.add("summary", "Search for " + modelClass.getSimpleName() + " resources");
get.add("operationId", "search" + modelClass.getSimpleName());
}
JsonArrayBuilder produces = factory.createArrayBuilder();
produces.add(FHIRMediaType.APPLICATION_FHIR_JSON);
get.add("produces", produces);
JsonArrayBuilder parameters = factory.createArrayBuilder();
// For compartment search, include parameter for {id}
if (compartmentModelClass != null) {
JsonObjectBuilder idParamRef = factory.createObjectBuilder();
idParamRef.add("$ref", "#/parameters/idParam");
parameters.add(idParamRef);
}
generateSearchParameters(modelClass, parameters);
get.add("parameters", parameters);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "Search " + modelClass.getSimpleName() + " operation successful");
JsonObjectBuilder schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/Bundle");
response.add("schema", schema);
responses.add("200", response);
get.add("responses", responses);
path.add("get", get);
}
private static void generateSearchParameters(Class> modelClass, JsonArrayBuilder parameters) throws Exception {
for (Entry entry : SearchUtil.getSearchParameters(modelClass.getSimpleName()).entrySet()) {
SearchParameter searchParameter = entry.getValue();
JsonObjectBuilder parameter = factory.createObjectBuilder();
String name = entry.getKey();
if (name.startsWith("_")) {
parameter.add("$ref", "#/parameters/" + name + "Param");
} else {
parameter.add("name", name);
parameter.add("description", searchParameter.getDescription().getValue());
parameter.add("in", "query");
parameter.add("type", "string");
parameter.add("required", false);
}
parameters.add(parameter);
}
}
private static void generateSearchViaPostPathItem(Class> compartmentModelClass, Class> modelClass, JsonObjectBuilder path) throws Exception {
JsonObjectBuilder post = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add(modelClass.getSimpleName());
post.add("tags", tags);
if (compartmentModelClass != null) {
post.add("summary", "Search for " + modelClass.getSimpleName() + " resources within " + compartmentModelClass.getSimpleName() + " compartment");
post.add("operationId", "searchViaPost" + compartmentModelClass.getSimpleName() + "Compartment"+ modelClass.getSimpleName());
} else {
post.add("summary", "Search for " + modelClass.getSimpleName() + " resources");
post.add("operationId", "searchViaPost" + modelClass.getSimpleName());
}
JsonArrayBuilder consumes = factory.createArrayBuilder();
consumes.add(APPLICATION_FORM);
post.add("consumes", consumes);
JsonArrayBuilder produces = factory.createArrayBuilder();
produces.add(FHIRMediaType.APPLICATION_FHIR_JSON);
post.add("produces", produces);
JsonArrayBuilder parameters = factory.createArrayBuilder();
// For compartment search, include parameter for {id}
if (compartmentModelClass != null) {
JsonObjectBuilder idParamRef = factory.createObjectBuilder();
idParamRef.add("$ref", "#/parameters/idParam");
parameters.add(idParamRef);
}
generateSearchParameters(modelClass, parameters);
generateSearchFormParameters(modelClass, parameters);
post.add("parameters", parameters);
JsonObjectBuilder schema = factory.createObjectBuilder();
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "Search " + modelClass.getSimpleName() + " operation successful");
schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/Bundle");
response.add("schema", schema);
responses.add("200", response);
post.add("responses", responses);
path.add("post", post);
}
private static void generateSearchFormParameters(Class> modelClass, JsonArrayBuilder parameters) throws Exception {
for (Entry entry : SearchUtil.getSearchParameters(modelClass.getSimpleName()).entrySet()) {
SearchParameter searchParameter = entry.getValue();
JsonObjectBuilder parameter = factory.createObjectBuilder();
String name = entry.getKey();
parameter.add("name", name);
parameter.add("description", searchParameter.getDescription().getValue());
parameter.add("in", "formData");
parameter.add("type", "string");
parameters.add(parameter);
}
}
private static void generateHistoryPathItem(Class> modelClass, JsonObjectBuilder path) {
JsonObjectBuilder get = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add(modelClass.getSimpleName());
get.add("tags", tags);
get.add("summary", "Return the history of" + getArticle(modelClass) + modelClass.getSimpleName() + " resource");
get.add("operationId", "history" + modelClass.getSimpleName());
JsonArrayBuilder produces = factory.createArrayBuilder();
produces.add(FHIRMediaType.APPLICATION_FHIR_JSON);
get.add("produces", produces);
JsonArrayBuilder parameters = factory.createArrayBuilder();
JsonObjectBuilder idParamRef = factory.createObjectBuilder();
idParamRef.add("$ref", "#/parameters/idParam");
parameters.add(idParamRef);
get.add("parameters", parameters);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "History " + modelClass.getSimpleName() + " operation successful");
JsonObjectBuilder schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/Bundle");
response.add("schema", schema);
responses.add("200", response);
get.add("responses", responses);
path.add("get", get);
}
private static void generateMetadataPathItem(JsonObjectBuilder path) {
JsonObjectBuilder get = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add("Other");
get.add("tags", tags);
get.add("summary", "Get the FHIR Capability statement for this endpoint");
get.add("operationId", "metadata");
JsonArrayBuilder produces = factory.createArrayBuilder();
produces.add(FHIRMediaType.APPLICATION_FHIR_JSON);
get.add("produces", produces);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "metadata operation successful");
JsonObjectBuilder schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/CapabilityStatement");
response.add("schema", schema);
responses.add("200", response);
get.add("responses", responses);
path.add("get", get);
}
private static void generateBatchPathItem(JsonObjectBuilder path) {
JsonObjectBuilder post = factory.createObjectBuilder();
JsonArrayBuilder tags = factory.createArrayBuilder();
tags.add("Other");
post.add("tags", tags);
post.add("summary", "Perform a batch operation");
post.add("operationId", "batch");
JsonArrayBuilder consumes = factory.createArrayBuilder();
consumes.add(FHIRMediaType.APPLICATION_FHIR_JSON);
post.add("consumes", consumes);
JsonArrayBuilder parameters = factory.createArrayBuilder();
JsonObjectBuilder bodyParameter = factory.createObjectBuilder();
bodyParameter.add("name", "body");
bodyParameter.add("in", "body");
bodyParameter.add("required", true);
JsonObjectBuilder schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/Bundle");
bodyParameter.add("schema", schema);
parameters.add(bodyParameter);
post.add("parameters", parameters);
JsonArrayBuilder produces = factory.createArrayBuilder();
produces.add(FHIRMediaType.APPLICATION_FHIR_JSON);
post.add("produces", produces);
JsonObjectBuilder responses = factory.createObjectBuilder();
JsonObjectBuilder response = factory.createObjectBuilder();
response.add("description", "batch operation successful");
schema = factory.createObjectBuilder();
schema.add("$ref", "#/definitions/Bundle");
response.add("schema", schema);
responses.add("200", response);
post.add("responses", responses);
path.add("post", post);
}
private static void generateDefinition(Class> modelClass, JsonObjectBuilder definitions) throws Exception {
if (!ModelSupport.isPrimitiveType(modelClass)) {
JsonObjectBuilder definition = factory.createObjectBuilder();
JsonObjectBuilder properties = factory.createObjectBuilder();
JsonArrayBuilder required = factory.createArrayBuilder();
StructureDefinition structureDefinition = getStructureDefinition(modelClass);
if (structureDefinition == null) {
System.err.println("Failed generateDefinition for: " + modelClass.getName());
return;
}
if (Resource.class.isAssignableFrom(modelClass)) {
// add the 'resourceType' property
JsonObjectBuilder property = factory.createObjectBuilder();
// Convert all the primitive types to json types.
property.add("type", "string");
if (Resource.class == modelClass) {
// TODO: when a filter was passed, limit this to just the resource types included in the filter
List typeNames = FHIROpenApiGenerator.getClassNames();
JsonArrayBuilder enumValues = factory.createArrayBuilder(typeNames);
property.add("enum", enumValues);
properties.add("resourceType", property.build());
required.add("resourceType");
} else {
// TODO how to "overwrite" the Resource definition and say that the value is fixed?
// https://github.com/OAI/OpenAPI-Specification/issues/1313
// property.add("enum", modelClass.getSimpleName());
}
}
for (Field field : modelClass.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isVolatile(field.getModifiers())) {
if (!ModelSupport.isChoiceElement(modelClass, ModelSupport.getElementName(field)) && field.isAnnotationPresent(Required.class)) {
required.add(ModelSupport.getElementName(field));
}
generateProperties(structureDefinition, modelClass, field, properties);
}
}
JsonArray requiredArray = required.build();
Class> superClass = modelClass.getSuperclass();
if (superClass != null
&& superClass.getPackage().getName().startsWith("com.ibm.fhir.model")
&& !superClass.equals(AbstractVisitable.class)) {
JsonArrayBuilder allOf = factory.createArrayBuilder();
JsonObjectBuilder ref = factory.createObjectBuilder();
ref.add("$ref", "#/definitions/" + superClass.getSimpleName());
allOf.add(ref);
JsonObjectBuilder wrapper = factory.createObjectBuilder();
wrapper.add("type", "object");
wrapper.add("properties", properties);
if (!requiredArray.isEmpty()) {
wrapper.add("required", requiredArray);
}
allOf.add(wrapper);
definition.add("allOf", allOf);
} else {
definition.add("type", "object");
if (Resource.class.equals(modelClass)) {
definition.add("discriminator", "resourceType");
}
definition.add("properties", properties);
if (!requiredArray.isEmpty()) {
definition.add("required", requiredArray);
}
}
if (Resource.class.isAssignableFrom(modelClass)) {
FHIROpenApiGenerator.addExamples(modelClass, definition);
}
definitions.add(getSimpleNameWithEnclosingNames(modelClass), definition);
}
}
private static String getSimpleNameWithEnclosingNames(Class> modelClass) {
StringBuilder fullName = new StringBuilder(modelClass.getSimpleName());
while (modelClass.isMemberClass()) {
modelClass = modelClass.getEnclosingClass();
fullName.insert(0, modelClass.getSimpleName() + "_");
}
return fullName.toString();
}
private static StructureDefinition getStructureDefinition(Class> modelClass) {
StructureDefinition structureDefinition = null;
// Use Code definition for all its sub-classes for now
if (Code.class.isAssignableFrom(modelClass)) {
try {
structureDefinition = structureDefinitionMap.get(Class.forName(FHIROpenApiGenerator.TYPEPACKAGENAME + "." + "Code"));
} catch (ClassNotFoundException e) {
structureDefinition = null;
}
} else {
structureDefinition = structureDefinitionMap.get(modelClass);
structureDefinition = (structureDefinition != null)
? structureDefinition : getEnclosingStructureDefinition(modelClass);
}
return structureDefinition;
}
private static StructureDefinition getEnclosingStructureDefinition(Class> modelClass) {
StructureDefinition structureDefinition = null;
int nameLength = 0;
for (Class> key : structureDefinitionMap.keySet()) {
String modelClassName;
if (modelClass.getName().contains(FHIROpenApiGenerator.TYPEPACKAGENAME)) {
modelClassName = modelClass.getName().substring(FHIROpenApiGenerator.TYPEPACKAGENAME.length()+1);
} else {
modelClassName = modelClass.getName().substring(FHIROpenApiGenerator.RESOURCEPACKAGENAME.length()+1);
}
if (modelClassName.startsWith(key.getSimpleName())
&& key.getSimpleName().length() > nameLength) {
structureDefinition = structureDefinitionMap.get(key);
nameLength = key.getSimpleName().length();
}
}
return structureDefinition;
}
private static void generateProperties(StructureDefinition structureDefinition, Class> modelClass, Field field,
JsonObjectBuilder properties) throws Exception {
boolean many = false;
Type fieldType = field.getType();
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
fieldType = parameterizedType.getActualTypeArguments()[0];
many = true;
}
String elementName = ModelSupport.getElementName(field);
if (ModelSupport.isChoiceElement(modelClass, elementName)) {
Set> choiceElementTypes = ModelSupport.getChoiceElementTypes(modelClass, elementName);
ElementDefinition elementDefinition = getElementDefinition(structureDefinition, modelClass, elementName + "[x]");
String description = elementDefinition.getDefinition().getValue();
Integer min = elementDefinition.getMin() != null ? elementDefinition.getMin().getValue() : null;
for (Class> choiceType : choiceElementTypes) {
if (FHIROpenApiGenerator.isApplicableForClass(choiceType, modelClass)) {
String choiceElementName = ModelSupport.getChoiceElementName(elementName, choiceType);
generateProperty(structureDefinition, modelClass, field, properties, choiceElementName, choiceType, many, description, min);
}
}
} else {
ElementDefinition elementDefinition = getElementDefinition(structureDefinition, modelClass, elementName);
String description = elementDefinition.getDefinition().getValue();
Integer min = elementDefinition.getMin() != null ? elementDefinition.getMin().getValue() : null;
generateProperty(structureDefinition, modelClass, field, properties, elementName, (Class>)fieldType, many, description, min);
}
}
private static void generateProperty(StructureDefinition structureDefinition, Class> modelClass, Field field,
JsonObjectBuilder properties, String elementName, Class> fieldClass, boolean many, String description, Integer min)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
JsonObjectBuilder property = factory.createObjectBuilder();
// Convert all the primitive types to json types.
if (isEnumerationWrapperClass(fieldClass)) {
property.add("type", "string");
JsonArrayBuilder constants = factory.createArrayBuilder();
Class> enumClass = Class.forName(fieldClass.getName() + "$ValueSet");
for (Object constant : enumClass.getEnumConstants()) {
Method method = constant.getClass().getMethod("value");
String value = (String) method.invoke(constant);
constants.add(value);
}
property.add("enum", constants);
} else if (com.ibm.fhir.model.type.String.class.isAssignableFrom(fieldClass)) {
property.add("type", "string");
if (com.ibm.fhir.model.type.Code.class.isAssignableFrom(fieldClass)) {
property.add("pattern", "[^\\s]+(\\s[^\\s]+)*");
} else if (com.ibm.fhir.model.type.Id.class.equals(fieldClass)) {
property.add("pattern", "[A-Za-z0-9\\-\\.]{1,64}");
} else {
property.add("pattern", "[ \\r\\n\\t\\S]+");
}
} else if (com.ibm.fhir.model.type.Uri.class.isAssignableFrom(fieldClass)) {
property.add("type", "string");
if (Oid.class.equals(fieldClass)) {
property.add("pattern", "urn:oid:[0-2](\\.(0|[1-9][0-9]*))+");
} else if (Uuid.class.equals(fieldClass)) {
property.add("pattern", "urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
} else {
property.add("pattern", "\\S*");
}
} else if (com.ibm.fhir.model.type.Boolean.class.equals(fieldClass)) {
property.add("type", "boolean");
} else if (com.ibm.fhir.model.type.Instant.class.equals(fieldClass)) {
property.add("type", "string");
property.add("pattern", "([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)-(0[1-9]"
+ "|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]"
+ "|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))");
} else if (com.ibm.fhir.model.type.Time.class.equals(fieldClass)) {
property.add("type", "string");
property.add("pattern","([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?");
} else if (com.ibm.fhir.model.type.Date.class.equals(fieldClass)) {
property.add("type", "string");
property.add("pattern","([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)"
+ "|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?");
} else if (com.ibm.fhir.model.type.DateTime.class.equals(fieldClass)) {
property.add("type", "string");
property.add("pattern","([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]"
+ "|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]"
+ "|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00)))?)?)?");
} else if (com.ibm.fhir.model.type.Decimal.class.equals(fieldClass)) {
property.add("type", "number");
} else if (com.ibm.fhir.model.type.Integer.class.isAssignableFrom(fieldClass)) {
property.add("type", "integer");
property.add("format", "int32");
} else if (com.ibm.fhir.model.type.Base64Binary.class.equals(fieldClass)) {
property.add("type", "string");
property.add("pattern","(\\s*([0-9a-zA-Z\\+/=]){4}\\s*)+");
} else if (String.class.equals(fieldClass)) {
property.add("type", "string");
if ("id".equals(elementName)) {
property.add("pattern", "[A-Za-z0-9\\-\\.]{1,64}");
} else if ("url".equals(elementName)) {
property.add("pattern", "\\S*");
}
} else if (com.ibm.fhir.model.type.Xhtml.class.equals(fieldClass)) {
property.add("type", "string");
} else {
// For non-primitives, point to the corresponding definition
property.add("$ref", "#/definitions/" + getSimpleNameWithEnclosingNames(fieldClass));
}
if (description != null) {
property.add("description", description);
}
// Include select examples to help tools avoid bumping into infinite recursion (if they try generate examples)
JsonStructure example = null;
if (Element.class.equals(modelClass) && Extension.class.equals(fieldClass)) {
// "example": [{"url":"http://example.com","valueString":"textValue"}]
JsonObject obj = factory.createObjectBuilder()
.add("url", "http://example.com")
.add("valueString", "text value")
.build();
example = factory.createArrayBuilder().add(obj).build();
} else if (Identifier.class.equals(modelClass) && Reference.class.equals(fieldClass)) {
// "example": {"reference":"Organization/123","type":"Organization","display":"The Assigning Organization"}
example = factory.createObjectBuilder()
.add("reference", "Organization/123")
.add("type", "Organization")
.add("display", "The Assigning Organization")
// skip assigner to break the recursion
.build();
}
if (many) {
JsonObjectBuilder wrapper = factory.createObjectBuilder();
wrapper.add("type", "array");
wrapper.add("items", property);
if (min != null && min > 0) {
wrapper.add("minItems", min.intValue());
}
if (example != null) {
wrapper.add("example", example);
}
properties.add(elementName, wrapper);
} else {
if (example != null) {
property.add("example", example);
}
properties.add(elementName, property);
}
}
/**
* Returns the ElementDefinition for the given elementName in the Type represented by modelClass
*/
private static ElementDefinition getElementDefinition(StructureDefinition structureDefinition, Class> modelClass, String elementName) {
String structureDefinitionName = structureDefinition.getName().getValue();
String path = structureDefinitionName;
String pathEnding = elementName;
if (BackboneElement.class.isAssignableFrom(modelClass) && !BackboneElement.class.equals(modelClass) && modelClass.isMemberClass()) {
String modelClassName = modelClass.getSimpleName();
modelClassName = modelClassName.substring(0, 1).toLowerCase() + modelClassName.substring(1);
if (Character.isDigit(modelClassName.charAt(modelClassName.length() - 1))) {
modelClassName = modelClassName.substring(0, modelClassName.length() - 1);
}
path += "." + modelClassName;
pathEnding = modelClassName + "." + elementName;
}
path += "." + elementName;
for (ElementDefinition elementDefinition : structureDefinition.getDifferential().getElement()) {
String elementDefinitionPath = elementDefinition.getPath().getValue();
if (elementDefinitionPath.equals(path) || (elementDefinitionPath.startsWith(structureDefinitionName)
&& elementDefinitionPath.endsWith(pathEnding))) {
return elementDefinition;
}
}
throw new RuntimeException("Unable to retrieve element definition for " + elementName + " in " + modelClass.getName());
}
private static List getCompartmentClassNames(String compartment) {
try {
return CompartmentUtil.getCompartmentResourceTypes(compartment);
} catch (FHIRSearchException e) {
return Collections.emptyList();
}
}
private static boolean isEnumerationWrapperClass(Class> type) {
try {
Class.forName(type.getName() + "$ValueSet");
return true;
} catch (Exception e) {
// do nothing
}
return false;
}
private static class Filter {
private Map> filterMap = null;
public Filter(Map> filterMap) {
this.filterMap = filterMap;
}
/**
* @return true if the operation is enables for the specified resourceType, otherwise false
*/
public boolean acceptResourceType(Class> resourceType) {
return filterMap.containsKey(resourceType.getSimpleName());
}
/**
* @return true if one or more of the resources in the filterMap includes the passed operation, otherwise false
*/
public boolean acceptOperation(Class> resourceType, String operation) {
return filterMap.get(resourceType.getSimpleName()).contains(operation);
}
public boolean acceptOperation(String operation) {
for (String resourceType : filterMap.keySet()) {
List operationList = filterMap.get(resourceType);
if (operationList.contains(operation)) {
return true;
}
}
return false;
}
}
private static Filter createFilter(String filterString) {
return new Filter(parseFilterString(filterString));
}
private static Map> parseFilterString(String filterString) {
Map> filterMap = new HashMap>();
for (String component : filterString.split(";")) {
String resourceType = component.substring(0, component.indexOf("("));
String operations = component.substring(component.indexOf("(") + 1, component.indexOf(")"));
List operationList = new ArrayList();
for (String operation : operations.split(",")) {
operationList.add(operation);
}
filterMap.put(resourceType, operationList);
}
return filterMap;
}
private static Filter createAcceptAllFilter() throws Exception {
return new Filter(buildAcceptAllFilterMap());
}
// build filter map for all domain resources and operations
private static Map> buildAcceptAllFilterMap() throws Exception {
Map> filterMap = new HashMap>();
for (String className : FHIROpenApiGenerator.getClassNames()) {
Class> modelClass = Class.forName(RESOURCEPACKAGENAME + "." + className);
if (DomainResource.class.isAssignableFrom(modelClass)) {
String resourceType = className;
// TODO: add patch
List operationList = Arrays.asList("create", "read", "vread", "update", "delete", "search",
"history", "batch", "transaction");
filterMap.put(resourceType, operationList);
}
}
return filterMap;
}
private static String getArticle(Class> modelClass) {
List prefixes = Arrays.asList("A", "E", "I", "O", "Un");
for (String prefix : prefixes) {
if (modelClass.getSimpleName().startsWith(prefix)) {
return " an ";
}
}
return " a ";
}
}