QQQ-14 Initial version of running processes in javalin

This commit is contained in:
2022-06-28 12:20:53 -05:00
parent 5c9a12bf5f
commit f6e28d5e31
4 changed files with 252 additions and 23 deletions

View File

@ -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.InsertAction;
import com.kingsrook.qqq.backend.core.actions.MetaDataAction; import com.kingsrook.qqq.backend.core.actions.MetaDataAction;
import com.kingsrook.qqq.backend.core.actions.QueryAction; 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.TableMetaDataAction;
import com.kingsrook.qqq.backend.core.actions.UpdateAction; import com.kingsrook.qqq.backend.core.actions.UpdateAction;
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter; 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.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractQRequest; 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.MetaDataResult;
import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataRequest; 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.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.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest; 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.query.QueryResult;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest; 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.update.UpdateResult;
import com.kingsrook.qqq.backend.core.model.data.QRecord; 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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.QAuthenticationModuleDispatcher; import com.kingsrook.qqq.backend.core.modules.QAuthenticationModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface; 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.ExceptionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -84,6 +90,7 @@ import static io.javalin.apibuilder.ApiBuilder.put;
public class QJavalinImplementation public class QJavalinImplementation
{ {
private static final Logger LOG = LogManager.getLogger(QJavalinImplementation.class); private static final Logger LOG = LogManager.getLogger(QJavalinImplementation.class);
private static final int SESSION_COOKIE_AGE = 60 * 60 * 24; private static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
private static QInstance qInstance; private static QInstance qInstance;
@ -154,6 +161,7 @@ public class QJavalinImplementation
path("/:table", () -> path("/:table", () ->
{ {
get("", QJavalinImplementation::tableMetaData); get("", QJavalinImplementation::tableMetaData);
// todo - process meta data - just under tables? or top-level too? maybe move tables to be under /tables/?
}); });
}); });
path("/data", () -> path("/data", () ->
@ -172,6 +180,14 @@ public class QJavalinImplementation
}); });
}); });
}); });
path("/processes", () ->
{
path("/:process", () ->
{
get("/init", QJavalinImplementation::processInit);
get("/step", QJavalinImplementation::processStep);
});
});
}); });
} }
@ -422,7 +438,6 @@ public class QJavalinImplementation
else else
{ {
LOG.warn("Exception in javalin request", e); LOG.warn("Exception in javalin request", e);
e.printStackTrace();
context.status(HttpStatus.INTERNAL_SERVER_ERROR_500) context.status(HttpStatus.INTERNAL_SERVER_ERROR_500)
.result("{\"error\":\"" + e.getClass().getSimpleName() + " (" + e.getMessage() + ")\"}"); .result("{\"error\":\"" + e.getClass().getSimpleName() + " (" + e.getMessage() + ")\"}");
} }
@ -462,4 +477,68 @@ public class QJavalinImplementation
return (null); 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)
{
}
} }

View File

@ -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<>());
}
}

View File

@ -285,4 +285,43 @@ class QJavalinImplementationTest
assertEquals(4, rowsFound); 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"));
}
} }

View File

@ -25,13 +25,23 @@ package com.kingsrook.qqq.backend.javalin;
import java.io.InputStream; import java.io.InputStream;
import java.sql.Connection; import java.sql.Connection;
import java.util.List; 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.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; 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.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; 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.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.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -53,7 +63,7 @@ public class TestUtils
public static void primeTestDatabase() throws Exception public static void primeTestDatabase() throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(TestUtils.defineBackend())); Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql"); InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
assertNotNull(primeTestDatabaseSqlStream); assertNotNull(primeTestDatabaseSqlStream);
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream); List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
@ -74,7 +84,7 @@ public class TestUtils
public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(defineBackend())); Connection connection = connectionManager.getConnection(defineBackend());
QueryManager.executeStatement(connection, sql, resultSetProcessor); QueryManager.executeStatement(connection, sql, resultSetProcessor);
} }
@ -90,6 +100,7 @@ public class TestUtils
qInstance.setAuthentication(defineAuthentication()); qInstance.setAuthentication(defineAuthentication());
qInstance.addBackend(defineBackend()); qInstance.addBackend(defineBackend());
qInstance.addTable(defineTablePerson()); qInstance.addTable(defineTablePerson());
qInstance.addProcess(defineProcessGreetPeople());
return (qInstance); return (qInstance);
} }
@ -112,16 +123,16 @@ public class TestUtils
** Define the h2 rdbms backend ** Define the h2 rdbms backend
** **
*******************************************************************************/ *******************************************************************************/
public static QBackendMetaData defineBackend() public static RDBMSBackendMetaData defineBackend()
{ {
return new QBackendMetaData() RDBMSBackendMetaData rdbmsBackendMetaData = new RDBMSBackendMetaData()
.withName("default") .withVendor("h2")
.withType("rdbms") .withHostName("mem")
.withValue("vendor", "h2") .withDatabaseName("test_database")
.withValue("hostName", "mem") .withUsername("sa")
.withValue("databaseName", "test_database") .withPassword("");
.withValue("username", "sa") rdbmsBackendMetaData.setName("default");
.withValue("password", ""); return (rdbmsBackendMetaData);
} }
@ -146,4 +157,38 @@ public class TestUtils
.withField(new QFieldMetaData("email", QFieldType.STRING)); .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"))))
);
}
} }