2424import java .util .ArrayList ;
2525import java .util .Collection ;
2626import java .util .List ;
27+ import java .util .Map ;
2728import java .util .concurrent .CompletableFuture ;
2829import java .util .concurrent .CompletionStage ;
2930import java .util .stream .Collectors ;
6364public class DataLoader <K , V > {
6465
6566 private final BatchLoader <K , V > batchLoadFunction ;
67+ private final MapBatchLoader <K , V > mapBatchLoadFunction ;
6668 private final DataLoaderOptions loaderOptions ;
6769 private final CacheMap <Object , CompletableFuture <V >> futureCache ;
6870 private final List <SimpleImmutableEntry <K , CompletableFuture <V >>> loaderQueue ;
@@ -96,6 +98,35 @@ public static <K, V> DataLoader<K, V> newDataLoader(BatchLoader<K, V> batchLoadF
9698 return new DataLoader <>(batchLoadFunction , options );
9799 }
98100
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+
99130 /**
100131 * Creates new DataLoader with the specified batch loader function and default options
101132 * (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>
134165 return new DataLoader <>((BatchLoader <K , V >) batchLoadFunction , options );
135166 }
136167
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+ }
137205
138206 /**
139207 * Creates a new data loader with the provided batch load function, and default options.
@@ -144,19 +212,53 @@ public DataLoader(BatchLoader<K, V> batchLoadFunction) {
144212 this (batchLoadFunction , null );
145213 }
146214
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+
147224 /**
148225 * Creates a new data loader with the provided batch load function and options.
149226 *
150227 * @param batchLoadFunction the batch load function to use
151228 * @param options the batch load options
152229 */
153230 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 );
156234 this .futureCache = determineCacheMap (loaderOptions );
157235 // order of keys matter in data loader
158236 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 ;
160262 }
161263
162264 @ SuppressWarnings ("unchecked" )
@@ -197,11 +299,7 @@ public CompletableFuture<V> load(K key) {
197299 stats .incrementBatchLoadCountBy (1 );
198300 // immediate execution of batch function
199301 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 );
205303 }
206304 if (cachingEnabled ) {
207305 futureCache .set (cacheKey , future );
@@ -210,6 +308,7 @@ public CompletableFuture<V> load(K key) {
210308 }
211309 }
212310
311+
213312 /**
214313 * Requests to load the list of data provided by the specified keys asynchronously, and returns a composite future
215314 * of the resulting values.
@@ -232,6 +331,25 @@ public CompletableFuture<List<V>> loadMany(List<K> keys) {
232331 }
233332 }
234333
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+
235353 /**
236354 * Dispatches the queued load requests to the batch execution function and returns a promise of the result.
237355 * <p>
@@ -302,17 +420,11 @@ private CompletableFuture<List<V>> sliceIntoBatchesOfBatches(List<K> keys, List<
302420 @ SuppressWarnings ("unchecked" )
303421 private CompletableFuture <List <V >> dispatchQueueBatch (List <K > keys , List <CompletableFuture <V >> queuedFutures ) {
304422 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 );
312424 return batchLoad
313425 .toCompletableFuture ()
314426 .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 );
316428
317429 for (int idx = 0 ; idx < queuedFutures .size (); idx ++) {
318430 Object value = values .get (idx );
@@ -351,6 +463,45 @@ private CompletableFuture<List<V>> dispatchQueueBatch(List<K> keys, List<Complet
351463 });
352464 }
353465
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+
354505 /**
355506 * Normally {@link #dispatch()} is an asynchronous operation but this version will 'join' on the
356507 * results if dispatch and wait for them to complete. If the {@link CompletableFuture} callbacks make more
0 commit comments