Skip to content

Commit c68f79c

Browse files
committed
Added the ability to have call context on batch loader calls
1 parent 26f91ee commit c68f79c

File tree

7 files changed

+179
-3
lines changed

7 files changed

+179
-3
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,34 @@ 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
169+
170+
Often there is a need to call the batch loader function with some sort of context, such as the calling users security
171+
credentials or the database connection parameters. You can do this by implementing a
172+
`org.dataloader.BatchContextProvider`.
173+
174+
```java
175+
BatchLoader<String, String> batchLoader = new BatchLoader<String, String>() {
176+
@Override
177+
public CompletionStage<List<String>> load(List<String> keys) {
178+
throw new UnsupportedOperationException("This wont be called if you implement the other defaulted method");
179+
}
180+
181+
@Override
182+
public CompletionStage<List<String>> load(List<String> keys, Object context) {
183+
SecurityCtx callCtx = (SecurityCtx) context;
184+
return callDatabaseForResults(callCtx, keys);
185+
}
186+
187+
};
188+
DataLoaderOptions options = DataLoaderOptions.newOptions()
189+
.setBatchContextProvider(() -> SecurityCtx.getCallingUserCtx());
190+
DataLoader<String, String> loader = new DataLoader<>(batchLoader, options);
191+
192+
```
193+
194+
The batch loading code will now receive this context object and it can be used to get to data layers or
195+
to connect to other systems.
168196

