diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000..fadf2cda --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..180d2ad4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,134 @@ + + + 4.0.0 + + com.kingsrook.qqq + qqq-backend-core + 0.0-SNAPSHOT + + + + + + + UTF-8 + UTF-8 + 17 + 17 + true + true + + + + + + + + + com.fasterxml.jackson.core + jackson-databind + 2.13.0 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.13.0 + + + org.json + json + 20210307 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + org.apache.logging.log4j + log4j-api + 2.14.1 + + + org.apache.logging.log4j + log4j-core + 2.14.1 + + + org.junit.jupiter + junit-jupiter-engine + 5.8.1 + test + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + -Xlint:unchecked + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + com.puppycrawl.tools + checkstyle + 9.0 + + + + + validate + validate + + checkstyle.xml + + UTF-8 + true + false + true + warning + **/target/generated-sources/*.* + + + + check + + + + + + + + + + diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/InsertAction.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/InsertAction.java new file mode 100644 index 00000000..c4b7eefb --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/InsertAction.java @@ -0,0 +1,32 @@ +package com.kingsrook.qqq.backend.core.actions; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.InsertRequest; +import com.kingsrook.qqq.backend.core.model.actions.InsertResult; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.modules.QModuleDispatcher; +import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class InsertAction +{ + /******************************************************************************* + ** + *******************************************************************************/ + public InsertResult execute(InsertRequest insertRequest) throws QException + { + QModuleDispatcher qModuleDispatcher = new QModuleDispatcher(); + + QBackendMetaData backend = insertRequest.getBackend(); + + QModuleInterface qModule = qModuleDispatcher.getQModule(backend); + // todo pre-customization - just get to modify the request? + InsertResult insertResult = qModule.getInsertInterface().execute(insertRequest); + // todo post-customization - can do whatever w/ the result if you want + return insertResult; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/QueryAction.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/QueryAction.java new file mode 100644 index 00000000..3a39f101 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/QueryAction.java @@ -0,0 +1,32 @@ +package com.kingsrook.qqq.backend.core.actions; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +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.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.modules.QModuleDispatcher; +import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QueryAction +{ + /******************************************************************************* + ** + *******************************************************************************/ + public QueryResult execute(QueryRequest queryRequest) throws QException + { + QModuleDispatcher qModuleDispatcher = new QModuleDispatcher(); + + QBackendMetaData backend = queryRequest.getBackend(); + + QModuleInterface qModule = qModuleDispatcher.getQModule(backend); + // todo pre-customization - just get to modify the request? + QueryResult queryResult = qModule.getQueryInterface().execute(queryRequest); + // todo post-customization - can do whatever w/ the result if you want + return queryResult; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapter.java b/src/main/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapter.java new file mode 100644 index 00000000..bf54b9af --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapter.java @@ -0,0 +1,50 @@ +package com.kingsrook.qqq.backend.core.adapters; + + +import com.kingsrook.qqq.backend.core.model.actions.AbstractQFieldMapping; +import com.kingsrook.qqq.backend.core.model.actions.QKeyBasedFieldMapping; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import org.json.JSONException; +import org.json.JSONObject; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class JsonToQFieldMappingAdapter +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public AbstractQFieldMapping buildMappingFromJson(String json) + { + if(!StringUtils.hasContent(json)) + { + throw (new IllegalArgumentException("Empty json value was provided.")); + } + + try + { + JSONObject jsonObject = JsonUtils.toJSONObject(json); + + ////////////////////////////////////////////////////////////////////////////////////////////// + // look at the keys in the mapping - if they're strings, then we're doing key-based mapping // + // if they're numbers, then we're doing index based -- and if they're a mix, that's illegal // + ////////////////////////////////////////////////////////////////////////////////////////////// + + QKeyBasedFieldMapping mapping = new QKeyBasedFieldMapping(); + for(String key : jsonObject.keySet()) + { + mapping.addMapping(key, jsonObject.getString(key)); + } + return (mapping); + } + catch(JSONException je) + { + throw (new IllegalArgumentException("Malformed JSON value: " + je.getMessage(), je)); + } + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapter.java b/src/main/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapter.java new file mode 100644 index 00000000..d4fdb33e --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapter.java @@ -0,0 +1,84 @@ +package com.kingsrook.qqq.backend.core.adapters; + + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class JsonToQRecordAdapter +{ + + /******************************************************************************* + ** todo - meta-data validation, mapping, type handling + *******************************************************************************/ + public List buildRecordsFromJson(String json) + { + if(!StringUtils.hasContent(json)) + { + throw (new IllegalArgumentException("Empty json value was provided.")); + } + + List rs = new ArrayList<>(); + try + { + if(JsonUtils.looksLikeObject(json)) + { + JSONObject jsonObject = JsonUtils.toJSONObject(json); + rs.add(buildRecordFromJsonObject(jsonObject)); + } + else if(JsonUtils.looksLikeArray(json)) + { + JSONArray jsonArray = JsonUtils.toJSONArray(json); + for(Object object : jsonArray) + { + if(object instanceof JSONObject jsonObject) + { + rs.add(buildRecordFromJsonObject(jsonObject)); + } + else + { + throw (new IllegalArgumentException("Element at index " + rs.size() + " in json array was not a json object.")); + } + } + } + else + { + throw (new IllegalArgumentException("Malformed JSON value - did not start with '{' or '['.")); + } + } + catch(JSONException je) + { + throw (new IllegalArgumentException("Malformed JSON value: " + je.getMessage(), je)); + } + + return (rs); + } + + + + /******************************************************************************* + ** todo - meta-data validation, mapping, type handling + *******************************************************************************/ + private QRecord buildRecordFromJsonObject(JSONObject jsonObject) + { + QRecord record = new QRecord(); + + for(String key : jsonObject.keySet()) + { + record.setValue(key, (Serializable) jsonObject.get(key)); + } + + return (record); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QException.java b/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QException.java new file mode 100644 index 00000000..68f2ffcb --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QException.java @@ -0,0 +1,27 @@ +package com.kingsrook.qqq.backend.core.exceptions; + + +/******************************************************************************* + * Base class for checked exceptions thrown in qqq. + * + *******************************************************************************/ +public class QException extends Exception +{ + /******************************************************************************* + ** + *******************************************************************************/ + public QException(String message) + { + super(message); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QModuleDispatchException.java b/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QModuleDispatchException.java new file mode 100644 index 00000000..5caef40c --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QModuleDispatchException.java @@ -0,0 +1,27 @@ +package com.kingsrook.qqq.backend.core.exceptions; + + +/******************************************************************************* + * Base class for checked exceptions thrown in qqq. + * + *******************************************************************************/ +public class QModuleDispatchException extends QException +{ + /******************************************************************************* + ** + *******************************************************************************/ + public QModuleDispatchException(String message) + { + super(message); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QModuleDispatchException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/QInstance.java b/src/main/java/com/kingsrook/qqq/backend/core/model/QInstance.java new file mode 100644 index 00000000..70c219da --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/QInstance.java @@ -0,0 +1,143 @@ +package com.kingsrook.qqq.backend.core.model; + + +import java.util.HashMap; +import java.util.Map; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QInstance +{ + private Map backends = new HashMap<>(); + private Map tables = new HashMap<>(); + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QBackendMetaData getBackendForTable(String tableName) + { + QTableMetaData table = tables.get(tableName); + if(table == null) + { + throw (new IllegalArgumentException("No table with name [" + tableName + "] found in this instance.")); + } + + QBackendMetaData backend = backends.get(table.getBackendName()); + if(backend == null) + { + throw (new IllegalArgumentException("Table [" + tableName + "] specified a backend name [" + table.getBackendName() + "] which was found in this instance.")); + } + + return (backend); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addBackend(QBackendMetaData backend) + { + this.backends.put(backend.getName(), backend); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addBackend(String name, QBackendMetaData backend) + { + this.backends.put(name, backend); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QBackendMetaData getBackend(String name) + { + return (this.backends.get(name)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addTable(QTableMetaData table) + { + this.tables.put(table.getName(), table); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addTable(String name, QTableMetaData table) + { + this.tables.put(name, table); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QTableMetaData getTable(String name) + { + return (this.tables.get(name)); + } + + + + /******************************************************************************* + ** Getter for backends + ** + *******************************************************************************/ + public Map getBackends() + { + return backends; + } + + + + /******************************************************************************* + ** Setter for backends + ** + *******************************************************************************/ + public void setBackends(Map backends) + { + this.backends = backends; + } + + + + /******************************************************************************* + ** Getter for tables + ** + *******************************************************************************/ + public Map getTables() + { + return tables; + } + + + + /******************************************************************************* + ** Setter for tables + ** + *******************************************************************************/ + public void setTables(Map tables) + { + this.tables = tables; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQFieldMapping.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQFieldMapping.java new file mode 100644 index 00000000..f5c6cb89 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQFieldMapping.java @@ -0,0 +1,13 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +/******************************************************************************* + ** + *******************************************************************************/ +public abstract class AbstractQFieldMapping +{ + /******************************************************************************* + ** Returns the fieldName for the input 'key' or 'index'. + *******************************************************************************/ + public abstract String getMappedField(T t); +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQRequest.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQRequest.java new file mode 100644 index 00000000..4481202c --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQRequest.java @@ -0,0 +1,63 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import com.kingsrook.qqq.backend.core.model.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public abstract class AbstractQRequest +{ + protected QInstance instance; + // todo session + + + + /******************************************************************************* + ** + *******************************************************************************/ + public abstract QBackendMetaData getBackend(); + + + + /******************************************************************************* + ** + *******************************************************************************/ + public AbstractQRequest() + { + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public AbstractQRequest(QInstance instance) + { + this.instance = instance; + } + + + + /******************************************************************************* + ** Getter for instance + ** + *******************************************************************************/ + public QInstance getInstance() + { + return instance; + } + + + + /******************************************************************************* + ** Setter for instance + ** + *******************************************************************************/ + public void setInstance(QInstance instance) + { + this.instance = instance; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQResult.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQResult.java new file mode 100644 index 00000000..d94bf557 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQResult.java @@ -0,0 +1,17 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +/******************************************************************************* + ** + *******************************************************************************/ +public abstract class AbstractQResult +{ + // todo - status codes? + + /******************************************************************************* + ** + *******************************************************************************/ + public AbstractQResult() + { + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQTableRequest.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQTableRequest.java new file mode 100644 index 00000000..2bb30f0a --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/AbstractQTableRequest.java @@ -0,0 +1,76 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import com.kingsrook.qqq.backend.core.model.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public abstract class AbstractQTableRequest extends AbstractQRequest +{ + private String tableName; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QBackendMetaData getBackend() + { + return (instance.getBackendForTable(getTableName())); + } + + + /******************************************************************************* + ** + *******************************************************************************/ + public QTableMetaData getTable() + { + return (instance.getTable(getTableName())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public AbstractQTableRequest() + { + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public AbstractQTableRequest(QInstance instance) + { + super(instance); + } + + + + /******************************************************************************* + ** Getter for tableName + ** + *******************************************************************************/ + public String getTableName() + { + return tableName; + } + + + + /******************************************************************************* + ** Setter for tableName + ** + *******************************************************************************/ + public void setTableName(String tableName) + { + this.tableName = tableName; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/InsertRequest.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/InsertRequest.java new file mode 100644 index 00000000..68d2ebde --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/InsertRequest.java @@ -0,0 +1,56 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.QInstance; +import com.kingsrook.qqq.backend.core.model.data.QRecord; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class InsertRequest extends AbstractQTableRequest +{ + private List records; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public InsertRequest() + { + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public InsertRequest(QInstance instance) + { + super(instance); + } + + + + /******************************************************************************* + ** Getter for records + ** + *******************************************************************************/ + public List getRecords() + { + return records; + } + + + + /******************************************************************************* + ** Setter for records + ** + *******************************************************************************/ + public void setRecords(List records) + { + this.records = records; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/InsertResult.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/InsertResult.java new file mode 100644 index 00000000..ff16c26d --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/InsertResult.java @@ -0,0 +1,35 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.data.QRecordWithStatus; + + +/******************************************************************************* + * Result for a query action + * + *******************************************************************************/ +public class InsertResult extends AbstractQResult +{ + List records; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List getRecords() + { + return records; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setRecords(List records) + { + this.records = records; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QCriteriaOperator.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QCriteriaOperator.java new file mode 100644 index 00000000..4ae04f6c --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QCriteriaOperator.java @@ -0,0 +1,12 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +/******************************************************************************* + ** + *******************************************************************************/ +public enum QCriteriaOperator +{ + EQUALS, + NOT_EQUALS, + IN +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QFilterCriteria.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QFilterCriteria.java new file mode 100644 index 00000000..763bcbf3 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QFilterCriteria.java @@ -0,0 +1,118 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import java.io.Serializable; +import java.util.List; + + +/******************************************************************************* + * Component of a Query + * + *******************************************************************************/ +public class QFilterCriteria +{ + private String fieldName; + private QCriteriaOperator operator; + private List values; + + + + /******************************************************************************* + ** Getter for fieldName + ** + *******************************************************************************/ + public String getFieldName() + { + return fieldName; + } + + + + /******************************************************************************* + ** Setter for fieldName + ** + *******************************************************************************/ + public void setFieldName(String fieldName) + { + this.fieldName = fieldName; + } + + + + /******************************************************************************* + ** Setter for fieldName + ** + *******************************************************************************/ + public QFilterCriteria withFieldName(String fieldName) + { + this.fieldName = fieldName; + return this; + } + + + + /******************************************************************************* + ** Getter for operator + ** + *******************************************************************************/ + public QCriteriaOperator getOperator() + { + return operator; + } + + + + /******************************************************************************* + ** Setter for operator + ** + *******************************************************************************/ + public void setOperator(QCriteriaOperator operator) + { + this.operator = operator; + } + + + + /******************************************************************************* + ** Setter for operator + ** + *******************************************************************************/ + public QFilterCriteria withOperator(QCriteriaOperator operator) + { + this.operator = operator; + return this; + } + + + + /******************************************************************************* + ** Getter for values + ** + *******************************************************************************/ + public List getValues() + { + return values; + } + + + + /******************************************************************************* + ** Setter for values + ** + *******************************************************************************/ + public void setValues(List values) + { + this.values = values; + } + + + /******************************************************************************* + ** Setter for values + ** + *******************************************************************************/ + public QFilterCriteria withValues(List values) + { + this.values = values; + return this; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QFilterOrderBy.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QFilterOrderBy.java new file mode 100644 index 00000000..c808c08f --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QFilterOrderBy.java @@ -0,0 +1,55 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QFilterOrderBy +{ + private String fieldName; + private boolean isAscending = true; + + + + /******************************************************************************* + ** Getter for fieldName + ** + *******************************************************************************/ + public String getFieldName() + { + return fieldName; + } + + + + /******************************************************************************* + ** Setter for fieldName + ** + *******************************************************************************/ + public void setFieldName(String fieldName) + { + this.fieldName = fieldName; + } + + + + /******************************************************************************* + ** Getter for isAscending + ** + *******************************************************************************/ + public boolean getIsAscending() + { + return isAscending; + } + + + + /******************************************************************************* + ** Setter for isAscending + ** + *******************************************************************************/ + public void setIsAscending(boolean ascending) + { + isAscending = ascending; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QIndexBasedFieldMapping.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QIndexBasedFieldMapping.java new file mode 100644 index 00000000..d68c31c9 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QIndexBasedFieldMapping.java @@ -0,0 +1,78 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import java.util.LinkedHashMap; +import java.util.Map; + + +/******************************************************************************* + ** Note; 1-based index!! + *******************************************************************************/ +public class QIndexBasedFieldMapping extends AbstractQFieldMapping +{ + private Map mapping; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public String getMappedField(Integer key) + { + if(mapping == null) + { + return (null); + } + + return (mapping.get(key)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addMapping(Integer key, String fieldName) + { + if(mapping == null) + { + mapping = new LinkedHashMap<>(); + } + mapping.put(key, fieldName); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QIndexBasedFieldMapping withMapping(Integer key, String fieldName) + { + addMapping(key, fieldName); + return (this); + } + + + + /******************************************************************************* + ** Getter for mapping + ** + *******************************************************************************/ + public Map getMapping() + { + return mapping; + } + + + + /******************************************************************************* + ** Setter for mapping + ** + *******************************************************************************/ + public void setMapping(Map mapping) + { + this.mapping = mapping; + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QKeyBasedFieldMapping.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QKeyBasedFieldMapping.java new file mode 100644 index 00000000..08a1cd11 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QKeyBasedFieldMapping.java @@ -0,0 +1,78 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import java.util.LinkedHashMap; +import java.util.Map; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QKeyBasedFieldMapping extends AbstractQFieldMapping +{ + private Map mapping; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public String getMappedField(String key) + { + if(mapping == null) + { + return (null); + } + + return (mapping.get(key)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addMapping(String key, String fieldName) + { + if(mapping == null) + { + mapping = new LinkedHashMap<>(); + } + mapping.put(key, fieldName); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QKeyBasedFieldMapping withMapping(String key, String fieldName) + { + addMapping(key, fieldName); + return (this); + } + + + + /******************************************************************************* + ** Getter for mapping + ** + *******************************************************************************/ + public Map getMapping() + { + return mapping; + } + + + + /******************************************************************************* + ** Setter for mapping + ** + *******************************************************************************/ + public void setMapping(Map mapping) + { + this.mapping = mapping; + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QQueryFilter.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QQueryFilter.java new file mode 100644 index 00000000..730baf1f --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QQueryFilter.java @@ -0,0 +1,111 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import java.util.ArrayList; +import java.util.List; + + +/******************************************************************************* + * Full "filter" for a query - a list of criteria and order-bys + * + *******************************************************************************/ +public class QQueryFilter +{ + private List criteria = new ArrayList<>(); + private List orderBys = new ArrayList<>(); + + + + /******************************************************************************* + ** Getter for criteria + ** + *******************************************************************************/ + public List getCriteria() + { + return criteria; + } + + + + /******************************************************************************* + ** Setter for criteria + ** + *******************************************************************************/ + public void setCriteria(List criteria) + { + this.criteria = criteria; + } + + + + /******************************************************************************* + ** Getter for order + ** + *******************************************************************************/ + public List getOrderBys() + { + return orderBys; + } + + + + /******************************************************************************* + ** Setter for order + ** + *******************************************************************************/ + public void setOrderBys(List orderBys) + { + this.orderBys = orderBys; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addCriteria(QFilterCriteria qFilterCriteria) + { + if(criteria == null) + { + criteria = new ArrayList<>(); + } + criteria.add(qFilterCriteria); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QQueryFilter withCriteria(QFilterCriteria qFilterCriteria) + { + addCriteria(qFilterCriteria); + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addOrderBy(QFilterOrderBy qFilterOrderBy) + { + if(orderBys == null) + { + orderBys = new ArrayList<>(); + } + orderBys.add(qFilterOrderBy); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QQueryFilter withOrderBy(QFilterOrderBy qFilterOrderBy) + { + addOrderBy(qFilterOrderBy); + return (this); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QueryRequest.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QueryRequest.java new file mode 100644 index 00000000..ba871fdf --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QueryRequest.java @@ -0,0 +1,101 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import com.kingsrook.qqq.backend.core.model.QInstance; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QueryRequest extends AbstractQTableRequest +{ + private QQueryFilter filter; + private Integer skip; + private Integer limit; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QueryRequest() + { + System.out.println("In the Query Request void-constructor!"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QueryRequest(QInstance instance) + { + super(instance); + } + + + + /******************************************************************************* + ** Getter for filter + ** + *******************************************************************************/ + public QQueryFilter getFilter() + { + return filter; + } + + + + /******************************************************************************* + ** Setter for filter + ** + *******************************************************************************/ + public void setFilter(QQueryFilter filter) + { + this.filter = filter; + } + + + + /******************************************************************************* + ** Getter for skip + ** + *******************************************************************************/ + public Integer getSkip() + { + return skip; + } + + + + /******************************************************************************* + ** Setter for skip + ** + *******************************************************************************/ + public void setSkip(Integer skip) + { + this.skip = skip; + } + + + + /******************************************************************************* + ** Getter for limit + ** + *******************************************************************************/ + public Integer getLimit() + { + return limit; + } + + + + /******************************************************************************* + ** Setter for limit + ** + *******************************************************************************/ + public void setLimit(Integer limit) + { + this.limit = limit; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QueryResult.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QueryResult.java new file mode 100644 index 00000000..b9cbb8f5 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/QueryResult.java @@ -0,0 +1,35 @@ +package com.kingsrook.qqq.backend.core.model.actions; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.data.QRecord; + + +/******************************************************************************* + * Result for a query action + * + *******************************************************************************/ +public class QueryResult extends AbstractQResult +{ + List records; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List getRecords() + { + return records; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setRecords(List records) + { + this.records = records; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java new file mode 100644 index 00000000..550635a3 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java @@ -0,0 +1,133 @@ +package com.kingsrook.qqq.backend.core.model.data; + + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + + +/******************************************************************************* + * Data Record within qqq + * + *******************************************************************************/ +public class QRecord +{ + private String tableName; + private Serializable primaryKey; + private Map values; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setValue(String fieldName, Serializable value) + { + if(values == null) + { + values = new LinkedHashMap<>(); + } + + values.put(fieldName, value); + } + + + + /******************************************************************************* + ** Getter for tableName + ** + *******************************************************************************/ + public String getTableName() + { + return tableName; + } + + + + /******************************************************************************* + ** Setter for tableName + ** + *******************************************************************************/ + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + + + /******************************************************************************* + ** Getter for primaryKey + ** + *******************************************************************************/ + public Serializable getPrimaryKey() + { + return primaryKey; + } + + + + /******************************************************************************* + ** Setter for primaryKey + ** + *******************************************************************************/ + public void setPrimaryKey(Serializable primaryKey) + { + this.primaryKey = primaryKey; + } + + + + /******************************************************************************* + ** Getter for values + ** + *******************************************************************************/ + public Map getValues() + { + return values; + } + + + + /******************************************************************************* + ** Setter for values + ** + *******************************************************************************/ + public void setValues(Map values) + { + this.values = values; + } + + + + /******************************************************************************* + ** Getter for a single field's value + ** + *******************************************************************************/ + public Serializable getValue(String fieldName) + { + return (values.get(fieldName)); + } + + + + /******************************************************************************* + ** Getter for a single field's value + ** + *******************************************************************************/ + public String getValueString(String fieldName) + { + return ((String) values.get(fieldName)); + } + + + + /******************************************************************************* + ** Getter for a single field's value + ** + *******************************************************************************/ + public Integer getValueInteger(String fieldName) + { + return ((Integer) values.get(fieldName)); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordWithStatus.java b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordWithStatus.java new file mode 100644 index 00000000..93134b46 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordWithStatus.java @@ -0,0 +1,56 @@ +package com.kingsrook.qqq.backend.core.model.data; + + +import java.util.List; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QRecordWithStatus extends QRecord +{ + private List errors; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QRecordWithStatus() + { + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QRecordWithStatus(QRecord record) + { + super.setTableName(record.getTableName()); + super.setPrimaryKey(record.getPrimaryKey()); + super.setValues(record.getValues()); + } + + + + /******************************************************************************* + ** Getter for errors + ** + *******************************************************************************/ + public List getErrors() + { + return errors; + } + + + + /******************************************************************************* + ** Setter for errors + ** + *******************************************************************************/ + public void setErrors(List errors) + { + this.errors = errors; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java new file mode 100644 index 00000000..a4d14204 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java @@ -0,0 +1,155 @@ +package com.kingsrook.qqq.backend.core.model.metadata; + + +import java.util.HashMap; +import java.util.Map; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QBackendMetaData +{ + private String name; + private String type; + private Map values; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public String getValue(String key) + { + if(values == null) + { + return null; + } + return values.get(key); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setValue(String key, String value) + { + if(values == null) + { + values = new HashMap<>(); + } + values.put(key, value); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QBackendMetaData withValue(String key, String value) + { + if(values == null) + { + values = new HashMap<>(); + } + values.put(key, value); + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public String getName() + { + return name; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setName(String name) + { + this.name = name; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QBackendMetaData withName(String name) + { + this.name = name; + return (this); + } + + + + /******************************************************************************* + ** Getter for type + ** + *******************************************************************************/ + public String getType() + { + return type; + } + + + + /******************************************************************************* + ** Setter for type + ** + *******************************************************************************/ + public void setType(String type) + { + this.type = type; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QBackendMetaData withType(String type) + { + this.type = type; + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Map getValues() + { + return values; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setValues(Map values) + { + this.values = values; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QBackendMetaData withVales(Map values) + { + this.values = values; + return (this); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldMetaData.java new file mode 100644 index 00000000..13f3db3b --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldMetaData.java @@ -0,0 +1,163 @@ +package com.kingsrook.qqq.backend.core.model.metadata; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QFieldMetaData +{ + private String name; + private String label; + private String backendName; + private QFieldType type; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFieldMetaData() + { + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFieldMetaData(String name, QFieldType type) + { + this.name = name; + this.type = type; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public String getName() + { + return name; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setName(String name) + { + this.name = name; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFieldMetaData withName(String name) + { + this.name = name; + return (this); + } + + + + /******************************************************************************* + ** Getter for type + ** + *******************************************************************************/ + public QFieldType getType() + { + return type; + } + + + + /******************************************************************************* + ** Setter for type + ** + *******************************************************************************/ + public void setType(QFieldType type) + { + this.type = type; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFieldMetaData withType(QFieldType type) + { + this.type = type; + return (this); + } + + + + /******************************************************************************* + ** Getter for label + ** + *******************************************************************************/ + public String getLabel() + { + return label; + } + + + + /******************************************************************************* + ** Setter for label + ** + *******************************************************************************/ + public void setLabel(String label) + { + this.label = label; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFieldMetaData withLabel(String label) + { + this.label = label; + return (this); + } + + + + /******************************************************************************* + ** Getter for backendName + ** + *******************************************************************************/ + public String getBackendName() + { + return backendName; + } + + + + /******************************************************************************* + ** Setter for backendName + ** + *******************************************************************************/ + public void setBackendName(String backendName) + { + this.backendName = backendName; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFieldMetaData withBackendName(String backendName) + { + this.backendName = backendName; + return (this); + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldType.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldType.java new file mode 100644 index 00000000..1291e762 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldType.java @@ -0,0 +1,18 @@ +package com.kingsrook.qqq.backend.core.model.metadata; + + +/******************************************************************************* + ** + *******************************************************************************/ +public enum QFieldType +{ + STRING, + INTEGER, + DECIMAL, + DATE, + TIME, + DATE_TIME, + TEXT, + HTML, + PASSWORD +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableMetaData.java new file mode 100644 index 00000000..ed3540aa --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableMetaData.java @@ -0,0 +1,216 @@ +package com.kingsrook.qqq.backend.core.model.metadata; + + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QTableMetaData +{ + private String name; + private String backendName; + private String primaryKeyField; + private List fields; + private Map _fieldMap; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFieldMetaData getField(String fieldName) + { + if(fields == null) + { + throw (new IllegalArgumentException("Table [" + name + "] does not have its fields defined.")); + } + + QFieldMetaData field = getFieldMap().get(fieldName); + if(field == null) + { + throw (new IllegalArgumentException("Field [" + fieldName + "] was not found in table [" + name + "].")); + } + + return (field); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private Map getFieldMap() + { + if(_fieldMap == null) + { + _fieldMap = new LinkedHashMap<>(); + for(QFieldMetaData field : fields) + { + _fieldMap.put(field.getName(), field); + } + } + + return (_fieldMap); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public String getName() + { + return name; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setName(String name) + { + this.name = name; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QTableMetaData withName(String name) + { + this.name = name; + return (this); + } + + + + /******************************************************************************* + ** Getter for backendName + ** + *******************************************************************************/ + public String getBackendName() + { + return backendName; + } + + + + /******************************************************************************* + ** Setter for backendName + ** + *******************************************************************************/ + public void setBackendName(String backendName) + { + this.backendName = backendName; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QTableMetaData withBackendName(String backendName) + { + this.backendName = backendName; + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public String getPrimaryKeyField() + { + return primaryKeyField; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setPrimaryKeyField(String primaryKeyField) + { + this.primaryKeyField = primaryKeyField; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QTableMetaData withPrimaryKeyField(String primaryKeyField) + { + this.primaryKeyField = primaryKeyField; + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List getFields() + { + return fields; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setFields(List fields) + { + this.fields = fields; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QTableMetaData withFields(List fields) + { + this.fields = fields; + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addField(QFieldMetaData field) + { + if(this.fields == null) + { + this.fields = new ArrayList<>(); + } + this.fields.add(field); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QTableMetaData withField(QFieldMetaData field) + { + if(this.fields == null) + { + this.fields = new ArrayList<>(); + } + this.fields.add(field); + return (this); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/modules/QModuleDispatcher.java b/src/main/java/com/kingsrook/qqq/backend/core/modules/QModuleDispatcher.java new file mode 100644 index 00000000..774ebb69 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/modules/QModuleDispatcher.java @@ -0,0 +1,58 @@ +package com.kingsrook.qqq.backend.core.modules; + + +import java.util.HashMap; +import java.util.Map; +import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QModuleDispatcher +{ + private Map backendTypeToModuleClassNameMap; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QModuleDispatcher() + { + backendTypeToModuleClassNameMap = new HashMap<>(); + backendTypeToModuleClassNameMap.put("rdbms", "com.kingsrook.qqq.backend.module.rdbms.RDBSMModule"); + backendTypeToModuleClassNameMap.put("nosql", "com.kingsrook.qqq.backend.module.nosql.NoSQLModule"); + // todo - let user define custom type -> classes + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QModuleInterface getQModule(QBackendMetaData backend) throws QModuleDispatchException + { + try + { + String className = backendTypeToModuleClassNameMap.get(backend.getType()); + if (className == null) + { + throw (new QModuleDispatchException("Unrecognized backend type [" + backend.getType() + "] in dispatcher.")); + } + + Class moduleClass = Class.forName(className); + return (QModuleInterface) moduleClass.getDeclaredConstructor().newInstance(); + } + catch(QModuleDispatchException qmde) + { + throw (qmde); + } + catch(Exception e) + { + throw (new QModuleDispatchException("Error getting module", e)); + } + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/InsertInterface.java b/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/InsertInterface.java new file mode 100644 index 00000000..a6dc5967 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/InsertInterface.java @@ -0,0 +1,18 @@ +package com.kingsrook.qqq.backend.core.modules.interfaces; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.InsertRequest; +import com.kingsrook.qqq.backend.core.model.actions.InsertResult; + + +/******************************************************************************* + ** + *******************************************************************************/ +public interface InsertInterface +{ + /******************************************************************************* + ** + *******************************************************************************/ + InsertResult execute(InsertRequest insertRequest) throws QException; +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QModuleInterface.java b/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QModuleInterface.java new file mode 100644 index 00000000..b1429d9e --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QModuleInterface.java @@ -0,0 +1,21 @@ +package com.kingsrook.qqq.backend.core.modules.interfaces; + + +import com.kingsrook.qqq.backend.core.actions.InsertAction; + + +/******************************************************************************* + ** + *******************************************************************************/ +public interface QModuleInterface +{ + /******************************************************************************* + ** + *******************************************************************************/ + QueryInterface getQueryInterface(); + + /******************************************************************************* + ** + *******************************************************************************/ + InsertInterface getInsertInterface(); +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QueryInterface.java b/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QueryInterface.java new file mode 100644 index 00000000..29368444 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QueryInterface.java @@ -0,0 +1,18 @@ +package com.kingsrook.qqq.backend.core.modules.interfaces; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.QueryRequest; +import com.kingsrook.qqq.backend.core.model.actions.QueryResult; + + +/******************************************************************************* + ** + *******************************************************************************/ +public interface QueryInterface +{ + /******************************************************************************* + ** + *******************************************************************************/ + QueryResult execute(QueryRequest queryRequest) throws QException; +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java new file mode 100755 index 00000000..67e54e04 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java @@ -0,0 +1,257 @@ +package com.kingsrook.qqq.backend.core.utils; + + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class CollectionUtils +{ + /******************************************************************************* + ** true if c is null or it's empty + ** + *******************************************************************************/ + public static boolean nullSafeIsEmpty(Collection c) + { + if(c == null || c.isEmpty()) + { + return (true); + } + + return (false); + } + + + + /******************************************************************************* + ** true if c is NOT null and it's not empty + ** + *******************************************************************************/ + public static boolean nullSafeHasContents(Collection c) + { + return (!nullSafeIsEmpty(c)); + } + + + + /******************************************************************************* + ** 0 if c is empty, otherwise, its size. + ** + *******************************************************************************/ + public static int nullSafeSize(Collection c) + { + if(c == null) + { + return (0); + } + + return (c.size()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static void addAllToMap(Map addingTo, Map addingFrom) + { + for(K key : addingFrom.keySet()) + { + addingTo.put(key, addingFrom.get(key)); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static Map listToMap(List values, Function keyFunction) + { + if(values == null) + { + return (null); + } + + Map rs = new HashMap<>(); + for(V value : values) + { + rs.put(keyFunction.apply(value), value); + } + + return (rs); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static Map listToMap(List elements, Function keyFunction, Function valueFunction) + { + if(elements == null) + { + return (null); + } + + Map rs = new HashMap<>(); + for(E element : elements) + { + rs.put(keyFunction.apply(element), valueFunction.apply(element)); + } + + return (rs); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static ListingHash listToListingHash(List values, Function keyFunction) + { + if(values == null) + { + return (null); + } + + ListingHash rs = new ListingHash<>(); + for(V value : values) + { + rs.add(keyFunction.apply(value), value); + } + + return (rs); + } + + + + /******************************************************************************* + *

Take a list of objects, and build a listing hash, using 2 lambdas to control + * how keys in the listing hash are created (from the objects), and how values + * are created.

+ * + *

For example, given a list of Work records, if we want a Listing hash with + * workStatusId as keys, and workId a values, we would call:

+ * + * listToListingHash(workList, Work::getWorkStatusId, Work::getId) + * + * @param elements list of objects to be mapped + * @param keyFunction function to map an object from elements list into keys + * for the listing hash + * @param valueFunction function to map an object from elements list into values + * for the listing hash + * + * @return ListingHash that might look like: + * 1 -> [ 73, 75, 68] + * 2 -> [ 74 ] + * 4 -> [ 76, 77, 79, 80, 81] + * + * end + *******************************************************************************/ + public static ListingHash listToListingHash(List elements, Function keyFunction, Function valueFunction) + { + if(elements == null) + { + return (null); + } + + ListingHash rs = new ListingHash<>(); + for(E element : elements) + { + rs.add(keyFunction.apply(element), valueFunction.apply(element)); + } + + return (rs); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static Map> listTo2LevelMap(List values, Function keyFunction1, Function keyFunction2) + { + if(values == null) + { + return (null); + } + + Map> rs = new HashMap<>(); + for(V value : values) + { + K1 k1 = keyFunction1.apply(value); + if(!rs.containsKey(k1)) + { + rs.put(k1, new HashMap<>()); + } + rs.get(k1).put(keyFunction2.apply(value), value); + } + + return (rs); + } + + + + /******************************************************************************* + ** split a large collection into lists of lists, with specified pageSize + ** + *******************************************************************************/ + public static List> getPages(Collection values, int pageSize) + { + List> rs = new LinkedList<>(); + + if(values.isEmpty()) + { + ////////////////////////////////////////////////////////////////// + // if there are no input values, return an empty list of lists. // + ////////////////////////////////////////////////////////////////// + return (rs); + } + + List currentPage = new LinkedList(); + rs.add(currentPage); + + for(T value : values) + { + if(currentPage.size() >= pageSize) + { + currentPage = new LinkedList(); + rs.add(currentPage); + } + + currentPage.add(value); + } + + return (rs); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static String getQuestionMarks(Collection c) + { + if(CollectionUtils.nullSafeIsEmpty(c)) + { + return (""); + } + + StringBuilder rs = new StringBuilder(); + for(int i = 0; i < c.size(); i++) + { + rs.append(i > 0 ? "," : "").append("?"); + } + + return (rs.toString()); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java new file mode 100644 index 00000000..ec386b8a --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java @@ -0,0 +1,119 @@ +package com.kingsrook.qqq.backend.core.utils; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + + +/******************************************************************************* + ** See: https://www.baeldung.com/jackson-vs-gson + ** + *******************************************************************************/ +public class JsonUtils +{ + private static final Logger LOG = LogManager.getLogger(JsonUtils.class); + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static String toJson(Object object) + { + try + { + ObjectMapper mapper = newObjectMapper(); + String jsonResult = mapper.writeValueAsString(object); + return (jsonResult); + } + catch(JsonProcessingException e) + { + LOG.error("Error serializing object of type [" + object.getClass().getSimpleName() + "] to json", e); + throw new IllegalArgumentException("Error in JSON Serialization", e); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static String toPrettyJson(Object object) + { + try + { + ObjectMapper mapper = newObjectMapper(); + ObjectWriter objectWriter = mapper.writerWithDefaultPrettyPrinter(); + String jsonResult = objectWriter.writeValueAsString(object); + return (jsonResult); + } + catch(JsonProcessingException e) + { + LOG.error("Error serializing object of type [" + object.getClass().getSimpleName() + "] to json", e); + throw new IllegalArgumentException("Error in JSON Serialization", e); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static JSONObject toJSONObject(String json) throws JSONException + { + JSONObject jsonObject = new JSONObject(json); + return (jsonObject); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static JSONArray toJSONArray(String json) throws JSONException + { + JSONArray jsonArray = new JSONArray(json); + return (jsonArray); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static ObjectMapper newObjectMapper() + { + ObjectMapper mapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + return (mapper); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static boolean looksLikeObject(String json) + { + return (json != null && json.matches("(?s)\\s*\\{.*")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static boolean looksLikeArray(String json) + { + return (json != null && json.matches("(?s)\\s*\\[.*")); + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/ListingHash.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/ListingHash.java new file mode 100755 index 00000000..4b39f0c5 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/ListingHash.java @@ -0,0 +1,267 @@ +package com.kingsrook.qqq.backend.core.utils; + + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + + +/******************************************************************************* + ** Hash that provides "listing" capability -- keys map to lists of values that + ** are automatically/easily added to + ** + *******************************************************************************/ +public class ListingHash implements Map>, Serializable +{ + public static final long serialVersionUID = 0L; + + private HashMap> hashMap = null; + + + + /******************************************************************************* + ** Default constructor + ** + *******************************************************************************/ + public ListingHash() + { + this.hashMap = new HashMap>(); + } + + + + /******************************************************************************* + ** Add a value to the entry/list for this key + ** + *******************************************************************************/ + public List add(K key, V value) + { + List list = getOrCreateListForKey(key); + list.add(value); + return (list); + } + + + + /******************************************************************************* + ** Add all elements of the collection of v's to this listing hash, using keys + ** generated by passing each v to the supplied keyFunction (which return's K's) + ** + *******************************************************************************/ + public void addAll(Collection vs, Function keyFunction) + { + if(vs == null || keyFunction == null) + { + return; + } + + for(V v : vs) + { + add(keyFunction.apply(v), v); + } + } + + + + /******************************************************************************* + ** Add multiple values to the entry/list for this key + ** + *******************************************************************************/ + public List addAll(K key, Collection values) + { + List list = getOrCreateListForKey(key); + list.addAll(values); + return (list); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addAll(ListingHash that) + { + if(that == null) + { + return; + } + + for(K key : that.keySet()) + { + addAll(key, that.get(key)); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private List getOrCreateListForKey(K key) + { + List list; + + if(!this.hashMap.containsKey(key)) + { + ///////////////////////////////// + // create list, place into map // + ///////////////////////////////// + list = new LinkedList(); + this.hashMap.put(key, list); + } + else + { + list = this.hashMap.get(key); + } + return list; + } + + ///////////////////////////////////////////////////////////////////////// + // wrappings of methods taken from the internal HashMap of this object // + ///////////////////////////////////////////////////////////////////////// + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void clear() + { + this.hashMap.clear(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public boolean containsKey(Object key) + { + return (this.hashMap.containsKey(key)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public boolean containsValue(Object value) + { + return (this.hashMap.containsValue(value)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Set>> entrySet() + { + return (this.hashMap.entrySet()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public boolean equals(Object o) + { + return (this.hashMap.equals(o)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List get(Object key) + { + return (this.hashMap.get(key)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public int hashCode() + { + return (this.hashMap.hashCode()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public boolean isEmpty() + { + return (this.hashMap.isEmpty()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Set keySet() + { + return (this.hashMap.keySet()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List put(K key, List value) + { + return (this.hashMap.put(key, value)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void putAll(Map> t) + { + this.hashMap.putAll(t); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List remove(Object key) + { + return (this.hashMap.remove(key)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public int size() + { + return (this.hashMap.size()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Collection> values() + { + return (this.hashMap.values()); + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java new file mode 100755 index 00000000..c5bd430e --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java @@ -0,0 +1,373 @@ +package com.kingsrook.qqq.backend.core.utils; + + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class StringUtils +{ + /******************************************************************************* + ** test if string is not null and is not empty (after being trimmed). + ** + ** @param input the string to test + ** @return Boolean + *******************************************************************************/ + public static Boolean hasContent(String input) + { + if(input != null && !input.trim().equals("")) + { + return true; + } + return false; + } + + + + /******************************************************************************* + ** returns input.toString() if not null, or nullOutput if input == null (as in SQL NVL) + ** + *******************************************************************************/ + public static String nvl(Object input, String nullOutput) + { + if(input == null) + { + return nullOutput; + } + return input.toString(); + } + + + + /******************************************************************************* + ** returns input if not null, or nullOutput if input == null (as in SQL NVL) + ** + *******************************************************************************/ + public static String nvl(String input, String nullOutput) + { + if(input == null) + { + return nullOutput; + } + return input; + } + + + + /******************************************************************************* + ** allCapsToMixedCase - ie, UNIT CODE -> Unit Code + ** + ** @param input + ** @return + *******************************************************************************/ + public static String allCapsToMixedCase(String input) + { + if(input == null) + { + return (null); + } + + StringBuilder rs = new StringBuilder(); + + /////////////////////////////////////////////////////////////////////// + // match for 0 or more non-capitals (which will pass through as-is), // + // then one capital (which will be kept uppercase), // + // then 0 or more capitals (which will be lowercased), // + // then 0 or more non-capitals (which will pass through as-is) // + // // + // Example matches are: // + // "UNIT CODE" -> ()(U)(NIT)( ), then ()(C)(ODE)() -> (Unit )(Code) // + // "UNITCODE" -> ()(U)(NITCODE)(), -> (Unitcode) // + // "UnitCode" -> ()(U)()(nit), then ()(C)()(ode) -> (Unit)(Code) // + // "UNIT0CODE" -> ()(U)(NIT)(0), then ()(C)(ODE)() -> (Unit0)(Code) // + // "0UNITCODE" -> (0)(U)(NITCODE)() -> (0Unitcode) // + /////////////////////////////////////////////////////////////////////// + Pattern pattern = Pattern.compile("([^A-Z]*)([A-Z])([A-Z]*)([^A-Z]*)"); + Matcher matcher = pattern.matcher(input); + while(matcher.find()) + { + rs.append(matcher.group(1) != null ? matcher.group(1) : ""); + rs.append(matcher.group(2) != null ? matcher.group(2) : ""); + rs.append(matcher.group(3) != null ? matcher.group(3).toLowerCase() : ""); + rs.append(matcher.group(4) != null ? matcher.group(4) : ""); + } + + /////////////////////////////////////////////////////////////////////// + // just in case no match was found, return the original input string // + /////////////////////////////////////////////////////////////////////// + if(rs.length() == 0) + { + return (input); + } + + return (rs.toString()); + } + + + + /******************************************************************************* + * truncate a string (null safely) at a max length. + * + *******************************************************************************/ + public static String safeTruncate(String input, int maxLength) + { + if(input == null) + { + return (null); + } + + if(input.length() <= maxLength) + { + return (input); + } + + return (input.substring(0, maxLength)); + } + + + + /******************************************************************************* + * + *******************************************************************************/ + public static String safeTruncate(String input, int maxLength, String suffix) + { + if(input == null) + { + return (null); + } + + if(input.length() <= maxLength) + { + return (input); + } + + return (input.substring(0, (maxLength - suffix.length())) + suffix); + } + + + + /******************************************************************************* + ** returns input if not null, or nullOutput if input == null (as in SQL NVL) + ** + *******************************************************************************/ + public static String safeTrim(String input) + { + if(input == null) + { + return null; + } + return input.trim(); + } + + + + /******************************************************************************* + ** Join a collection of objects into 1 string + ** + ** @param glue - String to insert between entries + ** @param collection - The collection of objects to join. + ** @return String + *******************************************************************************/ + public static String join(String glue, Collection collection) + { + StringBuffer rs = new StringBuffer(); + + int i = 0; + for(Object s : collection) + { + if(i++ > 0) + { + rs.append(glue); + } + rs.append(String.valueOf(s)); + } + + return (rs.toString()); + } + + + + /******************************************************************************* + ** joinWithCommasAndAnd + ** + ** [one] => [one] + ** [one, two] => [one and two] + ** [one, two, three] => [one, two, and three] + ** [one, two, three, four] => [one, two, three, and four] + ** etc. + ** + ** @param input + ** @return + *******************************************************************************/ + public static String joinWithCommasAndAnd(List input) + { + StringBuilder rs = new StringBuilder(); + int size = input.size(); + + for(int i = 0; i < size; i++) + { + if(i > 0 && size == 2) + { + rs.append(" and "); + } + else if(i > 0 && i < size - 1) + { + rs.append(", "); + } + else if(i > 0 && i == size - 1) + { + rs.append(", and "); + } + rs.append(input.get(i)); + } + + return (rs.toString()); + + } + + + + /******************************************************************************* + ** isNullOrEmptyString + ** + ** @param input + *******************************************************************************/ + private static boolean isNullOrBlankString(Object input) + { + if(input == null) + { + return (true); + } + else if(input instanceof String && !StringUtils.hasContent((String) input)) + { + return (true); + } + else + { + return (false); + } + } + + + + /******************************************************************************* + ** Split a string into tokens, broken up around a given regular expression pattern. + ** + ** @param original the string to do the splitting to. + ** @param pattern the pattern to split on. + ** @return a LinkedList with the elements found in the original string + *******************************************************************************/ + public static LinkedList split(String original, String pattern) + { + return (new LinkedList(Arrays.asList(original.split(pattern)))); + } + + + + /******************************************************************************* + ** Split a string into tokens, broken up around a given regular expression pattern. + ** + ** @param original the string to do the splitting to. + ** @param pattern the pattern to split on. + ** @param limit the max number of splits to make + ** @return a List with the elements found in the original string + *******************************************************************************/ + public static LinkedList split(String original, String pattern, Integer limit) + { + return (new LinkedList(Arrays.asList(original.split(pattern, limit)))); + } + + + + /******************************************************************************* + ** Trims leading white spaces from a String. Returns a blank ("") value if NULL + ** + ** @param input + ** @return String + *******************************************************************************/ + public static String ltrim(String input) + { + if(!hasContent(input)) + { + return ""; + } + + int i = 0; + while(i < input.length() && Character.isWhitespace(input.charAt(i))) + { + i++; + } + return (input.substring(i)); + } + + + + /******************************************************************************* + ** Trims trailing white spaces from a String. Returns a blank ("") value if NULL + ** + ** @param input + ** @return String + *******************************************************************************/ + public static String rtrim(String input) + { + if(!hasContent(input)) + { + return ""; + } + + int i = input.length() - 1; + while(i > 0 && Character.isWhitespace(input.charAt(i))) + { + i--; + } + return (input.substring(0, i + 1)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static String plural(Collection collection) + { + return (plural(collection == null ? 0 : collection.size())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static String plural(Integer size) + { + return (plural(size, "", "s")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static String plural(Collection collection, String ifOne, String ifNotOne) + { + return (plural(collection.size(), ifOne, ifNotOne)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static String plural(Integer size, String ifOne, String ifNotOne) + { + return (size != null && size.equals(1) ? ifOne : ifNotOne); + } + +} diff --git a/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapterTest.java b/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapterTest.java new file mode 100644 index 00000000..1bd256dd --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapterTest.java @@ -0,0 +1,93 @@ +package com.kingsrook.qqq.backend.core.adapters; + + +import com.kingsrook.qqq.backend.core.model.actions.AbstractQFieldMapping; +import com.kingsrook.qqq.backend.core.model.actions.QKeyBasedFieldMapping; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + + +/******************************************************************************* + ** + *******************************************************************************/ +class JsonToQFieldMappingAdapterTest +{ + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildMappingFromJson_nullInput() + { + testExpectedToThrow(null); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildMappingFromJson_emptyStringInput() + { + testExpectedToThrow(""); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildMappingFromJson_malformedJsonInput() + { + testExpectedToThrow("{foo=bar}"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildMappingFromJson_validInput() + { + JsonToQFieldMappingAdapter jsonToQFieldMappingAdapter = new JsonToQFieldMappingAdapter(); + AbstractQFieldMapping mapping = (QKeyBasedFieldMapping) jsonToQFieldMappingAdapter.buildMappingFromJson(""" + { + "Field1": "source1", + "Field2": "source2", + } + """); + System.out.println(mapping); + assertNotNull(mapping); + + // todo - are we backwards here?? + assertEquals("source1", mapping.getMappedField("Field1")); + assertEquals("source2", mapping.getMappedField("Field2")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void testExpectedToThrow(String json) + { + try + { + JsonToQFieldMappingAdapter jsonToQFieldMappingAdapter = new JsonToQFieldMappingAdapter(); + AbstractQFieldMapping mapping = jsonToQFieldMappingAdapter.buildMappingFromJson(json); + System.out.println(mapping); + } + catch(IllegalArgumentException iae) + { + System.out.println("Threw expected exception"); + return; + } + + fail("Didn't throw expected exception"); + } + +} \ No newline at end of file diff --git a/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapterTest.java b/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapterTest.java new file mode 100644 index 00000000..21923faa --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapterTest.java @@ -0,0 +1,149 @@ +package com.kingsrook.qqq.backend.core.adapters; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + + +/******************************************************************************* + ** + *******************************************************************************/ +class JsonToQRecordAdapterTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildRecordsFromJson_nullInput() + { + testExpectedToThrow(null); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildRecordsFromJson_emptyStringInput() + { + testExpectedToThrow(""); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildRecordsFromJson_inputDoesntLookLikeJson() + { + testExpectedToThrow(""); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildRecordsFromJson_inputLooksLikeJsonButIsMalformed() + { + testExpectedToThrow("{json=not}"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void testExpectedToThrow(String json) + { + try + { + JsonToQRecordAdapter jsonToQRecordAdapter = new JsonToQRecordAdapter(); + List qRecords = jsonToQRecordAdapter.buildRecordsFromJson(json); + System.out.println(qRecords); + } + catch(IllegalArgumentException iae) + { + System.out.println("Threw expected exception"); + return; + } + + fail("Didn't throw expected exception"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildRecordsFromJson_emptyList() + { + JsonToQRecordAdapter jsonToQRecordAdapter = new JsonToQRecordAdapter(); + List qRecords = jsonToQRecordAdapter.buildRecordsFromJson("[]"); + assertNotNull(qRecords); + assertTrue(qRecords.isEmpty()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildRecordsFromJson_inputObject() + { + JsonToQRecordAdapter jsonToQRecordAdapter = new JsonToQRecordAdapter(); + List qRecords = jsonToQRecordAdapter.buildRecordsFromJson(""" + { + "field1":"value1", + "field2":"value2" + } + """); + assertNotNull(qRecords); + assertEquals(1, qRecords.size()); + assertEquals("value1", qRecords.get(0).getValue("field1")); + assertEquals("value2", qRecords.get(0).getValue("field2")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildRecordsFromJson_inputList() + { + JsonToQRecordAdapter jsonToQRecordAdapter = new JsonToQRecordAdapter(); + List qRecords = jsonToQRecordAdapter.buildRecordsFromJson(""" + [ + { "field1":"value1", "field2":"value2" }, + { "fieldA":"valueA", "fieldB":"valueB" } + ] + """); + assertNotNull(qRecords); + assertEquals(2, qRecords.size()); + assertEquals("value1", qRecords.get(0).getValue("field1")); + assertEquals("value2", qRecords.get(0).getValue("field2")); + assertEquals("valueA", qRecords.get(1).getValue("fieldA")); + assertEquals("valueB", qRecords.get(1).getValue("fieldB")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_buildRecordsFromJson_inputListWithNonObjectMembers() + { + testExpectedToThrow("[ 1701 ]"); + } + +} \ No newline at end of file diff --git a/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java b/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java new file mode 100644 index 00000000..cd66e44b --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java @@ -0,0 +1,179 @@ +package com.kingsrook.qqq.backend.core.utils; + + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + + +/******************************************************************************* + ** + *******************************************************************************/ +class JsonUtilsTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_toJsonQRecordInput() + { + QRecord qRecord = getQRecord(); + String json = JsonUtils.toJson(qRecord); + assertEquals("{\"tableName\":\"foo\",\"primaryKey\":1,\"values\":{\"foo\":\"Foo\",\"bar\":3.14159}}", json); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_toPrettyJsonQRecordInput() + { + QRecord qRecord = getQRecord(); + String json = JsonUtils.toPrettyJson(qRecord); + // todo assertEquals("{\"tableName\":\"foo\",\"primaryKey\":1,\"values\":{\"foo\":\"Foo\",\"bar\":3.14159}}", json); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_toJSONObject() + { + JSONObject jsonObject = JsonUtils.toJSONObject(""" + { + "Foo": "Bar", + "Baz": [1, 2, 3] + } + """); + assertNotNull(jsonObject); + assertTrue(jsonObject.has("Foo")); + assertEquals("Bar", jsonObject.getString("Foo")); + assertTrue(jsonObject.has("Baz")); + assertEquals(3, jsonObject.getJSONArray("Baz").length()); + assertEquals(1, jsonObject.getJSONArray("Baz").get(0)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_toJSONObjectNonJsonObject() + { + try + { + JsonUtils.toJSONObject(""); + } + catch(JSONException je) + { + System.out.println("Caught Expected exception"); + return; + } + + fail("Did not catch expected exception"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_toJSONArray() + { + JSONArray jsonArray = JsonUtils.toJSONArray(""" + [ + {"Foo": "Bar"}, + {"Baz": [1, 2, 3]} + ] + """); + assertNotNull(jsonArray); + assertEquals(2, jsonArray.length()); + assertTrue(jsonArray.getJSONObject(0).has("Foo")); + assertEquals("Bar", jsonArray.getJSONObject(0).getString("Foo")); + assertTrue(jsonArray.getJSONObject(1).has("Baz")); + assertEquals(3, jsonArray.getJSONObject(1).getJSONArray("Baz").length()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private QRecord getQRecord() + { + QRecord qRecord = new QRecord(); + qRecord.setTableName("foo"); + qRecord.setPrimaryKey(1); + Map values = new LinkedHashMap<>(); + qRecord.setValues(values); + values.put("foo", "Foo"); + values.put("bar", new BigDecimal("3.14159")); + return qRecord; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_toJsonNullInput() + { + String json = JsonUtils.toJson(null); + assertEquals("null", json); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_looksLikeObject() + { + assertFalse(JsonUtils.looksLikeObject("")); + assertFalse(JsonUtils.looksLikeObject(null)); + assertFalse(JsonUtils.looksLikeObject("json")); + assertFalse(JsonUtils.looksLikeObject("[]")); + assertTrue(JsonUtils.looksLikeObject("{}")); + assertTrue(JsonUtils.looksLikeObject(" {}")); + assertTrue(JsonUtils.looksLikeObject("\n\n\n{}")); + assertTrue(JsonUtils.looksLikeObject("\n{\n[]\n}\n")); + assertTrue(JsonUtils.looksLikeObject("\n\n\n { \n}\n\n\n")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_looksLikeArray() + { + assertFalse(JsonUtils.looksLikeArray("")); + assertFalse(JsonUtils.looksLikeArray(null)); + assertFalse(JsonUtils.looksLikeArray("json")); + assertFalse(JsonUtils.looksLikeArray("{json[]}")); + assertFalse(JsonUtils.looksLikeArray("{}")); + assertTrue(JsonUtils.looksLikeArray("[]")); + assertTrue(JsonUtils.looksLikeArray(" []")); + assertTrue(JsonUtils.looksLikeArray("\n\n\n[]")); + assertTrue(JsonUtils.looksLikeArray("\n[\n{}\n}\n")); + assertTrue(JsonUtils.looksLikeArray("\n\n\n [ \n]\n\n\n")); + } + +} \ No newline at end of file