Skip to content

Commit 38bca5d

Browse files
committed
Added support for a delegating data loader
1 parent 4ec5fe8 commit 38bca5d

File tree

6 files changed

+322
-8
lines changed

6 files changed

+322
-8
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package org.dataloader;
2+
3+
import org.dataloader.annotations.PublicApi;
4+
import org.dataloader.stats.Statistics;
5+
6+
import java.time.Duration;
7+
import java.time.Instant;
8+
import java.util.List;
9+
import java.util.Optional;
10+
import java.util.concurrent.CompletableFuture;
11+
import java.util.function.BiConsumer;
12+
import java.util.function.Consumer;
13+
14+
/**
15+
* This delegating {@link DataLoader} makes it easier to create wrappers of {@link DataLoader}s in case you want to change how
16+
* values are returned for example
17+
*
18+
* @param <K> type parameter indicating the type of the data load keys
19+
* @param <V> type parameter indicating the type of the data that is returned
20+
*/
21+
@PublicApi
22+
public class DelegatingDataLoader<K, V> extends DataLoader<K, V> {
23+
24+
protected final DataLoader<K, V> delegate;
25+
26+
/**
27+
* This can be called to unwrap a given {@link DataLoader} such that if it's a {@link DelegatingDataLoader} the underlying
28+
* {@link DataLoader} is returned otherwise it's just passed in data loader
29+
*
30+
* @param dataLoader the dataLoader to unwrap
31+
* @param <K> type parameter indicating the type of the data load keys
32+
* @param <V> type parameter indicating the type of the data that is returned
33+
* @return the delegate dataLoader OR just this current one if it's not wrapped
34+
*/
35+
public static <K, V> DataLoader<K, V> unwrap(DataLoader<K, V> dataLoader) {
36+
if (dataLoader instanceof DelegatingDataLoader) {
37+
return ((DelegatingDataLoader<K, V>) dataLoader).getDelegate();
38+
}
39+
return dataLoader;
40+
}
41+
42+
public DelegatingDataLoader(DataLoader<K, V> delegate) {
43+
super(delegate.getBatchLoadFunction(), delegate.getOptions());
44+
this.delegate = delegate;
45+
}
46+
47+
public DataLoader<K, V> getDelegate() {
48+
return delegate;
49+
}
50+
51+
/**
52+
* The {@link DataLoader#load(Object)} and {@link DataLoader#loadMany(List)} type methods all call back
53+
* to the {@link DataLoader#load(Object, Object)} and hence we don't override them.
54+
*
55+
* @param key the key to load
56+
* @param keyContext a context object that is specific to this key
57+
* @return the future of the value
58+
*/
59+
@Override
60+
public CompletableFuture<V> load(K key, Object keyContext) {
61+
return delegate.load(key, keyContext);
62+
}
63+
64+
65+
@Override
66+
public DataLoader<K, V> transform(Consumer<DataLoaderFactory.Builder<K, V>> builderConsumer) {
67+
return delegate.transform(builderConsumer);
68+
}
69+
70+
@Override
71+
public Instant getLastDispatchTime() {
72+
return delegate.getLastDispatchTime();
73+
}
74+
75+
@Override
76+
public Duration getTimeSinceDispatch() {
77+
return delegate.getTimeSinceDispatch();
78+
}
79+
80+
@Override
81+
public Optional<CompletableFuture<V>> getIfPresent(K key) {
82+
return delegate.getIfPresent(key);
83+
}
84+
85+
@Override
86+
public Optional<CompletableFuture<V>> getIfCompleted(K key) {
87+
return delegate.getIfCompleted(key);
88+
}
89+
90+
@Override
91+
public CompletableFuture<List<V>> dispatch() {
92+
return delegate.dispatch();
93+
}
94+
95+
@Override
96+
public DispatchResult<V> dispatchWithCounts() {
97+
return delegate.dispatchWithCounts();
98+
}
99+
100+
@Override
101+
public List<V> dispatchAndJoin() {
102+
return delegate.dispatchAndJoin();
103+
}
104+
105+
@Override
106+
public int dispatchDepth() {
107+
return delegate.dispatchDepth();
108+
}
109+
110+
@Override
111+
public Object getCacheKey(K key) {
112+
return delegate.getCacheKey(key);
113+
}
114+
115+
@Override
116+
public Statistics getStatistics() {
117+
return delegate.getStatistics();
118+
}
119+
120+
@Override
121+
public CacheMap<Object, V> getCacheMap() {
122+
return delegate.getCacheMap();
123+
}
124+
125+
@Override
126+
public ValueCache<K, V> getValueCache() {
127+
return delegate.getValueCache();
128+
}
129+
130+
@Override
131+
public DataLoader<K, V> clear(K key) {
132+
delegate.clear(key);
133+
return this;
134+
}
135+
136+
@Override
137+
public DataLoader<K, V> clear(K key, BiConsumer<Void, Throwable> handler) {
138+
delegate.clear(key, handler);
139+
return this;
140+
}
141+
142+
@Override
143+
public DataLoader<K, V> clearAll() {
144+
delegate.clearAll();
145+
return this;
146+
}
147+
148+
@Override
149+
public DataLoader<K, V> clearAll(BiConsumer<Void, Throwable> handler) {
150+
delegate.clearAll(handler);
151+
return this;
152+
}
153+
154+
@Override
155+
public DataLoader<K, V> prime(K key, V value) {
156+
delegate.prime(key, value);
157+
return this;
158+
}
159+
160+
@Override
161+
public DataLoader<K, V> prime(K key, Exception error) {
162+
delegate.prime(key, error);
163+
return this;
164+
}
165+
166+
@Override
167+
public DataLoader<K, V> prime(K key, CompletableFuture<V> value) {
168+
delegate.prime(key, value);
169+
return this;
170+
}
171+
}

