Support multiple api's within a q instance. For science!

This commit is contained in:
2023-04-04 13:40:32 -05:00
parent e35761e0a6
commit e779c392bb
21 changed files with 1171 additions and 341 deletions

View File

@ -30,15 +30,16 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.api.model.openapi.Components;
import com.kingsrook.qqq.api.model.openapi.Contact;
import com.kingsrook.qqq.api.model.openapi.Content;
@ -187,21 +188,43 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
*******************************************************************************/
public GenerateOpenApiSpecOutput execute(GenerateOpenApiSpecInput input) throws QException
{
String version = input.getVersion();
String basePath = "/api/" + version + "/";
QInstance qInstance = QContext.getQInstance();
String version = input.getVersion();
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(qInstance);
if(apiInstanceMetaDataContainer == null)
{
throw new QException("No ApiInstanceMetaDataContainer exists in this instance");
}
if(!StringUtils.hasContent(input.getApiName()))
{
throw new QException("Missing required input: apiName");
}
ApiInstanceMetaData apiInstanceMetaData = apiInstanceMetaDataContainer.getApiInstanceMetaData(input.getApiName());
if(apiInstanceMetaData == null)
{
throw new QException("Could not find apiInstanceMetaData named [" + input.getApiName() + "] in this instance");
}
if(!StringUtils.hasContent(input.getVersion()))
{
throw new QException("Missing required input: version");
}
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
if(!apiInstanceMetaData.getSupportedVersions().contains(new APIVersion(version)))
{
throw (new QException("[" + version + "] is not a supported API Version."));
}
String basePath = apiInstanceMetaData.getPath() + version + "/";
String apiName = apiInstanceMetaData.getName();
OpenAPI openAPI = new OpenAPI()
.withVersion("3.0.3")
.withInfo(new Info()
.withTitle(apiInstanceMetaData.getName())
.withTitle(apiInstanceMetaData.getLabel())
.withDescription(apiInstanceMetaData.getDescription())
.withContact(new Contact()
.withEmail(apiInstanceMetaData.getContactEmail()))
@ -272,7 +295,14 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
continue;
}
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
if(apiTableMetaDataContainer == null)
{
LOG.debug("Omitting table [" + tableName + "] because it does not have an apiTableMetaDataContainer");
continue;
}
ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApiTableMetaData(apiName);
if(apiTableMetaData == null)
{
LOG.debug("Omitting table [" + tableName + "] because it does not have any apiTableMetaData");
@ -312,8 +342,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
String primaryKeyName = table.getPrimaryKeyField();
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
String primaryKeyLabel = primaryKeyField.getLabel();
String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(primaryKeyField);
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(apiName, primaryKeyField);
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version).withApiName(apiName)).getFields();
///////////////////////////////
// permissions for the table //
@ -365,13 +395,13 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
for(QFieldMetaData field : tableApiFields)
{
Schema fieldSchema = getFieldSchema(table, field);
tableFields.put(ApiFieldMetaData.getEffectiveApiFieldName(field), fieldSchema);
tableFields.put(ApiFieldMetaData.getEffectiveApiFieldName(apiInstanceMetaData.getName(), field), fieldSchema);
}
//////////////////////////////////
// recursively add associations //
//////////////////////////////////
addAssociations(table, tableSchema);
addAssociations(apiName, table, tableSchema);
//////////////////////////////////////////////////////////////////////////////
// table as a search result (the base search result, plus the table itself) //
@ -770,13 +800,13 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/*******************************************************************************
**
*******************************************************************************/
private static void addAssociations(QTableMetaData table, Schema tableSchema)
private static void addAssociations(String apiName, QTableMetaData table, Schema tableSchema)
{
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
String associatedTableName = association.getAssociatedTableName();
QTableMetaData associatedTable = QContext.getQInstance().getTable(associatedTableName);
ApiTableMetaData associatedApiTableMetaData = Objects.requireNonNullElse(ApiTableMetaData.of(associatedTable), new ApiTableMetaData());
ApiTableMetaData associatedApiTableMetaData = ObjectUtils.tryElse(() -> ApiTableMetaDataContainer.of(associatedTable).getApiTableMetaData(apiName), new ApiTableMetaData());
String associatedTableApiName = StringUtils.hasContent(associatedApiTableMetaData.getApiTableName()) ? associatedApiTableMetaData.getApiTableName() : associatedTableName;
tableSchema.getProperties().put(association.getName(), new Schema()

View File

@ -30,13 +30,16 @@ import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsOutput;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.commons.lang.BooleanUtils;
@ -73,7 +76,7 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
fieldList.sort(Comparator.comparing(QFieldMetaData::getLabel));
for(QFieldMetaData field : fieldList)
{
if(!isExcluded(field) && getApiVersionRange(field).includes(version))
if(!isExcluded(input.getApiName(), field) && getApiVersionRange(input.getApiName(), field).includes(version))
{
fields.add(field);
}
@ -82,9 +85,9 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
//////////////////////////////////////////////////////////////////////////////////////////////////
// look for removed fields (e.g., not currently in the table anymore), that are in this version //
//////////////////////////////////////////////////////////////////////////////////////////////////
for(QFieldMetaData field : CollectionUtils.nonNullList(getRemovedApiFields(table)))
for(QFieldMetaData field : CollectionUtils.nonNullList(getRemovedApiFields(input.getApiName(), table)))
{
if(!isExcluded(field) && getApiVersionRangeForRemovedField(field).includes(version))
if(!isExcluded(input.getApiName(), field) && getApiVersionRangeForRemovedField(input.getApiName(), field).includes(version))
{
fields.add(field);
}
@ -98,9 +101,9 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
/*******************************************************************************
**
*******************************************************************************/
private boolean isExcluded(QFieldMetaData field)
private boolean isExcluded(String apiName, QFieldMetaData field)
{
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
if(apiFieldMetaData != null && BooleanUtils.isTrue(apiFieldMetaData.getIsExcluded()))
{
return (true);
@ -114,14 +117,14 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
/*******************************************************************************
**
*******************************************************************************/
private APIVersionRange getApiVersionRangeForRemovedField(QFieldMetaData field)
private APIVersionRange getApiVersionRangeForRemovedField(String apiName, QFieldMetaData field)
{
ApiFieldMetaData middlewareMetaData = ApiFieldMetaData.of(field);
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
if(apiFieldMetaData != null && apiFieldMetaData.getInitialVersion() != null)
{
if(StringUtils.hasContent(middlewareMetaData.getFinalVersion()))
if(StringUtils.hasContent(apiFieldMetaData.getFinalVersion()))
{
return (APIVersionRange.betweenAndIncluding(middlewareMetaData.getInitialVersion(), middlewareMetaData.getFinalVersion()));
return (APIVersionRange.betweenAndIncluding(apiFieldMetaData.getInitialVersion(), apiFieldMetaData.getFinalVersion()));
}
else
{
@ -139,12 +142,12 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
/*******************************************************************************
**
*******************************************************************************/
private APIVersionRange getApiVersionRange(QFieldMetaData field)
private APIVersionRange getApiVersionRange(String apiName, QFieldMetaData field)
{
ApiFieldMetaData middlewareMetaData = ApiFieldMetaData.of(field);
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
if(apiFieldMetaData != null && apiFieldMetaData.getInitialVersion() != null)
{
return (APIVersionRange.afterAndIncluding(middlewareMetaData.getInitialVersion()));
return (APIVersionRange.afterAndIncluding(apiFieldMetaData.getInitialVersion()));
}
return (APIVersionRange.none());
@ -155,9 +158,19 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
/*******************************************************************************
**
*******************************************************************************/
private List<QFieldMetaData> getRemovedApiFields(QTableMetaData table)
private static ApiFieldMetaData getApiFieldMetaData(String apiName, QFieldMetaData field)
{
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
return ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
}
/*******************************************************************************
**
*******************************************************************************/
private List<QFieldMetaData> getRemovedApiFields(String apiName, QTableMetaData table)
{
ApiTableMetaData apiTableMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiTableMetaDataContainer.of(table).getApiTableMetaData(apiName), new ApiTableMetaData());
if(apiTableMetaData != null)
{
return (apiTableMetaData.getRemovedApiFields());

View File

@ -32,6 +32,7 @@ import java.util.stream.Collectors;
import com.kingsrook.qqq.api.javalin.QBadRequestException;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -40,7 +41,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.Pair;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
@ -53,17 +54,17 @@ public class QRecordApiAdapter
{
private static final QLogger LOG = QLogger.getLogger(QRecordApiAdapter.class);
private static Map<Pair<String, String>, List<QFieldMetaData>> fieldListCache = new HashMap<>();
private static Map<Pair<String, String>, Map<String, QFieldMetaData>> fieldMapCache = new HashMap<>();
private static Map<ApiNameVersionAndTableName, List<QFieldMetaData>> fieldListCache = new HashMap<>();
private static Map<ApiNameVersionAndTableName, Map<String, QFieldMetaData>> fieldMapCache = new HashMap<>();
/*******************************************************************************
** Convert a QRecord to a map for the API
*******************************************************************************/
public static Map<String, Serializable> qRecordToApiMap(QRecord record, String tableName, String apiVersion) throws QException
public static Map<String, Serializable> qRecordToApiMap(QRecord record, String tableName, String apiName, String apiVersion) throws QException
{
List<QFieldMetaData> tableApiFields = getTableApiFieldList(tableName, apiVersion);
List<QFieldMetaData> tableApiFields = getTableApiFieldList(new ApiNameVersionAndTableName(apiName, apiVersion, tableName));
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
/////////////////////////////////////////
@ -71,9 +72,8 @@ public class QRecordApiAdapter
/////////////////////////////////////////
for(QFieldMetaData field : tableApiFields)
{
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(field);
ApiFieldMetaData apiFieldMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(apiName, field);
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
{
outputRecord.put(apiFieldName, record.getValue(apiFieldMetaData.getReplacedByFieldName()));
@ -96,7 +96,7 @@ public class QRecordApiAdapter
for(QRecord associatedRecord : CollectionUtils.nonNullList(CollectionUtils.nonNullMap(record.getAssociatedRecords()).get(association.getName())))
{
associationList.add(qRecordToApiMap(associatedRecord, association.getAssociatedTableName(), apiVersion));
associationList.add(qRecordToApiMap(associatedRecord, association.getAssociatedTableName(), apiName, apiVersion));
}
}
@ -108,12 +108,12 @@ public class QRecordApiAdapter
/*******************************************************************************
**
*******************************************************************************/
public static QRecord apiJsonObjectToQRecord(JSONObject jsonObject, String tableName, String apiVersion, boolean includePrimaryKey) throws QException
public static QRecord apiJsonObjectToQRecord(JSONObject jsonObject, String tableName, String apiName, String apiVersion, boolean includePrimaryKey) throws QException
{
////////////////////////////////////////////////////////////////////////////////
// make map of apiFieldNames (e.g., names as api uses them) to QFieldMetaData //
////////////////////////////////////////////////////////////////////////////////
Map<String, QFieldMetaData> apiFieldsMap = getTableApiFieldMap(tableName, apiVersion);
Map<String, QFieldMetaData> apiFieldsMap = getTableApiFieldMap(new ApiNameVersionAndTableName(apiName, apiVersion, tableName));
List<String> unrecognizedFieldNames = new ArrayList<>();
QRecord qRecord = new QRecord();
@ -153,7 +153,7 @@ public class QRecordApiAdapter
}
}
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
ApiFieldMetaData apiFieldMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
{
qRecord.setValue(apiFieldMetaData.getReplacedByFieldName(), value);
@ -178,7 +178,7 @@ public class QRecordApiAdapter
{
if(subObject instanceof JSONObject subJsonObject)
{
QRecord subRecord = apiJsonObjectToQRecord(subJsonObject, association.getAssociatedTableName(), apiVersion, includePrimaryKey);
QRecord subRecord = apiJsonObjectToQRecord(subJsonObject, association.getAssociatedTableName(), apiName, apiVersion, includePrimaryKey);
qRecord.withAssociatedRecord(association.getName(), subRecord);
}
else
@ -214,16 +214,15 @@ public class QRecordApiAdapter
/*******************************************************************************
**
*******************************************************************************/
private static Map<String, QFieldMetaData> getTableApiFieldMap(String tableName, String apiVersion) throws QException
private static Map<String, QFieldMetaData> getTableApiFieldMap(ApiNameVersionAndTableName apiNameVersionAndTableName) throws QException
{
Pair<String, String> key = new Pair<>(tableName, apiVersion);
if(!fieldMapCache.containsKey(key))
if(!fieldMapCache.containsKey(apiNameVersionAndTableName))
{
Map<String, QFieldMetaData> map = getTableApiFieldList(tableName, apiVersion).stream().collect(Collectors.toMap(f -> (ApiFieldMetaData.getEffectiveApiFieldName(f)), f -> f));
fieldMapCache.put(key, map);
Map<String, QFieldMetaData> map = getTableApiFieldList(apiNameVersionAndTableName).stream().collect(Collectors.toMap(f -> (ApiFieldMetaData.getEffectiveApiFieldName(apiNameVersionAndTableName.apiName(), f)), f -> f));
fieldMapCache.put(apiNameVersionAndTableName, map);
}
return (fieldMapCache.get(key));
return (fieldMapCache.get(apiNameVersionAndTableName));
}
@ -231,14 +230,27 @@ public class QRecordApiAdapter
/*******************************************************************************
**
*******************************************************************************/
private static List<QFieldMetaData> getTableApiFieldList(String tableName, String apiVersion) throws QException
private static List<QFieldMetaData> getTableApiFieldList(ApiNameVersionAndTableName apiNameVersionAndTableName) throws QException
{
Pair<String, String> key = new Pair<>(tableName, apiVersion);
if(!fieldListCache.containsKey(key))
if(!fieldListCache.containsKey(apiNameVersionAndTableName))
{
List<QFieldMetaData> value = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields();
fieldListCache.put(key, value);
List<QFieldMetaData> value = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput()
.withTableName(apiNameVersionAndTableName.tableName())
.withVersion(apiNameVersionAndTableName.apiVersion())
.withApiName(apiNameVersionAndTableName.apiName())).getFields();
fieldListCache.put(apiNameVersionAndTableName, value);
}
return (fieldListCache.get(key));
return (fieldListCache.get(apiNameVersionAndTableName));
}
/*******************************************************************************
**
*******************************************************************************/
private record ApiNameVersionAndTableName(String apiName, String apiVersion, String tableName)
{
}
}

View File

@ -44,7 +44,9 @@ import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
import com.kingsrook.qqq.api.model.metadata.APILogMetaDataProvider;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
@ -92,6 +94,7 @@ import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModu
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.Pair;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
@ -121,7 +124,10 @@ public class QJavalinApiHandler
private static QInstance qInstance;
private static Map<String, Map<String, QTableMetaData>> tableApiNameMap = new HashMap<>();
/////////////////////////////////////
// key: Pair<apiName, apiVersion> //
/////////////////////////////////////
private static Map<Pair<String, String>, Map<String, QTableMetaData>> tableApiNameMap = new HashMap<>();
private static Map<String, Integer> apiLogUserIdCache = new HashMap<>();
@ -156,56 +162,59 @@ public class QJavalinApiHandler
ApiBuilder.get("/api/docs/js/rapidoc.min.js", (context) -> QJavalinApiHandler.serveResource(context, "rapidoc/rapidoc-9.3.4.min.js", MapBuilder.of("Content-Type", ContentType.JAVASCRIPT)));
ApiBuilder.get("/api/docs/css/qqq-api-styles.css", (context) -> QJavalinApiHandler.serveResource(context, "rapidoc/rapidoc-overrides.css", MapBuilder.of("Content-Type", ContentType.CSS)));
//////////////////////////////////////////////
// default page is the current version spec //
//////////////////////////////////////////////
ApiBuilder.get("/api/", QJavalinApiHandler::doSpecHtml);
ApiBuilder.path("/api/{version}", () -> // todo - configurable, that /api/ bit?
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(qInstance);
for(Map.Entry<String, ApiInstanceMetaData> entry : apiInstanceMetaDataContainer.getApis().entrySet())
{
////////////////////////////////////////////
// default page for a version is its spec //
////////////////////////////////////////////
ApiBuilder.get("/", QJavalinApiHandler::doSpecHtml);
ApiInstanceMetaData apiInstanceMetaData = entry.getValue();
String rootPath = apiInstanceMetaData.getPath();
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml);
ApiBuilder.get("/openapi.json", QJavalinApiHandler::doSpecJson);
ApiBuilder.get("/openapi.html", QJavalinApiHandler::doSpecHtml);
//////////////////////////////////////////////
// default page is the current version spec //
//////////////////////////////////////////////
ApiBuilder.get(rootPath, context -> doSpecHtml(context, apiInstanceMetaData));
ApiBuilder.path("/{tableName}", () ->
ApiBuilder.path(rootPath + "{version}", () ->
{
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml);
ApiBuilder.get("/openapi.json", QJavalinApiHandler::doSpecJson);
////////////////////////////////////////////
// default page for a version is its spec //
////////////////////////////////////////////
ApiBuilder.get("/", context -> doSpecHtml(context, apiInstanceMetaData));
ApiBuilder.post("/", QJavalinApiHandler::doInsert);
ApiBuilder.get("/openapi.yaml", context -> doSpecYaml(context, apiInstanceMetaData));
ApiBuilder.get("/openapi.json", context -> doSpecJson(context, apiInstanceMetaData));
ApiBuilder.get("/openapi.html", context -> doSpecHtml(context, apiInstanceMetaData));
ApiBuilder.get("/query", QJavalinApiHandler::doQuery);
// ApiBuilder.post("/query", QJavalinApiHandler::doQuery);
ApiBuilder.path("/{tableName}", () ->
{
ApiBuilder.get("/openapi.yaml", context -> doSpecYaml(context, apiInstanceMetaData));
ApiBuilder.get("/openapi.json", context -> doSpecJson(context, apiInstanceMetaData));
ApiBuilder.post("/bulk", QJavalinApiHandler::bulkInsert);
ApiBuilder.patch("/bulk", QJavalinApiHandler::bulkUpdate);
ApiBuilder.delete("/bulk", QJavalinApiHandler::bulkDelete);
ApiBuilder.post("/", context -> doInsert(context, apiInstanceMetaData));
//////////////////////////////////////////////////////////////////
// remember to keep the wildcard paths after the specific paths //
//////////////////////////////////////////////////////////////////
ApiBuilder.get("/{primaryKey}", QJavalinApiHandler::doGet);
ApiBuilder.patch("/{primaryKey}", QJavalinApiHandler::doUpdate);
ApiBuilder.delete("/{primaryKey}", QJavalinApiHandler::doDelete);
ApiBuilder.get("/query", context -> doQuery(context, apiInstanceMetaData));
// ApiBuilder.post("/query", context -> doQuery(context, apiInstanceMetaData));
ApiBuilder.post("/bulk", context -> bulkInsert(context, apiInstanceMetaData));
ApiBuilder.patch("/bulk", context -> bulkUpdate(context, apiInstanceMetaData));
ApiBuilder.delete("/bulk", context -> bulkDelete(context, apiInstanceMetaData));
//////////////////////////////////////////////////////////////////
// remember to keep the wildcard paths after the specific paths //
//////////////////////////////////////////////////////////////////
ApiBuilder.get("/{primaryKey}", context -> doGet(context, apiInstanceMetaData));
ApiBuilder.patch("/{primaryKey}", context -> doUpdate(context, apiInstanceMetaData));
ApiBuilder.delete("/{primaryKey}", context -> doDelete(context, apiInstanceMetaData));
});
});
});
ApiBuilder.get("/api/versions.json", QJavalinApiHandler::doVersions);
ApiBuilder.before("/*", QJavalinApiHandler::setupCORS);
//////////////////////////////////////////////////////////////////////////////////////////////
// default all other /api/ requests (for the methods we support) to a standard 404 response //
//////////////////////////////////////////////////////////////////////////////////////////////
ApiBuilder.get("/api/*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.delete("/api/*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.patch("/api/*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.post("/api/*", QJavalinApiHandler::doPathNotFound);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// default all other requests under the root path (for the methods we support) to a standard 404 response //
////////////////////////////////////////////////////////////////////////////////////////////////////////////
ApiBuilder.get(rootPath + "*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.delete(rootPath + "*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.patch(rootPath + "*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.post(rootPath + "*", QJavalinApiHandler::doPathNotFound);
}
///////////////////////////////////////////////////////////////////////////////////
// if the main implementation class has a hot-swapper installed, use it here too //
@ -241,10 +250,8 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doVersions(Context context)
private static void doVersions(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
Map<String, Object> rs = new HashMap<>();
rs.put("supportedVersions", apiInstanceMetaData.getSupportedVersions().stream().map(String::valueOf).collect(Collectors.toList()));
rs.put("currentVersion", apiInstanceMetaData.getCurrentVersion().toString());
@ -401,7 +408,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doSpecYaml(Context context)
private static void doSpecYaml(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
try
{
@ -409,6 +416,8 @@ public class QJavalinApiHandler
String version = context.pathParam("version");
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
input.setApiName(apiInstanceMetaData.getName());
try
{
if(StringUtils.hasContent(context.pathParam("tableName")))
@ -438,13 +447,14 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doSpecJson(Context context)
private static void doSpecJson(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
try
{
QContext.init(qInstance, null);
String version = context.pathParam("version");
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
input.setApiName(apiInstanceMetaData.getName());
try
{
@ -475,7 +485,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doSpecHtml(Context context)
private static void doSpecHtml(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version;
@ -485,11 +495,10 @@ public class QJavalinApiHandler
}
catch(Exception e)
{
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
version = apiInstanceMetaData.getCurrentVersion().toString();
}
doSpecHtml(context, version);
doSpecHtml(context, version, apiInstanceMetaData);
}
@ -497,12 +506,11 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doSpecHtml(Context context, String version)
private static void doSpecHtml(Context context, String version, ApiInstanceMetaData apiInstanceMetaData)
{
try
{
QBrandingMetaData branding = qInstance.getBranding();
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
QBrandingMetaData branding = qInstance.getBranding();
if(!apiInstanceMetaData.getSupportedVersions().contains(new APIVersion(version)))
{
@ -536,7 +544,7 @@ public class QJavalinApiHandler
StringBuilder otherVersionOptions = new StringBuilder();
for(APIVersion supportedVersion : apiInstanceMetaData.getSupportedVersions())
{
otherVersionOptions.append("<option value=\"/api/").append(supportedVersion).append("/openapi.html\">").append(supportedVersion).append("</option>");
otherVersionOptions.append("<option value=\"").append(apiInstanceMetaData.getPath()).append(supportedVersion).append("/openapi.html\">").append(supportedVersion).append("</option>");
}
html = html.replace("{otherVersionOptions}", otherVersionOptions.toString());
@ -565,7 +573,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doGet(Context context)
private static void doGet(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
@ -574,7 +582,7 @@ public class QJavalinApiHandler
try
{
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName();
GetInput getInput = new GetInput();
@ -605,7 +613,7 @@ public class QJavalinApiHandler
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
}
Map<String, Serializable> outputRecord = QRecordApiAdapter.qRecordToApiMap(record, tableName, version);
Map<String, Serializable> outputRecord = QRecordApiAdapter.qRecordToApiMap(record, tableName, apiInstanceMetaData.getName(), version);
QJavalinAccessLogger.logEndSuccess();
String resultString = JsonUtils.toJson(outputRecord);
@ -817,7 +825,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doQuery(Context context)
private static void doQuery(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
@ -828,7 +836,7 @@ public class QJavalinApiHandler
{
List<String> badRequestMessages = new ArrayList<>();
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName();
QueryInput queryInput = new QueryInput();
@ -1023,7 +1031,7 @@ public class QJavalinApiHandler
ArrayList<Map<String, Serializable>> records = new ArrayList<>();
for(QRecord record : queryOutput.getRecords())
{
records.add(QRecordApiAdapter.qRecordToApiMap(record, tableName, version));
records.add(QRecordApiAdapter.qRecordToApiMap(record, tableName, apiInstanceMetaData.getName(), version));
}
/////////////////////////////
@ -1057,11 +1065,11 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static QTableMetaData validateTableAndVersion(Context context, String version, String tableApiName) throws QNotFoundException
private static QTableMetaData validateTableAndVersion(Context context, ApiInstanceMetaData apiInstanceMetaData, String version, String tableApiName) throws QNotFoundException
{
QNotFoundException qNotFoundException = new QNotFoundException("Could not find any resources at path " + context.path());
QTableMetaData table = getTableByApiName(version, tableApiName);
QTableMetaData table = getTableByApiName(apiInstanceMetaData.getName(), version, tableApiName);
if(table == null)
{
@ -1073,7 +1081,13 @@ public class QJavalinApiHandler
throw (qNotFoundException);
}
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
if(apiTableMetaDataContainer == null)
{
throw (qNotFoundException);
}
ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApiTableMetaData(apiInstanceMetaData.getName());
if(apiTableMetaData == null)
{
throw (qNotFoundException);
@ -1085,7 +1099,7 @@ public class QJavalinApiHandler
}
APIVersion requestApiVersion = new APIVersion(version);
List<APIVersion> supportedVersions = ApiInstanceMetaData.of(qInstance).getSupportedVersions();
List<APIVersion> supportedVersions = apiInstanceMetaData.getSupportedVersions();
if(CollectionUtils.nullSafeIsEmpty(supportedVersions) || !supportedVersions.contains(requestApiVersion))
{
throw (qNotFoundException);
@ -1104,27 +1118,40 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static QTableMetaData getTableByApiName(String version, String tableApiName)
private static QTableMetaData getTableByApiName(String apiName, String version, String tableApiName)
{
if(tableApiNameMap.get(version) == null)
/////////////////////////////////////////////////////////////////////////////////////////////
// tableApiNameMap is a map of (apiName,apiVersion) => Map<String, QTableMetaData>. //
// that is to say, a 2-level map. The first level is keyed by (apiName,apiVersion) pairs. //
// the second level is keyed by tableApiNames. //
/////////////////////////////////////////////////////////////////////////////////////////////
Pair<String, String> key = new Pair<>(apiName, version);
if(tableApiNameMap.get(key) == null)
{
Map<String, QTableMetaData> map = new HashMap<>();
for(QTableMetaData table : qInstance.getTables().values())
{
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
String name = table.getName();
if(apiTableMetaData != null && StringUtils.hasContent(apiTableMetaData.getApiTableName()))
ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
if(apiTableMetaDataContainer != null)
{
name = apiTableMetaData.getApiTableName();
ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApiTableMetaData(apiName);
if(apiTableMetaData != null)
{
String name = table.getName();
if(StringUtils.hasContent(apiTableMetaData.getApiTableName()))
{
name = apiTableMetaData.getApiTableName();
}
map.put(name, table);
}
}
map.put(name, table);
}
tableApiNameMap.put(version, map);
tableApiNameMap.put(key, map);
}
return (tableApiNameMap.get(version).get(tableApiName));
return (tableApiNameMap.get(key).get(tableApiName));
}
@ -1258,7 +1285,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doInsert(Context context)
private static void doInsert(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
@ -1266,7 +1293,7 @@ public class QJavalinApiHandler
try
{
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName();
InsertInput insertInput = new InsertInput();
@ -1288,7 +1315,7 @@ public class QJavalinApiHandler
JSONTokener jsonTokener = new JSONTokener(context.body().trim());
JSONObject jsonObject = new JSONObject(jsonTokener);
insertInput.setRecords(List.of(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version, false)));
insertInput.setRecords(List.of(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, false)));
if(jsonTokener.more())
{
@ -1328,7 +1355,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void bulkInsert(Context context)
private static void bulkInsert(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
@ -1336,7 +1363,7 @@ public class QJavalinApiHandler
try
{
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName();
InsertInput insertInput = new InsertInput();
@ -1367,7 +1394,7 @@ public class QJavalinApiHandler
for(int i = 0; i < jsonArray.length(); i++)
{
JSONObject jsonObject = jsonArray.getJSONObject(i);
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version, false));
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, false));
}
if(jsonTokener.more())
@ -1437,7 +1464,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void bulkUpdate(Context context)
private static void bulkUpdate(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
@ -1445,7 +1472,7 @@ public class QJavalinApiHandler
try
{
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName();
UpdateInput updateInput = new UpdateInput();
@ -1476,7 +1503,7 @@ public class QJavalinApiHandler
for(int i = 0; i < jsonArray.length(); i++)
{
JSONObject jsonObject = jsonArray.getJSONObject(i);
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version, true));
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, true));
}
if(jsonTokener.more())
@ -1579,7 +1606,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void bulkDelete(Context context)
private static void bulkDelete(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
@ -1587,7 +1614,7 @@ public class QJavalinApiHandler
try
{
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName();
DeleteInput deleteInput = new DeleteInput();
@ -1710,7 +1737,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doUpdate(Context context)
private static void doUpdate(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
@ -1719,7 +1746,7 @@ public class QJavalinApiHandler
try
{
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName();
UpdateInput updateInput = new UpdateInput();
@ -1741,7 +1768,7 @@ public class QJavalinApiHandler
JSONTokener jsonTokener = new JSONTokener(context.body().trim());
JSONObject jsonObject = new JSONObject(jsonTokener);
QRecord qRecord = QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version, false);
QRecord qRecord = QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, false);
qRecord.setValue(table.getPrimaryKeyField(), primaryKey);
updateInput.setRecords(List.of(qRecord));
@ -1794,7 +1821,7 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doDelete(Context context)
private static void doDelete(Context context, ApiInstanceMetaData apiInstanceMetaData)
{
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
@ -1803,7 +1830,7 @@ public class QJavalinApiHandler
try
{
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName();
DeleteInput deleteInput = new DeleteInput();

View File

@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
*******************************************************************************/
public class GenerateOpenApiSpecInput extends AbstractActionInput
{
private String apiName;
private String version;
private String tableName;
@ -95,4 +96,35 @@ public class GenerateOpenApiSpecInput extends AbstractActionInput
return (this);
}
/*******************************************************************************
** Getter for apiName
*******************************************************************************/
public String getApiName()
{
return (this.apiName);
}
/*******************************************************************************
** Setter for apiName
*******************************************************************************/
public void setApiName(String apiName)
{
this.apiName = apiName;
}
/*******************************************************************************
** Fluent setter for apiName
*******************************************************************************/
public GenerateOpenApiSpecInput withApiName(String apiName)
{
this.apiName = apiName;
return (this);
}
}

View File

@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
*******************************************************************************/
public class GetTableApiFieldsInput extends AbstractActionInput
{
private String apiName;
private String tableName;
private String version;
@ -95,4 +96,35 @@ public class GetTableApiFieldsInput extends AbstractActionInput
return (this);
}
/*******************************************************************************
** Getter for apiName
*******************************************************************************/
public String getApiName()
{
return (this.apiName);
}
/*******************************************************************************
** Setter for apiName
*******************************************************************************/
public void setApiName(String apiName)
{
this.apiName = apiName;
}
/*******************************************************************************
** Fluent setter for apiName
*******************************************************************************/
public GetTableApiFieldsInput withApiName(String apiName)
{
this.apiName = apiName;
return (this);
}
}

View File

@ -23,8 +23,9 @@ package com.kingsrook.qqq.api.model.metadata;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.function.Consumer;
import com.kingsrook.qqq.api.model.APILog;
import com.kingsrook.qqq.api.model.APIVersion;
@ -104,12 +105,20 @@ public class APILogMetaDataProvider
new QPossibleValue<>(500, "500 (Internal Server Error)")
)));
List<QPossibleValue<?>> apiVersionPossibleValues = new ArrayList<>();
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(instance);
LinkedHashSet<APIVersion> allVersions = new LinkedHashSet<>();
allVersions.addAll(apiInstanceMetaData.getPastVersions());
allVersions.addAll(apiInstanceMetaData.getSupportedVersions());
allVersions.addAll(apiInstanceMetaData.getFutureVersions());
List<QPossibleValue<?>> apiVersionPossibleValues = new ArrayList<>();
////////////////////////////////////////////////////////////////////////////////////////////////////
// todo... this, this whole thing, should probably have "which api" as another field too... ugh. //
////////////////////////////////////////////////////////////////////////////////////////////////////
TreeSet<APIVersion> allVersions = new TreeSet<>();
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(instance);
for(Map.Entry<String, ApiInstanceMetaData> entry : apiInstanceMetaDataContainer.getApis().entrySet())
{
ApiInstanceMetaData apiInstanceMetaData = entry.getValue();
allVersions.addAll(apiInstanceMetaData.getPastVersions());
allVersions.addAll(apiInstanceMetaData.getSupportedVersions());
allVersions.addAll(apiInstanceMetaData.getFutureVersions());
}
for(APIVersion version : allVersions)
{

View File

@ -24,14 +24,14 @@ package com.kingsrook.qqq.api.model.metadata;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.api.model.openapi.Server;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMiddlewareInstanceMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -41,9 +41,11 @@ import org.apache.commons.lang.BooleanUtils;
/*******************************************************************************
**
*******************************************************************************/
public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
public class ApiInstanceMetaData
{
private String name;
private String label;
private String path;
private String description;
private String contactEmail;
@ -59,58 +61,45 @@ public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiInstanceMetaData()
public void validate(String apiName, QInstance qInstance, QInstanceValidator validator)
{
setType(ApiMiddlewareType.NAME);
}
validator.assertCondition(Objects.equals(apiName, name), "Name mismatch for instance api (" + apiName + " != " + name + ")");
validator.assertCondition(StringUtils.hasContent(name), "Missing name for api " + apiName);
if(validator.assertCondition(StringUtils.hasContent(path), "Missing path for api " + apiName))
{
validator.assertCondition(path.startsWith("/"), "Path for api " + apiName + " does not start with '/' (but it needs to).");
validator.assertCondition(path.endsWith("/"), "Path for api " + apiName + " does not end with '/' (but it needs to).");
}
/*******************************************************************************
**
*******************************************************************************/
public static ApiInstanceMetaData of(QInstance qInstance)
{
return ((ApiInstanceMetaData) qInstance.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void validate(QInstance qInstance, QInstanceValidator validator)
{
validator.assertCondition(StringUtils.hasContent(name), "Missing name for instance api");
validator.assertCondition(StringUtils.hasContent(description), "Missing description for instance api");
validator.assertCondition(StringUtils.hasContent(contactEmail), "Missing contactEmail for instance api");
validator.assertCondition(StringUtils.hasContent(label), "Missing label for api " + apiName);
validator.assertCondition(StringUtils.hasContent(description), "Missing description for api " + apiName);
validator.assertCondition(StringUtils.hasContent(contactEmail), "Missing contactEmail for api " + apiName);
Set<APIVersion> allVersions = new HashSet<>();
if(validator.assertCondition(currentVersion != null, "Missing currentVersion for instance api"))
if(validator.assertCondition(currentVersion != null, "Missing currentVersion for api " + apiName))
{
allVersions.add(currentVersion);
}
if(validator.assertCondition(supportedVersions != null, "Missing supportedVersions for instance api"))
if(validator.assertCondition(supportedVersions != null, "Missing supportedVersions for api " + apiName))
{
validator.assertCondition(supportedVersions.contains(currentVersion), "supportedVersions [" + supportedVersions + "] does not contain currentVersion [" + currentVersion + "] for instance api");
validator.assertCondition(supportedVersions.contains(currentVersion), "supportedVersions [" + supportedVersions + "] does not contain currentVersion [" + currentVersion + "] for api " + apiName);
allVersions.addAll(supportedVersions);
}
for(APIVersion pastVersion : CollectionUtils.nonNullList(pastVersions))
{
validator.assertCondition(pastVersion.compareTo(currentVersion) < 0, "pastVersion [" + pastVersion + "] is not lexicographically before currentVersion [" + currentVersion + "] for instance api");
validator.assertCondition(pastVersion.compareTo(currentVersion) < 0, "pastVersion [" + pastVersion + "] is not lexicographically before currentVersion [" + currentVersion + "] for api " + apiName);
allVersions.add(pastVersion);
}
for(APIVersion futureVersion : CollectionUtils.nonNullList(futureVersions))
{
validator.assertCondition(futureVersion.compareTo(currentVersion) > 0, "futureVersion [" + futureVersion + "] is not lexicographically after currentVersion [" + currentVersion + "] for instance api");
validator.assertCondition(futureVersion.compareTo(currentVersion) > 0, "futureVersion [" + futureVersion + "] is not lexicographically after currentVersion [" + currentVersion + "] for api " + apiName);
allVersions.add(futureVersion);
}
@ -119,12 +108,16 @@ public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
/////////////////////////////////
for(QTableMetaData table : qInstance.getTables().values())
{
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
if(apiTableMetaData != null)
ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
if(apiTableMetaDataContainer != null)
{
if(BooleanUtils.isNotTrue(apiTableMetaData.getIsExcluded()))
ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApiTableMetaData(apiName);
if(apiTableMetaData != null)
{
validator.assertCondition(allVersions.contains(new APIVersion(apiTableMetaData.getInitialVersion())), "Table " + table.getName() + "'s initial API version is not a recognized version.");
if(BooleanUtils.isNotTrue(apiTableMetaData.getIsExcluded()))
{
validator.assertCondition(allVersions.contains(new APIVersion(apiTableMetaData.getInitialVersion())), "Table " + table.getName() + "'s initial API version is not a recognized version for api " + apiName);
}
}
}
}
@ -411,4 +404,66 @@ public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
return (this);
}
/*******************************************************************************
** Getter for label
*******************************************************************************/
public String getLabel()
{
return (this.label);
}
/*******************************************************************************
** Setter for label
*******************************************************************************/
public void setLabel(String label)
{
this.label = label;
}
/*******************************************************************************
** Fluent setter for label
*******************************************************************************/
public ApiInstanceMetaData withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for path
*******************************************************************************/
public String getPath()
{
return (this.path);
}
/*******************************************************************************
** Setter for path
*******************************************************************************/
public void setPath(String path)
{
this.path = path;
}
/*******************************************************************************
** Fluent setter for path
*******************************************************************************/
public ApiInstanceMetaData withPath(String path)
{
this.path = path;
return (this);
}
}

View File

@ -0,0 +1,137 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.api.model.metadata;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMiddlewareInstanceMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
**
*******************************************************************************/
public class ApiInstanceMetaDataContainer extends QMiddlewareInstanceMetaData
{
private Map<String, ApiInstanceMetaData> apis;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiInstanceMetaDataContainer()
{
setType(ApiMiddlewareType.NAME);
}
/*******************************************************************************
**
*******************************************************************************/
public static ApiInstanceMetaDataContainer of(QInstance qInstance)
{
return ((ApiInstanceMetaDataContainer) qInstance.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void validate(QInstance qInstance, QInstanceValidator validator)
{
for(Map.Entry<String, ApiInstanceMetaData> entry : CollectionUtils.nonNullMap(apis).entrySet())
{
entry.getValue().validate(entry.getKey(), qInstance, validator);
}
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public Map<String, ApiInstanceMetaData> getApis()
{
return (this.apis);
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public ApiInstanceMetaData getApiInstanceMetaData(String apiName)
{
if(this.apis == null)
{
return (null);
}
return (this.apis.get(apiName));
}
/*******************************************************************************
** Setter for apis
*******************************************************************************/
public void setApis(Map<String, ApiInstanceMetaData> apis)
{
this.apis = apis;
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiInstanceMetaDataContainer withApis(Map<String, ApiInstanceMetaData> apis)
{
this.apis = apis;
return (this);
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiInstanceMetaDataContainer withApiInstanceMetaData(ApiInstanceMetaData apiInstanceMetaData)
{
if(this.apis == null)
{
this.apis = new LinkedHashMap<>();
}
this.apis.put(apiInstanceMetaData.getName(), apiInstanceMetaData);
return (this);
}
}

View File

@ -22,16 +22,14 @@
package com.kingsrook.qqq.api.model.metadata.fields;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QMiddlewareFieldMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
**
*******************************************************************************/
public class ApiFieldMetaData extends QMiddlewareFieldMetaData
public class ApiFieldMetaData
{
private String initialVersion;
private String finalVersion;
@ -44,35 +42,18 @@ public class ApiFieldMetaData extends QMiddlewareFieldMetaData
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiFieldMetaData()
public static String getEffectiveApiFieldName(String apiName, QFieldMetaData field)
{
setType("api");
}
/*******************************************************************************
**
*******************************************************************************/
public static ApiFieldMetaData of(QFieldMetaData field)
{
return ((ApiFieldMetaData) field.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
public static String getEffectiveApiFieldName(QFieldMetaData field)
{
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
if(apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.apiFieldName))
ApiFieldMetaDataContainer apiFieldMetaDataContainer = ApiFieldMetaDataContainer.of(field);
if(apiFieldMetaDataContainer != null)
{
return (apiFieldMetaData.apiFieldName);
ApiFieldMetaData apiFieldMetaData = apiFieldMetaDataContainer.getApiFieldMetaData(apiName);
if(apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.apiFieldName))
{
return (apiFieldMetaData.apiFieldName);
}
}
return (field.getName());

View File

@ -0,0 +1,121 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.api.model.metadata.fields;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QMiddlewareFieldMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class ApiFieldMetaDataContainer extends QMiddlewareFieldMetaData
{
private Map<String, ApiFieldMetaData> apis;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiFieldMetaDataContainer()
{
setType("api");
}
/*******************************************************************************
**
*******************************************************************************/
public static ApiFieldMetaDataContainer of(QFieldMetaData field)
{
return ((ApiFieldMetaDataContainer) field.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public Map<String, ApiFieldMetaData> getApis()
{
return (this.apis);
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public ApiFieldMetaData getApiFieldMetaData(String apiName)
{
if(this.apis == null)
{
return (null);
}
return (this.apis.get(apiName));
}
/*******************************************************************************
** Setter for apis
*******************************************************************************/
public void setApis(Map<String, ApiFieldMetaData> apis)
{
this.apis = apis;
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiFieldMetaDataContainer withApis(Map<String, ApiFieldMetaData> apis)
{
this.apis = apis;
return (this);
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiFieldMetaDataContainer withApiFieldMetaData(String apiName, ApiFieldMetaData apiFieldMetaData)
{
if(this.apis == null)
{
this.apis = new LinkedHashMap<>();
}
this.apis.put(apiName, apiFieldMetaData);
return (this);
}
}

View File

@ -27,8 +27,8 @@ import java.util.List;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -36,7 +36,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
**
*******************************************************************************/
public class ApiTableMetaData extends QMiddlewareTableMetaData
public class ApiTableMetaData
{
private String initialVersion;
private String finalVersion;
@ -48,16 +48,6 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
/*******************************************************************************
**
*******************************************************************************/
public static ApiTableMetaData of(QTableMetaData table)
{
return ((ApiTableMetaData) table.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
@ -78,16 +68,13 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
/*******************************************************************************
**
*******************************************************************************/
@Override
public void enrich(QTableMetaData table)
public void enrich(String apiName, QTableMetaData table)
{
super.enrich(table);
if(initialVersion != null)
{
for(QFieldMetaData field : table.getFields().values())
{
ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(field);
ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(apiName, field);
if(apiFieldMetaData.getInitialVersion() == null)
{
apiFieldMetaData.setInitialVersion(initialVersion);
@ -96,7 +83,7 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
for(QFieldMetaData field : CollectionUtils.nonNullList(removedApiFields))
{
ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(field);
ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(apiName, field);
if(apiFieldMetaData.getInitialVersion() == null)
{
apiFieldMetaData.setInitialVersion(initialVersion);
@ -110,25 +97,20 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
/*******************************************************************************
**
*******************************************************************************/
private static ApiFieldMetaData ensureFieldHasApiMiddlewareMetaData(QFieldMetaData field)
private static ApiFieldMetaData ensureFieldHasApiMiddlewareMetaData(String apiName, QFieldMetaData field)
{
if(field.getMiddlewareMetaData(ApiMiddlewareType.NAME) == null)
{
field.withMiddlewareMetaData(new ApiFieldMetaData());
field.withMiddlewareMetaData(new ApiFieldMetaDataContainer());
}
return (ApiFieldMetaData.of(field));
}
ApiFieldMetaDataContainer apiFieldMetaDataContainer = ApiFieldMetaDataContainer.of(field);
if(apiFieldMetaDataContainer.getApiFieldMetaData(apiName) == null)
{
apiFieldMetaDataContainer.withApiFieldMetaData(apiName, new ApiFieldMetaData());
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiTableMetaData()
{
setType("api");
return (apiFieldMetaDataContainer.getApiFieldMetaData(apiName));
}

View File

@ -0,0 +1,138 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.api.model.metadata.tables;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
**
*******************************************************************************/
public class ApiTableMetaDataContainer extends QMiddlewareTableMetaData
{
private Map<String, ApiTableMetaData> apis;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiTableMetaDataContainer()
{
setType("api");
}
/*******************************************************************************
**
*******************************************************************************/
public static ApiTableMetaDataContainer of(QTableMetaData table)
{
return ((ApiTableMetaDataContainer) table.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void enrich(QTableMetaData table)
{
super.enrich(table);
for(Map.Entry<String, ApiTableMetaData> entry : CollectionUtils.nonNullMap(apis).entrySet())
{
entry.getValue().enrich(entry.getKey(), table);
}
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public Map<String, ApiTableMetaData> getApis()
{
return (this.apis);
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public ApiTableMetaData getApiTableMetaData(String apiName)
{
if(this.apis == null)
{
return (null);
}
return (this.apis.get(apiName));
}
/*******************************************************************************
** Setter for apis
*******************************************************************************/
public void setApis(Map<String, ApiTableMetaData> apis)
{
this.apis = apis;
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiTableMetaDataContainer withApis(Map<String, ApiTableMetaData> apis)
{
this.apis = apis;
return (this);
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiTableMetaDataContainer withApiTableMetaData(String apiName, ApiTableMetaData apiTableMetaData)
{
if(this.apis == null)
{
this.apis = new LinkedHashMap<>();
}
this.apis.put(apiName, apiTableMetaData);
return (this);
}
}