diff --git a/pom.xml b/pom.xml
index aa639590..04f34242 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,11 @@
3.23.1
test
+
+ com.github.hervian
+ safety-mirror
+ 4.0.1
+
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
index aa503b4f..6151c672 100644
--- 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
@@ -27,6 +27,7 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.Map;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@@ -255,7 +256,6 @@ public class QRecord implements Serializable
-
/*******************************************************************************
**
*******************************************************************************/
@@ -343,4 +343,13 @@ public class QRecord implements Serializable
return (String) this.backendDetails.get(key);
}
+
+
+ /*******************************************************************************
+ ** Convert this record to an QRecordEntity
+ *******************************************************************************/
+ public T toEntity(Class c) throws QException
+ {
+ return (QRecordEntity.fromQRecord(c, this));
+ }
}
diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntity.java b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntity.java
new file mode 100644
index 00000000..df5c8ecc
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntity.java
@@ -0,0 +1,213 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.data;
+
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.utils.ListingHash;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/*******************************************************************************
+ ** Base class for entity beans that are interoperable with QRecords.
+ *******************************************************************************/
+public abstract class QRecordEntity
+{
+ private static final Logger LOG = LogManager.getLogger(QRecordEntity.class);
+
+ private static final ListingHash, QRecordEntityField> fieldMapping = new ListingHash<>();
+
+
+
+ /*******************************************************************************
+ ** Build an entity of this QRecord type from a QRecord
+ **
+ *******************************************************************************/
+ public static T fromQRecord(Class c, QRecord qRecord) throws QException
+ {
+ try
+ {
+ T entity = c.getConstructor().newInstance();
+
+ List fieldList = getFieldList(c);
+ for(QRecordEntityField qRecordEntityField : fieldList)
+ {
+ Serializable value = qRecord.getValue(qRecordEntityField.getFieldName());
+ Object typedValue = qRecordEntityField.convertValueType(value);
+ qRecordEntityField.getSetter().invoke(entity, typedValue);
+ }
+
+ return (entity);
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error building entity from qRecord.", e));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Convert this entity to a QRecord.
+ **
+ *******************************************************************************/
+ public QRecord toQRecord() throws QException
+ {
+ try
+ {
+ QRecord qRecord = new QRecord();
+
+ List fieldList = getFieldList(this.getClass());
+ for(QRecordEntityField qRecordEntityField : fieldList)
+ {
+ qRecord.setValue(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
+ }
+
+ return (qRecord);
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error building qRecord from entity.", e));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static List getFieldList(Class extends QRecordEntity> c)
+ {
+ if(!fieldMapping.containsKey(c))
+ {
+ List fieldList = new ArrayList<>();
+ for(Method possibleGetter : c.getMethods())
+ {
+ if(isGetter(possibleGetter))
+ {
+ Optional setter = getSetterForGetter(c, possibleGetter);
+ if(setter.isPresent())
+ {
+ String name = getFieldNameFromGetter(possibleGetter);
+ fieldList.add(new QRecordEntityField(name, possibleGetter, setter.get(), possibleGetter.getReturnType()));
+ }
+ else
+ {
+ LOG.info("Getter method [" + possibleGetter.getName() + "] does not have a corresponding setter.");
+ }
+ }
+ }
+ fieldMapping.put(c, fieldList);
+ }
+ return (fieldMapping.get(c));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String getFieldNameFromGetter(Method getter)
+ {
+ String nameWithoutGet = getter.getName().replaceFirst("^get", "");
+ if(nameWithoutGet.length() == 1)
+ {
+ return (nameWithoutGet.toLowerCase(Locale.ROOT));
+ }
+ return (nameWithoutGet.substring(0, 1).toLowerCase(Locale.ROOT) + nameWithoutGet.substring(1));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static boolean isGetter(Method method)
+ {
+ if(method.getParameterTypes().length == 0 && method.getName().matches("^get[A-Z].*"))
+ {
+ if(isSupportedFieldType(method.getReturnType()))
+ {
+ return (true);
+ }
+ else
+ {
+ if(!method.getName().equals("getClass"))
+ {
+ LOG.info("Method [" + method.getName() + "] looks like a getter, but its return type, [" + method.getReturnType() + "], isn't supported.");
+ }
+ }
+ }
+ return (false);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static Optional getSetterForGetter(Class extends QRecordEntity> c, Method getter)
+ {
+ String setterName = getter.getName().replaceFirst("^get", "set");
+ for(Method method : c.getMethods())
+ {
+ if(method.getName().equals(setterName))
+ {
+ if(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(getter.getReturnType()))
+ {
+ return (Optional.of(method));
+ }
+ else
+ {
+ LOG.info("Method [" + method.getName() + "] looks like a setter for [" + getter.getName() + "], but its parameters, [" + Arrays.toString(method.getParameterTypes()) + "], don't match the getter's return type [" + getter.getReturnType() + "]");
+ }
+ }
+ }
+ return (Optional.empty());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static boolean isSupportedFieldType(Class> returnType)
+ {
+ // todo - more types!!
+ return (returnType.equals(String.class)
+ || returnType.equals(Integer.class)
+ || returnType.equals(int.class)
+ || returnType.equals(Boolean.class)
+ || returnType.equals(boolean.class)
+ || returnType.equals(BigDecimal.class));
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityField.java b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityField.java
new file mode 100644
index 00000000..b03dbea6
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityField.java
@@ -0,0 +1,138 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.data;
+
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import com.kingsrook.qqq.backend.core.exceptions.QValueException;
+import com.kingsrook.qqq.backend.core.utils.ValueUtils;
+
+
+/*******************************************************************************
+ ** Reflective information about a field in a QRecordEntity
+ *******************************************************************************/
+public class QRecordEntityField
+{
+ private final String fieldName;
+ private final Method getter;
+ private final Method setter;
+ private final Class> type;
+
+
+
+ /*******************************************************************************
+ ** Constructor.
+ *******************************************************************************/
+ public QRecordEntityField(String fieldName, Method getter, Method setter, Class> type)
+ {
+ this.fieldName = fieldName;
+ this.getter = getter;
+ this.setter = setter;
+ this.type = type;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for fieldName
+ **
+ *******************************************************************************/
+ public String getFieldName()
+ {
+ return fieldName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for getter
+ **
+ *******************************************************************************/
+ public Method getGetter()
+ {
+ return getter;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for setter
+ **
+ *******************************************************************************/
+ public Method getSetter()
+ {
+ return setter;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for type
+ **
+ *******************************************************************************/
+ public Class> getType()
+ {
+ return type;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public Object convertValueType(Serializable value)
+ {
+ if(value == null)
+ {
+ return (null);
+ }
+
+ if(value.getClass().equals(type))
+ {
+ return (value);
+ }
+
+ if(type.equals(String.class))
+ {
+ return (ValueUtils.getValueAsString(value));
+ }
+
+ if(type.equals(Integer.class) || type.equals(int.class))
+ {
+ return (ValueUtils.getValueAsInteger(value));
+ }
+
+ if(type.equals(Boolean.class) || type.equals(boolean.class))
+ {
+ return (ValueUtils.getValueAsBoolean(value));
+ }
+
+ if(type.equals(BigDecimal.class))
+ {
+ return (ValueUtils.getValueAsBigDecimal(value));
+ }
+
+ throw (new QValueException("Unhandled value type [" + type + "] for field [" + fieldName + "]"));
+ }
+}
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
index 36a757c3..dc53cf76 100644
--- 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
@@ -23,6 +23,10 @@ package com.kingsrook.qqq.backend.core.model.metadata;
import java.io.Serializable;
+import java.lang.reflect.Method;
+import com.github.hervian.reflection.Fun;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
@@ -31,14 +35,14 @@ import java.io.Serializable;
*******************************************************************************/
public class QFieldMetaData
{
- private String name;
- private String label;
- private String backendName;
+ private String name;
+ private String label;
+ private String backendName;
private QFieldType type;
- private boolean isRequired = false;
+ private boolean isRequired = false;
private Serializable defaultValue;
- private String possibleValueSourceName;
+ private String possibleValueSourceName;
@@ -62,6 +66,30 @@ public class QFieldMetaData
+ /*******************************************************************************
+ ** Initialize a fieldMetaData from a reference to a getter on an entity.
+ ** e.g., new QFieldMetaData(Order::getOrderNo).
+ *******************************************************************************/
+ public QFieldMetaData(Fun.With1ParamAndVoid getterRef) throws QException
+ {
+ try
+ {
+ Method getter = Fun.toMethod(getterRef);
+ this.name = QRecordEntity.getFieldNameFromGetter(getter);
+ this.type = QFieldType.fromClass(getter.getReturnType());
+ }
+ catch(QException qe)
+ {
+ throw (qe);
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error constructing field from getterRef: " + getterRef, e));
+ }
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
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
index 4fca53d4..f52ef8d8 100644
--- 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
@@ -22,6 +22,10 @@
package com.kingsrook.qqq.backend.core.model.metadata;
+import java.math.BigDecimal;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+
+
/*******************************************************************************
** Possible data types for Q-fields.
**
@@ -36,5 +40,28 @@ public enum QFieldType
DATE_TIME,
TEXT,
HTML,
- PASSWORD
+ PASSWORD;
+
+
+
+ /*******************************************************************************
+ ** Get a field type enum constant for a java class.
+ *******************************************************************************/
+ public static QFieldType fromClass(Class> c) throws QException
+ {
+ if(c.equals(String.class))
+ {
+ return (STRING);
+ }
+ if(c.equals(Integer.class) || c.equals(int.class))
+ {
+ return (INTEGER);
+ }
+ if(c.equals(BigDecimal.class))
+ {
+ return (DECIMAL);
+ }
+
+ throw (new QException("Unrecognized class [" + c + "]"));
+ }
}
diff --git a/src/test/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityTest.java b/src/test/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityTest.java
new file mode 100644
index 00000000..f7289b1e
--- /dev/null
+++ b/src/test/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityTest.java
@@ -0,0 +1,195 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.data;
+
+
+import java.math.BigDecimal;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.data.testentities.Item;
+import com.kingsrook.qqq.backend.core.model.data.testentities.ItemWithPrimitives;
+import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+class QRecordEntityTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testItemToQRecord() throws QException
+ {
+ Item item = new Item();
+ item.setSku("ABC-123");
+ item.setDescription("My Item");
+ item.setQuantity(47);
+ item.setPrice(new BigDecimal("3.50"));
+ item.setFeatured(true);
+
+ QRecord qRecord = item.toQRecord();
+ assertEquals("ABC-123", qRecord.getValueString("sku"));
+ assertEquals("My Item", qRecord.getValueString("description"));
+ assertEquals(47, qRecord.getValueInteger("quantity"));
+ assertEquals(new BigDecimal("3.50"), qRecord.getValueBigDecimal("price"));
+ assertTrue(qRecord.getValueBoolean("featured"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testQRecordToItem() throws QException
+ {
+ QRecord qRecord = new QRecord()
+ .withValue("sku", "WXYZ-9876")
+ .withValue("description", "Items are cool")
+ .withValue("quantity", 42)
+ .withValue("price", new BigDecimal("3.50"))
+ .withValue("featured", false);
+
+ Item item = qRecord.toEntity(Item.class);
+ assertEquals("WXYZ-9876", item.getSku());
+ assertEquals("Items are cool", item.getDescription());
+ assertEquals(42, item.getQuantity());
+ assertEquals(new BigDecimal("3.50"), item.getPrice());
+ assertFalse(item.getFeatured());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testItemWithPrimitivesToQRecord() throws QException
+ {
+ ItemWithPrimitives item = new ItemWithPrimitives();
+ item.setSku("ABC-123");
+ item.setDescription(null);
+ item.setQuantity(47);
+ item.setPrice(new BigDecimal("3.50"));
+ item.setFeatured(true);
+
+ QRecord qRecord = item.toQRecord();
+ assertEquals("ABC-123", qRecord.getValueString("sku"));
+ assertNull(qRecord.getValueString("description"));
+ assertEquals(47, qRecord.getValueInteger("quantity"));
+ assertEquals(new BigDecimal("3.50"), qRecord.getValueBigDecimal("price"));
+ assertTrue(qRecord.getValueBoolean("featured"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testQRecordToItemWithPrimitives() throws QException
+ {
+ QRecord qRecord = new QRecord()
+ .withValue("sku", "WXYZ-9876")
+ .withValue("description", null)
+ .withValue("quantity", 42)
+ .withValue("price", new BigDecimal("3.50"))
+ .withValue("featured", false);
+
+ ItemWithPrimitives item = qRecord.toEntity(ItemWithPrimitives.class);
+ assertEquals("WXYZ-9876", item.getSku());
+ assertNull(item.getDescription());
+ assertEquals(42, item.getQuantity());
+ assertEquals(new BigDecimal("3.50"), item.getPrice());
+ assertFalse(item.getFeatured());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testQRecordWithAllStringValuesToItem() throws QException
+ {
+ QRecord qRecord = new QRecord()
+ .withValue("sku", "WXYZ-9876")
+ .withValue("description", "Items are cool")
+ .withValue("quantity", "42")
+ .withValue("price", "3.50")
+ .withValue("featured", "false");
+
+ Item item = qRecord.toEntity(Item.class);
+ assertEquals("WXYZ-9876", item.getSku());
+ assertEquals("Items are cool", item.getDescription());
+ assertEquals(42, item.getQuantity());
+ assertEquals(new BigDecimal("3.50"), item.getPrice());
+ assertFalse(item.getFeatured());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @Test
+ void testQTableConstructionFromEntity() throws QException
+ {
+ QTableMetaData qTableMetaData = new QTableMetaData()
+ .withField(new QFieldMetaData(Item::getSku))
+ .withField(new QFieldMetaData(Item::getDescription))
+ .withField(new QFieldMetaData(Item::getQuantity));
+
+ assertEquals(QFieldType.STRING, qTableMetaData.getField("sku").getType());
+ assertEquals(QFieldType.INTEGER, qTableMetaData.getField("quantity").getType());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @Test
+ void testQTableConstructionWithPrimitives() throws QException
+ {
+ QTableMetaData qTableMetaData = new QTableMetaData()
+ .withField(new QFieldMetaData(ItemWithPrimitives::getSku))
+ .withField(new QFieldMetaData(ItemWithPrimitives::getDescription))
+ .withField(new QFieldMetaData(ItemWithPrimitives::getQuantity));
+
+ assertEquals(QFieldType.STRING, qTableMetaData.getField("sku").getType());
+ assertEquals(QFieldType.INTEGER, qTableMetaData.getField("quantity").getType());
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/Item.java b/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/Item.java
new file mode 100644
index 00000000..4412122b
--- /dev/null
+++ b/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/Item.java
@@ -0,0 +1,149 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.data.testentities;
+
+
+import java.math.BigDecimal;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+
+
+/*******************************************************************************
+ ** Sample of an entity that can be converted to & from a QRecord
+ *******************************************************************************/
+public class Item extends QRecordEntity
+{
+ private String sku;
+ private String description;
+ private Integer quantity;
+ private BigDecimal price;
+ private Boolean featured;
+
+
+
+ /*******************************************************************************
+ ** Getter for sku
+ **
+ *******************************************************************************/
+ public String getSku()
+ {
+ return sku;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for sku
+ **
+ *******************************************************************************/
+ public void setSku(String sku)
+ {
+ this.sku = sku;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for description
+ **
+ *******************************************************************************/
+ public String getDescription()
+ {
+ return description;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for description
+ **
+ *******************************************************************************/
+ public void setDescription(String description)
+ {
+ this.description = description;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for quantity
+ **
+ *******************************************************************************/
+ public Integer getQuantity()
+ {
+ return quantity;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for quantity
+ **
+ *******************************************************************************/
+ public void setQuantity(Integer quantity)
+ {
+ this.quantity = quantity;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for price
+ **
+ *******************************************************************************/
+ public BigDecimal getPrice()
+ {
+ return price;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for price
+ **
+ *******************************************************************************/
+ public void setPrice(BigDecimal price)
+ {
+ this.price = price;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for featured
+ **
+ *******************************************************************************/
+ public Boolean getFeatured()
+ {
+ return featured;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for featured
+ **
+ *******************************************************************************/
+ public void setFeatured(Boolean featured)
+ {
+ this.featured = featured;
+ }
+}
diff --git a/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/ItemWithPrimitives.java b/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/ItemWithPrimitives.java
new file mode 100644
index 00000000..df612118
--- /dev/null
+++ b/src/test/java/com/kingsrook/qqq/backend/core/model/data/testentities/ItemWithPrimitives.java
@@ -0,0 +1,149 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.data.testentities;
+
+
+import java.math.BigDecimal;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+
+
+/*******************************************************************************
+ ** Sample of an entity that can be converted to & from a QRecord
+ *******************************************************************************/
+public class ItemWithPrimitives extends QRecordEntity
+{
+ private String sku;
+ private String description;
+ private int quantity;
+ private BigDecimal price;
+ private boolean featured;
+
+
+
+ /*******************************************************************************
+ ** Getter for sku
+ **
+ *******************************************************************************/
+ public String getSku()
+ {
+ return sku;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for sku
+ **
+ *******************************************************************************/
+ public void setSku(String sku)
+ {
+ this.sku = sku;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for description
+ **
+ *******************************************************************************/
+ public String getDescription()
+ {
+ return description;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for description
+ **
+ *******************************************************************************/
+ public void setDescription(String description)
+ {
+ this.description = description;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for quantity
+ **
+ *******************************************************************************/
+ public int getQuantity()
+ {
+ return quantity;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for quantity
+ **
+ *******************************************************************************/
+ public void setQuantity(int quantity)
+ {
+ this.quantity = quantity;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for price
+ **
+ *******************************************************************************/
+ public BigDecimal getPrice()
+ {
+ return price;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for price
+ **
+ *******************************************************************************/
+ public void setPrice(BigDecimal price)
+ {
+ this.price = price;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for featured
+ **
+ *******************************************************************************/
+ public boolean getFeatured()
+ {
+ return featured;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for featured
+ **
+ *******************************************************************************/
+ public void setFeatured(boolean featured)
+ {
+ this.featured = featured;
+ }
+}