-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Open
Labels
keep-openTells Stale Bot to keep PRs and issues openTells Stale Bot to keep PRs and issues open
Description
Hello,
in our projects we would like to leverage a different java.util.Map
implementation (e.g. eclipse-collections or a custom immutable map that preserves the order). Currently we have the following class to do that:
package graphql.execution;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.execution.incremental.DeferredExecutionSupport;
import graphql.execution.instrumentation.ExecuteObjectInstrumentationContext;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
/**
* Base class to control how the map is build. Must be in this graphql.execution package because it used
* package-private methods.
*/
public abstract class CustomizableExecutionStrategy extends AsyncExecutionStrategy {
// copy-pasted code
protected Object /* CompletableFuture<Map<String, Object>> | Map<String, Object> */
executeObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = executionContext.getDataLoaderDispatcherStrategy();
dataLoaderDispatcherStrategy.executeObject(executionContext, parameters);
Instrumentation instrumentation = executionContext.getInstrumentation();
InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters);
ExecuteObjectInstrumentationContext resolveObjectCtx = ExecuteObjectInstrumentationContext.nonNullCtx(
instrumentation.beginExecuteObject(instrumentationParameters, executionContext.getInstrumentationState())
);
List<String> fieldNames = parameters.getFields().getKeys();
DeferredExecutionSupport deferredExecutionSupport = createDeferredExecutionSupport(executionContext, parameters);
Async.CombinedBuilder<FieldValueInfo> resolvedFieldFutures = super.getAsyncFieldValueInfo(executionContext, parameters, deferredExecutionSupport);
CompletableFuture<Map<String, Object>> overallResult = new CompletableFuture<>();
List<String> fieldsExecutedOnInitialResult = deferredExecutionSupport.getNonDeferredFieldNames(fieldNames);
BiConsumer<List<Object>, Throwable> handleResultsConsumer = buildFieldValueMap(fieldsExecutedOnInitialResult, overallResult, executionContext);
resolveObjectCtx.onDispatched();
Object fieldValueInfosResult = resolvedFieldFutures.awaitPolymorphic();
if (fieldValueInfosResult instanceof CompletableFuture) {
CompletableFuture<List<FieldValueInfo>> fieldValueInfos = (CompletableFuture<List<FieldValueInfo>>) fieldValueInfosResult;
fieldValueInfos.whenComplete((completeValueInfos, throwable) -> {
if (throwable != null) {
handleResultsConsumer.accept(null, throwable);
return;
}
Async.CombinedBuilder<Object> resultFutures = fieldValuesCombinedBuilder(completeValueInfos);
dataLoaderDispatcherStrategy.executeObjectOnFieldValuesInfo(completeValueInfos, parameters);
resolveObjectCtx.onFieldValuesInfo(completeValueInfos);
resultFutures.await().whenComplete(handleResultsConsumer);
}).exceptionally((ex) -> {
// if there are any issues with combining/handling the field results,
// complete the future at all costs and bubble up any thrown exception so
// the execution does not hang.
dataLoaderDispatcherStrategy.executeObjectOnFieldValuesException(ex, parameters);
resolveObjectCtx.onFieldValuesException();
overallResult.completeExceptionally(ex);
return null;
});
overallResult.whenComplete(resolveObjectCtx::onCompleted);
return overallResult;
} else {
List<FieldValueInfo> completeValueInfos = (List<FieldValueInfo>) fieldValueInfosResult;
Async.CombinedBuilder<Object> resultFutures = fieldValuesCombinedBuilder(completeValueInfos);
dataLoaderDispatcherStrategy.executeObjectOnFieldValuesInfo(completeValueInfos, parameters);
resolveObjectCtx.onFieldValuesInfo(completeValueInfos);
Object completedValuesObject = resultFutures.awaitPolymorphic();
if (completedValuesObject instanceof CompletableFuture) {
CompletableFuture<List<Object>> completedValues = (CompletableFuture<List<Object>>) completedValuesObject;
completedValues.whenComplete(handleResultsConsumer);
overallResult.whenComplete(resolveObjectCtx::onCompleted);
return overallResult;
} else {
Map<String, Object> fieldValueMap = buildFieldValueMap(fieldsExecutedOnInitialResult, (List<Object>) completedValuesObject);
resolveObjectCtx.onCompleted(fieldValueMap, null);
return fieldValueMap;
}
}
}
private static Async.CombinedBuilder<Object> fieldValuesCombinedBuilder(List<FieldValueInfo> completeValueInfos) {
Async.CombinedBuilder<Object> resultFutures = Async.ofExpectedSize(completeValueInfos.size());
for (FieldValueInfo completeValueInfo : completeValueInfos) {
resultFutures.addObject(completeValueInfo.getFieldValueObject());
}
return resultFutures;
}
private BiConsumer<List<Object>, Throwable> buildFieldValueMap(List<String> fieldNames, CompletableFuture<Map<String, Object>> overallResult, ExecutionContext executionContext) {
return (List<Object> results, Throwable exception) -> {
if (exception != null) {
handleValueException(overallResult, exception, executionContext);
return;
}
final Map<String, Object> resolvedValuesByField = buildFieldValueMap(fieldNames, results);
overallResult.complete(resolvedValuesByField);
};
}
@Override
protected BiConsumer<List<Object>, Throwable> handleResults(ExecutionContext executionContext, List<String> fieldNames, CompletableFuture<ExecutionResult> overallResult) {
return (List<Object> results, Throwable exception) -> {
if (exception != null) {
handleNonNullException(executionContext, overallResult, exception);
return;
}
final var map = buildFieldValueMap(fieldNames, results);
overallResult.complete(new ExecutionResultImpl(map, executionContext.getErrors()));
};
}
/**
* This is the idea of class: concrete classes can override it to provide custom implementation of the resulting Map.
*/
protected abstract Map<String, Object> buildFieldValueMap(List<String> fieldNames, List<Object> results);
}
while this is working, it is not ideal:
- it is using the package of graphql-java to access some package-private methods;
- it copy-paste a couple of methods;
- it is using inheritance: maybe delegate to a specific interface it would be better (e.g. something like ExceptionHandler, with a default implementation that produces
LinkedHashMap
as by now).
I guess other integrations and projects could also benefit by this extra customization point.
I would be happy to hear what do you think about it.
Metadata
Metadata
Assignees
Labels
keep-openTells Stale Bot to keep PRs and issues openTells Stale Bot to keep PRs and issues open