Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.dataloader.DataLoaderHelper Maven / Gradle / Ivy
package org.dataloader;
import org.dataloader.impl.CompletableFutureKit;
import org.dataloader.stats.StatisticsCollector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.dataloader.impl.Assertions.assertState;
import static org.dataloader.impl.Assertions.nonNull;
/**
* This helps break up the large DataLoader class functionality and it contains the logic to dispatch the
* promises on behalf of its peer dataloader
*
* @param the type of keys
* @param the type of values
*/
class DataLoaderHelper {
class LoaderQueueEntry {
final K key;
final V value;
final Object callContext;
public LoaderQueueEntry(K key, V value, Object callContext) {
this.key = key;
this.value = value;
this.callContext = callContext;
}
K getKey() {
return key;
}
V getValue() {
return value;
}
Object getCallContext() {
return callContext;
}
}
private final DataLoader dataLoader;
private final Object batchLoadFunction;
private final DataLoaderOptions loaderOptions;
private final CacheMap> futureCache;
private final List>> loaderQueue;
private final StatisticsCollector stats;
DataLoaderHelper(DataLoader dataLoader, Object batchLoadFunction, DataLoaderOptions loaderOptions, CacheMap> futureCache, StatisticsCollector stats) {
this.dataLoader = dataLoader;
this.batchLoadFunction = batchLoadFunction;
this.loaderOptions = loaderOptions;
this.futureCache = futureCache;
this.loaderQueue = new ArrayList<>();
this.stats = stats;
}
CompletableFuture load(K key, Object loadContext) {
synchronized (dataLoader) {
Object cacheKey = getCacheKey(nonNull(key));
stats.incrementLoadCount();
boolean batchingEnabled = loaderOptions.batchingEnabled();
boolean cachingEnabled = loaderOptions.cachingEnabled();
if (cachingEnabled) {
if (futureCache.containsKey(cacheKey)) {
stats.incrementCacheHitCount();
return futureCache.get(cacheKey);
}
}
CompletableFuture future = new CompletableFuture<>();
if (batchingEnabled) {
loaderQueue.add(new LoaderQueueEntry<>(key, future, loadContext));
} else {
stats.incrementBatchLoadCountBy(1);
// immediate execution of batch function
future = invokeLoaderImmediately(key, loadContext);
}
if (cachingEnabled) {
futureCache.set(cacheKey, future);
}
return future;
}
}
@SuppressWarnings("unchecked")
Object getCacheKey(K key) {
return loaderOptions.cacheKeyFunction().isPresent() ?
loaderOptions.cacheKeyFunction().get().getKey(key) : key;
}
CompletableFuture> dispatch() {
boolean batchingEnabled = loaderOptions.batchingEnabled();
//
// we copy the pre-loaded set of futures ready for dispatch
final List keys = new ArrayList<>();
final List callContexts = new ArrayList<>();
final List> queuedFutures = new ArrayList<>();
synchronized (dataLoader) {
loaderQueue.forEach(entry -> {
keys.add(entry.getKey());
queuedFutures.add(entry.getValue());
callContexts.add(entry.getCallContext());
});
loaderQueue.clear();
}
if (!batchingEnabled || keys.size() == 0) {
return CompletableFuture.completedFuture(emptyList());
}
//
// order of keys -> values matter in data loader hence the use of linked hash map
//
// See https://github.com/facebook/dataloader/blob/master/README.md for more details
//
//
// when the promised list of values completes, we transfer the values into
// the previously cached future objects that the client already has been given
// via calls to load("foo") and loadMany(["foo","bar"])
//
int maxBatchSize = loaderOptions.maxBatchSize();
if (maxBatchSize > 0 && maxBatchSize < keys.size()) {
return sliceIntoBatchesOfBatches(keys, queuedFutures, callContexts, maxBatchSize);
} else {
return dispatchQueueBatch(keys, callContexts, queuedFutures);
}
}
private CompletableFuture> sliceIntoBatchesOfBatches(List keys, List> queuedFutures, List callContexts, int maxBatchSize) {
// the number of keys is > than what the batch loader function can accept
// so make multiple calls to the loader
List>> allBatches = new ArrayList<>();
int len = keys.size();
int batchCount = (int) Math.ceil(len / (double) maxBatchSize);
for (int i = 0; i < batchCount; i++) {
int fromIndex = i * maxBatchSize;
int toIndex = Math.min((i + 1) * maxBatchSize, len);
List subKeys = keys.subList(fromIndex, toIndex);
List> subFutures = queuedFutures.subList(fromIndex, toIndex);
List subCallContexts = callContexts.subList(fromIndex, toIndex);
allBatches.add(dispatchQueueBatch(subKeys, subCallContexts, subFutures));
}
//
// now reassemble all the futures into one that is the complete set of results
return CompletableFuture.allOf(allBatches.toArray(new CompletableFuture[allBatches.size()]))
.thenApply(v -> allBatches.stream()
.map(CompletableFuture::join)
.flatMap(Collection::stream)
.collect(Collectors.toList()));
}
@SuppressWarnings("unchecked")
private CompletableFuture> dispatchQueueBatch(List keys, List callContexts, List> queuedFutures) {
stats.incrementBatchLoadCountBy(keys.size());
CompletionStage> batchLoad = invokeLoader(keys, callContexts);
return batchLoad
.toCompletableFuture()
.thenApply(values -> {
assertResultSize(keys, values);
for (int idx = 0; idx < queuedFutures.size(); idx++) {
Object value = values.get(idx);
CompletableFuture future = queuedFutures.get(idx);
if (value instanceof Throwable) {
stats.incrementLoadErrorCount();
future.completeExceptionally((Throwable) value);
// we don't clear the cached view of this entry to avoid
// frequently loading the same error
} else if (value instanceof Try) {
// we allow the batch loader to return a Try so we can better represent a computation
// that might have worked or not.
Try tryValue = (Try) value;
if (tryValue.isSuccess()) {
future.complete(tryValue.get());
} else {
stats.incrementLoadErrorCount();
future.completeExceptionally(tryValue.getThrowable());
}
} else {
V val = (V) value;
future.complete(val);
}
}
return values;
}).exceptionally(ex -> {
stats.incrementBatchLoadExceptionCount();
for (int idx = 0; idx < queuedFutures.size(); idx++) {
K key = keys.get(idx);
CompletableFuture future = queuedFutures.get(idx);
future.completeExceptionally(ex);
// clear any cached view of this key because they all failed
dataLoader.clear(key);
}
return emptyList();
});
}
private void assertResultSize(List keys, List values) {
assertState(keys.size() == values.size(), "The size of the promised values MUST be the same size as the key list");
}
CompletableFuture invokeLoaderImmediately(K key, Object keyContext) {
List keys = singletonList(key);
CompletionStage singleLoadCall;
try {
Object context = loaderOptions.getBatchLoaderContextProvider().getContext();
Map keyContextMap = mkKeyContextMap(keys, singletonList(keyContext));
BatchLoaderEnvironment environment = BatchLoaderEnvironment.newBatchLoaderEnvironment()
.context(context).keyContexts(keyContextMap).build();
if (isMapLoader()) {
singleLoadCall = invokeMapBatchLoader(keys, environment).thenApply(list -> list.get(0));
} else {
singleLoadCall = invokeListBatchLoader(keys, environment).thenApply(list -> list.get(0));
}
return singleLoadCall.toCompletableFuture();
} catch (Exception e) {
return CompletableFutureKit.failedFuture(e);
}
}
CompletionStage> invokeLoader(List keys, List keyContexts) {
CompletionStage> batchLoad;
try {
Object context = loaderOptions.getBatchLoaderContextProvider().getContext();
Map keyContextMap = mkKeyContextMap(keys, keyContexts);
BatchLoaderEnvironment environment = BatchLoaderEnvironment.newBatchLoaderEnvironment()
.context(context).keyContexts(keyContextMap).build();
if (isMapLoader()) {
batchLoad = invokeMapBatchLoader(keys, environment);
} else {
batchLoad = invokeListBatchLoader(keys, environment);
}
} catch (Exception e) {
batchLoad = CompletableFutureKit.failedFuture(e);
}
return batchLoad;
}
@SuppressWarnings("unchecked")
private CompletionStage> invokeListBatchLoader(List keys, BatchLoaderEnvironment environment) {
CompletionStage> loadResult;
if (batchLoadFunction instanceof BatchLoaderWithContext) {
loadResult = ((BatchLoaderWithContext) batchLoadFunction).load(keys, environment);
} else {
loadResult = ((BatchLoader) batchLoadFunction).load(keys);
}
return nonNull(loadResult, "Your batch loader function MUST return a non null CompletionStage promise");
}
/*
* Turns a map of results that MAY be smaller than the key list back into a list by mapping null
* to missing elements.
*/
@SuppressWarnings("unchecked")
private CompletionStage> invokeMapBatchLoader(List keys, BatchLoaderEnvironment environment) {
CompletionStage> loadResult;
if (batchLoadFunction instanceof MappedBatchLoaderWithContext) {
loadResult = ((MappedBatchLoaderWithContext) batchLoadFunction).load(keys, environment);
} else {
loadResult = ((MappedBatchLoader) batchLoadFunction).load(keys);
}
CompletionStage> mapBatchLoad = nonNull(loadResult, "Your batch loader function MUST return a non null CompletionStage promise");
return mapBatchLoad.thenApply(map -> {
List values = new ArrayList<>();
for (K key : keys) {
V value = map.get(key);
values.add(value);
}
return values;
});
}
private boolean isMapLoader() {
return batchLoadFunction instanceof MappedBatchLoader || batchLoadFunction instanceof MappedBatchLoaderWithContext;
}
private Map mkKeyContextMap(List keys, List keyContexts) {
Map map = new HashMap<>();
for (int i = 0; i < keys.size(); i++) {
K key = keys.get(i);
Object keyContext = null;
if (i < keyContexts.size()) {
keyContext = keyContexts.get(i);
}
if (keyContext != null) {
map.put(key, keyContext);
}
}
return map;
}
int dispatchDepth() {
synchronized (dataLoader) {
return loaderQueue.size();
}
}
}