24
24
import java .util .ArrayList ;
25
25
import java .util .Collection ;
26
26
import java .util .List ;
27
+ import java .util .Map ;
27
28
import java .util .concurrent .CompletableFuture ;
28
29
import java .util .concurrent .CompletionStage ;
29
30
import java .util .stream .Collectors ;
63
64
public class DataLoader <K , V > {
64
65
65
66
private final BatchLoader <K , V > batchLoadFunction ;
67
+ private final MapBatchLoader <K , V > mapBatchLoadFunction ;
66
68
private final DataLoaderOptions loaderOptions ;
67
69
private final CacheMap <Object , CompletableFuture <V >> futureCache ;
68
70
private final List <SimpleImmutableEntry <K , CompletableFuture <V >>> loaderQueue ;
@@ -96,6 +98,35 @@ public static <K, V> DataLoader<K, V> newDataLoader(BatchLoader<K, V> batchLoadF
96
98
return new DataLoader <>(batchLoadFunction , options );
97
99
}
98
100
101
+ /**
102
+ * Creates new DataLoader with the specified map batch loader function and default options
103
+ * (batching, caching and unlimited batch size).
104
+ *
105
+ * @param mapBatchLoaderFunction the batch load function to use
106
+ * @param <K> the key type
107
+ * @param <V> the value type
108
+ *
109
+ * @return a new DataLoader
110
+ */
111
+ public static <K , V > DataLoader <K , V > newDataLoader (MapBatchLoader <K , V > mapBatchLoaderFunction ) {
112
+ return newDataLoader (mapBatchLoaderFunction , null );
113
+ }
114
+
115
+ /**
116
+ * Creates new DataLoader with the specified map batch loader function with the provided options
117
+ *
118
+ * @param mapBatchLoaderFunction the batch load function to use
119
+ * @param options the options to use
120
+ * @param <K> the key type
121
+ * @param <V> the value type
122
+ *
123
+ * @return a new DataLoader
124
+ */
125
+ public static <K , V > DataLoader <K , V > newDataLoader (MapBatchLoader <K , V > mapBatchLoaderFunction , DataLoaderOptions options ) {
126
+ return new DataLoader <>(mapBatchLoaderFunction , options );
127
+ }
128
+
129
+
99
130
/**
100
131
* Creates new DataLoader with the specified batch loader function and default options
101
132
* (batching, caching and unlimited batch size) where the batch loader function returns a list of
@@ -134,6 +165,43 @@ public static <K, V> DataLoader<K, V> newDataLoaderWithTry(BatchLoader<K, Try<V>
134
165
return new DataLoader <>((BatchLoader <K , V >) batchLoadFunction , options );
135
166
}
136
167
168
+ /**
169
+ * Creates new DataLoader with the specified map abatch loader function and default options
170
+ * (batching, caching and unlimited batch size) where the batch loader function returns a list of
171
+ * {@link org.dataloader.Try} objects.
172
+ *
173
+ * This allows you to capture both the value that might be returned and also whether exception that might have occurred getting that individual value. If its important you to
174
+ * know gather exact status of each item in a batch call and whether it threw exceptions when fetched then
175
+ * you can use this form to create the data loader.
176
+ *
177
+ * @param mapBatchLoaderFunction the map batch load function to use that uses {@link org.dataloader.Try} objects
178
+ * @param <K> the key type
179
+ * @param <V> the value type
180
+ *
181
+ * @return a new DataLoader
182
+ */
183
+ public static <K , V > DataLoader <K , V > newDataLoaderWithTry (MapBatchLoader <K , Try <V >> mapBatchLoaderFunction ) {
184
+ return newDataLoaderWithTry (mapBatchLoaderFunction , null );
185
+ }
186
+
187
+ /**
188
+ * Creates new DataLoader with the specified map batch loader function and with the provided options
189
+ * where the batch loader function returns a list of
190
+ * {@link org.dataloader.Try} objects.
191
+ *
192
+ * @param mapBatchLoaderFunction the map batch load function to use that uses {@link org.dataloader.Try} objects
193
+ * @param options the options to use
194
+ * @param <K> the key type
195
+ * @param <V> the value type
196
+ *
197
+ * @return a new DataLoader
198
+ *
199
+ * @see #newDataLoaderWithTry(MapBatchLoader)
200
+ */
201
+ @ SuppressWarnings ("unchecked" )
202
+ public static <K , V > DataLoader <K , V > newDataLoaderWithTry (MapBatchLoader <K , Try <V >> mapBatchLoaderFunction , DataLoaderOptions options ) {
203
+ return new DataLoader <>((MapBatchLoader <K , V >) mapBatchLoaderFunction , options );
204
+ }
137
205
138
206
/**
139
207
* Creates a new data loader with the provided batch load function, and default options.
@@ -144,19 +212,53 @@ public DataLoader(BatchLoader<K, V> batchLoadFunction) {
144
212
this (batchLoadFunction , null );
145
213
}
146
214
215
+ /**
216
+ * Creates a new data loader with the provided map batch load function.
217
+ *
218
+ * @param mapBatchLoadFunction the map batch load function to use
219
+ */
220
+ public DataLoader (MapBatchLoader <K , V > mapBatchLoadFunction ) {
221
+ this (mapBatchLoadFunction , null );
222
+ }
223
+
147
224
/**
148
225
* Creates a new data loader with the provided batch load function and options.
149
226
*
150
227
* @param batchLoadFunction the batch load function to use
151
228
* @param options the batch load options
152
229
*/
153
230
public DataLoader (BatchLoader <K , V > batchLoadFunction , DataLoaderOptions options ) {
154
- this .batchLoadFunction = nonNull (batchLoadFunction );
155
- this .loaderOptions = options == null ? new DataLoaderOptions () : options ;
231
+ this .batchLoadFunction = batchLoadFunction ;
232
+ this .mapBatchLoadFunction = null ;
233
+ this .loaderOptions = determineOptions (options );
156
234
this .futureCache = determineCacheMap (loaderOptions );
157
235
// order of keys matter in data loader
158
236
this .loaderQueue = new ArrayList <>();
159
- this .stats = nonNull (this .loaderOptions .getStatisticsCollector ());
237
+ this .stats = determineCollector (this .loaderOptions );
238
+ }
239
+
240
+ /**
241
+ * Creates a new data loader with the provided map batch load function and options.
242
+ *
243
+ * @param mapBatchLoadFunction the map batch load function to use
244
+ * @param options the batch load options
245
+ */
246
+ public DataLoader (MapBatchLoader <K , V > mapBatchLoadFunction , DataLoaderOptions options ) {
247
+ this .batchLoadFunction = null ;
248
+ this .mapBatchLoadFunction = mapBatchLoadFunction ;
249
+ this .loaderOptions = determineOptions (options );
250
+ this .futureCache = determineCacheMap (loaderOptions );
251
+ // order of keys matter in data loader
252
+ this .loaderQueue = new ArrayList <>();
253
+ this .stats = determineCollector (this .loaderOptions );
254
+ }
255
+
256
+ private StatisticsCollector determineCollector (DataLoaderOptions loaderOptions ) {
257
+ return nonNull (loaderOptions .getStatisticsCollector ());
258
+ }
259
+
260
+ private DataLoaderOptions determineOptions (DataLoaderOptions options ) {
261
+ return options == null ? new DataLoaderOptions () : options ;
160
262
}
161
263
162
264
@ SuppressWarnings ("unchecked" )
@@ -197,11 +299,7 @@ public CompletableFuture<V> load(K key) {
197
299
stats .incrementBatchLoadCountBy (1 );
198
300
// immediate execution of batch function
199
301
Object context = loaderOptions .getBatchContextProvider ().get ();
200
- CompletableFuture <List <V >> batchedLoad = batchLoadFunction
201
- .load (singletonList (key ), context )
202
- .toCompletableFuture ();
203
- future = batchedLoad
204
- .thenApply (list -> list .get (0 ));
302
+ future = invokeLoaderImmediately (key , context );
205
303
}
206
304
if (cachingEnabled ) {
207
305
futureCache .set (cacheKey , future );
@@ -210,6 +308,7 @@ public CompletableFuture<V> load(K key) {
210
308
}
211
309
}
212
310
311
+
213
312
/**
214
313
* Requests to load the list of data provided by the specified keys asynchronously, and returns a composite future
215
314
* of the resulting values.
@@ -232,6 +331,25 @@ public CompletableFuture<List<V>> loadMany(List<K> keys) {
232
331
}
233
332
}
234
333
334
+ private CompletableFuture <V > invokeLoaderImmediately (K key , Object context ) {
335
+ List <K > keys = singletonList (key );
336
+ CompletionStage <V > singleLoadCall ;
337
+ if (isMapLoader ()) {
338
+ singleLoadCall = mapBatchLoadFunction
339
+ .load (keys , context )
340
+ .thenApply (map -> map .get (key ));
341
+ } else {
342
+ singleLoadCall = batchLoadFunction
343
+ .load (keys , context )
344
+ .thenApply (list -> list .get (0 ));
345
+ }
346
+ return singleLoadCall .toCompletableFuture ();
347
+ }
348
+
349
+ private boolean isMapLoader () {
350
+ return mapBatchLoadFunction != null ;
351
+ }
352
+
235
353
/**
236
354
* Dispatches the queued load requests to the batch execution function and returns a promise of the result.
237
355
* <p>
@@ -302,17 +420,11 @@ private CompletableFuture<List<V>> sliceIntoBatchesOfBatches(List<K> keys, List<
302
420
@ SuppressWarnings ("unchecked" )
303
421
private CompletableFuture <List <V >> dispatchQueueBatch (List <K > keys , List <CompletableFuture <V >> queuedFutures ) {
304
422
stats .incrementBatchLoadCountBy (keys .size ());
305
- CompletionStage <List <V >> batchLoad ;
306
- try {
307
- Object context = loaderOptions .getBatchContextProvider ().get ();
308
- batchLoad = nonNull (batchLoadFunction .load (keys , context ), "Your batch loader function MUST return a non null CompletionStage promise" );
309
- } catch (Exception e ) {
310
- batchLoad = CompletableFutureKit .failedFuture (e );
311
- }
423
+ CompletionStage <List <V >> batchLoad = invokeBatchFunction (keys );
312
424
return batchLoad
313
425
.toCompletableFuture ()
314
426
.thenApply (values -> {
315
- assertState (keys . size () == values . size (), "The size of the promised values MUST be the same size as the key list" );
427
+ assertResultSize (keys , values );
316
428
317
429
for (int idx = 0 ; idx < queuedFutures .size (); idx ++) {
318
430
Object value = values .get (idx );
@@ -351,6 +463,45 @@ private CompletableFuture<List<V>> dispatchQueueBatch(List<K> keys, List<Complet
351
463
});
352
464
}
353
465
466
+ private CompletionStage <List <V >> invokeBatchFunction (List <K > keys ) {
467
+ CompletionStage <List <V >> batchLoad ;
468
+ try {
469
+ Object context = loaderOptions .getBatchContextProvider ().get ();
470
+ if (isMapLoader ()) {
471
+ batchLoad = invokeMapBatchLoader (keys , context );
472
+ } else {
473
+ batchLoad = invokeListBatchLoader (keys , context );
474
+ }
475
+ } catch (Exception e ) {
476
+ batchLoad = CompletableFutureKit .failedFuture (e );
477
+ }
478
+ return batchLoad ;
479
+ }
480
+
481
+ private CompletionStage <List <V >> invokeListBatchLoader (List <K > keys , Object context ) {
482
+ return nonNull (batchLoadFunction .load (keys , context ), "Your batch loader function MUST return a non null CompletionStage promise" );
483
+ }
484
+
485
+ /*
486
+ * Turns a map of results that MAY be smaller than the key list back into a list by mapping null
487
+ * to missing elements.
488
+ */
489
+ private CompletionStage <List <V >> invokeMapBatchLoader (List <K > keys , Object context ) {
490
+ CompletionStage <Map <K , V >> mapBatchLoad = nonNull (mapBatchLoadFunction .load (keys , context ), "Your batch loader function MUST return a non null CompletionStage promise" );
491
+ return mapBatchLoad .thenApply (map -> {
492
+ List <V > values = new ArrayList <>();
493
+ for (K key : keys ) {
494
+ V value = map .get (key );
495
+ values .add (value );
496
+ }
497
+ return values ;
498
+ });
499
+ }
500
+
501
+ private void assertResultSize (List <K > keys , List <V > values ) {
502
+ assertState (keys .size () == values .size (), "The size of the promised values MUST be the same size as the key list" );
503
+ }
504
+
354
505
/**
355
506
* Normally {@link #dispatch()} is an asynchronous operation but this version will 'join' on the
356
507
* results if dispatch and wait for them to complete. If the {@link CompletableFuture} callbacks make more
0 commit comments