mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Merge branch 'release/0.2.0'
This commit is contained in:
@ -24,6 +24,8 @@ commands:
|
||||
name: Run Maven
|
||||
command: |
|
||||
mvn -s .circleci/mvn-settings.xml << parameters.maven_subcommand >>
|
||||
- store_artifacts:
|
||||
path: target/site/jacoco
|
||||
- run:
|
||||
name: Save test results
|
||||
command: |
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
target/
|
||||
*.iml
|
||||
.env
|
||||
|
||||
|
||||
#############################################
|
||||
@ -28,3 +29,4 @@ target/
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
.DS_Store
|
||||
|
14
pom.xml
14
pom.xml
@ -25,7 +25,7 @@
|
||||
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-middleware-javalin</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<version>0.2.0</version>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com:Kingsrook/qqq-middleware-javalin.git</connection>
|
||||
@ -53,12 +53,12 @@
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-core</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<version>0.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-module-rdbms</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<version>0.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
<dependency>
|
||||
<groupId>io.javalin</groupId>
|
||||
<artifactId>javalin</artifactId>
|
||||
<version>3.13.13</version>
|
||||
<version>4.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.konghq</groupId>
|
||||
@ -85,6 +85,12 @@
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.36</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.23.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Common deps for all qqq modules -->
|
||||
<dependency>
|
||||
|
@ -24,50 +24,61 @@ package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.MetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.ProcessMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.UpdateAction;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.ProcessMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ReportAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractQRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.count.CountRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.count.CountResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.insert.InsertRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.process.ProcessMetaDataRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.process.ProcessMetaDataResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.ProcessMetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.ProcessMetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
@ -97,6 +108,7 @@ public class QJavalinImplementation
|
||||
private static final Logger LOG = LogManager.getLogger(QJavalinImplementation.class);
|
||||
|
||||
private static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
|
||||
private static final String SESSION_ID_COOKIE_NAME = "sessionId";
|
||||
|
||||
static QInstance qInstance;
|
||||
|
||||
@ -182,36 +194,46 @@ public class QJavalinImplementation
|
||||
{
|
||||
return (() ->
|
||||
{
|
||||
/////////////////////
|
||||
// metadata routes //
|
||||
/////////////////////
|
||||
path("/metaData", () ->
|
||||
{
|
||||
get("/", QJavalinImplementation::metaData);
|
||||
path("/table/:table", () ->
|
||||
path("/table/{table}", () ->
|
||||
{
|
||||
get("", QJavalinImplementation::tableMetaData);
|
||||
});
|
||||
path("/process/:processName", () ->
|
||||
path("/process/{processName}", () ->
|
||||
{
|
||||
get("", QJavalinImplementation::processMetaData);
|
||||
});
|
||||
});
|
||||
path("/data", () ->
|
||||
{
|
||||
path("/:table", () ->
|
||||
{
|
||||
get("/", QJavalinImplementation::dataQuery);
|
||||
post("/", QJavalinImplementation::dataInsert); // todo - internal to that method, if input is a list, do a bulk - else, single.
|
||||
get("/count", QJavalinImplementation::dataCount);
|
||||
|
||||
// todo - add put and/or patch at this level (without a primaryKey) to do a bulk update based on primaryKeys in the records.
|
||||
path("/:primaryKey", () ->
|
||||
{
|
||||
get("", QJavalinImplementation::dataGet);
|
||||
patch("", QJavalinImplementation::dataUpdate);
|
||||
put("", QJavalinImplementation::dataUpdate); // todo - want different semantics??
|
||||
delete("", QJavalinImplementation::dataDelete);
|
||||
});
|
||||
/////////////////////////
|
||||
// table (data) routes //
|
||||
/////////////////////////
|
||||
path("/data/{table}", () ->
|
||||
{
|
||||
get("/", QJavalinImplementation::dataQuery);
|
||||
post("/", QJavalinImplementation::dataInsert); // todo - internal to that method, if input is a list, do a bulk - else, single.
|
||||
get("/count", QJavalinImplementation::dataCount);
|
||||
get("/export", QJavalinImplementation::dataExportWithoutFilename);
|
||||
get("/export/{filename}", QJavalinImplementation::dataExportWithFilename);
|
||||
|
||||
// todo - add put and/or patch at this level (without a primaryKey) to do a bulk update based on primaryKeys in the records.
|
||||
path("/{primaryKey}", () ->
|
||||
{
|
||||
get("", QJavalinImplementation::dataGet);
|
||||
patch("", QJavalinImplementation::dataUpdate);
|
||||
put("", QJavalinImplementation::dataUpdate); // todo - want different semantics??
|
||||
delete("", QJavalinImplementation::dataDelete);
|
||||
});
|
||||
});
|
||||
|
||||
////////////////////
|
||||
// process routes //
|
||||
////////////////////
|
||||
path("", QJavalinProcessHandler.getRoutes());
|
||||
});
|
||||
}
|
||||
@ -221,18 +243,30 @@ public class QJavalinImplementation
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static void setupSession(Context context, AbstractQRequest request) throws QModuleDispatchException
|
||||
static void setupSession(Context context, AbstractActionInput input) throws QModuleDispatchException
|
||||
{
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData());
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(input.getAuthenticationMetaData());
|
||||
|
||||
// todo - does this need some per-provider logic actually? mmm...
|
||||
Map<String, String> authenticationContext = new HashMap<>();
|
||||
authenticationContext.put("sessionId", context.cookie("sessionId"));
|
||||
QSession session = authenticationModule.createSession(authenticationContext);
|
||||
request.setSession(session);
|
||||
try
|
||||
{
|
||||
Map<String, String> authenticationContext = new HashMap<>();
|
||||
authenticationContext.put(SESSION_ID_COOKIE_NAME, context.cookie(SESSION_ID_COOKIE_NAME));
|
||||
QSession session = authenticationModule.createSession(qInstance, authenticationContext);
|
||||
input.setSession(session);
|
||||
|
||||
context.cookie("sessionId", session.getIdReference(), SESSION_COOKIE_AGE);
|
||||
context.cookie(SESSION_ID_COOKIE_NAME, session.getIdReference(), SESSION_COOKIE_AGE);
|
||||
}
|
||||
catch(QAuthenticationException qae)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// if exception caught, clear out the cookie so the frontend will reauthorize //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
if(authenticationModule instanceof Auth0AuthenticationModule)
|
||||
{
|
||||
context.removeCookie(SESSION_ID_COOKIE_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -244,17 +278,17 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
String table = context.pathParam("table");
|
||||
String table = context.pathParam("table");
|
||||
List<Serializable> primaryKeys = new ArrayList<>();
|
||||
primaryKeys.add(context.pathParam("primaryKey"));
|
||||
|
||||
DeleteRequest deleteRequest = new DeleteRequest(qInstance);
|
||||
setupSession(context, deleteRequest);
|
||||
deleteRequest.setTableName(table);
|
||||
deleteRequest.setPrimaryKeys(primaryKeys);
|
||||
DeleteInput deleteInput = new DeleteInput(qInstance);
|
||||
setupSession(context, deleteInput);
|
||||
deleteInput.setTableName(table);
|
||||
deleteInput.setPrimaryKeys(primaryKeys);
|
||||
|
||||
DeleteAction deleteAction = new DeleteAction();
|
||||
DeleteResult deleteResult = deleteAction.execute(deleteRequest);
|
||||
DeleteOutput deleteResult = deleteAction.execute(deleteInput);
|
||||
|
||||
context.result(JsonUtils.toJson(deleteResult));
|
||||
}
|
||||
@ -273,9 +307,9 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
String table = context.pathParam("table");
|
||||
String table = context.pathParam("table");
|
||||
List<QRecord> recordList = new ArrayList<>();
|
||||
QRecord record = new QRecord();
|
||||
QRecord record = new QRecord();
|
||||
record.setTableName(table);
|
||||
recordList.add(record);
|
||||
|
||||
@ -292,13 +326,13 @@ public class QJavalinImplementation
|
||||
|
||||
record.setValue(tableMetaData.getPrimaryKeyField(), context.pathParam("primaryKey"));
|
||||
|
||||
UpdateRequest updateRequest = new UpdateRequest(qInstance);
|
||||
setupSession(context, updateRequest);
|
||||
updateRequest.setTableName(table);
|
||||
updateRequest.setRecords(recordList);
|
||||
UpdateInput updateInput = new UpdateInput(qInstance);
|
||||
setupSession(context, updateInput);
|
||||
updateInput.setTableName(table);
|
||||
updateInput.setRecords(recordList);
|
||||
|
||||
UpdateAction updateAction = new UpdateAction();
|
||||
UpdateResult updateResult = updateAction.execute(updateRequest);
|
||||
UpdateOutput updateResult = updateAction.execute(updateInput);
|
||||
|
||||
context.result(JsonUtils.toJson(updateResult));
|
||||
}
|
||||
@ -317,9 +351,9 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
String table = context.pathParam("table");
|
||||
String table = context.pathParam("table");
|
||||
List<QRecord> recordList = new ArrayList<>();
|
||||
QRecord record = new QRecord();
|
||||
QRecord record = new QRecord();
|
||||
record.setTableName(table);
|
||||
recordList.add(record);
|
||||
|
||||
@ -332,15 +366,15 @@ public class QJavalinImplementation
|
||||
}
|
||||
}
|
||||
|
||||
InsertRequest insertRequest = new InsertRequest(qInstance);
|
||||
setupSession(context, insertRequest);
|
||||
insertRequest.setTableName(table);
|
||||
insertRequest.setRecords(recordList);
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
setupSession(context, insertInput);
|
||||
insertInput.setTableName(table);
|
||||
insertInput.setRecords(recordList);
|
||||
|
||||
InsertAction insertAction = new InsertAction();
|
||||
InsertResult insertResult = insertAction.execute(insertRequest);
|
||||
InsertOutput insertOutput = insertAction.execute(insertInput);
|
||||
|
||||
context.result(JsonUtils.toJson(insertResult));
|
||||
context.result(JsonUtils.toJson(insertOutput));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -357,13 +391,13 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
String tableName = context.pathParam("table");
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
String primaryKey = context.pathParam("primaryKey");
|
||||
QueryRequest queryRequest = new QueryRequest(qInstance);
|
||||
String tableName = context.pathParam("table");
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
String primaryKey = context.pathParam("primaryKey");
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
|
||||
setupSession(context, queryRequest);
|
||||
queryRequest.setTableName(tableName);
|
||||
setupSession(context, queryInput);
|
||||
queryInput.setTableName(tableName);
|
||||
|
||||
// todo - validate that the primary key is of the proper type (e.g,. not a string for an id field)
|
||||
// and throw a 400-series error (tell the user bad-request), rather than, we're doing a 500 (server error)
|
||||
@ -371,25 +405,25 @@ public class QJavalinImplementation
|
||||
///////////////////////////////////////////////////////
|
||||
// setup a filter for the primaryKey = the path-pram //
|
||||
///////////////////////////////////////////////////////
|
||||
queryRequest.setFilter(new QQueryFilter()
|
||||
queryInput.setFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria()
|
||||
.withFieldName(table.getPrimaryKeyField())
|
||||
.withOperator(QCriteriaOperator.EQUALS)
|
||||
.withValues(List.of(primaryKey))));
|
||||
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryResult queryResult = queryAction.execute(queryRequest);
|
||||
QueryOutput queryOutput = queryAction.execute(queryInput);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// throw a not found error if the record isn't found //
|
||||
///////////////////////////////////////////////////////
|
||||
if(queryResult.getRecords().isEmpty())
|
||||
if(queryOutput.getRecords().isEmpty())
|
||||
{
|
||||
throw (new QNotFoundException("Could not find " + table.getLabel() + " with "
|
||||
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||
}
|
||||
|
||||
context.result(JsonUtils.toJson(queryResult.getRecords().get(0)));
|
||||
context.result(JsonUtils.toJson(queryOutput.getRecords().get(0)));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -415,20 +449,20 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
CountRequest countRequest = new CountRequest(qInstance);
|
||||
setupSession(context, countRequest);
|
||||
countRequest.setTableName(context.pathParam("table"));
|
||||
CountInput countInput = new CountInput(qInstance);
|
||||
setupSession(context, countInput);
|
||||
countInput.setTableName(context.pathParam("table"));
|
||||
|
||||
String filter = stringQueryParam(context, "filter");
|
||||
if(filter != null)
|
||||
{
|
||||
countRequest.setFilter(JsonUtils.toObject(filter, QQueryFilter.class));
|
||||
countInput.setFilter(JsonUtils.toObject(filter, QQueryFilter.class));
|
||||
}
|
||||
|
||||
CountAction countAction = new CountAction();
|
||||
CountResult countResult = countAction.execute(countRequest);
|
||||
CountOutput countOutput = countAction.execute(countInput);
|
||||
|
||||
context.result(JsonUtils.toJson(countResult));
|
||||
context.result(JsonUtils.toJson(countOutput));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -456,22 +490,22 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
QueryRequest queryRequest = new QueryRequest(qInstance);
|
||||
setupSession(context, queryRequest);
|
||||
queryRequest.setTableName(context.pathParam("table"));
|
||||
queryRequest.setSkip(integerQueryParam(context, "skip"));
|
||||
queryRequest.setLimit(integerQueryParam(context, "limit"));
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
setupSession(context, queryInput);
|
||||
queryInput.setTableName(context.pathParam("table"));
|
||||
queryInput.setSkip(integerQueryParam(context, "skip"));
|
||||
queryInput.setLimit(integerQueryParam(context, "limit"));
|
||||
|
||||
String filter = stringQueryParam(context, "filter");
|
||||
if(filter != null)
|
||||
{
|
||||
queryRequest.setFilter(JsonUtils.toObject(filter, QQueryFilter.class));
|
||||
queryInput.setFilter(JsonUtils.toObject(filter, QQueryFilter.class));
|
||||
}
|
||||
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryResult queryResult = queryAction.execute(queryRequest);
|
||||
QueryOutput queryOutput = queryAction.execute(queryInput);
|
||||
|
||||
context.result(JsonUtils.toJson(queryResult));
|
||||
context.result(JsonUtils.toJson(queryOutput));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -488,12 +522,12 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
MetaDataRequest metaDataRequest = new MetaDataRequest(qInstance);
|
||||
setupSession(context, metaDataRequest);
|
||||
MetaDataInput metaDataInput = new MetaDataInput(qInstance);
|
||||
setupSession(context, metaDataInput);
|
||||
MetaDataAction metaDataAction = new MetaDataAction();
|
||||
MetaDataResult metaDataResult = metaDataAction.execute(metaDataRequest);
|
||||
MetaDataOutput metaDataOutput = metaDataAction.execute(metaDataInput);
|
||||
|
||||
context.result(JsonUtils.toJson(metaDataResult));
|
||||
context.result(JsonUtils.toJson(metaDataOutput));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -510,13 +544,13 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
TableMetaDataRequest tableMetaDataRequest = new TableMetaDataRequest(qInstance);
|
||||
setupSession(context, tableMetaDataRequest);
|
||||
tableMetaDataRequest.setTableName(context.pathParam("table"));
|
||||
TableMetaDataInput tableMetaDataInput = new TableMetaDataInput(qInstance);
|
||||
setupSession(context, tableMetaDataInput);
|
||||
tableMetaDataInput.setTableName(context.pathParam("table"));
|
||||
TableMetaDataAction tableMetaDataAction = new TableMetaDataAction();
|
||||
TableMetaDataResult tableMetaDataResult = tableMetaDataAction.execute(tableMetaDataRequest);
|
||||
TableMetaDataOutput tableMetaDataOutput = tableMetaDataAction.execute(tableMetaDataInput);
|
||||
|
||||
context.result(JsonUtils.toJson(tableMetaDataResult));
|
||||
context.result(JsonUtils.toJson(tableMetaDataOutput));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -533,13 +567,153 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessMetaDataRequest processMetaDataRequest = new ProcessMetaDataRequest(qInstance);
|
||||
setupSession(context, processMetaDataRequest);
|
||||
processMetaDataRequest.setProcessName(context.pathParam("processName"));
|
||||
ProcessMetaDataInput processMetaDataInput = new ProcessMetaDataInput(qInstance);
|
||||
setupSession(context, processMetaDataInput);
|
||||
processMetaDataInput.setProcessName(context.pathParam("processName"));
|
||||
ProcessMetaDataAction processMetaDataAction = new ProcessMetaDataAction();
|
||||
ProcessMetaDataResult processMetaDataResult = processMetaDataAction.execute(processMetaDataRequest);
|
||||
ProcessMetaDataOutput processMetaDataOutput = processMetaDataAction.execute(processMetaDataInput);
|
||||
|
||||
context.result(JsonUtils.toJson(processMetaDataResult));
|
||||
context.result(JsonUtils.toJson(processMetaDataOutput));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void dataExportWithFilename(Context context)
|
||||
{
|
||||
String filename = context.pathParam("filename");
|
||||
dataExport(context, Optional.of(filename));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void dataExportWithoutFilename(Context context)
|
||||
{
|
||||
dataExport(context, Optional.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void dataExport(Context context, Optional<String> optionalFilename)
|
||||
{
|
||||
try
|
||||
{
|
||||
//////////////////////////////////////////
|
||||
// read params from the request context //
|
||||
//////////////////////////////////////////
|
||||
String tableName = context.pathParam("table");
|
||||
String format = context.queryParam("format");
|
||||
String filter = context.queryParam("filter");
|
||||
Integer limit = integerQueryParam(context, "limit");
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if a format query param wasn't given, then try to get file extension from file name //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(!StringUtils.hasContent(format) && optionalFilename.isPresent() && StringUtils.hasContent(optionalFilename.get()))
|
||||
{
|
||||
String filename = optionalFilename.get();
|
||||
if(filename.contains("."))
|
||||
{
|
||||
format = filename.substring(filename.lastIndexOf(".") + 1);
|
||||
}
|
||||
}
|
||||
|
||||
ReportFormat reportFormat;
|
||||
try
|
||||
{
|
||||
reportFormat = ReportFormat.fromString(format);
|
||||
}
|
||||
catch(QUserFacingException e)
|
||||
{
|
||||
handleException(HttpStatus.Code.BAD_REQUEST, context, e);
|
||||
return;
|
||||
}
|
||||
|
||||
String filename = optionalFilename.orElse(tableName + "." + reportFormat.toString().toLowerCase(Locale.ROOT));
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// set up the report action's input object //
|
||||
/////////////////////////////////////////////
|
||||
ReportInput reportInput = new ReportInput(qInstance);
|
||||
setupSession(context, reportInput);
|
||||
reportInput.setTableName(tableName);
|
||||
reportInput.setReportFormat(reportFormat);
|
||||
reportInput.setFilename(filename);
|
||||
reportInput.setLimit(limit);
|
||||
|
||||
String fields = stringQueryParam(context, "fields");
|
||||
if(StringUtils.hasContent(fields))
|
||||
{
|
||||
reportInput.setFieldNames(List.of(fields.split(",")));
|
||||
}
|
||||
|
||||
if(filter != null)
|
||||
{
|
||||
reportInput.setQueryFilter(JsonUtils.toObject(filter, QQueryFilter.class));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set up the I/O pipe streams. //
|
||||
// Critically, we must NOT open the outputStream in a try-with-resources. The thread that writes to //
|
||||
// the stream must close it when it's done writing. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
PipedOutputStream pipedOutputStream = new PipedOutputStream();
|
||||
PipedInputStream pipedInputStream = new PipedInputStream();
|
||||
pipedOutputStream.connect(pipedInputStream);
|
||||
reportInput.setReportOutputStream(pipedOutputStream);
|
||||
|
||||
ReportAction reportAction = new ReportAction();
|
||||
reportAction.preExecute(reportInput);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// start the async job. //
|
||||
// Critically, this must happen before the pipedInputStream is passed to the javalin result method //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new AsyncJobManager().startJob("Javalin>ReportAction", (o) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
reportAction.execute(reportInput);
|
||||
return (true);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
pipedOutputStream.write(("Error generating report: " + e.getMessage()).getBytes());
|
||||
pipedOutputStream.close();
|
||||
return (false);
|
||||
}
|
||||
});
|
||||
|
||||
////////////////////////////////////////////
|
||||
// set the response content type & stream //
|
||||
////////////////////////////////////////////
|
||||
context.contentType(reportFormat.getMimeType());
|
||||
context.header("Content-Disposition", "filename=" + filename);
|
||||
context.result(pipedInputStream);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we'd like to check to see if the job failed, and if so, to give the user an error... //
|
||||
// but if we "block" here, then piped streams seem to never flush, so we deadlock things. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// AsyncJobStatus asyncJobStatus = asyncJobManager.waitForJob(jobUUID);
|
||||
// if(asyncJobStatus.getState().equals(AsyncJobState.ERROR))
|
||||
// {
|
||||
// System.out.println("Well, here we are...");
|
||||
// throw (new QUserFacingException("Error running report: " + asyncJobStatus.getCaughtException().getMessage()));
|
||||
// }
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -553,27 +727,46 @@ public class QJavalinImplementation
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void handleException(Context context, Exception e)
|
||||
{
|
||||
handleException(null, context, e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void handleException(HttpStatus.Code statusCode, Context context, Exception e)
|
||||
{
|
||||
QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class);
|
||||
if(userFacingException != null)
|
||||
{
|
||||
if(userFacingException instanceof QNotFoundException)
|
||||
{
|
||||
context.status(HttpStatus.NOT_FOUND_404)
|
||||
.result("{\"error\":\"" + userFacingException.getMessage() + "\"}");
|
||||
int code = Objects.requireNonNullElse(statusCode, HttpStatus.Code.NOT_FOUND).getCode();
|
||||
context.status(code).result("{\"error\":\"" + userFacingException.getMessage() + "\"}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("User-facing exception", e);
|
||||
context.status(HttpStatus.INTERNAL_SERVER_ERROR_500)
|
||||
.result("{\"error\":\"" + userFacingException.getMessage() + "\"}");
|
||||
int code = Objects.requireNonNullElse(statusCode, HttpStatus.Code.INTERNAL_SERVER_ERROR).getCode();
|
||||
context.status(code).result("{\"error\":\"" + userFacingException.getMessage() + "\"}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(e instanceof QAuthenticationException)
|
||||
{
|
||||
context.status(HttpStatus.UNAUTHORIZED_401).result("{\"error\":\"" + e.getMessage() + "\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// default exception handling //
|
||||
////////////////////////////////
|
||||
LOG.warn("Exception in javalin request", e);
|
||||
context.status(HttpStatus.INTERNAL_SERVER_ERROR_500)
|
||||
.result("{\"error\":\"" + e.getClass().getSimpleName() + " (" + e.getMessage() + ")\"}");
|
||||
int code = Objects.requireNonNullElse(statusCode, HttpStatus.Code.INTERNAL_SERVER_ERROR).getCode();
|
||||
context.status(code).result("{\"error\":\"" + e.getClass().getSimpleName() + " (" + e.getMessage() + ")\"}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,32 +33,37 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.actions.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.JobGoingAsyncException;
|
||||
import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.state.StateType;
|
||||
import com.kingsrook.qqq.backend.core.state.TempFileStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
|
||||
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.StringUtils;
|
||||
import io.javalin.apibuilder.EndpointGroup;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.http.UploadedFile;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -87,15 +92,15 @@ public class QJavalinProcessHandler
|
||||
{
|
||||
path("/processes", () ->
|
||||
{
|
||||
path("/:processName", () ->
|
||||
path("/{processName}", () ->
|
||||
{
|
||||
get("/init", QJavalinProcessHandler::processInit);
|
||||
post("/init", QJavalinProcessHandler::processInit);
|
||||
|
||||
path("/:processUUID", () ->
|
||||
path("/{processUUID}", () ->
|
||||
{
|
||||
post("/step/:step", QJavalinProcessHandler::processStep);
|
||||
get("/status/:jobUUID", QJavalinProcessHandler::processStatus);
|
||||
post("/step/{step}", QJavalinProcessHandler::processStep);
|
||||
get("/status/{jobUUID}", QJavalinProcessHandler::processStatus);
|
||||
get("/records", QJavalinProcessHandler::processRecords);
|
||||
});
|
||||
});
|
||||
@ -135,31 +140,31 @@ public class QJavalinProcessHandler
|
||||
LOG.info(startAfterStep == null ? "Initiating process [" + processName + "] [" + processUUID + "]"
|
||||
: "Resuming process [" + processName + "] [" + processUUID + "] after step [" + startAfterStep + "]");
|
||||
|
||||
RunProcessRequest runProcessRequest = new RunProcessRequest(QJavalinImplementation.qInstance);
|
||||
QJavalinImplementation.setupSession(context, runProcessRequest);
|
||||
runProcessRequest.setProcessName(processName);
|
||||
runProcessRequest.setFrontendStepBehavior(RunProcessRequest.FrontendStepBehavior.BREAK);
|
||||
runProcessRequest.setProcessUUID(processUUID);
|
||||
runProcessRequest.setStartAfterStep(startAfterStep);
|
||||
populateRunProcessRequestWithValuesFromContext(context, runProcessRequest);
|
||||
RunProcessInput runProcessInput = new RunProcessInput(QJavalinImplementation.qInstance);
|
||||
QJavalinImplementation.setupSession(context, runProcessInput);
|
||||
runProcessInput.setProcessName(processName);
|
||||
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
|
||||
runProcessInput.setProcessUUID(processUUID);
|
||||
runProcessInput.setStartAfterStep(startAfterStep);
|
||||
populateRunProcessRequestWithValuesFromContext(context, runProcessInput);
|
||||
|
||||
////////////////////////////////////////
|
||||
// run the process as an async action //
|
||||
////////////////////////////////////////
|
||||
Integer timeout = getTimeoutMillis(context);
|
||||
RunProcessResult runProcessResult = new AsyncJobManager().startJob(timeout, TimeUnit.MILLISECONDS, (callback) ->
|
||||
RunProcessOutput runProcessOutput = new AsyncJobManager().startJob(timeout, TimeUnit.MILLISECONDS, (callback) ->
|
||||
{
|
||||
runProcessRequest.setAsyncJobCallback(callback);
|
||||
return (new RunProcessAction().execute(runProcessRequest));
|
||||
runProcessInput.setAsyncJobCallback(callback);
|
||||
return (new RunProcessAction().execute(runProcessInput));
|
||||
});
|
||||
|
||||
LOG.info("Process result error? " + runProcessResult.getException());
|
||||
for(QFieldMetaData outputField : QJavalinImplementation.qInstance.getProcess(runProcessRequest.getProcessName()).getOutputFields())
|
||||
LOG.info("Process result error? " + runProcessOutput.getException());
|
||||
for(QFieldMetaData outputField : QJavalinImplementation.qInstance.getProcess(runProcessInput.getProcessName()).getOutputFields())
|
||||
{
|
||||
LOG.info("Process result output value: " + outputField.getName() + ": " + runProcessResult.getValues().get(outputField.getName()));
|
||||
LOG.info("Process result output value: " + outputField.getName() + ": " + runProcessOutput.getValues().get(outputField.getName()));
|
||||
}
|
||||
|
||||
serializeRunProcessResultForCaller(resultForCaller, runProcessResult);
|
||||
serializeRunProcessResultForCaller(resultForCaller, runProcessOutput);
|
||||
}
|
||||
catch(JobGoingAsyncException jgae)
|
||||
{
|
||||
@ -185,17 +190,17 @@ public class QJavalinProcessHandler
|
||||
** Whether a step finished synchronously or asynchronously, return its data
|
||||
** to the caller the same way.
|
||||
*******************************************************************************/
|
||||
private static void serializeRunProcessResultForCaller(Map<String, Object> resultForCaller, RunProcessResult runProcessResult)
|
||||
private static void serializeRunProcessResultForCaller(Map<String, Object> resultForCaller, RunProcessOutput runProcessOutput)
|
||||
{
|
||||
if(runProcessResult.getException().isPresent())
|
||||
if(runProcessOutput.getException().isPresent())
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// per code coverage, this path may never actually get hit... //
|
||||
////////////////////////////////////////////////////////////////
|
||||
serializeRunProcessExceptionForCaller(resultForCaller, runProcessResult.getException().get());
|
||||
serializeRunProcessExceptionForCaller(resultForCaller, runProcessOutput.getException().get());
|
||||
}
|
||||
resultForCaller.put("values", runProcessResult.getValues());
|
||||
runProcessResult.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep));
|
||||
resultForCaller.put("values", runProcessOutput.getValues());
|
||||
runProcessOutput.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep));
|
||||
}
|
||||
|
||||
|
||||
@ -216,7 +221,7 @@ public class QJavalinProcessHandler
|
||||
{
|
||||
Throwable rootException = ExceptionUtils.getRootException(exception);
|
||||
LOG.warn("Uncaught Exception in process", exception);
|
||||
resultForCaller.put("error", "Original error message: " + rootException.getMessage());
|
||||
resultForCaller.put("error", "Error message: " + rootException.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,25 +229,59 @@ public class QJavalinProcessHandler
|
||||
|
||||
/*******************************************************************************
|
||||
** take values from query-string params, and put them into the run process request
|
||||
** todo - better from POST body, or with a "field-" type of prefix??
|
||||
** todo - make query params have a "field-" type of prefix??
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void populateRunProcessRequestWithValuesFromContext(Context context, RunProcessRequest runProcessRequest) throws IOException
|
||||
private static void populateRunProcessRequestWithValuesFromContext(Context context, RunProcessInput runProcessInput) throws IOException
|
||||
{
|
||||
//////////////////////////
|
||||
// process query string //
|
||||
//////////////////////////
|
||||
for(Map.Entry<String, List<String>> queryParam : context.queryParamMap().entrySet())
|
||||
{
|
||||
String fieldName = queryParam.getKey();
|
||||
List<String> values = queryParam.getValue();
|
||||
if(CollectionUtils.nullSafeHasContents(values))
|
||||
{
|
||||
runProcessRequest.addValue(fieldName, values.get(0));
|
||||
runProcessInput.addValue(fieldName, values.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
QQueryFilter initialRecordsFilter = buildProcessInitRecordsFilter(context, runProcessRequest);
|
||||
////////////////////////////
|
||||
// process form/post body //
|
||||
////////////////////////////
|
||||
for(Map.Entry<String, List<String>> formParam : context.formParamMap().entrySet())
|
||||
{
|
||||
String fieldName = formParam.getKey();
|
||||
List<String> values = formParam.getValue();
|
||||
if(CollectionUtils.nullSafeHasContents(values))
|
||||
{
|
||||
runProcessInput.addValue(fieldName, values.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// process uploaded files //
|
||||
////////////////////////////
|
||||
for(UploadedFile uploadedFile : context.uploadedFiles())
|
||||
{
|
||||
QUploadedFile qUploadedFile = new QUploadedFile();
|
||||
qUploadedFile.setBytes(uploadedFile.getContent().readAllBytes());
|
||||
qUploadedFile.setFilename(uploadedFile.getFilename());
|
||||
|
||||
UUIDAndTypeStateKey key = new UUIDAndTypeStateKey(StateType.UPLOADED_FILE);
|
||||
TempFileStateProvider.getInstance().put(key, qUploadedFile);
|
||||
LOG.info("Stored uploaded file in TempFileStateProvider under key: " + key);
|
||||
runProcessInput.addValue(QUploadedFile.DEFAULT_UPLOADED_FILE_FIELD_NAME, key);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// deal with params that specify an initial-records filter //
|
||||
/////////////////////////////////////////////////////////////
|
||||
QQueryFilter initialRecordsFilter = buildProcessInitRecordsFilter(context, runProcessInput);
|
||||
if(initialRecordsFilter != null)
|
||||
{
|
||||
runProcessRequest.setCallback(new QProcessCallback()
|
||||
runProcessInput.setCallback(new QProcessCallback()
|
||||
{
|
||||
@Override
|
||||
public QQueryFilter getQueryFilter()
|
||||
@ -266,10 +305,10 @@ public class QJavalinProcessHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QQueryFilter buildProcessInitRecordsFilter(Context context, RunProcessRequest runProcessRequest) throws IOException
|
||||
private static QQueryFilter buildProcessInitRecordsFilter(Context context, RunProcessInput runProcessInput) throws IOException
|
||||
{
|
||||
QInstance instance = runProcessRequest.getInstance();
|
||||
QProcessMetaData process = instance.getProcess(runProcessRequest.getProcessName());
|
||||
QInstance instance = runProcessInput.getInstance();
|
||||
QProcessMetaData process = instance.getProcess(runProcessInput.getProcessName());
|
||||
QTableMetaData table = instance.getTable(process.getTableName());
|
||||
|
||||
if(table == null)
|
||||
@ -331,11 +370,12 @@ public class QJavalinProcessHandler
|
||||
*******************************************************************************/
|
||||
public static void processStatus(Context context)
|
||||
{
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
|
||||
String processUUID = context.pathParam("processUUID");
|
||||
String jobUUID = context.pathParam("jobUUID");
|
||||
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
resultForCaller.put("processUUID", processUUID);
|
||||
|
||||
LOG.info("Request for status of process " + processUUID + ", job " + jobUUID);
|
||||
Optional<AsyncJobStatus> optionalJobStatus = new AsyncJobManager().getJobStatus(jobUUID);
|
||||
if(optionalJobStatus.isEmpty())
|
||||
@ -358,8 +398,8 @@ public class QJavalinProcessHandler
|
||||
Optional<ProcessState> processState = RunProcessAction.getState(processUUID);
|
||||
if(processState.isPresent())
|
||||
{
|
||||
RunProcessResult runProcessResult = new RunProcessResult(processState.get());
|
||||
serializeRunProcessResultForCaller(resultForCaller, runProcessResult);
|
||||
RunProcessOutput runProcessOutput = new RunProcessOutput(processState.get());
|
||||
serializeRunProcessResultForCaller(resultForCaller, runProcessOutput);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -412,6 +452,7 @@ public class QJavalinProcessHandler
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
List<QRecord> recordPage = CollectionUtils.safelyGetPage(records, skip, limit);
|
||||
resultForCaller.put("records", recordPage);
|
||||
resultForCaller.put("totalRecords", records.size());
|
||||
context.result(JsonUtils.toJson(resultForCaller));
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -27,6 +27,7 @@ import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.Unirest;
|
||||
@ -34,6 +35,7 @@ import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
@ -52,8 +54,6 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test the top-level meta-data endpoint
|
||||
**
|
||||
@ -269,7 +269,7 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
@Test
|
||||
public void test_dataQueryWithFilter()
|
||||
{
|
||||
String filterJson = "{\"criteria\":[{\"fieldName\":\"firstName\",\"operator\":\"EQUALS\",\"values\":[\"Tim\"]}]}";
|
||||
String filterJson = getFirstNameEqualsTimFilterJSON();
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person?filter=" + URLEncoder.encode(filterJson, StandardCharsets.UTF_8)).asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
@ -284,6 +284,17 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getFirstNameEqualsTimFilterJSON()
|
||||
{
|
||||
return """
|
||||
{"criteria":[{"fieldName":"firstName","operator":"EQUALS","values":["Tim"]}]}""";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test an insert
|
||||
**
|
||||
@ -362,8 +373,7 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertEquals(1, jsonObject.getJSONArray("records").length());
|
||||
assertEquals(3, jsonObject.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id"));
|
||||
assertEquals(1, jsonObject.getInt("deletedRecordCount"));
|
||||
TestUtils.runTestSql("SELECT id FROM person", (rs -> {
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
@ -375,4 +385,89 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testExportCsvPerFileName()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/export/MyPersonExport.csv").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals("text/csv", response.getHeaders().get("Content-Type").get(0));
|
||||
assertEquals("filename=MyPersonExport.csv", response.getHeaders().get("Content-Disposition").get(0));
|
||||
String[] csvLines = response.getBody().split("\n");
|
||||
assertEquals(6, csvLines.length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testExportNoFormat()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/export/").asString();
|
||||
assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus());
|
||||
assertThat(response.getBody()).contains("Report format was not specified");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testExportExcelPerFormatQueryParam()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/export/?format=xlsx").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(ReportFormat.XLSX.getMimeType(), response.getHeaders().get("Content-Type").get(0));
|
||||
assertEquals("filename=person.xlsx", response.getHeaders().get("Content-Disposition").get(0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testExportFilterQueryParam()
|
||||
{
|
||||
String filterJson = getFirstNameEqualsTimFilterJSON();
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/export/Favorite People.csv?filter=" + URLEncoder.encode(filterJson, StandardCharsets.UTF_8)).asString();
|
||||
assertEquals("filename=Favorite People.csv", response.getHeaders().get("Content-Disposition").get(0));
|
||||
String[] csvLines = response.getBody().split("\n");
|
||||
assertEquals(2, csvLines.length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testExportFieldsQueryParam()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/export/People.csv?fields=id,birthDate").asString();
|
||||
String[] csvLines = response.getBody().split("\n");
|
||||
assertEquals("""
|
||||
"Id","Birth Date\"""", csvLines[0]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testExportSupportedFormat()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/export/?format=docx").asString();
|
||||
assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus());
|
||||
assertThat(response.getBody()).contains("Unsupported report format");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,9 +26,9 @@ import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.Unirest;
|
||||
|
@ -27,18 +27,19 @@ import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
import com.kingsrook.qqq.backend.core.interfaces.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.interfaces.mock.MockBackendStep;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepResult;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||
@ -134,7 +135,7 @@ public class TestUtils
|
||||
{
|
||||
return new QAuthenticationMetaData()
|
||||
.withName("mock")
|
||||
.withType("mock");
|
||||
.withType(QAuthenticationType.MOCK);
|
||||
}
|
||||
|
||||
|
||||
@ -311,11 +312,11 @@ public class TestUtils
|
||||
**
|
||||
******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepRequest runBackendStepRequest, RunBackendStepResult runBackendStepResult) throws QException
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(runBackendStepRequest.getValueInteger(FIELD_SLEEP_MILLIS));
|
||||
Thread.sleep(runBackendStepInput.getValueInteger(FIELD_SLEEP_MILLIS));
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
@ -357,12 +358,12 @@ public class TestUtils
|
||||
**
|
||||
******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepRequest runBackendStepRequest, RunBackendStepResult runBackendStepResult) throws QException
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
int sleepMillis;
|
||||
try
|
||||
{
|
||||
sleepMillis = runBackendStepRequest.getValueInteger(FIELD_SLEEP_MILLIS);
|
||||
sleepMillis = runBackendStepInput.getValueInteger(FIELD_SLEEP_MILLIS);
|
||||
}
|
||||
catch(QValueException qve)
|
||||
{
|
||||
|
Reference in New Issue
Block a user