Checkpoint: Added Update (edit) action; Stub of PossibleValues, displayValues; Load definition from JSON

This commit is contained in:
Darin Kelkhoff
2022-03-01 18:27:43 -06:00
parent ce22916d20
commit f58a59438b
27 changed files with 1313 additions and 55 deletions

View File

@ -0,0 +1,35 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.actions;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
/*******************************************************************************
** Action to update one or more records.
**
*******************************************************************************/
public class UpdateAction
{
/*******************************************************************************
**
*******************************************************************************/
public UpdateResult execute(UpdateRequest updateRequest) throws QException
{
ActionHelper.validateSession(updateRequest);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQModule(updateRequest.getBackend());
// todo pre-customization - just get to modify the request?
UpdateResult updateResult = qModule.getUpdateInterface().execute(updateRequest);
// todo post-customization - can do whatever w/ the result if you want
return updateResult;
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.adapters;
import java.io.IOException;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import org.json.JSONObject;
/*******************************************************************************
** Methods for adapting qInstances to serialized (string) formats (e.g., json),
** and vice versa.
*******************************************************************************/
public class QInstanceAdapter
{
/*******************************************************************************
** Convert a qInstance to JSON.
**
*******************************************************************************/
public String qInstanceToJson(QInstance qInstance)
{
return (JsonUtils.toJson(qInstance));
}
/*******************************************************************************
** Convert a qInstance to JSON.
**
*******************************************************************************/
public String qInstanceToJsonIncludingBackend(QInstance qInstance)
{
String jsonString = JsonUtils.toJson(qInstance);
JSONObject jsonObject = JsonUtils.toJSONObject(jsonString);
String backendsJsonString = JsonUtils.toJson(qInstance.getBackends());
JSONObject backendsJsonObject = JsonUtils.toJSONObject(backendsJsonString);
jsonObject.put("backends", backendsJsonObject);
return (jsonObject.toString());
}
/*******************************************************************************
** Build a qInstance from JSON.
**
*******************************************************************************/
public QInstance jsonToQInstance(String json) throws IOException
{
return (JsonUtils.toObject(json, QInstance.class));
}
/*******************************************************************************
** Build a qInstance from JSON.
**
*******************************************************************************/
public QInstance jsonToQInstanceIncludingBackends(String json) throws IOException
{
QInstance qInstance = JsonUtils.toObject(json, QInstance.class);
JSONObject jsonObject = JsonUtils.toJSONObject(json);
JSONObject backendsJsonObject = jsonObject.getJSONObject("backends");
Map<String, QBackendMetaData> backends = JsonUtils.toObject(backendsJsonObject.toString(), new TypeReference<>()
{
});
qInstance.setBackends(backends);
return qInstance;
}
}

View File

@ -60,27 +60,58 @@ public class QInstanceValidator
List<String> errors = new ArrayList<>();
try
{
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getBackends()), "At least 1 backend must be defined."))
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getBackends()),
"At least 1 backend must be defined."))
{
qInstance.getBackends().forEach((backendName, backend) ->
{
assertCondition(errors, Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
assertCondition(errors, Objects.equals(backendName, backend.getName()),
"Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
});
}
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getTables()), "At least 1 table must be defined."))
/////////////////////////
// validate the tables //
/////////////////////////
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getTables()),
"At least 1 table must be defined."))
{
qInstance.getTables().forEach((tableName, table) ->
{
assertCondition(errors, Objects.equals(tableName, table.getName()), "Inconsistent naming for table: " + tableName + "/" + table.getName() + ".");
assertCondition(errors, Objects.equals(tableName, table.getName()),
"Inconsistent naming for table: " + tableName + "/" + table.getName() + ".");
if(assertCondition(errors, StringUtils.hasContent(table.getBackendName()), "Missing backend name for table " + tableName + "."))
////////////////////////////////////////
// validate the backend for the table //
////////////////////////////////////////
if(assertCondition(errors, StringUtils.hasContent(table.getBackendName()),
"Missing backend name for table " + tableName + "."))
{
if(CollectionUtils.nullSafeHasContents(qInstance.getBackends()))
{
assertCondition(errors, qInstance.getBackendForTable(tableName) != null, "Unrecognized backend " + table.getBackendName() + " for table " + tableName + ".");
assertCondition(errors, qInstance.getBackendForTable(tableName) != null,
"Unrecognized backend " + table.getBackendName() + " for table " + tableName + ".");
}
}
//////////////////////////////////
// validate fields in the table //
//////////////////////////////////
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(table.getFields()),
"At least 1 field must be defined in table " + tableName + "."))
{
table.getFields().forEach((fieldName, field) ->
{
assertCondition(errors, Objects.equals(fieldName, field.getName()),
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
if(field.getPossibleValueSourceName() != null)
{
assertCondition(errors, qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
}
});
}
});
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.model.actions.update;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractQTableRequest;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Request data handler for the update action
**
*******************************************************************************/
public class UpdateRequest extends AbstractQTableRequest
{
private List<QRecord> records;
/*******************************************************************************
**
*******************************************************************************/
public UpdateRequest()
{
}
/*******************************************************************************
**
*******************************************************************************/
public UpdateRequest(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for records
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return records;
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.records = records;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.model.actions.update;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractQResult;
import com.kingsrook.qqq.backend.core.model.data.QRecordWithStatus;
/*******************************************************************************
* Result for an update action
*
*******************************************************************************/
public class UpdateResult extends AbstractQResult
{
List<QRecordWithStatus> records;
/*******************************************************************************
**
*******************************************************************************/
public List<QRecordWithStatus> getRecords()
{
return records;
}
/*******************************************************************************
**
*******************************************************************************/
public void setRecords(List<QRecordWithStatus> records)
{
this.records = records;
}
}

View File

@ -13,12 +13,18 @@ import java.util.Map;
/*******************************************************************************
* Data Record within qqq. e.g., a single row from a database.
*
* Actual values (e.g., as stored in the backend system) are in the `values`
* map. Keys in this map are fieldNames from the QTableMetaData.
*
* "Display values" (e.g., labels for possible values, or formatted numbers
* (e.g., quantities with commas)) are in the displayValues map.
*******************************************************************************/
public class QRecord
{
private String tableName;
private Serializable primaryKey;
private Map<String, Serializable> values;
//x private Serializable primaryKey;
private Map<String, Serializable> values = new LinkedHashMap<>();
private Map<String, String> displayValues = new LinkedHashMap<>();
@ -27,11 +33,6 @@ public class QRecord
*******************************************************************************/
public void setValue(String fieldName, Serializable value)
{
if(values == null)
{
values = new LinkedHashMap<>();
}
values.put(fieldName, value);
}
@ -48,6 +49,27 @@ public class QRecord
/*******************************************************************************
**
*******************************************************************************/
public void setDisplayValue(String fieldName, String displayValue)
{
displayValues.put(fieldName, displayValue);
}
/*******************************************************************************
**
*******************************************************************************/
public QRecord withDisplayValue(String fieldName, String displayValue)
{
setDisplayValue(fieldName, displayValue);
return (this);
}
/*******************************************************************************
** Getter for tableName
**
@ -82,37 +104,37 @@ public class QRecord
/*******************************************************************************
** Getter for primaryKey
**
*******************************************************************************/
public Serializable getPrimaryKey()
{
return primaryKey;
}
//x /*******************************************************************************
//x ** Getter for primaryKey
//x **
//x *******************************************************************************/
//x public Serializable getPrimaryKey()
//x {
//x return primaryKey;
//x }
/*******************************************************************************
** Setter for primaryKey
**
*******************************************************************************/
public void setPrimaryKey(Serializable primaryKey)
{
this.primaryKey = primaryKey;
}
//x /*******************************************************************************
//x ** Setter for primaryKey
//x **
//x *******************************************************************************/
//x public void setPrimaryKey(Serializable primaryKey)
//x {
//x this.primaryKey = primaryKey;
//x }
/*******************************************************************************
** Setter for primaryKey
**
*******************************************************************************/
public QRecord withPrimaryKey(Serializable primaryKey)
{
this.primaryKey = primaryKey;
return (this);
}
//x /*******************************************************************************
//x ** Setter for primaryKey
//x **
//x *******************************************************************************/
//x public QRecord withPrimaryKey(Serializable primaryKey)
//x {
//x this.primaryKey = primaryKey;
//x return (this);
//x }
@ -149,6 +171,39 @@ public class QRecord
/*******************************************************************************
** Getter for displayValues
**
*******************************************************************************/
public Map<String, String> getDisplayValues()
{
return displayValues;
}
/*******************************************************************************
** Setter for displayValues
**
*******************************************************************************/
public void setDisplayValues(Map<String, String> displayValues)
{
this.displayValues = displayValues;
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public String getDisplayValue(String fieldName)
{
return (displayValues.get(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**

View File

@ -36,7 +36,6 @@ public class QRecordWithStatus extends QRecord
public QRecordWithStatus(QRecord record)
{
super.setTableName(record.getTableName());
super.setPrimaryKey(record.getPrimaryKey());
super.setValues(record.getValues());
}

View File

@ -16,6 +16,8 @@ public class QFieldMetaData
private String backendName;
private QFieldType type;
private String possibleValueSourceName;
/*******************************************************************************
@ -165,4 +167,36 @@ public class QFieldMetaData
this.backendName = backendName;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public String getPossibleValueSourceName()
{
return possibleValueSourceName;
}
/*******************************************************************************
**
*******************************************************************************/
public void setPossibleValueSourceName(String possibleValueSourceName)
{
this.possibleValueSourceName = possibleValueSourceName;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withPossibleValueSourceName(String possibleValueSourceName)
{
this.possibleValueSourceName = possibleValueSourceName;
return (this);
}
}

View File

@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
@ -30,9 +31,11 @@ public class QInstance
private QAuthenticationMetaData authentication = null;
private Map<String, QTableMetaData> tables = new HashMap<>();
private Map<String, QPossibleValueSource<?>> possibleValueSources = new HashMap<>();
private Map<String, QProcessMetaData> processes = new HashMap<>();
// todo - lock down the object (no more changes allowed) after it's been validated?
@JsonIgnore
private boolean hasBeenValidated = false;
@ -147,6 +150,36 @@ public class QInstance
/*******************************************************************************
**
*******************************************************************************/
public void addPossibleValueSource(QPossibleValueSource<?> possibleValueSource)
{
this.possibleValueSources.put(possibleValueSource.getName(), possibleValueSource);
}
/*******************************************************************************
**
*******************************************************************************/
public void addPossibleValueSource(String name, QPossibleValueSource possibleValueSource)
{
this.possibleValueSources.put(name, possibleValueSource);
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource getPossibleValueSource(String name)
{
return (this.possibleValueSources.get(name));
}
/*******************************************************************************
**
*******************************************************************************/
@ -227,6 +260,28 @@ public class QInstance
/*******************************************************************************
** Getter for possibleValueSources
**
*******************************************************************************/
public Map<String, QPossibleValueSource<?>> getPossibleValueSources()
{
return possibleValueSources;
}
/*******************************************************************************
** Setter for possibleValueSources
**
*******************************************************************************/
public void setPossibleValueSources(Map<String, QPossibleValueSource<?>> possibleValueSources)
{
this.possibleValueSources = possibleValueSources;
}
/*******************************************************************************
** Getter for processes
**

View File

@ -0,0 +1,144 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
import java.util.ArrayList;
import java.util.List;
/*******************************************************************************
** Meta-data to represent a single field in a table.
**
*******************************************************************************/
public class QPossibleValueSource<T>
{
private String name;
private QPossibleValueSourceType type;
// should these be in sub-types??
private List<T> enumValues;
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource()
{
}
/*******************************************************************************
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource<T> withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSourceType getType()
{
return type;
}
/*******************************************************************************
**
*******************************************************************************/
public void setType(QPossibleValueSourceType type)
{
this.type = type;
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource<T> withType(QPossibleValueSourceType type)
{
this.type = type;
return (this);
}
/*******************************************************************************
** Getter for enumValues
**
*******************************************************************************/
public List<T> getEnumValues()
{
return enumValues;
}
/*******************************************************************************
** Setter for enumValues
**
*******************************************************************************/
public void setEnumValues(List<T> enumValues)
{
this.enumValues = enumValues;
}
/*******************************************************************************
** Fluent setter for enumValues
**
*******************************************************************************/
public QPossibleValueSource<T> withEnumValues(List<T> enumValues)
{
this.enumValues = enumValues;
return this;
}
/*******************************************************************************
** Fluent adder for enumValues
**
*******************************************************************************/
public QPossibleValueSource<T> addEnumValue(T enumValue)
{
if(this.enumValues == null)
{
this.enumValues = new ArrayList<>();
}
this.enumValues.add(enumValue);
return this;
}
}

View File

@ -0,0 +1,17 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
/*******************************************************************************
** Possible types (e.g, kinds of data sources) for a possible value source.
**
*******************************************************************************/
public enum QPossibleValueSourceType
{
ENUM,
TABLE,
CUSTOM
}

View File

@ -49,12 +49,22 @@ public class QAuthenticationModuleDispatcher
throw (new QModuleDispatchException("No authentication meta data defined."));
}
return getQModule(authenticationMetaData.getType());
}
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationModuleInterface getQModule(String authenticationType) throws QModuleDispatchException
{
try
{
String className = authenticationTypeToModuleClassNameMap.get(authenticationMetaData.getType());
String className = authenticationTypeToModuleClassNameMap.get(authenticationType);
if(className == null)
{
throw (new QModuleDispatchException("Unrecognized authentication type [" + authenticationMetaData.getType() + "] in dispatcher."));
throw (new QModuleDispatchException("Unrecognized authentication type [" + authenticationType + "] in dispatcher."));
}
Class<?> moduleClass = Class.forName(className);
@ -66,7 +76,8 @@ public class QAuthenticationModuleDispatcher
}
catch(Exception e)
{
throw (new QModuleDispatchException("Error getting authentication module of type: " + authenticationMetaData.getType(), e));
throw (new QModuleDispatchException("Error getting authentication module of type: " + authenticationType, e));
}
}
}

View File

@ -32,6 +32,15 @@ public interface QBackendModuleInterface
return null;
}
/*******************************************************************************
**
*******************************************************************************/
default UpdateInterface getUpdateInterface()
{
throwNotImplemented("Update");
return null;
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,23 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.modules.interfaces;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
/*******************************************************************************
** Interface for the update action.
**
*******************************************************************************/
public interface UpdateInterface
{
/*******************************************************************************
**
*******************************************************************************/
UpdateResult execute(UpdateRequest updateRequest) throws QException;
}

View File

@ -9,6 +9,7 @@ import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface;
/*******************************************************************************
@ -41,6 +42,16 @@ public class MockBackendModule implements QBackendModuleInterface
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public UpdateInterface getUpdateInterface()
{
return (new MockUpdateAction());
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -31,7 +31,7 @@ public class MockDeleteAction implements DeleteInterface
rs.setRecords(deleteRequest.getPrimaryKeys().stream().map(primaryKey ->
{
QRecord qRecord = new QRecord().withTableName(deleteRequest.getTableName()).withPrimaryKey(primaryKey);
QRecord qRecord = new QRecord().withTableName(deleteRequest.getTableName()).withValue("id", primaryKey);
return new QRecordWithStatus(qRecord);
}).toList());

View File

@ -0,0 +1,44 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.modules.mock;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
import com.kingsrook.qqq.backend.core.model.data.QRecordWithStatus;
import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface;
/*******************************************************************************
** Mocked up version of update action.
**
*******************************************************************************/
public class MockUpdateAction implements UpdateInterface
{
/*******************************************************************************
**
*******************************************************************************/
public UpdateResult execute(UpdateRequest updateRequest) throws QException
{
try
{
UpdateResult rs = new UpdateResult();
rs.setRecords(updateRequest.getRecords().stream().map(qRecord ->
{
return new QRecordWithStatus(qRecord);
}).toList());
return rs;
}
catch(Exception e)
{
throw new QException("Error executing update: " + e.getMessage(), e);
}
}
}

View File

@ -24,10 +24,9 @@ public class ExceptionUtils
return (null);
}
if(e.getClass().equals(targetClass))
if(targetClass.isInstance(e))
{
//noinspection unchecked
return ((T) e);
return targetClass.cast(e);
}
if(e.getCause() == null)

View File

@ -7,6 +7,7 @@ package com.kingsrook.qqq.backend.core.utils;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
@ -91,6 +92,21 @@ public class JsonUtils
/*******************************************************************************
** De-serialize a json string into an object of the specified class.
**
** Internally using jackson - so jackson annotations apply!
**
*******************************************************************************/
public static <T> T toObject(String json, TypeReference<T> typeReference) throws IOException
{
ObjectMapper objectMapper = newObjectMapper();
T t = objectMapper.readValue(json, typeReference);
return t;
}
/*******************************************************************************
** De-serialize a json string into a JSONObject (string must start with "{")
**

View File

@ -0,0 +1,47 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.actions;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/*******************************************************************************
** Unit test for UpdateAction
**
*******************************************************************************/
class UpdateActionTest
{
/*******************************************************************************
** At the core level, there isn't much that can be asserted, as it uses the
** mock implementation - just confirming that all of the "wiring" works.
**
*******************************************************************************/
@Test
public void test() throws QException
{
UpdateRequest request = new UpdateRequest(TestUtils.defineInstance());
request.setSession(TestUtils.getMockSession());
request.setTableName("person");
List<QRecord> records =new ArrayList<>();
QRecord record = new QRecord();
record.setValue("id", "47");
record.setValue("firstName", "James");
records.add(record);
request.setRecords(records);
UpdateResult result = new UpdateAction().execute(request);
assertNotNull(result);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.adapters;
import java.io.File;
import java.io.IOException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
**
*******************************************************************************/
class QInstanceAdapterTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void qInstanceToJson()
{
QInstance qInstance = TestUtils.defineInstance();
String json = new QInstanceAdapter().qInstanceToJson(qInstance);
System.out.println(json);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void qInstanceToJsonIncludingBackend()
{
QInstance qInstance = TestUtils.defineInstance();
String json = new QInstanceAdapter().qInstanceToJsonIncludingBackend(qInstance);
System.out.println(json);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void jsonToQInstance() throws IOException
{
String json = FileUtils.readFileToString(new File("src/test/resources/personQInstance.json"));
QInstance qInstance = new QInstanceAdapter().jsonToQInstance(json);
System.out.println(qInstance);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void jsonToQInstanceIncludingBackend() throws IOException
{
String json = FileUtils.readFileToString(new File("src/test/resources/personQInstanceIncludingBackend.json"));
QInstance qInstance = new QInstanceAdapter().jsonToQInstanceIncludingBackends(json);
System.out.println(qInstance);
assertNotNull(qInstance.getBackends());
assertTrue(qInstance.getBackends().size() > 0);
}
}

View File

@ -35,7 +35,7 @@ class QInstanceValidatorTest
/*******************************************************************************
** Test an instane with null backends - should throw.
** Test an instance with null backends - should throw.
**
*******************************************************************************/
@Test
@ -57,7 +57,7 @@ class QInstanceValidatorTest
/*******************************************************************************
** Test an instane with empty map of backends - should throw.
** Test an instance with empty map of backends - should throw.
**
*******************************************************************************/
@Test
@ -79,7 +79,7 @@ class QInstanceValidatorTest
/*******************************************************************************
** Test an instane with null tables - should throw.
** Test an instance with null tables - should throw.
**
*******************************************************************************/
@Test
@ -101,7 +101,7 @@ class QInstanceValidatorTest
/*******************************************************************************
** Test an instane with empty map of tables - should throw.
** Test an instance with empty map of tables - should throw.
**
*******************************************************************************/
@Test
@ -123,7 +123,7 @@ class QInstanceValidatorTest
/*******************************************************************************
** Test an instane where a table and a backend each have a name attribute that
** Test an instance where a table and a backend each have a name attribute that
** doesn't match the key that those objects have in the instance's maps - should throw.
**
*******************************************************************************/
@ -191,6 +191,62 @@ class QInstanceValidatorTest
/*******************************************************************************
** Test that a table with no fields fails.
**
*******************************************************************************/
@Test
public void test_validateTableWithNoFields()
{
try
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable("person").setFields(null);
new QInstanceValidator().validate(qInstance);
fail("Should have thrown validationException");
}
catch(QInstanceValidationException e)
{
assertReason("At least 1 field", e);
}
try
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable("person").setFields(new HashMap<>());
new QInstanceValidator().validate(qInstance);
fail("Should have thrown validationException");
}
catch(QInstanceValidationException e)
{
assertReason("At least 1 field", e);
}
}
/*******************************************************************************
** Test that if a field specifies a backend that doesn't exist, that it fails.
**
*******************************************************************************/
@Test
public void test_validateFieldWithMissingPossibleValueSource()
{
try
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable("person").getField("homeState").setPossibleValueSourceName("not a real possible value source");
new QInstanceValidator().validate(qInstance);
fail("Should have thrown validationException");
}
catch(QInstanceValidationException e)
{
assertReason("Unrecognized possibleValueSourceName", e);
}
}
/*******************************************************************************
** utility method for asserting that a specific reason string is found within
** the list of reasons in the QInstanceValidationException.

View File

@ -0,0 +1,78 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.modules;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
/*******************************************************************************
** Unit test for QModuleDispatcher
**
*******************************************************************************/
class QAuthenticationModuleDispatcherTest
{
/*******************************************************************************
** Test success case - a valid backend.
**
*******************************************************************************/
@Test
public void test_getQAuthenticationModule() throws QModuleDispatchException
{
QAuthenticationModuleInterface mock = new QAuthenticationModuleDispatcher().getQModule(TestUtils.defineAuthentication());
assertNotNull(mock);
}
/*******************************************************************************
** Test success case - a valid backend.
**
*******************************************************************************/
@Test
public void test_getQAuthenticationModuleByType_valid() throws QModuleDispatchException
{
QAuthenticationModuleInterface mock = new QAuthenticationModuleDispatcher().getQModule("mock");
assertNotNull(mock);
}
/*******************************************************************************
** Test failure case, a backend name that doesn't exist
**
*******************************************************************************/
@Test
public void test_getQAuthenticationModule_typeNotFound()
{
assertThrows(QModuleDispatchException.class, () ->
{
new QAuthenticationModuleDispatcher().getQModule("aTypeThatWontExist");
});
}
/*******************************************************************************
** Test failure case, null argument
**
*******************************************************************************/
@Test
public void test_getQAuthenticationModule_nullArgument()
{
assertThrows(QModuleDispatchException.class, () ->
{
new QAuthenticationModuleDispatcher().getQModule((QAuthenticationMetaData) null);
});
}
}

View File

@ -44,7 +44,7 @@ class JsonUtilsTest
{
QRecord qRecord = getQRecord();
String json = JsonUtils.toJson(qRecord);
assertEquals("{\"tableName\":\"foo\",\"primaryKey\":1,\"values\":{\"foo\":\"Foo\",\"bar\":3.14159}}", json);
assertEquals("{\"tableName\":\"foo\",\"values\":{\"foo\":\"Foo\",\"bar\":3.14159},\"displayValues\":{}}", json);
}
@ -188,7 +188,7 @@ class JsonUtilsTest
{
QRecord qRecord = new QRecord();
qRecord.setTableName("foo");
qRecord.setPrimaryKey(1);
qRecord.setValue("id", 1);
Map<String, Serializable> values = new LinkedHashMap<>();
qRecord.setValues(values);
values.put("foo", "Foo");

View File

@ -15,6 +15,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
@ -42,17 +44,32 @@ public class TestUtils
qInstance.setAuthentication(defineAuthentication());
qInstance.addBackend(defineBackend());
qInstance.addTable(defineTablePerson());
qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
qInstance.addProcess(defineProcessGreetPeople());
return (qInstance);
}
/*******************************************************************************
** Define the "states" possible value source used in standard tests
**
*******************************************************************************/
private static QPossibleValueSource<String> defineStatesPossibleValueSource()
{
return new QPossibleValueSource<String>()
.withName("state")
.withType(QPossibleValueSourceType.ENUM)
.withEnumValues(List.of("IL", "MO"));
}
/*******************************************************************************
** Define the authentication used in standard tests - using 'mock' type.
**
*******************************************************************************/
private static QAuthenticationMetaData defineAuthentication()
public static QAuthenticationMetaData defineAuthentication()
{
return new QAuthenticationMetaData()
.withName("mock")
@ -89,7 +106,8 @@ public class TestUtils
.withField(new QFieldMetaData("firstName", QFieldType.STRING))
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE))
.withField(new QFieldMetaData("email", QFieldType.STRING));
.withField(new QFieldMetaData("email", QFieldType.STRING))
.withField(new QFieldMetaData("homeState", QFieldType.STRING).withPossibleValueSourceName("state"));
}

View File

@ -0,0 +1,156 @@
{
"authentication": {
"name": "mock",
"type": "mock",
"values": null
},
"tables": {
"person": {
"name": "person",
"label": "Person",
"backendName": "default",
"primaryKeyField": "id",
"fields": {
"id": {
"name": "id",
"label": null,
"backendName": null,
"type": "INTEGER",
"possibleValueSourceName": null
},
"createDate": {
"name": "createDate",
"label": null,
"backendName": null,
"type": "DATE_TIME",
"possibleValueSourceName": null
},
"modifyDate": {
"name": "modifyDate",
"label": null,
"backendName": null,
"type": "DATE_TIME",
"possibleValueSourceName": null
},
"firstName": {
"name": "firstName",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
},
"lastName": {
"name": "lastName",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
},
"birthDate": {
"name": "birthDate",
"label": null,
"backendName": null,
"type": "DATE",
"possibleValueSourceName": null
},
"email": {
"name": "email",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
},
"homeState": {
"name": "homeState",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": "state"
}
}
}
},
"possibleValueSources": {
"state": {
"name": "state",
"type": "ENUM",
"enumValues": [
"IL",
"MO"
]
}
},
"processes": {
"greet": {
"name": "greet",
"tableName": "person",
"functionList": [
{
"name": "prepare",
"label": null,
"inputMetaData": {
"recordListMetaData": {
"tableName": "person",
"fields": null
},
"fieldList": [
{
"name": "greetingPrefix",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
},
{
"name": "greetingSuffix",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
}
]
},
"outputMetaData": {
"recordListMetaData": {
"tableName": "person",
"fields": {
"fullGreeting": {
"name": "fullGreeting",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
}
}
},
"fieldList": [
{
"name": "outputMessage",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
}
]
},
"code": {
"name": "com.kingsrook.qqq.backend.core.interfaces.mock.MockFunctionBody",
"codeType": "JAVA",
"codeUsage": "FUNCTION"
},
"outputView": {
"messageField": "outputMessage",
"recordListView": {
"fieldNames": [
"id",
"firstName",
"lastName",
"fullGreeting"
]
}
}
}
]
}
}
}

View File

@ -0,0 +1,163 @@
{
"tables": {
"person": {
"primaryKeyField": "id",
"name": "person",
"backendName": "default",
"label": "Person",
"fields": {
"firstName": {
"name": "firstName",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
"lastName": {
"name": "lastName",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
"modifyDate": {
"name": "modifyDate",
"backendName": null,
"label": null,
"type": "DATE_TIME",
"possibleValueSourceName": null
},
"homeState": {
"name": "homeState",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": "state"
},
"id": {
"name": "id",
"backendName": null,
"label": null,
"type": "INTEGER",
"possibleValueSourceName": null
},
"birthDate": {
"name": "birthDate",
"backendName": null,
"label": null,
"type": "DATE",
"possibleValueSourceName": null
},
"email": {
"name": "email",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
"createDate": {
"name": "createDate",
"backendName": null,
"label": null,
"type": "DATE_TIME",
"possibleValueSourceName": null
}
}
}
},
"processes": {
"greet": {
"functionList": [
{
"code": {
"codeUsage": "FUNCTION",
"codeType": "JAVA",
"name": "com.kingsrook.qqq.backend.core.interfaces.mock.MockFunctionBody"
},
"inputMetaData": {
"recordListMetaData": {
"fields": null,
"tableName": "person"
},
"fieldList": [
{
"name": "greetingPrefix",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
{
"name": "greetingSuffix",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
}
]
},
"outputMetaData": {
"recordListMetaData": {
"fields": {
"fullGreeting": {
"name": "fullGreeting",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
}
},
"tableName": "person"
},
"fieldList": [
{
"name": "outputMessage",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
}
]
},
"outputView": {
"messageField": "outputMessage",
"recordListView": {
"fieldNames": [
"id",
"firstName",
"lastName",
"fullGreeting"
]
}
},
"name": "prepare",
"label": null
}
],
"name": "greet",
"tableName": "person"
}
},
"possibleValueSources": {
"state": {
"name": "state",
"type": "ENUM",
"enumValues": [
"IL",
"MO"
]
}
},
"backends": {
"default": {
"values": null,
"name": "default",
"type": "mock"
}
},
"authentication": {
"values": null,
"name": "mock",
"type": "mock"
}
}