4141import java .util .Map ;
4242import java .util .Objects ;
4343import java .util .Optional ;
44+ import java .util .function .Function ;
4445import com .kingsrook .qqq .backend .core .exceptions .QException ;
4546import com .kingsrook .qqq .backend .core .exceptions .QRuntimeException ;
4647import com .kingsrook .qqq .backend .core .logging .QLogger ;
48+ import com .kingsrook .qqq .backend .core .model .metadata .tables .QTableMetaData ;
4749import com .kingsrook .qqq .backend .core .utils .CollectionUtils ;
4850import com .kingsrook .qqq .backend .core .utils .ListingHash ;
51+ import com .kingsrook .qqq .backend .core .utils .ObjectUtils ;
4952import static com .kingsrook .qqq .backend .core .logging .LogUtils .logPair ;
5053
5154
@@ -61,6 +64,11 @@ public abstract class QRecordEntity
6164
6265 private Map <String , Serializable > originalRecordValues ;
6366
67+ ////////////////////////////////////////////////////////////////////////////////
68+ // map of entity class names to QTableMetaData objects that they helped build //
69+ ////////////////////////////////////////////////////////////////////////////////
70+ private static Map <String , QTableMetaData > tableReferences = new HashMap <>();
71+
6472
6573
6674 /*******************************************************************************
@@ -95,6 +103,19 @@ public static <T extends QRecordEntity> T fromQRecord(Class<T> c, QRecord qRecor
95103
96104
97105
106+ /***************************************************************************
107+ ** register a mapping between an entity class and a table that it is associated with.
108+ ***************************************************************************/
109+ public static void registerTable (Class <? extends QRecordEntity > entityClass , QTableMetaData table )
110+ {
111+ if (entityClass != null && table != null )
112+ {
113+ tableReferences .put (entityClass .getName (), table );
114+ }
115+ }
116+
117+
118+
98119 /*******************************************************************************
99120 ** Build an entity of this QRecord type from a QRecord
100121 **
@@ -176,7 +197,10 @@ protected <T extends QRecordEntity> void populateFromQRecord(QRecord qRecord, St
176197
177198
178199 /*******************************************************************************
179- ** Convert this entity to a QRecord.
200+ ** Convert this entity to a QRecord. ALL fields in the entity will be set
201+ ** in the QRecord. Note that, if you're using this for an input to the UpdateAction,
202+ ** that this could cause values to be set to null, e.g., if you constructed
203+ ** a entity from scratch, and didn't set all values in it!!
180204 **
181205 *******************************************************************************/
182206 public QRecord toQRecord () throws QRuntimeException
@@ -190,25 +214,7 @@ public QRecord toQRecord() throws QRuntimeException
190214 qRecord .setValue (qRecordEntityField .getFieldName (), (Serializable ) qRecordEntityField .getGetter ().invoke (this ));
191215 }
192216
193- for (QRecordEntityAssociation qRecordEntityAssociation : getAssociationList (this .getClass ()))
194- {
195- @ SuppressWarnings ("unchecked" )
196- List <? extends QRecordEntity > associatedEntities = (List <? extends QRecordEntity >) qRecordEntityAssociation .getGetter ().invoke (this );
197- String associationName = qRecordEntityAssociation .getAssociationAnnotation ().name ();
198-
199- if (associatedEntities != null )
200- {
201- /////////////////////////////////////////////////////////////////////////////////
202- // do this so an empty list in the entity becomes an empty list in the QRecord //
203- /////////////////////////////////////////////////////////////////////////////////
204- qRecord .withAssociatedRecords (associationName , new ArrayList <>());
205- }
206-
207- for (QRecordEntity associatedEntity : CollectionUtils .nonNullList (associatedEntities ))
208- {
209- qRecord .withAssociatedRecord (associationName , associatedEntity .toQRecord ());
210- }
211- }
217+ toQRecordProcessAssociations (qRecord , (entity ) -> entity .toQRecord ());
212218
213219 return (qRecord );
214220 }
@@ -220,15 +226,65 @@ public QRecord toQRecord() throws QRuntimeException
220226
221227
222228
229+ /***************************************************************************
230+ *
231+ ***************************************************************************/
232+ private void toQRecordProcessAssociations (QRecord outputRecord , Function <QRecordEntity , QRecord > toRecordFunction ) throws Exception
233+ {
234+ for (QRecordEntityAssociation qRecordEntityAssociation : getAssociationList (this .getClass ()))
235+ {
236+ @ SuppressWarnings ("unchecked" )
237+ List <? extends QRecordEntity > associatedEntities = (List <? extends QRecordEntity >) qRecordEntityAssociation .getGetter ().invoke (this );
238+ String associationName = qRecordEntityAssociation .getAssociationAnnotation ().name ();
239+
240+ if (associatedEntities != null )
241+ {
242+ outputRecord .withAssociatedRecords (associationName , new ArrayList <>());
243+ for (QRecordEntity associatedEntity : associatedEntities )
244+ {
245+ outputRecord .withAssociatedRecord (associationName , toRecordFunction .apply (associatedEntity ));
246+ }
247+ }
248+ }
249+ }
250+
251+
252+
223253 /*******************************************************************************
224- **
254+ ** Overload of toQRecordOnlyChangedFields that preserves original behavior of
255+ ** that method, which is, to NOT includePrimaryKey
225256 *******************************************************************************/
257+ @ Deprecated (since = "includePrimaryKey param was added" )
226258 public QRecord toQRecordOnlyChangedFields ()
259+ {
260+ return toQRecordOnlyChangedFields (false );
261+ }
262+
263+
264+
265+ /*******************************************************************************
266+ ** Useful for the use-case of:
267+ ** - fetch a QRecord (e.g., QueryAction or GetAction)
268+ ** - build a QRecordEntity out of it
269+ ** - change a field (or two) in it
270+ ** - want to pass it into an UpdateAction, and want to see only the fields that
271+ ** you know you changed get passed in to UpdateAction (e.g., PATCH semantics).
272+ **
273+ ** But also - per the includePrimaryKey param, include the primaryKey in the
274+ ** records (e.g., to tell the Update which records to update).
275+ **
276+ ** Also, useful for:
277+ ** - construct new entity, calling setters to populate some fields
278+ ** - pass that entity into
279+ *******************************************************************************/
280+ public QRecord toQRecordOnlyChangedFields (boolean includePrimaryKey )
227281 {
228282 try
229283 {
230284 QRecord qRecord = new QRecord ();
231285
286+ String primaryKeyFieldName = ObjectUtils .tryElse (() -> tableReferences .get (getClass ().getName ()).getPrimaryKeyField (), null );
287+
232288 for (QRecordEntityField qRecordEntityField : getFieldList (this .getClass ()))
233289 {
234290 Serializable thisValue = (Serializable ) qRecordEntityField .getGetter ().invoke (this );
@@ -238,31 +294,16 @@ public QRecord toQRecordOnlyChangedFields()
238294 originalValue = originalRecordValues .get (qRecordEntityField .getFieldName ());
239295 }
240296
241- if (!Objects .equals (thisValue , originalValue ))
297+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
298+ // if this value and the original value don't match - OR - this is the table's primary key field - then put the value in the record. //
299+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
300+ if (!Objects .equals (thisValue , originalValue ) || (includePrimaryKey && Objects .equals (primaryKeyFieldName , qRecordEntityField .getFieldName ())))
242301 {
243302 qRecord .setValue (qRecordEntityField .getFieldName (), thisValue );
244303 }
245304 }
246305
247- for (QRecordEntityAssociation qRecordEntityAssociation : getAssociationList (this .getClass ()))
248- {
249- @ SuppressWarnings ("unchecked" )
250- List <? extends QRecordEntity > associatedEntities = (List <? extends QRecordEntity >) qRecordEntityAssociation .getGetter ().invoke (this );
251- String associationName = qRecordEntityAssociation .getAssociationAnnotation ().name ();
252-
253- if (associatedEntities != null )
254- {
255- /////////////////////////////////////////////////////////////////////////////////
256- // do this so an empty list in the entity becomes an empty list in the QRecord //
257- /////////////////////////////////////////////////////////////////////////////////
258- qRecord .withAssociatedRecords (associationName , new ArrayList <>());
259- }
260-
261- for (QRecordEntity associatedEntity : CollectionUtils .nonNullList (associatedEntities ))
262- {
263- qRecord .withAssociatedRecord (associationName , associatedEntity .toQRecord ());
264- }
265- }
306+ toQRecordProcessAssociations (qRecord , (entity ) -> entity .toQRecordOnlyChangedFields (includePrimaryKey ));
266307
267308 return (qRecord );
268309 }
@@ -488,15 +529,15 @@ private static boolean isSupportedFieldType(Class<?> returnType)
488529 {
489530 // todo - more types!!
490531 return (returnType .equals (String .class )
491- || returnType .equals (Integer .class )
492- || returnType .equals (int .class )
493- || returnType .equals (Boolean .class )
494- || returnType .equals (boolean .class )
495- || returnType .equals (BigDecimal .class )
496- || returnType .equals (Instant .class )
497- || returnType .equals (LocalDate .class )
498- || returnType .equals (LocalTime .class )
499- || returnType .equals (byte [].class ));
532+ || returnType .equals (Integer .class )
533+ || returnType .equals (int .class )
534+ || returnType .equals (Boolean .class )
535+ || returnType .equals (boolean .class )
536+ || returnType .equals (BigDecimal .class )
537+ || returnType .equals (Instant .class )
538+ || returnType .equals (LocalDate .class )
539+ || returnType .equals (LocalTime .class )
540+ || returnType .equals (byte [].class ));
500541 /////////////////////////////////////////////
501542 // note - this list has implications upon: //
502543 // - QFieldType.fromClass //
0 commit comments