Skip to content

Commit 8d3636e

Browse files
committed
Added the BatchLoadingEnvironment as a way to hang future objects into the call context
1 parent d579ef0 commit 8d3636e

12 files changed

+162
-104
lines changed

README.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -165,33 +165,40 @@ a list of user ids in one call.
165165

166166
That said, with key caching turn on (the default), it will still be more efficient using `dataloader` than without it.
167167

168-
### Calling the batch loader function with context
168+
### Calling the batch loader function with call context environment
169169

170-
Often there is a need to call the batch loader function with some sort of context, such as the calling users security
170+
Often there is a need to call the batch loader function with some sort of call context environment, such as the calling users security
171171
credentials or the database connection parameters. You can do this by implementing a
172-
`org.dataloader.BatchContextProvider`.
172+
`org.dataloader.BatchLoaderEnvironmentProvider`.
173173

174174
```java
175175
BatchLoader<String, String> batchLoader = new BatchLoader<String, String>() {
176+
//
177+
// the reason this method exists is for backwards compatibility. There are a large
178+
// number of existing dataloader clients out there that used this method before the call context was invented
179+
// and hence to preserve compatibility we have this unfortunate method declaration.
180+
//
176181
@Override
177182
public CompletionStage<List<String>> load(List<String> keys) {
178-
throw new UnsupportedOperationException("This wont be called if you implement the other defaulted method");
183+
throw new UnsupportedOperationException();
179184
}
180185

181186
@Override
182-
public CompletionStage<List<String>> load(List<String> keys, Object context) {
183-
SecurityCtx callCtx = (SecurityCtx) context;
187+
public CompletionStage<List<String>> load(List<String> keys, BatchLoaderEnvironment environment) {
188+
SecurityCtx callCtx = environment.getContext();
184189
return callDatabaseForResults(callCtx, keys);
185190
}
186-
187191
};
192+
193+
BatchLoaderEnvironment batchLoaderEnvironment = BatchLoaderEnvironment.newBatchLoaderEnvironment()
194+
.context(SecurityCtx.getCallingUserCtx()).build();
195+
188196
DataLoaderOptions options = DataLoaderOptions.newOptions()
189-
.setBatchContextProvider(() -> SecurityCtx.getCallingUserCtx());
197+
.setBatchLoaderEnvironmentProvider(() -> batchLoaderEnvironment);
190198
DataLoader<String, String> loader = new DataLoader<>(batchLoader, options);
191-
192199
```
193200

194-
The batch loading code will now receive this context object and it can be used to get to data layers or
201+
The batch loading code will now receive this environment object and it can be used to get context perhaps allowing it
195202
to connect to other systems.
196203

