From d0b04933bdbf79d30641352a879fdab40129beae Mon Sep 17 00:00:00 2001 From: Artem Grachev <65827946+artem-ag@users.noreply.github.com> Date: Thu, 17 Dec 2020 00:09:24 -0800 Subject: [PATCH 1/3] Update graphql-java-servlet to support graphql-java 16. This is a breaking change that makes library incompatible with prior versions of graphql-java. --- build.gradle | 2 +- gradle.properties | 9 ++- graphql-java-kickstart/build.gradle | 1 + .../execution/DecoratedExecutionResult.java | 7 +- .../execution/GraphQLObjectMapper.java | 15 +--- .../AbstractTrackingApproach.java | 53 +------------ .../ConfigurableDispatchInstrumentation.java | 24 ------ .../instrumentation/TrackingApproach.java | 11 --- graphql-java-servlet/build.gradle | 3 +- .../servlet/OsgiGraphQLHttpServlet.java | 18 ++++- ...SingleAsynchronousQueryResponseWriter.java | 4 - .../AbstractGraphQLHttpServletSpec.groovy | 78 ------------------- .../kickstart/servlet/TestUtils.groovy | 1 - 13 files changed, 29 insertions(+), 197 deletions(-) diff --git a/build.gradle b/build.gradle index 9f961355..6241c446 100644 --- a/build.gradle +++ b/build.gradle @@ -71,7 +71,7 @@ subprojects { compileJava.dependsOn(processResources) lombok { - version = "1.18.4" + version = "1.18.16" sha256 = "" } diff --git a/gradle.properties b/gradle.properties index 409bafd9..e105005a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=10.1.0-SNAPSHOT +version=11.0.0-SNAPSHOT group=com.graphql-java-kickstart PROJECT_NAME=graphql-java-servlet PROJECT_DESC=GraphQL Java Kickstart @@ -7,8 +7,9 @@ PROJECT_LICENSE=MIT PROJECT_LICENSE_URL=https://github.com/graphql-java-kickstart/spring-boot-graphql/blob/master/LICENSE.md PROJECT_DEV_ID=apottere PROJECT_DEV_NAME=Andrew Potter -LIB_GRAPHQL_JAVA_VER=15.0 -LIB_JACKSON_VER=2.10.0 +LIB_GRAPHQL_JAVA_VER=16.0 +LIB_JACKSON_VER=2.12.0 +LIB_SLF4J_VER=1.7.30 SOURCE_COMPATIBILITY=1.8 TARGET_COMPATIBILITY=1.8 -GRADLE_WRAPPER_VER=6.0.1 +GRADLE_WRAPPER_VER=6.7.1 diff --git a/graphql-java-kickstart/build.gradle b/graphql-java-kickstart/build.gradle index 72dc7c6f..91980b72 100644 --- a/graphql-java-kickstart/build.gradle +++ b/graphql-java-kickstart/build.gradle @@ -7,6 +7,7 @@ jar { dependencies { // GraphQL compile "com.graphql-java:graphql-java:$LIB_GRAPHQL_JAVA_VER" + implementation "org.slf4j:slf4j-api:$LIB_SLF4J_VER" // JSON compile "com.fasterxml.jackson.core:jackson-core:$LIB_JACKSON_VER" diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java index ed8e04e1..6fac9c72 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java @@ -14,12 +14,7 @@ class DecoratedExecutionResult implements ExecutionResult { private final ExecutionResult result; boolean isAsynchronous() { - return result.getData() instanceof Publisher || isDeferred(); - } - - private boolean isDeferred() { - return result.getExtensions() != null && result.getExtensions() - .containsKey(GraphQL.DEFERRED_RESULTS); + return result.getData() instanceof Publisher; } @Override diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLObjectMapper.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLObjectMapper.java index e60ab746..ca526627 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLObjectMapper.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLObjectMapper.java @@ -7,12 +7,10 @@ import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; -import graphql.DeferredExecutionResult; -import graphql.DeferredExecutionResultImpl; import graphql.ExecutionResult; import graphql.ExecutionResultImpl; import graphql.GraphQLError; -import graphql.execution.ExecutionPath; +import graphql.execution.ResultPath; import graphql.kickstart.execution.config.ConfiguringObjectMapperProvider; import graphql.kickstart.execution.config.GraphQLServletObjectMapperConfigurer; import graphql.kickstart.execution.config.ObjectMapperProvider; @@ -137,13 +135,6 @@ public ExecutionResult sanitizeErrors(ExecutionResult executionResult) { public Map createResultFromExecutionResult(ExecutionResult executionResult) { ExecutionResult sanitizedExecutionResult = sanitizeErrors(executionResult); - if (executionResult instanceof DeferredExecutionResult) { - sanitizedExecutionResult = DeferredExecutionResultImpl - .newDeferredExecutionResult() - .from(executionResult) - .path(ExecutionPath.fromList(((DeferredExecutionResult) executionResult).getPath())) - .build(); - } return convertSanitizedExecutionResult(sanitizedExecutionResult); } @@ -168,10 +159,6 @@ public Map convertSanitizedExecutionResult(ExecutionResult execu result.put("data", executionResult.getData()); } - if (executionResult instanceof DeferredExecutionResult) { - result.put("path", ((DeferredExecutionResult) executionResult).getPath()); - } - return result; } diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java index ad77e5cc..67b9b96e 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java @@ -2,13 +2,11 @@ import graphql.ExecutionResult; import graphql.execution.ExecutionId; -import graphql.execution.ExecutionPath; import graphql.execution.FieldValueInfo; import graphql.execution.MergedField; -import graphql.execution.instrumentation.DeferredFieldInstrumentationContext; +import graphql.execution.ResultPath; import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; import graphql.execution.instrumentation.InstrumentationContext; -import graphql.execution.instrumentation.parameters.InstrumentationDeferredFieldParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; import java.util.Collections; @@ -42,7 +40,7 @@ protected RequestStack getStack() { public ExecutionStrategyInstrumentationContext beginExecutionStrategy( InstrumentationExecutionStrategyParameters parameters) { ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); - ExecutionPath path = parameters.getExecutionStrategyParameters().getPath(); + ResultPath path = parameters.getExecutionStrategyParameters().getPath(); int parentLevel = path.getLevel(); int curLevel = parentLevel + 1; int fieldCount = parameters.getExecutionStrategyParameters().getFields().size(); @@ -72,18 +70,6 @@ public void onFieldValuesInfo(List fieldValueInfoList) { } } } - - @Override - public void onDeferredField(MergedField field) { - // fake fetch count for this field - synchronized (stack) { - stack.increaseFetchCount(executionId, curLevel); - stack.setStatus(executionId, dispatchIfNeeded(stack, executionId, curLevel)); - if (stack.allReady()) { - dispatchWithoutLocking(); - } - } - } }; } @@ -118,44 +104,11 @@ private int getCountForList(FieldValueInfo fieldValueInfo) { return result; } - @Override - public DeferredFieldInstrumentationContext beginDeferredField( - InstrumentationDeferredFieldParameters parameters) { - ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); - int level = parameters.getExecutionStrategyParameters().getPath().getLevel(); - synchronized (stack) { - stack.clearAndMarkCurrentLevelAsReady(executionId, level); - } - - return new DeferredFieldInstrumentationContext() { - @Override - public void onDispatched(CompletableFuture result) { - - } - - @Override - public void onCompleted(ExecutionResult result, Throwable t) { - } - - @Override - public void onFieldValueInfo(FieldValueInfo fieldValueInfo) { - synchronized (stack) { - stack.setStatus(executionId, - handleOnFieldValuesInfo(Collections.singletonList(fieldValueInfo), stack, executionId, - level)); - if (stack.allReady()) { - dispatchWithoutLocking(); - } - } - } - }; - } - @Override public InstrumentationContext beginFieldFetch( InstrumentationFieldFetchParameters parameters) { ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); - ExecutionPath path = parameters.getEnvironment().getExecutionStepInfo().getPath(); + ResultPath path = parameters.getEnvironment().getExecutionStepInfo().getPath(); int level = path.getLevel(); return new InstrumentationContext() { diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java index 9ca12ef6..97145463 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java @@ -5,7 +5,6 @@ import graphql.execution.AsyncExecutionStrategy; import graphql.execution.ExecutionContext; import graphql.execution.ExecutionStrategy; -import graphql.execution.instrumentation.DeferredFieldInstrumentationContext; import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; @@ -13,7 +12,6 @@ import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation; import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions; import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; -import graphql.execution.instrumentation.parameters.InstrumentationDeferredFieldParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; @@ -134,28 +132,6 @@ public void onCompleted(ExecutionResult result, Throwable t) { return state.getApproach().beginExecutionStrategy(parameters); } - @Override - public DeferredFieldInstrumentationContext beginDeferredField( - InstrumentationDeferredFieldParameters parameters) { - DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); - // - // if there are no data loaders, there is nothing to do - // - if (state.hasNoDataLoaders()) { - return new DeferredFieldInstrumentationContext() { - @Override - public void onDispatched(CompletableFuture result) { - } - - @Override - public void onCompleted(ExecutionResult result, Throwable t) { - } - }; - - } - return state.getApproach().beginDeferredField(parameters); - } - @Override public InstrumentationContext beginFieldFetch( InstrumentationFieldFetchParameters parameters) { diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java index e8cc908f..679e719b 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java @@ -1,11 +1,9 @@ package graphql.kickstart.execution.instrumentation; import graphql.execution.ExecutionId; -import graphql.execution.instrumentation.DeferredFieldInstrumentationContext; import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.parameters.InstrumentationDeferredFieldParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; @@ -33,15 +31,6 @@ public interface TrackingApproach extends InstrumentationState { ExecutionStrategyInstrumentationContext beginExecutionStrategy( InstrumentationExecutionStrategyParameters parameters); - /** - * Handles approach specific logic for DataLoaderDispatcherInstrumentation. - * - * @param parameters parameters supplied to DataLoaderDispatcherInstrumentation - * @return the instrumented context - */ - DeferredFieldInstrumentationContext beginDeferredField( - InstrumentationDeferredFieldParameters parameters); - /** * Handles approach specific logic for DataLoaderDispatcherInstrumentation. * diff --git a/graphql-java-servlet/build.gradle b/graphql-java-servlet/build.gradle index 9c1c794d..af5a0815 100644 --- a/graphql-java-servlet/build.gradle +++ b/graphql-java-servlet/build.gradle @@ -19,6 +19,7 @@ dependencies { // Servlet compile 'javax.servlet:javax.servlet-api:3.1.0' compile 'javax.websocket:javax.websocket-api:1.1' + implementation "org.slf4j:slf4j-api:$LIB_SLF4J_VER" // OSGi compileOnly 'org.osgi:org.osgi.core:6.0.0' @@ -35,7 +36,7 @@ dependencies { testCompile "org.spockframework:spock-core:1.1-groovy-2.4-rc-3" testRuntime "cglib:cglib-nodep:3.2.4" testRuntime "org.objenesis:objenesis:2.5.1" - testCompile 'org.slf4j:slf4j-simple:1.7.24' + testCompile "org.slf4j:slf4j-simple:$LIB_SLF4J_VER" testCompile 'org.springframework:spring-test:4.3.7.RELEASE' testRuntime 'org.springframework:spring-web:4.3.7.RELEASE' testCompile 'com.google.guava:guava:24.1.1-jre' diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/OsgiGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/OsgiGraphQLHttpServlet.java index 20a34c2d..a6c33068 100644 --- a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/OsgiGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/OsgiGraphQLHttpServlet.java @@ -3,6 +3,7 @@ import static graphql.schema.GraphQLObjectType.newObject; import static graphql.schema.GraphQLSchema.newSchema; +import graphql.Scalars; import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; import graphql.execution.preparsed.PreparsedDocumentProvider; import graphql.kickstart.execution.GraphQLObjectMapper; @@ -29,6 +30,7 @@ import graphql.kickstart.servlet.osgi.GraphQLSubscriptionProvider; import graphql.kickstart.servlet.osgi.GraphQLTypesProvider; import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLType; import java.util.ArrayList; @@ -153,10 +155,20 @@ private void doUpdateSchema() { final GraphQLObjectType.Builder queryTypeBuilder = newObject().name("Query") .description("Root query type"); - for (GraphQLQueryProvider provider : queryProviders) { - if (provider.getQueries() != null && !provider.getQueries().isEmpty()) { - provider.getQueries().forEach(queryTypeBuilder::field); + if (!queryProviders.isEmpty()) { + for (GraphQLQueryProvider provider : queryProviders) { + if (provider.getQueries() != null && !provider.getQueries().isEmpty()) { + provider.getQueries().forEach(queryTypeBuilder::field); + } } + } else { + // graphql-java enforces Query type to be there with at least some field. + queryTypeBuilder.field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("_empty") + .type(Scalars.GraphQLBoolean) + .build()); } final Set types = new HashSet<>(); diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleAsynchronousQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleAsynchronousQueryResponseWriter.java index 8c598dd8..87311ede 100644 --- a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleAsynchronousQueryResponseWriter.java +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleAsynchronousQueryResponseWriter.java @@ -43,10 +43,6 @@ public void write(HttpServletRequest request, HttpServletResponse response) { publishers.add(result.getData()); } else { publishers.add(new StaticDataPublisher<>(result)); - final Publisher deferredResultsPublisher = (Publisher) result - .getExtensions() - .get(GraphQL.DEFERRED_RESULTS); - publishers.add(deferredResultsPublisher); } publishers.forEach(it -> it.subscribe(subscriber)); diff --git a/graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/AbstractGraphQLHttpServletSpec.groovy b/graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/AbstractGraphQLHttpServletSpec.groovy index bf954e29..625a64be 100644 --- a/graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/AbstractGraphQLHttpServletSpec.groovy +++ b/graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/AbstractGraphQLHttpServletSpec.groovy @@ -313,27 +313,6 @@ class AbstractGraphQLHttpServletSpec extends Specification { getBatchedResponseContent()[1].data.echo == "test" } - def "deferred query over HTTP GET"() { - setup: - request.addParameter('query', 'query { echo(arg:"test") @defer }') - - when: - servlet.doGet(request, response) - - then: - response.getStatus() == STATUS_OK - response.getContentType() == CONTENT_TYPE_SERVER_SENT_EVENTS - getSubscriptionResponseContent()[0].data.echo == null - - when: - subscriptionLatch.await(1, TimeUnit.SECONDS) - - then: - def content = getSubscriptionResponseContent() - content[1].data == "test" - content[1].path == ["echo"] - } - def "Batch Execution Handler allows limiting batches and sending error messages."() { setup: servlet = TestUtils.createBatchCustomizedServlet({ env -> env.arguments.arg }, { env -> env.arguments.arg }, { env -> @@ -1098,63 +1077,6 @@ class AbstractGraphQLHttpServletSpec extends Specification { getSubscriptionResponseContent()[1].data.echo == "Second\n\ntest" } - def "defer query over HTTP POST"() { - setup: - request.setContent('{"query": "subscription Subscription($arg: String!) { echo(arg: $arg) }", "operationName": "Subscription", "variables": {"arg": "test"}}'.bytes) - request.setAsyncSupported(true) - request.setMethod("POST") - - when: - servlet.doPost(request, response) - then: - response.getStatus() == STATUS_OK - response.getContentType() == CONTENT_TYPE_SERVER_SENT_EVENTS - - when: - subscriptionLatch.await(1, TimeUnit.SECONDS) - then: - getSubscriptionResponseContent()[0].data.echo == "First\n\ntest" - getSubscriptionResponseContent()[1].data.echo == "Second\n\ntest" - } - - def "deferred query that takes longer than initial results, should still be sent second"() { - setup: - servlet = TestUtils.createDefaultServlet({ env -> - if (env.getField().name == "a") { - Thread.sleep(1000) - } - env.arguments.arg - }) - request.setContent(mapper.writeValueAsBytes([ - query: ''' - { - object { - a(arg: "Hello") - b(arg: "World") @defer - } - } - ''' - ])) - request.setAsyncSupported(true) - request.setMethod("POST") - - when: - servlet.doPost(request, response) - - then: - response.getStatus() == STATUS_OK - response.getContentType() == CONTENT_TYPE_SERVER_SENT_EVENTS - getSubscriptionResponseContent()[0].data.object.a == "Hello" // a has a Thread.sleep - - when: - subscriptionLatch.await(1, TimeUnit.SECONDS) - - then: - def content = getSubscriptionResponseContent() - content[1].data == "World" - content[1].path == ["object", "b"] - } - def "errors before graphql schema execution return internal server error"() { setup: servlet = SimpleGraphQLHttpServlet.newBuilder(GraphQLInvocationInputFactory.newBuilder { diff --git a/graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/TestUtils.groovy b/graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/TestUtils.groovy index 907c56b7..b812017d 100644 --- a/graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/TestUtils.groovy +++ b/graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/TestUtils.groovy @@ -197,7 +197,6 @@ class TestUtils { .mutation(mutation) .subscription(subscription) .additionalType(graphql.kickstart.servlet.apollo.ApolloScalars.Upload) - .additionalDirective(Directives.DeferDirective) .build() } From 6431334155f3d71d69d618e69cbca95183423a74 Mon Sep 17 00:00:00 2001 From: Artem Grachev <65827946+artem-ag@users.noreply.github.com> Date: Thu, 17 Dec 2020 11:26:03 -0800 Subject: [PATCH 2/3] Synchronize gradle wrapper versions --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 33682bbb..1f3fdbc5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 2e5fee396d06f4c69fb1034875f14c4e310b9224 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Fri, 18 Dec 2020 10:38:26 +0100 Subject: [PATCH 3/3] Bump graphql-java to 16.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e105005a..8f50b738 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ PROJECT_LICENSE=MIT PROJECT_LICENSE_URL=https://github.com/graphql-java-kickstart/spring-boot-graphql/blob/master/LICENSE.md PROJECT_DEV_ID=apottere PROJECT_DEV_NAME=Andrew Potter -LIB_GRAPHQL_JAVA_VER=16.0 +LIB_GRAPHQL_JAVA_VER=16.1 LIB_JACKSON_VER=2.12.0 LIB_SLF4J_VER=1.7.30 SOURCE_COMPATIBILITY=1.8