mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
QQQ-14 Initial version of running processes in javalin
This commit is contained in:
@ -33,9 +33,11 @@ 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.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
|
||||
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.AbstractQRequest;
|
||||
@ -47,17 +49,21 @@ 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.table.TableMetaDataRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataResult;
|
||||
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.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.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.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.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;
|
||||
@ -84,6 +90,7 @@ import static io.javalin.apibuilder.ApiBuilder.put;
|
||||
public class QJavalinImplementation
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QJavalinImplementation.class);
|
||||
|
||||
private static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
|
||||
|
||||
private static QInstance qInstance;
|
||||
@ -154,6 +161,7 @@ public class QJavalinImplementation
|
||||
path("/:table", () ->
|
||||
{
|
||||
get("", QJavalinImplementation::tableMetaData);
|
||||
// todo - process meta data - just under tables? or top-level too? maybe move tables to be under /tables/?
|
||||
});
|
||||
});
|
||||
path("/data", () ->
|
||||
@ -172,6 +180,14 @@ public class QJavalinImplementation
|
||||
});
|
||||
});
|
||||
});
|
||||
path("/processes", () ->
|
||||
{
|
||||
path("/:process", () ->
|
||||
{
|
||||
get("/init", QJavalinImplementation::processInit);
|
||||
get("/step", QJavalinImplementation::processStep);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -183,7 +199,7 @@ public class QJavalinImplementation
|
||||
private static void setupSession(Context context, AbstractQRequest request) throws QModuleDispatchException
|
||||
{
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData());
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData());
|
||||
|
||||
// todo - does this need some per-provider logic actually? mmm...
|
||||
Map<String, String> authenticationContext = new HashMap<>();
|
||||
@ -203,7 +219,7 @@ public class QJavalinImplementation
|
||||
{
|
||||
try
|
||||
{
|
||||
String table = context.pathParam("table");
|
||||
String table = context.pathParam("table");
|
||||
List<Serializable> primaryKeys = new ArrayList<>();
|
||||
primaryKeys.add(context.pathParam("primaryKey"));
|
||||
|
||||
@ -232,9 +248,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);
|
||||
|
||||
@ -276,9 +292,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);
|
||||
|
||||
@ -422,7 +438,6 @@ public class QJavalinImplementation
|
||||
else
|
||||
{
|
||||
LOG.warn("Exception in javalin request", e);
|
||||
e.printStackTrace();
|
||||
context.status(HttpStatus.INTERNAL_SERVER_ERROR_500)
|
||||
.result("{\"error\":\"" + e.getClass().getSimpleName() + " (" + e.getMessage() + ")\"}");
|
||||
}
|
||||
@ -462,4 +477,68 @@ public class QJavalinImplementation
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Init a process (named in path param :process)
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processInit(Context context) throws QException
|
||||
{
|
||||
RunProcessRequest runProcessRequest = new RunProcessRequest(qInstance);
|
||||
setupSession(context, runProcessRequest);
|
||||
runProcessRequest.setProcessName(context.pathParam("process"));
|
||||
runProcessRequest.setCallback(new QJavalinProcessCallback());
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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?? //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
///////////////////////////////////////////////////////
|
||||
// run the process //
|
||||
// todo - async? some "job id" to return to caller? //
|
||||
///////////////////////////////////////////////////////
|
||||
LOG.info("Running process [" + runProcessRequest.getProcessName() + "]");
|
||||
RunProcessResult runProcessResult = new RunProcessAction().execute(runProcessRequest);
|
||||
LOG.info("Process result error? " + runProcessResult.getError());
|
||||
for(QFieldMetaData outputField : qInstance.getProcess(runProcessRequest.getProcessName()).getOutputFields())
|
||||
{
|
||||
LOG.info("Process result output value: " + outputField.getName() + ": " + runProcessResult.getValues().get(outputField.getName()));
|
||||
}
|
||||
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
resultForCaller.put("error", runProcessResult.getError());
|
||||
resultForCaller.put("values", runProcessResult.getValues());
|
||||
context.result(JsonUtils.toJson(resultForCaller));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run a step in a process (named in path param :process)
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processStep(Context context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.backend.javalin;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QJavalinProcessCallback implements QProcessCallback
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QJavalinProcessCallback.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QQueryFilter getQueryFilter()
|
||||
{
|
||||
LOG.warn("Getting a query filter in javalin is NOT yet implemented");
|
||||
return (new QQueryFilter());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Map<String, Serializable> getFieldValues(List<QFieldMetaData> fields)
|
||||
{
|
||||
LOG.warn("Getting field values in javalin is NOT yet implemented");
|
||||
return (new HashMap<>());
|
||||
}
|
||||
}
|
@ -285,4 +285,43 @@ class QJavalinImplementationTest
|
||||
assertEquals(4, rowsFound);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processGreetInit() throws Exception
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init")
|
||||
.header("Content-Type", "application/json")
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertEquals("null X null", jsonObject.getJSONObject("values").getString("outputMessage"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process with field values on the query string
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processGreetInitWithQueryValues() throws Exception
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init?greetingPrefix=Hey&greetingSuffix=Jude")
|
||||
.header("Content-Type", "application/json")
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertEquals("Hey X Jude", jsonObject.getJSONObject("values").getString("outputMessage"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,13 +25,23 @@ package com.kingsrook.qqq.backend.javalin;
|
||||
import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.interfaces.mock.MockFunctionBody;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
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.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QOutputView;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListView;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
@ -52,9 +62,9 @@ public class TestUtils
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void primeTestDatabase() throws Exception
|
||||
{
|
||||
ConnectionManager connectionManager = new ConnectionManager();
|
||||
Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(TestUtils.defineBackend()));
|
||||
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
|
||||
ConnectionManager connectionManager = new ConnectionManager();
|
||||
Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
|
||||
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
|
||||
assertNotNull(primeTestDatabaseSqlStream);
|
||||
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
|
||||
lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
|
||||
@ -74,7 +84,7 @@ public class TestUtils
|
||||
public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
|
||||
{
|
||||
ConnectionManager connectionManager = new ConnectionManager();
|
||||
Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(defineBackend()));
|
||||
Connection connection = connectionManager.getConnection(defineBackend());
|
||||
QueryManager.executeStatement(connection, sql, resultSetProcessor);
|
||||
}
|
||||
|
||||
@ -90,6 +100,7 @@ public class TestUtils
|
||||
qInstance.setAuthentication(defineAuthentication());
|
||||
qInstance.addBackend(defineBackend());
|
||||
qInstance.addTable(defineTablePerson());
|
||||
qInstance.addProcess(defineProcessGreetPeople());
|
||||
return (qInstance);
|
||||
}
|
||||
|
||||
@ -112,16 +123,16 @@ public class TestUtils
|
||||
** Define the h2 rdbms backend
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QBackendMetaData defineBackend()
|
||||
public static RDBMSBackendMetaData defineBackend()
|
||||
{
|
||||
return new QBackendMetaData()
|
||||
.withName("default")
|
||||
.withType("rdbms")
|
||||
.withValue("vendor", "h2")
|
||||
.withValue("hostName", "mem")
|
||||
.withValue("databaseName", "test_database")
|
||||
.withValue("username", "sa")
|
||||
.withValue("password", "");
|
||||
RDBMSBackendMetaData rdbmsBackendMetaData = new RDBMSBackendMetaData()
|
||||
.withVendor("h2")
|
||||
.withHostName("mem")
|
||||
.withDatabaseName("test_database")
|
||||
.withUsername("sa")
|
||||
.withPassword("");
|
||||
rdbmsBackendMetaData.setName("default");
|
||||
return (rdbmsBackendMetaData);
|
||||
}
|
||||
|
||||
|
||||
@ -146,4 +157,38 @@ public class TestUtils
|
||||
.withField(new QFieldMetaData("email", QFieldType.STRING));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the 'greet people' process
|
||||
*******************************************************************************/
|
||||
private static QProcessMetaData defineProcessGreetPeople()
|
||||
{
|
||||
return new QProcessMetaData()
|
||||
.withName("greet")
|
||||
.withTableName("person")
|
||||
.addFunction(new QFunctionMetaData()
|
||||
.withName("prepare")
|
||||
.withCode(new QCodeReference()
|
||||
.withName(MockFunctionBody.class.getName())
|
||||
.withCodeType(QCodeType.JAVA)
|
||||
.withCodeUsage(QCodeUsage.FUNCTION)) // todo - needed, or implied in this context?
|
||||
.withInputData(new QFunctionInputMetaData()
|
||||
.withRecordListMetaData(new QRecordListMetaData().withTableName("person"))
|
||||
.withFieldList(List.of(
|
||||
new QFieldMetaData("greetingPrefix", QFieldType.STRING),
|
||||
new QFieldMetaData("greetingSuffix", QFieldType.STRING)
|
||||
)))
|
||||
.withOutputMetaData(new QFunctionOutputMetaData()
|
||||
.withRecordListMetaData(new QRecordListMetaData()
|
||||
.withTableName("person")
|
||||
.addField(new QFieldMetaData("fullGreeting", QFieldType.STRING))
|
||||
)
|
||||
.withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING))))
|
||||
.withOutputView(new QOutputView()
|
||||
.withMessageField("outputMessage")
|
||||
.withRecordListView(new QRecordListView().withFieldNames(List.of("id", "firstName", "lastName", "fullGreeting"))))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user