src/test/java/org/dataloader/DataLoaderTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ public void should_work_with_duplicate_keys_when_caching_disabled(TestDataLoader
785785
assertThat(future1.get(), equalTo("A"));
786786
assertThat(future2.get(), equalTo("B"));
787787
assertThat(future3.get(), equalTo("A"));
788-
if (factory instanceof MappedDataLoaderFactory || factory instanceof MappedPublisherDataLoaderFactory) {
788+
if (factory.unwrap() instanceof MappedDataLoaderFactory || factory.unwrap() instanceof MappedPublisherDataLoaderFactory) {
789789
assertThat(loadCalls, equalTo(singletonList(asList("A", "B"))));
790790
} else {
791791
assertThat(loadCalls, equalTo(singletonList(asList("A", "B", "A"))));
@@ -1152,12 +1152,12 @@ public void when_values_size_are_less_then_key_size(TestDataLoaderFactory factor
11521152

11531153
await().atMost(Duration.FIVE_SECONDS).until(() -> areAllDone(cf1, cf2, cf3, cf4));
11541154

1155-
if (factory instanceof ListDataLoaderFactory) {
1155+
if (factory.unwrap() instanceof ListDataLoaderFactory) {
11561156
assertThat(cause(cf1), instanceOf(DataLoaderAssertionException.class));
11571157
assertThat(cause(cf2), instanceOf(DataLoaderAssertionException.class));
11581158
assertThat(cause(cf3), instanceOf(DataLoaderAssertionException.class));
11591159
assertThat(cause(cf4), instanceOf(DataLoaderAssertionException.class));
1160-
} else if (factory instanceof PublisherDataLoaderFactory) {
1160+
} else if (factory.unwrap() instanceof PublisherDataLoaderFactory) {
11611161
// some have completed progressively but the other never did
11621162
assertThat(cf1.join(), equalTo("A"));
11631163
assertThat(cf2.join(), equalTo("B"));
@@ -1187,7 +1187,7 @@ public void when_values_size_are_more_then_key_size(TestDataLoaderFactory factor
11871187
await().atMost(Duration.FIVE_SECONDS).until(() -> areAllDone(cf1, cf2, cf3, cf4));
11881188

11891189

1190-
if (factory instanceof ListDataLoaderFactory) {
1190+
if (factory.unwrap() instanceof ListDataLoaderFactory) {
11911191
assertThat(cause(cf1), instanceOf(DataLoaderAssertionException.class));
11921192
assertThat(cause(cf2), instanceOf(DataLoaderAssertionException.class));
11931193
assertThat(cause(cf3), instanceOf(DataLoaderAssertionException.class));
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.dataloader;
2+
3+
import org.dataloader.fixtures.TestKit;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.List;
7+
import java.util.concurrent.CompletableFuture;
8+
9+
import static org.awaitility.Awaitility.await;
10+
import static org.hamcrest.CoreMatchers.equalTo;
11+
import static org.hamcrest.CoreMatchers.is;
12+
import static org.hamcrest.MatcherAssert.assertThat;
13+
14+
/**
15+
* There are WAY more tests via the {@link org.dataloader.fixtures.parameterized.DelegatingDataLoaderFactory}
16+
* parameterized tests. All the basic {@link DataLoader} tests pass when wrapped in a {@link DelegatingDataLoader}
17+
*/
18+
public class DelegatingDataLoaderTest {
19+
20+
@Test
21+
void canUnwrapDataLoaders() {
22+
DataLoader<Object, Object> rawLoader = TestKit.idLoader();
23+
DataLoader<Object, Object> delegateLoader = new DelegatingDataLoader<>(rawLoader);
24+
25+
assertThat(DelegatingDataLoader.unwrap(rawLoader), is(rawLoader));
26+
assertThat(DelegatingDataLoader.unwrap(delegateLoader), is(rawLoader));
27+
}
28+
29+
@Test
30+
void canCreateAClassOk() {
31+
DataLoader<String, String> rawLoader = TestKit.idLoader();
32+
DelegatingDataLoader<String, String> delegatingDataLoader = new DelegatingDataLoader<>(rawLoader) {
33+
@Override
34+
public CompletableFuture<String> load(String key, Object keyContext) {
35+
CompletableFuture<String> cf = super.load(key, keyContext);
36+
return cf.thenApply(v -> "|" + v + "|");
37+
}
38+
};
39+
40+
assertThat(delegatingDataLoader.getDelegate(), is(rawLoader));
41+
42+
43+
CompletableFuture<String> cfA = delegatingDataLoader.load("A");
44+
CompletableFuture<String> cfB = delegatingDataLoader.load("B");
45+
CompletableFuture<List<String>> cfCD = delegatingDataLoader.loadMany(List.of("C", "D"));
46+
47+
CompletableFuture<List<String>> dispatch = delegatingDataLoader.dispatch();
48+
49+
await().until(dispatch::isDone);
50+
51+
assertThat(cfA.join(), equalTo("|A|"));
52+
assertThat(cfB.join(), equalTo("|B|"));
53+
assertThat(cfCD.join(), equalTo(List.of("|C|", "|D|")));
54+
55+
assertThat(delegatingDataLoader.getIfPresent("A").isEmpty(), equalTo(false));
56+
assertThat(delegatingDataLoader.getIfPresent("X").isEmpty(), equalTo(true));
57+
58+
assertThat(delegatingDataLoader.getIfCompleted("A").isEmpty(), equalTo(false));
59+
assertThat(delegatingDataLoader.getIfCompleted("X").isEmpty(), equalTo(true));
60+
}
61+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package org.dataloader.fixtures.parameterized;
2+
3+
import org.dataloader.DataLoader;
4+
import org.dataloader.DataLoaderOptions;
5+
import org.dataloader.DelegatingDataLoader;
6+
7+
import java.time.Duration;
8+
import java.util.ArrayList;
9+
import java.util.Collection;
10+
import java.util.List;
11+
12+
public class DelegatingDataLoaderFactory implements TestDataLoaderFactory {
13+
// its delegates all the way down to the turtles
14+
private final TestDataLoaderFactory delegateFactory;
15+
16+
public DelegatingDataLoaderFactory(TestDataLoaderFactory delegateFactory) {
17+
this.delegateFactory = delegateFactory;
18+
}
19+
20+
@Override
21+
public String toString() {
22+
return "DelegatingDataLoaderFactory{" +
23+
"delegateFactory=" + delegateFactory +
24+
'}';
25+
}
26+
27+
@Override
28+
public TestDataLoaderFactory unwrap() {
29+
return delegateFactory.unwrap();
30+
}
31+
32+
private <K, V> DataLoader<K, V> mkDelegateDataLoader(DataLoader<K, V> dataLoader) {
33+
return new DelegatingDataLoader<>(dataLoader);
34+
}
35+
36+
@Override
37+
public <K> DataLoader<K, K> idLoader(DataLoaderOptions options, List<Collection<K>> loadCalls) {
38+
return mkDelegateDataLoader(delegateFactory.idLoader(options, loadCalls));
39+
}
40+
41+
@Override
42+
public <K> DataLoader<K, K> idLoaderDelayed(DataLoaderOptions options, List<Collection<K>> loadCalls, Duration delay) {
43+
return mkDelegateDataLoader(delegateFactory.idLoaderDelayed(options, loadCalls, delay));
44+
}
45+
46+
@Override
47+
public <K> DataLoader<K, K> idLoaderBlowsUps(
48+
DataLoaderOptions options, List<Collection<K>> loadCalls) {
49+
return mkDelegateDataLoader(delegateFactory.idLoaderBlowsUps(options, loadCalls));
50+
}
51+
52+
@Override
53+
public <K> DataLoader<K, Object> idLoaderAllExceptions(DataLoaderOptions options, List<Collection<K>> loadCalls) {
54+
return mkDelegateDataLoader(delegateFactory.idLoaderAllExceptions(options, loadCalls));
55+
}
56+
57+
@Override
58+
public DataLoader<Integer, Object> idLoaderOddEvenExceptions(DataLoaderOptions options, List<Collection<Integer>> loadCalls) {
59+
return mkDelegateDataLoader(delegateFactory.idLoaderOddEvenExceptions(options, loadCalls));
60+
}
61+
62+
@Override
63+
public DataLoader<String, String> onlyReturnsNValues(int N, DataLoaderOptions options, ArrayList<Object> loadCalls) {
64+
return mkDelegateDataLoader(delegateFactory.onlyReturnsNValues(N, options, loadCalls));
65+
}
66+
67+
@Override
68+
public DataLoader<String, String> idLoaderReturnsTooMany(int howManyMore, DataLoaderOptions options, ArrayList<Object> loadCalls) {
69+
return mkDelegateDataLoader(delegateFactory.idLoaderReturnsTooMany(howManyMore, options, loadCalls));
70+
}
71+
}

src/test/java/org/dataloader/fixtures/parameterized/TestDataLoaderFactories.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,21 @@
55

66
import java.util.stream.Stream;
77

8+
@SuppressWarnings("unused")
89
public class TestDataLoaderFactories {
910

1011
public static Stream<Arguments> get() {
1112
return Stream.of(
12-
Arguments.of(Named.of("List DataLoader", new ListDataLoaderFactory())),
13-
Arguments.of(Named.of("Mapped DataLoader", new MappedDataLoaderFactory())),
14-
Arguments.of(Named.of("Publisher DataLoader", new PublisherDataLoaderFactory())),
15-
Arguments.of(Named.of("Mapped Publisher DataLoader", new MappedPublisherDataLoaderFactory()))
13+
Arguments.of(Named.of("List DataLoader", new ListDataLoaderFactory())),
14+
Arguments.of(Named.of("Mapped DataLoader", new MappedDataLoaderFactory())),
15+
Arguments.of(Named.of("Publisher DataLoader", new PublisherDataLoaderFactory())),
16+
Arguments.of(Named.of("Mapped Publisher DataLoader", new MappedPublisherDataLoaderFactory())),
17+
18+
// runs all the above via a DelegateDataLoader
19+
Arguments.of(Named.of("Delegate List DataLoader", new DelegatingDataLoaderFactory(new ListDataLoaderFactory()))),
20+
Arguments.of(Named.of("Delegate Mapped DataLoader", new DelegatingDataLoaderFactory(new MappedDataLoaderFactory()))),
21+
Arguments.of(Named.of("Delegate Publisher DataLoader", new DelegatingDataLoaderFactory(new PublisherDataLoaderFactory()))),
22+
Arguments.of(Named.of("Delegate Mapped Publisher DataLoader", new DelegatingDataLoaderFactory(new MappedPublisherDataLoaderFactory())))
1623
);
1724
}
1825
}

src/test/java/org/dataloader/fixtures/parameterized/TestDataLoaderFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,8 @@ default <K> DataLoader<K, K> idLoader() {
3939
default <K> DataLoader<K, K> idLoaderDelayed(Duration delay) {
4040
return idLoaderDelayed(null, new ArrayList<>(), delay);
4141
}
42+
43+
default TestDataLoaderFactory unwrap() {
44+
return this;
45+
}
4246
}

0 commit comments

Comments
 (0)