From eb9b1f308db432086fdb2e53ede1eb1f6ecc3925 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 2 Dec 2022 15:32:21 +0100 Subject: [PATCH] Add reflection hint for GraphQlWebSocketMessage Prior to this commit, GraphQL applications using the WebSocket transport would fail with an `java.lang.IllegalArgumentException: No JSON Encoder` as the JSON codec was reflecting on the `GraphQlWebSocketMessage` to check that it can handle this type, during startup time. `GraphQlWebSocketMessage` is the message type being used for sending and receiving GraphQL requests. This commit registers binding reflection on this type as soon as a `GraphQlWebSocketHandler` (mvc or webflux) is registered as a bean in the application. This type is also used on the client side in the client `WebSocketGraphQlTransport`. Because clients cannot be reliably detected here in the application context or via classpath checks, this commit also adds a JSON metadata file that adds reflection on the type, if and only if the `WebSocketGraphQlTransport` is reachable by the static analysis performed by GraalVM. Fixes gh-560 --- .../webflux/GraphQlWebSocketHandler.java | 2 ++ .../webmvc/GraphQlWebSocketHandler.java | 2 ++ .../spring-graphql/reflect-config.json | 11 ++++++++++ .../webflux/GraphQlWebSocketHandlerTests.java | 21 +++++++++++++++++++ .../webmvc/GraphQlWebSocketHandlerTests.java | 21 +++++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 spring-graphql/src/main/resources/META-INF/native-image/org.springframework.graphql/spring-graphql/reflect-config.json diff --git a/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandler.java index 90db9056..3cf8a24f 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandler.java @@ -35,6 +35,7 @@ import org.reactivestreams.Subscription; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.graphql.server.WebGraphQlHandler; import org.springframework.graphql.server.WebGraphQlResponse; import org.springframework.graphql.server.WebSocketGraphQlInterceptor; @@ -59,6 +60,7 @@ import org.springframework.web.reactive.socket.WebSocketSession; * @author Rossen Stoyanchev * @since 1.0.0 */ +@RegisterReflectionForBinding(GraphQlWebSocketMessage.class) public class GraphQlWebSocketHandler implements WebSocketHandler { private static final Log logger = LogFactory.getLog(GraphQlWebSocketHandler.class); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandler.java index f9686793..87913fa5 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandler.java @@ -46,6 +46,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; +import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.graphql.execution.ErrorType; import org.springframework.graphql.execution.SubscriptionPublisherException; import org.springframework.graphql.server.WebGraphQlHandler; @@ -84,6 +85,7 @@ import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler * @author Rossen Stoyanchev * @since 1.0.0 */ +@RegisterReflectionForBinding(GraphQlWebSocketMessage.class) public class GraphQlWebSocketHandler extends TextWebSocketHandler implements SubProtocolCapable { private static final Log logger = LogFactory.getLog(GraphQlWebSocketHandler.class); diff --git a/spring-graphql/src/main/resources/META-INF/native-image/org.springframework.graphql/spring-graphql/reflect-config.json b/spring-graphql/src/main/resources/META-INF/native-image/org.springframework.graphql/spring-graphql/reflect-config.json new file mode 100644 index 00000000..fab40865 --- /dev/null +++ b/spring-graphql/src/main/resources/META-INF/native-image/org.springframework.graphql/spring-graphql/reflect-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"org.springframework.graphql.server.support.GraphQlWebSocketMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "condition": { + "typeReachable": "org.springframework.graphql.client.WebSocketGraphQlTransport" + } + } +] diff --git a/spring-graphql/src/test/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandlerTests.java index 051fa27a..057e7ec3 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandlerTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/server/webflux/GraphQlWebSocketHandlerTests.java @@ -32,6 +32,10 @@ import reactor.core.publisher.Mono; import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar; +import org.springframework.aot.hint.predicate.ReflectionHintsPredicates; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; @@ -353,6 +357,23 @@ public class GraphQlWebSocketHandlerTests extends WebSocketHandlerTestSupport { .verify(TIMEOUT); } + @Test + void registerBindingReflectionOnWebSocketMessage() { + RuntimeHints runtimeHints = new RuntimeHints(); + new ReflectiveRuntimeHintsRegistrar().registerRuntimeHints(runtimeHints, GraphQlWebSocketHandler.class); + ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection(); + assertThat(reflection.onType(GraphQlWebSocketMessage.class)).accepts(runtimeHints); + assertThat(reflection.onField(GraphQlWebSocketMessage.class, "id")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getId")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setId")).accepts(runtimeHints); + assertThat(reflection.onField(GraphQlWebSocketMessage.class, "type")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getType")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setType")).accepts(runtimeHints); + assertThat(reflection.onField(GraphQlWebSocketMessage.class, "payload")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getPayload")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setPayload")).accepts(runtimeHints); + } + private TestWebSocketSession handle(Flux input, WebGraphQlInterceptor... interceptors) { GraphQlWebSocketHandler handler = new GraphQlWebSocketHandler( initHandler(interceptors), diff --git a/spring-graphql/src/test/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandlerTests.java index 7cddbb04..7c3d9d05 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandlerTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/server/webmvc/GraphQlWebSocketHandlerTests.java @@ -36,6 +36,10 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar; +import org.springframework.aot.hint.predicate.ReflectionHintsPredicates; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.graphql.GraphQlSetup; import org.springframework.graphql.TestThreadLocalAccessor; import org.springframework.graphql.execution.ErrorType; @@ -406,6 +410,23 @@ public class GraphQlWebSocketHandlerTests extends WebSocketHandlerTestSupport { } } + @Test + void registerBindingReflectionOnWebSocketMessage() { + RuntimeHints runtimeHints = new RuntimeHints(); + new ReflectiveRuntimeHintsRegistrar().registerRuntimeHints(runtimeHints, GraphQlWebSocketHandler.class); + ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection(); + assertThat(reflection.onType(GraphQlWebSocketMessage.class)).accepts(runtimeHints); + assertThat(reflection.onField(GraphQlWebSocketMessage.class, "id")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getId")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setId")).accepts(runtimeHints); + assertThat(reflection.onField(GraphQlWebSocketMessage.class, "type")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getType")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setType")).accepts(runtimeHints); + assertThat(reflection.onField(GraphQlWebSocketMessage.class, "payload")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getPayload")).accepts(runtimeHints); + assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setPayload")).accepts(runtimeHints); + } + private void handle(GraphQlWebSocketHandler handler, TextMessage... textMessages) throws Exception { handler.afterConnectionEstablished(this.session);