Initial code checkin

This commit is contained in:
Darin Kelkhoff
2021-11-08 21:19:06 -06:00
parent 2fe4ef5b24
commit aa56e52024
7 changed files with 906 additions and 0 deletions

View File

@ -0,0 +1,272 @@
package com.kingsrook.qqq.backend.javalin;
import com.kingsrook.qqq.backend.core.actions.MetaDataAction;
import com.kingsrook.qqq.backend.core.actions.QueryAction;
import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.MetaDataRequest;
import com.kingsrook.qqq.backend.core.model.actions.MetaDataResult;
import com.kingsrook.qqq.backend.core.model.actions.QueryRequest;
import com.kingsrook.qqq.backend.core.model.actions.QueryResult;
import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataRequest;
import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataResult;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
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.Javalin;
import io.javalin.apibuilder.EndpointGroup;
import io.javalin.http.Context;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jetty.http.HttpStatus;
import static io.javalin.apibuilder.ApiBuilder.delete;
import static io.javalin.apibuilder.ApiBuilder.get;
import static io.javalin.apibuilder.ApiBuilder.patch;
import static io.javalin.apibuilder.ApiBuilder.path;
import static io.javalin.apibuilder.ApiBuilder.post;
/*******************************************************************************
**
*******************************************************************************/
public class QJavalinImplementation
{
private static final Logger LOG = LogManager.getLogger(QJavalinImplementation.class);
private static QInstance qInstance;
private static int PORT = 8001;
/*******************************************************************************
**
*******************************************************************************/
public static void main(String[] args)
{
QInstance qInstance = new QInstance();
// todo - parse args to look up metaData and prime instance
// qInstance.addBackend(QMetaDataProvider.getQBackend());
new QJavalinImplementation(qInstance).startJavalinServer(PORT);
}
/*******************************************************************************
**
*******************************************************************************/
public QJavalinImplementation(QInstance qInstance)
{
QJavalinImplementation.qInstance = qInstance;
}
/*******************************************************************************
** Setter for qInstance
**
*******************************************************************************/
public static void setQInstance(QInstance qInstance)
{
QJavalinImplementation.qInstance = qInstance;
}
/*******************************************************************************
**
*******************************************************************************/
void startJavalinServer(int port)
{
// todo port from arg
// todo base path from arg?
Javalin service = Javalin.create().start(port);
service.routes(getRoutes());
}
/*******************************************************************************
**
*******************************************************************************/
public EndpointGroup getRoutes()
{
return (() ->
{
path("/metaData", () ->
{
get("/", QJavalinImplementation::metaData);
path("/:table", () ->
{
get("", QJavalinImplementation::tableMetaData);
});
});
path("/data", () ->
{
path("/:table", () ->
{
get("/", QJavalinImplementation::dataQuery);
post("/", QJavalinImplementation::dataInsert);
path("/:id", () ->
{
get("", QJavalinImplementation::dataGet);
patch("", QJavalinImplementation::dataUpdate);
delete("", QJavalinImplementation::dataDelete);
});
});
});
});
}
/*******************************************************************************
**
*******************************************************************************/
private static void dataDelete(Context context)
{
context.result("{\"deleteResult\":{}}");
}
/*******************************************************************************
**
*******************************************************************************/
private static void dataUpdate(Context context)
{
context.result("{\"updateResult\":{}}");
}
/*******************************************************************************
**
*******************************************************************************/
private static void dataInsert(Context context)
{
context.result("{\"insertResult\":{}}");
}
/*******************************************************************************
**
********************************************************************************/
private static void dataGet(Context context)
{
context.result("{\"getResult\":{}}");
}
/*******************************************************************************
**
*******************************************************************************/
static void dataQuery(Context context)
{
try
{
QueryRequest queryRequest = new QueryRequest(qInstance);
queryRequest.setTableName(context.pathParam("table"));
queryRequest.setSkip(integerQueryParam(context, "skip"));
queryRequest.setLimit(integerQueryParam(context, "limit"));
QueryAction queryAction = new QueryAction();
QueryResult queryResult = queryAction.execute(queryRequest);
context.result(JsonUtils.toJson(queryResult));
}
catch(Exception e)
{
handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static void metaData(Context context)
{
try
{
MetaDataRequest metaDataRequest = new MetaDataRequest(qInstance);
MetaDataAction metaDataAction = new MetaDataAction();
MetaDataResult metaDataResult = metaDataAction.execute(metaDataRequest);
context.result(JsonUtils.toJson(metaDataResult));
}
catch(Exception e)
{
handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void tableMetaData(Context context)
{
try
{
TableMetaDataRequest tableMetaDataRequest = new TableMetaDataRequest(qInstance);
tableMetaDataRequest.setTableName(context.pathParam("table"));
TableMetaDataAction tableMetaDataAction = new TableMetaDataAction();
TableMetaDataResult tableMetaDataResult = tableMetaDataAction.execute(tableMetaDataRequest);
context.result(JsonUtils.toJson(tableMetaDataResult));
}
catch(Exception e)
{
handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void handleException(Context context, Exception e)
{
QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class);
if(userFacingException != null)
{
LOG.info("User-facing exception", e);
context.status(HttpStatus.INTERNAL_SERVER_ERROR_500)
.result("{\"error\":\"" + userFacingException.getMessage() + "\"}");
}
else
{
LOG.warn("Exception in javalin request", e);
e.printStackTrace();
context.status(HttpStatus.INTERNAL_SERVER_ERROR_500)
.result("{\"error\":\"" + e.getClass().getSimpleName() + "\"}");
}
}
/*******************************************************************************
** Returns Integer if context has a valid int query parameter by the given name,
* Returns null if no param (or empty value).
* Throws NumberFormatException for malformed numbers.
*******************************************************************************/
private static Integer integerQueryParam(Context context, String name) throws NumberFormatException
{
String value = context.queryParam(name);
if(StringUtils.hasContent(value))
{
return (Integer.parseInt(value));
}
return (null);
}
}

View File

@ -0,0 +1,133 @@
package com.kingsrook.qqq.backend.javalin;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import org.eclipse.jetty.http.HttpStatus;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** based on https://javalin.io/tutorials/testing
**
*******************************************************************************/
class QJavalinImplementationTest
{
private static final int PORT = 6262;
private static final String BASE_URL = "http://localhost:" + PORT;
/*******************************************************************************
**
*******************************************************************************/
@BeforeAll
public static void beforeAll() throws Exception
{
QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(TestUtils.defineInstance());
qJavalinImplementation.startJavalinServer(PORT);
}
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
public void beforeEach() throws Exception
{
TestUtils.primeTestDatabase();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void test_metaData()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/metaData").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertTrue(jsonObject.has("tables"));
JSONObject tables = jsonObject.getJSONObject("tables");
assertEquals(1, tables.length());
JSONObject table0 = tables.getJSONObject("person");
assertTrue(table0.has("name"));
assertEquals("person", table0.getString("name"));
assertTrue(table0.has("label"));
assertEquals("Person", table0.getString("label"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void test_tableMetaData()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/metaData/person").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertEquals(1, jsonObject.keySet().size(), "Number of top-level keys");
JSONObject table = jsonObject.getJSONObject("table");
assertEquals(4, table.keySet().size(), "Number of mid-level keys");
assertEquals("person", table.getString("name"));
assertEquals("Person", table.getString("label"));
assertEquals("id", table.getString("primaryKeyField"));
JSONObject fields = table.getJSONObject("fields");
JSONObject field0 = fields.getJSONObject("id");
assertEquals("id", field0.getString("name"));
assertEquals("INTEGER", field0.getString("type"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void test_tableMetaData_notFound()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/metaData/notAnActualTable").asString();
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR_500, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertEquals(1, jsonObject.keySet().size(), "Number of top-level keys");
String error = jsonObject.getString("error");
assertTrue(error.contains("not found"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void test_dataQuery()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertTrue(jsonObject.has("records"));
JSONArray records = jsonObject.getJSONArray("records");
assertEquals(5, records.length());
JSONObject record0 = records.getJSONObject(0);
assertTrue(record0.has("values"));
assertEquals("person", record0.getString("tableName"));
assertTrue(record0.has("primaryKey"));
JSONObject values0 = record0.getJSONObject("values");
assertTrue(values0.has("firstName"));
}
}

View File

@ -0,0 +1,93 @@
package com.kingsrook.qqq.backend.javalin;
import java.io.InputStream;
import java.sql.Connection;
import java.util.List;
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.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.module.rdbms.RDBSMBackendMetaData;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import org.apache.commons.io.IOUtils;
import static junit.framework.Assert.assertNotNull;
/*******************************************************************************
**
*******************************************************************************/
public class TestUtils
{
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public static void primeTestDatabase() throws Exception
{
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(new RDBSMBackendMetaData(TestUtils.defineBackend()));
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
assertNotNull(primeTestDatabaseSqlStream);
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
String joinedSQL = String.join("\n", lines);
for(String sql : joinedSQL.split(";"))
{
QueryManager.executeUpdate(connection, sql);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static QInstance defineInstance()
{
QInstance qInstance = new QInstance();
qInstance.addBackend(defineBackend());
qInstance.addTable(defineTablePerson());
return (qInstance);
}
/*******************************************************************************
**
*******************************************************************************/
public static QBackendMetaData defineBackend()
{
return new QBackendMetaData()
.withName("default")
.withType("rdbms")
.withValue("vendor", "h2")
.withValue("hostName", "mem")
.withValue("databaseName", "test_database")
.withValue("username", "sa")
.withValue("password", "");
}
/*******************************************************************************
**
*******************************************************************************/
public static QTableMetaData defineTablePerson()
{
return new QTableMetaData()
.withName("person")
.withLabel("Person")
.withBackendName(defineBackend().getName())
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date"))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date"))
.withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name"))
.withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name"))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
.withField(new QFieldMetaData("email", QFieldType.STRING));
}
}

View File

@ -0,0 +1,18 @@
DROP TABLE IF EXISTS person;
CREATE TABLE person
(
id SERIAL,
create_date TIMESTAMP DEFAULT now(),
modify_date TIMESTAMP DEFAULT now(),
first_name VARCHAR(80) NOT NULL,
last_name VARCHAR(80) NOT NULL,
birth_date DATE,
email VARCHAR(250) NOT NULL
);
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com');
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com');
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com');
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (4, 'Tyler', 'Samples', '1990-01-01', 'tsamples@mmltholdings.com');
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com');