2626import com .aerospike .client .query .RecordSet ;
2727import com .aerospike .client .query .Statement ;
2828import com .aerospike .mapper .tools .ClassCache .PolicyType ;
29+ import com .aerospike .mapper .tools .DeferredObjectLoader .DeferredObject ;
30+ import com .aerospike .mapper .tools .DeferredObjectLoader .DeferredObjectSetter ;
2931import com .aerospike .mapper .tools .TypeUtils .AnnotatedType ;
3032import com .aerospike .mapper .tools .configuration .ClassConfig ;
3133import com .aerospike .mapper .tools .configuration .Configuration ;
@@ -239,9 +241,11 @@ public Object translateToAerospike(Object obj) {
239241 * @return
240242 */
241243 @ SuppressWarnings ("unchecked" )
242- public <T > T translateFromAerospike (Object obj , Class <T > expectedClazz ) {
244+ public <T > T translateFromAerospike (@ NotNull Object obj , @ NotNull Class <T > expectedClazz ) {
243245 TypeMapper thisMapper = TypeUtils .getMapper (expectedClazz , AnnotatedType .getDefaultAnnotateType (), this );
244- return (T )(thisMapper == null ? obj : thisMapper .fromAerospikeFormat (obj ));
246+ T result = (T )(thisMapper == null ? obj : thisMapper .fromAerospikeFormat (obj ));
247+ resolveDependencies (ClassCache .getInstance ().loadClass (expectedClazz , this ));
248+ return result ;
245249 }
246250
247251
@@ -276,34 +280,61 @@ public void update(@NotNull Object object, String ... binNames) throws Aerospike
276280 save (null , object , RecordExistsAction .UPDATE , binNames );
277281 }
278282
279-
280283 public <T > T readFromDigest (Policy readPolicy , @ NotNull Class <T > clazz , @ NotNull byte [] digest ) throws AerospikeException {
284+ return this .readFromDigest (readPolicy , clazz , digest , true );
285+ }
286+
287+ /**
288+ * This method should not be used except by mappers
289+ */
290+ public <T > T readFromDigest (Policy readPolicy , @ NotNull Class <T > clazz , @ NotNull byte [] digest , boolean resolveDependencies ) throws AerospikeException {
281291 ClassCacheEntry <T > entry = getEntryAndValidateNamespace (clazz );
282292 Key key = new Key (entry .getNamespace (), digest , entry .getSetName (), null );
283- return this .read (readPolicy , clazz , key , entry );
293+ return this .read (readPolicy , clazz , key , entry , resolveDependencies );
284294 }
285295
286296 public <T > T readFromDigest (@ NotNull Class <T > clazz , @ NotNull byte [] digest ) throws AerospikeException {
297+ return this .readFromDigest (clazz , digest , true );
298+ }
299+
300+ /**
301+ * This method should not be used except by mappers
302+ */
303+ public <T > T readFromDigest (@ NotNull Class <T > clazz , @ NotNull byte [] digest , boolean resolveDependencies ) throws AerospikeException {
287304 ClassCacheEntry <T > entry = getEntryAndValidateNamespace (clazz );
288305 Key key = new Key (entry .getNamespace (), digest , entry .getSetName (), null );
289- return this .read (null , clazz , key , entry );
306+ return this .read (null , clazz , key , entry , resolveDependencies );
290307 }
291308
292309 public <T > T read (Policy readPolicy , @ NotNull Class <T > clazz , @ NotNull Object userKey ) throws AerospikeException {
310+ return this .read (readPolicy , clazz , userKey , true );
311+ }
312+
313+ /**
314+ * This method should not be used except by mappers
315+ */
316+ public <T > T read (Policy readPolicy , @ NotNull Class <T > clazz , @ NotNull Object userKey , boolean resolveDependencies ) throws AerospikeException {
293317 ClassCacheEntry <T > entry = getEntryAndValidateNamespace (clazz );
294318 String set = entry .getSetName ();
295319 Key key = new Key (entry .getNamespace (), set , Value .get (entry .translateKeyToAerospikeKey (userKey )));
296- return read (readPolicy , clazz , key , entry );
320+ return read (readPolicy , clazz , key , entry , resolveDependencies );
297321 }
298322
299323 public <T > T read (@ NotNull Class <T > clazz , @ NotNull Object userKey ) throws AerospikeException {
324+ return this .read (clazz , userKey , true );
325+ }
326+
327+ /**
328+ * This method should not be used: It is used by mappers to correctly resolved dependencies. Use read(clazz, userkey) instead
329+ */
330+ public <T > T read (@ NotNull Class <T > clazz , @ NotNull Object userKey , boolean resolveDependencies ) throws AerospikeException {
300331 ClassCacheEntry <T > entry = getEntryAndValidateNamespace (clazz );
301332 String set = entry .getSetName ();
302333 Key key = new Key (entry .getNamespace (), set , Value .get (entry .translateKeyToAerospikeKey (userKey )));
303- return read (null , clazz , key , entry );
334+ return read (null , clazz , key , entry , resolveDependencies );
304335 }
305336
306- private <T > T read (Policy readPolicy , @ NotNull Class <T > clazz , @ NotNull Key key , @ NotNull ClassCacheEntry <T > entry ) {
337+ private <T > T read (Policy readPolicy , @ NotNull Class <T > clazz , @ NotNull Key key , @ NotNull ClassCacheEntry <T > entry , boolean resolveDepenencies ) {
307338 if (readPolicy == null ) {
308339 readPolicy = entry .getReadPolicy ();
309340 }
@@ -314,7 +345,7 @@ private <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Key key,
314345 } else {
315346 try {
316347 ThreadLocalKeySaver .save (key );
317- T result = (T ) convertToObject (clazz , record , entry );
348+ T result = (T ) convertToObject (clazz , record , entry , resolveDepenencies );
318349 return result ;
319350 } catch (ReflectiveOperationException e ) {
320351 throw new AerospikeException (e );
@@ -325,6 +356,7 @@ private <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Key key,
325356 }
326357 }
327358
359+
328360 public <T > boolean delete (@ NotNull Class <T > clazz , @ NotNull Object userKey ) throws AerospikeException {
329361 return this .delete (null , clazz , userKey );
330362 }
@@ -461,18 +493,39 @@ public <T> T convertToObject(Class<T> clazz, Record record) {
461493 }
462494
463495 public <T > T convertToObject (Class <T > clazz , Record record , ClassCacheEntry <T > entry ) throws ReflectiveOperationException {
496+ return this .convertToObject (clazz , record , entry , true );
497+ }
498+
499+ /**
500+ * This method should not be used, it is public only to allow mappers to see it.
501+ */
502+ public <T > T convertToObject (Class <T > clazz , Record record , ClassCacheEntry <T > entry , boolean resolveDependencies ) throws ReflectiveOperationException {
464503 if (entry == null ) {
465504 entry = ClassCache .getInstance ().loadClass (clazz , this );
466505 }
467- return entry .constructAndHydrate (clazz , record );
506+ T result = entry .constructAndHydrate (clazz , record );
507+ if (resolveDependencies ) {
508+ resolveDependencies (entry );
509+ }
510+ return result ;
468511 }
469512
470513 public <T > T convertToObject (Class <T > clazz , List <Object > record ) {
514+ return this .convertToObject (clazz , record , true );
515+ }
516+
517+ /**
518+ * This method should not be used, it is public only to allow mappers to see it.
519+ */
520+ public <T > T convertToObject (Class <T > clazz , List <Object > record , boolean resolveDependencies ) {
471521 try {
472522 ClassCacheEntry <T > entry = ClassCache .getInstance ().loadClass (clazz , this );
473523 T result ;
474524 result = clazz .getConstructor ().newInstance ();
475525 entry .hydrateFromList (record , result );
526+ if (resolveDependencies ) {
527+ resolveDependencies (entry );
528+ }
476529 return result ;
477530 } catch (ReflectiveOperationException e ) {
478531 throw new AerospikeException (e );
@@ -499,4 +552,74 @@ public <T> Map<String, Object> convertToMap(@NotNull T instance) {
499552 ClassCacheEntry <T > entry = (ClassCacheEntry <T >) ClassCache .getInstance ().loadClass (instance .getClass (), this );
500553 return entry .getMap (instance , false );
501554 }
555+
556+ /**
557+ * If an object refers to other objects (eg A has a list of B via references), then reading the object will populate the
558+ * ids. If configured to do so, these objects can be loaded via a batch load and populated back into the references which
559+ * contain them. This method performs this batch load, translating the records to objects and mapping them back to the
560+ * references.
561+ * <p/>
562+ * These loaded child objects can themselves have other references to other objects, so we iterate through this until
563+ * the list of deferred objects is empty. The deferred objects are stored in a <pre>ThreadLocalData<pre> list, so are thread safe
564+ * @param parentEntity - the ClassCacheEntry of the parent entity. This is used to get the batch policy to use.
565+ */
566+ private void resolveDependencies (ClassCacheEntry <?> parentEntity ) {
567+ List <DeferredObjectSetter > deferredObjects = DeferredObjectLoader .getAndClear ();
568+
569+ if (deferredObjects .size () == 0 ) {
570+ return ;
571+ }
572+
573+ BatchPolicy batchPolicy = parentEntity == null ? mClient .getBatchPolicyDefault () : parentEntity .getBatchPolicy ();
574+ BatchPolicy batchPolicyClone = new BatchPolicy (batchPolicy );
575+
576+ while (deferredObjects != null && !deferredObjects .isEmpty ()) {
577+ int size = deferredObjects .size ();
578+
579+ ClassCacheEntry <?>[] classCaches = new ClassCacheEntry <?>[size ];
580+ Key [] keys = new Key [size ];
581+
582+ for (int i = 0 ; i < size ; i ++) {
583+ DeferredObjectSetter thisObjectSetter = deferredObjects .get (i );
584+ DeferredObject deferredObject = thisObjectSetter .getObject ();
585+ Class <?> clazz = deferredObject .getType ();
586+ ClassCacheEntry <?> entry = (ClassCacheEntry <?>) ClassCache .getInstance ().loadClass (clazz , this );
587+ classCaches [i ] = entry ;
588+
589+ if (deferredObject .isDigest ()) {
590+ keys [i ] = new Key (entry .getNamespace (), (byte [])deferredObject .getKey (), entry .getSetName (), null );
591+ }
592+ else {
593+ keys [i ] = new Key (entry .getNamespace (), entry .getSetName (), Value .get (entry .translateKeyToAerospikeKey (deferredObject .getKey ())));
594+ }
595+ }
596+
597+ // Load the data
598+ if (keys .length <= 2 ) {
599+ // Just single-thread these keys for speed
600+ batchPolicyClone .maxConcurrentThreads = 1 ;
601+ }
602+ else {
603+ batchPolicyClone .maxConcurrentThreads = batchPolicy .maxConcurrentThreads ;
604+ }
605+ Record [] records = this .mClient .get (batchPolicyClone , keys );
606+
607+ for (int i = 0 ; i < size ; i ++) {
608+ DeferredObjectSetter thisObjectSetter = deferredObjects .get (i );
609+ try {
610+ ThreadLocalKeySaver .save (keys [i ]);
611+ Object result = this .convertToObject ((Class )thisObjectSetter .getObject ().getType (), records [i ], classCaches [i ], false );
612+ thisObjectSetter .getSetter ().setValue (result );
613+ } catch (ReflectiveOperationException e ) {
614+ throw new AerospikeException (e );
615+ }
616+ finally {
617+ ThreadLocalKeySaver .clear ();
618+ }
619+ }
620+ deferredObjects = DeferredObjectLoader .getAndClear ();
621+ }
622+ }
623+
624+
502625}
0 commit comments