197204
### Returning a Map of results from your batch loader
@@ -219,8 +226,8 @@ For example, let's assume you want to load users from a database, you could prob
219226
```java
220227
MapBatchLoader<Long, User> mapBatchLoader = new MapBatchLoader<Long, User>() {
221228
@Override
222-
public CompletionStage<Map<Long, User>> load(List<Long> userIds, Object context) {
223-
SecurityCtx callCtx = (SecurityCtx) context;
229+
public CompletionStage<Map<Long, User>> load(List<Long> userIds, BatchLoaderEnvironment environment) {
230+
SecurityCtx callCtx = environment.getContext();
224231
return CompletableFuture.supplyAsync(() -> userManager.loadMapOfUsersById(callCtx, userIds));
225232
}
226233
};
@@ -267,7 +274,7 @@ and some of which may have failed. From that data loader can infer the right be
267274
```java
268275
DataLoader<String, User> dataLoader = DataLoader.newDataLoaderWithTry(new BatchLoader<String, Try<User>>() {
269276
@Override
270-
public CompletionStage<List<Try<User>>> load(List<String> keys) {
277+
public CompletionStage<List<Try<User>>> load(List<String> keys, BatchLoaderEnvironment environment) {
271278
return CompletableFuture.supplyAsync(() -> {
272279
List<Try<User>> users = new ArrayList<>();
273280
for (String key : keys) {
@@ -278,7 +285,6 @@ and some of which may have failed. From that data loader can infer the right be
278285
});
279286
}
280287
});
281-
282288
```
283289

284290
On the above example if one of the `Try` objects represents a failure, then its `load()` promise will complete exceptionally and you can

src/main/java/org/dataloader/BatchContextProvider.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/main/java/org/dataloader/BatchLoader.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package org.dataloader;
1818

19+
import java.util.Collections;
1920
import java.util.List;
21+
import java.util.concurrent.CompletableFuture;
2022
import java.util.concurrent.CompletionStage;
2123

2224
/**
@@ -76,31 +78,32 @@ public interface BatchLoader<K, V> {
7678
/**
7779
* Called to batch load the provided keys and return a promise to a list of values.
7880
*
79-
* If you need calling context then implement the {@link #load(java.util.List, Object)} method
80-
* instead.
81+
* If you need calling context then implement the {@link #load(java.util.List, BatchLoaderEnvironment)} method
82+
* instead which is the preferred method.
8183
*
8284
* @param keys the collection of keys to load
8385
*
8486
* @return a promise of the values for those keys
87+
*
8588
*/
8689
CompletionStage<List<V>> load(List<K> keys);
8790

8891
/**
8992
* Called to batch load the provided keys and return a promise to a list of values. This default
90-
* version can be given a context object to that maybe be useful during the call. A typical use case
93+
* version can be given an environment object to that maybe be useful during the call. A typical use case
9194
* is passing in security credentials or database connection details say.
9295
*
9396
* This method is implemented as a default method in order to preserve the API for previous
9497
* callers. It is always called first by the {@link org.dataloader.DataLoader} code and simply
9598
* delegates to the {@link #load(java.util.List)} method.
9699
*
97-
* @param keys the collection of keys to load
98-
* @param context a context object that can help with the call
100+
* @param keys the collection of keys to load
101+
* @param environment an environment object that can help with the call
99102
*
100103
* @return a promise of the values for those keys
101104
*/
102-
@SuppressWarnings("unused")
103-
default CompletionStage<List<V>> load(List<K> keys, Object context) {
105+
@SuppressWarnings({"unused", "deprecation"})
106+
default CompletionStage<List<V>> load(List<K> keys, BatchLoaderEnvironment environment) {
104107
return load(keys);
105108
}
106109
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.dataloader;
2+
3+
/**
4+
* This object is passed to a batch loader as calling context. It could contain security credentials
5+
* of the calling users say or database connection parameters that allow the data layer call to succeed.
6+
*/
7+
public class BatchLoaderEnvironment {
8+
9+
private final Object context;
10+
11+
private BatchLoaderEnvironment(Object context) {
12+
this.context = context;
13+
}
14+
15+
@SuppressWarnings("unchecked")
16+
public <T> T getContext() {
17+
return (T) context;
18+
}
19+
20+
public static Builder newBatchLoaderEnvironment() {
21+
return new Builder();
22+
}
23+
24+
public static class Builder {
25+
private Object context;
26+
27+
private Builder() {
28+
29+
}
30+
31+
public Builder context(Object context) {
32+
this.context = context;
33+
return this;
34+
}
35+
36+
public BatchLoaderEnvironment build() {
37+
return new BatchLoaderEnvironment(context);
38+
}
39+
}
40+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.dataloader;
2+
3+
/**
4+
* A BatchLoaderEnvironmentProvider is used by the {@link org.dataloader.DataLoader} code to
5+
* provide {@link org.dataloader.BatchLoaderEnvironment} calling context to
6+
* the {@link org.dataloader.BatchLoader} call. A common use
7+
* case is for propagating user security credentials or database connection parameters.
8+
*/
9+
public interface BatchLoaderEnvironmentProvider {
10+
/**
11+
* @return a {@link org.dataloader.BatchLoaderEnvironment} that may be needed in batch calls
12+
*/
13+
BatchLoaderEnvironment get();
14+
}

src/main/java/org/dataloader/DataLoader.java

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,7 @@ public CompletableFuture<V> load(K key) {
298298
} else {
299299
stats.incrementBatchLoadCountBy(1);
300300
// immediate execution of batch function
301-
Object context = loaderOptions.getBatchContextProvider().get();
302-
future = invokeLoaderImmediately(key, context);
301+
future = invokeLoaderImmediately(key);
303302
}
304303
if (cachingEnabled) {
305304
futureCache.set(cacheKey, future);
@@ -331,25 +330,6 @@ public CompletableFuture<List<V>> loadMany(List<K> keys) {
331330
}
332331
}
333332

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-
353333
/**
354334
* Dispatches the queued load requests to the batch execution function and returns a promise of the result.
355335
* <p>
@@ -420,7 +400,7 @@ private CompletableFuture<List<V>> sliceIntoBatchesOfBatches(List<K> keys, List<
420400
@SuppressWarnings("unchecked")
421401
private CompletableFuture<List<V>> dispatchQueueBatch(List<K> keys, List<CompletableFuture<V>> queuedFutures) {
422402
stats.incrementBatchLoadCountBy(keys.size());
423-
CompletionStage<List<V>> batchLoad = invokeBatchFunction(keys);
403+
CompletionStage<List<V>> batchLoad = invokeLoader(keys);
424404
return batchLoad
425405
.toCompletableFuture()
426406
.thenApply(values -> {
@@ -463,31 +443,51 @@ private CompletableFuture<List<V>> dispatchQueueBatch(List<K> keys, List<Complet
463443
});
464444
}
465445

466-
private CompletionStage<List<V>> invokeBatchFunction(List<K> keys) {
446+
private boolean isMapLoader() {
447+
return mapBatchLoadFunction != null;
448+
}
449+
450+
private CompletableFuture<V> invokeLoaderImmediately(K key) {
451+
BatchLoaderEnvironment environment = loaderOptions.getBatchLoaderEnvironmentProvider().get();
452+
List<K> keys = singletonList(key);
453+
CompletionStage<V> singleLoadCall;
454+
if (isMapLoader()) {
455+
singleLoadCall = mapBatchLoadFunction
456+
.load(keys, environment)
457+
.thenApply(map -> map.get(key));
458+
} else {
459+
singleLoadCall = batchLoadFunction
460+
.load(keys, environment)
461+
.thenApply(list -> list.get(0));
462+
}
463+
return singleLoadCall.toCompletableFuture();
464+
}
465+
466+
private CompletionStage<List<V>> invokeLoader(List<K> keys) {
467467
CompletionStage<List<V>> batchLoad;
468468
try {
469-
Object context = loaderOptions.getBatchContextProvider().get();
469+
BatchLoaderEnvironment environment = loaderOptions.getBatchLoaderEnvironmentProvider().get();
470470
if (isMapLoader()) {
471-
batchLoad = invokeMapBatchLoader(keys, context);
471+
batchLoad = invokeMapBatchLoader(keys, environment);
472472
} else {
473-
batchLoad = invokeListBatchLoader(keys, context);
473+
batchLoad = invokeListBatchLoader(keys, environment);
474474
}
475475
} catch (Exception e) {
476476
batchLoad = CompletableFutureKit.failedFuture(e);
477477
}
478478
return batchLoad;
479479
}
480480

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");
481+
private CompletionStage<List<V>> invokeListBatchLoader(List<K> keys, BatchLoaderEnvironment environment) {
482+
return nonNull(batchLoadFunction.load(keys, environment), "Your batch loader function MUST return a non null CompletionStage promise");
483483
}
484484

485485
/*
486486
* Turns a map of results that MAY be smaller than the key list back into a list by mapping null
487487
* to missing elements.
488488
*/
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");
489+
private CompletionStage<List<V>> invokeMapBatchLoader(List<K> keys, BatchLoaderEnvironment environment) {
490+
CompletionStage<Map<K, V>> mapBatchLoad = nonNull(mapBatchLoadFunction.load(keys, environment), "Your batch loader function MUST return a non null CompletionStage promise");
491491
return mapBatchLoad.thenApply(map -> {
492492
List<V> values = new ArrayList<>();
493493
for (K key : keys) {

src/main/java/org/dataloader/DataLoaderOptions.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@
3131
*/
3232
public class DataLoaderOptions {
3333

34-
private static final BatchContextProvider NULL_PROVIDER = () -> null;
34+
private static final BatchLoaderEnvironmentProvider NULL_PROVIDER = () -> BatchLoaderEnvironment.newBatchLoaderEnvironment().build();
3535

3636
private boolean batchingEnabled;
3737
private boolean cachingEnabled;
3838
private CacheKey cacheKeyFunction;
3939
private CacheMap cacheMap;
4040
private int maxBatchSize;
4141
private Supplier<StatisticsCollector> statisticsCollector;
42-
private BatchContextProvider contextProvider;
42+
private BatchLoaderEnvironmentProvider environmentProvider;
4343

4444
/**
4545
* Creates a new data loader options with default settings.
@@ -49,7 +49,7 @@ public DataLoaderOptions() {
4949
cachingEnabled = true;
5050
maxBatchSize = -1;
5151
statisticsCollector = SimpleStatisticsCollector::new;
52-
contextProvider = NULL_PROVIDER;
52+
environmentProvider = NULL_PROVIDER;
5353
}
5454

5555
/**
@@ -65,7 +65,7 @@ public DataLoaderOptions(DataLoaderOptions other) {
6565
this.cacheMap = other.cacheMap;
6666
this.maxBatchSize = other.maxBatchSize;
6767
this.statisticsCollector = other.statisticsCollector;
68-
this.contextProvider = other.contextProvider;
68+
this.environmentProvider = other.environmentProvider;
6969
}
7070

7171
/**
@@ -208,21 +208,21 @@ public DataLoaderOptions setStatisticsCollector(Supplier<StatisticsCollector> st
208208
}
209209

210210
/**
211-
* @return the batch context provider that will be used to give context to batch load functions
211+
* @return the batch environment provider that will be used to give context to batch load functions
212212
*/
213-
public BatchContextProvider getBatchContextProvider() {
214-
return contextProvider;
213+
public BatchLoaderEnvironmentProvider getBatchLoaderEnvironmentProvider() {
214+
return environmentProvider;
215215
}
216216

217217
/**
218-
* Sets the batch context provider that will be used to give context to batch load functions
218+
* Sets the batch loader environment provider that will be used to give context to batch load functions
219219
*
220-
* @param contextProvider the batch context provider
220+
* @param environmentProvider the batch loader environment provider
221221
*
222222
* @return the data loader options for fluent coding
223223
*/
224-
public DataLoaderOptions setBatchContextProvider(BatchContextProvider contextProvider) {
225-
this.contextProvider = nonNull(contextProvider);
224+
public DataLoaderOptions setBatchLoaderEnvironmentProvider(BatchLoaderEnvironmentProvider environmentProvider) {
225+
this.environmentProvider = nonNull(environmentProvider);
226226
return this;
227227
}
228228
}

src/main/java/org/dataloader/MapBatchLoader.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,19 @@
5757
* @param <K> type parameter indicating the type of keys to use for data load requests.
5858
* @param <V> type parameter indicating the type of values returned
5959
*
60-
* @author <a href="https://github.com/aschrijver/">Arnold Schrijver</a>
6160
* @author <a href="https://github.com/bbakerman/">Brad Baker</a>
6261
*/
63-
@FunctionalInterface
6462
public interface MapBatchLoader<K, V> {
6563

6664
/**
67-
* Called to batch load the provided keys and return a promise to a map of values. It can be given a context object to
65+
* Called to batch load the provided keys and return a promise to a map of values. It can be given an environment object to
6866
* that maybe be useful during the call. A typical use case is passing in security credentials or database connection details say.
6967
*
70-
* @param keys the collection of keys to load
71-
* @param context a context object that can help with the call
68+
* @param keys the collection of keys to load
69+
* @param environment an environment object that can help with the call
7270
*
7371
* @return a promise to a map of values for those keys
7472
*/
7573
@SuppressWarnings("unused")
76-
CompletionStage<Map<K, V>> load(List<K> keys, Object context);
74+
CompletionStage<Map<K, V>> load(List<K> keys, BatchLoaderEnvironment environment);
7775
}

0 commit comments

Comments
 (0)