169197
### Error object is not a thing in a type safe Java world
170198

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.dataloader;
2+
3+
/**
4+
* A BatchContextProvider is used by the {@link org.dataloader.DataLoader} code to
5+
* provide context to the {@link org.dataloader.BatchLoader} call. A common use
6+
* case is for propagating user security credentials or database connection parameters.
7+
*/
8+
public interface BatchContextProvider {
9+
/**
10+
* @return a context object that may be needed in batch calls
11+
*/
12+
Object get();
13+
}

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,33 @@
7474
public interface BatchLoader<K, V> {
7575

7676
/**
77-
* Called to batch load the provided keys and return a promise to a list of values
77+
* Called to batch load the provided keys and return a promise to a list of values.
78+
*
79+
* If you need calling context then implement the {@link #load(java.util.List, Object)} method
80+
* instead.
7881
*
7982
* @param keys the collection of keys to load
8083
*
8184
* @return a promise of the values for those keys
8285
*/
8386
CompletionStage<List<V>> load(List<K> keys);
87+
88+
/**
89+
* 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
91+
* is passing in security credentials or database connection details say.
92+
*
93+
* This method is implemented as a default method in order to preserve the API for previous
94+
* callers. It is always called first by the {@link org.dataloader.DataLoader} code and simply
95+
* delegates to the {@link #load(java.util.List)} method.
96+
*
97+
* @param keys the collection of keys to load
98+
* @param context a context object that can help with the call
99+
*
100+
* @return a promise of the values for those keys
101+
*/
102+
@SuppressWarnings("unused")
103+
default CompletionStage<List<V>> load(List<K> keys, Object context) {
104+
return load(keys);
105+
}
84106
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,9 @@ public CompletableFuture<V> load(K key) {
196196
} else {
197197
stats.incrementBatchLoadCountBy(1);
198198
// immediate execution of batch function
199+
Object context = loaderOptions.getBatchContextProvider().get();
199200
CompletableFuture<List<V>> batchedLoad = batchLoadFunction
200-
.load(singletonList(key))
201+
.load(singletonList(key), context)
201202
.toCompletableFuture();
202203
future = batchedLoad
203204
.thenApply(list -> list.get(0));
@@ -303,7 +304,8 @@ private CompletableFuture<List<V>> dispatchQueueBatch(List<K> keys, List<Complet
303304
stats.incrementBatchLoadCountBy(keys.size());
304305
CompletionStage<List<V>> batchLoad;
305306
try {
306-
batchLoad = nonNull(batchLoadFunction.load(keys), "Your batch loader function MUST return a non null CompletionStage promise");
307+
Object context = loaderOptions.getBatchContextProvider().get();
308+
batchLoad = nonNull(batchLoadFunction.load(keys, context), "Your batch loader function MUST return a non null CompletionStage promise");
307309
} catch (Exception e) {
308310
batchLoad = CompletableFutureKit.failedFuture(e);
309311
}

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

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

34+
private static final BatchContextProvider NULL_PROVIDER = () -> null;
35+
3436
private boolean batchingEnabled;
3537
private boolean cachingEnabled;
3638
private CacheKey cacheKeyFunction;
3739
private CacheMap cacheMap;
3840
private int maxBatchSize;
3941
private Supplier<StatisticsCollector> statisticsCollector;
42+
private BatchContextProvider contextProvider;
4043

4144
/**
4245
* Creates a new data loader options with default settings.
@@ -46,6 +49,7 @@ public DataLoaderOptions() {
4649
cachingEnabled = true;
4750
maxBatchSize = -1;
4851
statisticsCollector = SimpleStatisticsCollector::new;
52+
contextProvider = NULL_PROVIDER;
4953
}
5054

5155
/**
@@ -61,6 +65,7 @@ public DataLoaderOptions(DataLoaderOptions other) {
6165
this.cacheMap = other.cacheMap;
6266
this.maxBatchSize = other.maxBatchSize;
6367
this.statisticsCollector = other.statisticsCollector;
68+
this.contextProvider = other.contextProvider;
6469
}
6570

6671
/**
@@ -202,5 +207,22 @@ public DataLoaderOptions setStatisticsCollector(Supplier<StatisticsCollector> st
202207
return this;
203208
}
204209

210+
/**
211+
* @return the batch context provider that will be used to give context to batch load functions
212+
*/
213+
public BatchContextProvider getBatchContextProvider() {
214+
return contextProvider;
215+
}
205216

217+
/**
218+
* Sets the batch context provider that will be used to give context to batch load functions
219+
*
220+
* @param contextProvider the batch context provider
221+
*
222+
* @return the data loader options for fluent coding
223+
*/
224+
public DataLoaderOptions setBatchContextProvider(BatchContextProvider contextProvider) {
225+
this.contextProvider = nonNull(contextProvider);
226+
return this;
227+
}
206228
}

src/test/java/ReadmeExamples.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,36 @@ public CompletionStage<List<User>> load(List<Long> userIds) {
7878
userLoader.dispatchAndJoin();
7979
}
8080

81+
private static class SecurityCtx {
82+
83+
public static Object getCallingUserCtx() {
84+
return null;
85+
}
86+
}
87+
88+
private void callContextExample() {
89+
BatchLoader<String, String> batchLoader = new BatchLoader<String, String>() {
90+
@Override
91+
public CompletionStage<List<String>> load(List<String> keys) {
92+
throw new UnsupportedOperationException("This wont be called if you implement the other defaulted method");
93+
}
94+
95+
@Override
96+
public CompletionStage<List<String>> load(List<String> keys, Object context) {
97+
SecurityCtx callCtx = (SecurityCtx) context;
98+
return callDatabaseForResults(callCtx, keys);
99+
}
100+
101+
};
102+
DataLoaderOptions options = DataLoaderOptions.newOptions()
103+
.setBatchContextProvider(() -> SecurityCtx.getCallingUserCtx());
104+
DataLoader<String, String> loader = new DataLoader<>(batchLoader, options);
105+
}
106+
107+
private CompletionStage<List<String>> callDatabaseForResults(SecurityCtx callCtx, List<String> keys) {
108+
return null;
109+
}
110+
81111

82112
private void tryExample() {
83113
Try<String> tryS = Try.tryCall(() -> {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.dataloader;
2+
3+
import org.junit.Test;
4+
5+
import java.util.List;
6+
import java.util.concurrent.CompletableFuture;
7+
import java.util.concurrent.CompletionStage;
8+
import java.util.stream.Collectors;
9+
10+
import static java.util.Arrays.asList;
11+
import static org.hamcrest.Matchers.equalTo;
12+
import static org.junit.Assert.assertThat;
13+
14+
/**
15+
* Tests related to context. DataLoaderTest is getting to big and needs refactoring
16+
*/
17+
public class DataLoaderContextTest {
18+
19+
@Test
20+
public void context_is_passed_to_batch_loader_function() throws Exception {
21+
BatchLoader<String, String> batchLoader = new BatchLoader<String, String>() {
22+
@Override
23+
public CompletionStage<List<String>> load(List<String> keys) {
24+
throw new UnsupportedOperationException("this wont be called");
25+
}
26+
27+
@Override
28+
public CompletionStage<List<String>> load(List<String> keys, Object context) {
29+
List<String> list = keys.stream().map(k -> k + "-" + context).collect(Collectors.toList());
30+
return CompletableFuture.completedFuture(list);
31+
}
32+
};
33+
DataLoaderOptions options = DataLoaderOptions.newOptions()
34+
.setBatchContextProvider(() -> "ctx");
35+
DataLoader<String, String> loader = new DataLoader<>(batchLoader, options);
36+
37+
loader.load("A");
38+
loader.load("B");
39+
loader.loadMany(asList("C", "D"));
40+
41+
List<String> results = loader.dispatchAndJoin();
42+
43+
assertThat(results, equalTo(asList("A-ctx", "B-ctx", "C-ctx", "D-ctx")));
44+
}
45+
46+
@Test
47+
public void null_is_passed_as_context_if_you_do_nothing() throws Exception {
48+
BatchLoader<String, String> batchLoader = CompletableFuture::completedFuture;
49+
DataLoader<String, String> loader = new DataLoader<>(batchLoader);
50+
51+
loader.load("A");
52+
loader.load("B");
53+
loader.loadMany(asList("C", "D"));
54+
55+
List<String> results = loader.dispatchAndJoin();
56+
57+
assertThat(results, equalTo(asList("A", "B", "C", "D")));
58+
}
59+
}

0 commit comments

Comments
 (0)