Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit 42d40e2

Browse files
Mikhail MamontovNokinori
Mikhail Mamontov
authored andcommitted
feat: add rsocket support
1 parent f35b798 commit 42d40e2

22 files changed

+515
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
dependencies {
2+
api(project(':graphql-kickstart-spring-rsocket'))
3+
compileOnly(project(":graphql-kickstart-spring-boot-starter-tools"))
4+
5+
implementation "org.springframework.boot:spring-boot-autoconfigure"
6+
implementation "com.graphql-java-kickstart:graphql-java-kickstart:$LIB_GRAPHQL_SERVLET_VER"
7+
implementation "org.springframework.boot:spring-boot-starter-rsocket"
8+
9+
testImplementation "org.springframework.boot:spring-boot-starter-test"
10+
testImplementation(project(":graphql-kickstart-spring-boot-starter-tools"))
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package graphql.kickstart.spring.rsocket.boot;
2+
3+
import static graphql.kickstart.execution.GraphQLObjectMapper.newBuilder;
4+
5+
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions;
6+
import graphql.kickstart.execution.BatchedDataLoaderGraphQLBuilder;
7+
import graphql.kickstart.execution.GraphQLInvoker;
8+
import graphql.kickstart.execution.GraphQLObjectMapper;
9+
import graphql.kickstart.execution.config.DefaultGraphQLSchemaProvider;
10+
import graphql.kickstart.execution.config.GraphQLBuilder;
11+
import graphql.kickstart.execution.config.GraphQLSchemaProvider;
12+
import graphql.kickstart.execution.config.ObjectMapperProvider;
13+
import graphql.kickstart.spring.error.ErrorHandlerSupplier;
14+
import graphql.kickstart.spring.error.GraphQLErrorStartupListener;
15+
import graphql.kickstart.spring.rsocket.DefaultGraphQLSpringRSocketRootObjectBuilder;
16+
import graphql.kickstart.spring.rsocket.DefaultGraphQlSpringRSocketContextBuilder;
17+
import graphql.kickstart.spring.rsocket.DefaultGraphQlSpringRSocketInvocationInputFactory;
18+
import graphql.kickstart.spring.rsocket.GraphQLSpringRSocketRootObjectBuilder;
19+
import graphql.kickstart.spring.rsocket.GraphQlMessageHandler;
20+
import graphql.kickstart.spring.rsocket.GraphQlSpringRSocketContextBuilder;
21+
import graphql.kickstart.spring.rsocket.GraphQlSpringRSocketInvocationInputFactory;
22+
import graphql.kickstart.tools.boot.GraphQLJavaToolsAutoConfiguration;
23+
import graphql.schema.GraphQLSchema;
24+
import java.util.function.Supplier;
25+
import org.springframework.beans.factory.ObjectProvider;
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
28+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
31+
import org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration;
32+
import org.springframework.context.annotation.Bean;
33+
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.context.annotation.Import;
35+
import org.springframework.context.annotation.PropertySource;
36+
37+
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
38+
@Configuration
39+
@ConditionalOnBean(GraphQLSchema.class)
40+
@AutoConfigureAfter(GraphQLJavaToolsAutoConfiguration.class)
41+
@AutoConfigureBefore(RSocketServerAutoConfiguration.class)
42+
@Import(GraphQlMessageHandler.class)
43+
@PropertySource("classpath:graphql.properties")
44+
public class GraphQlSpringRSocketAutoConfiguration {
45+
46+
@Bean
47+
@ConditionalOnMissingBean
48+
public ErrorHandlerSupplier errorHandlerSupplier() {
49+
return new ErrorHandlerSupplier(null);
50+
}
51+
52+
@Bean
53+
public GraphQLErrorStartupListener graphQLErrorStartupListener(
54+
ErrorHandlerSupplier errorHandlerSupplier) {
55+
return new GraphQLErrorStartupListener(errorHandlerSupplier, true);
56+
}
57+
58+
@Bean
59+
@ConditionalOnMissingBean
60+
public GraphQLObjectMapper graphQLObjectMapper(
61+
ObjectProvider<ObjectMapperProvider> provider, ErrorHandlerSupplier errorHandlerSupplier) {
62+
GraphQLObjectMapper.Builder builder = newBuilder();
63+
builder.withGraphQLErrorHandler(errorHandlerSupplier);
64+
provider.ifAvailable(builder::withObjectMapperProvider);
65+
return builder.build();
66+
}
67+
68+
@Bean
69+
@ConditionalOnMissingBean
70+
public GraphQlSpringRSocketContextBuilder graphQLSpringWebfluxContextBuilder() {
71+
return new DefaultGraphQlSpringRSocketContextBuilder();
72+
}
73+
74+
@Bean
75+
@ConditionalOnMissingBean
76+
public GraphQLSpringRSocketRootObjectBuilder graphQLSpringWebfluxRootObjectBuilder() {
77+
return new DefaultGraphQLSpringRSocketRootObjectBuilder();
78+
}
79+
80+
@Bean
81+
@ConditionalOnMissingBean
82+
public GraphQLSchemaProvider graphQLSchemaProvider(GraphQLSchema schema) {
83+
return new DefaultGraphQLSchemaProvider(schema);
84+
}
85+
86+
@Bean
87+
@ConditionalOnMissingBean
88+
public GraphQlSpringRSocketInvocationInputFactory graphQLSpringInvocationInputFactory(
89+
GraphQLSchemaProvider graphQLSchemaProvider,
90+
@Autowired(required = false) GraphQlSpringRSocketContextBuilder contextBuilder,
91+
@Autowired(required = false) GraphQLSpringRSocketRootObjectBuilder rootObjectBuilder) {
92+
return new DefaultGraphQlSpringRSocketInvocationInputFactory(
93+
graphQLSchemaProvider, contextBuilder, rootObjectBuilder);
94+
}
95+
96+
@Bean
97+
@ConditionalOnMissingBean
98+
public GraphQLBuilder graphQLBuilder() {
99+
return new GraphQLBuilder();
100+
}
101+
102+
@Bean
103+
@ConditionalOnMissingBean
104+
public BatchedDataLoaderGraphQLBuilder batchedDataLoaderGraphQLBuilder(
105+
@Autowired(required = false)
106+
Supplier<DataLoaderDispatcherInstrumentationOptions> optionsSupplier) {
107+
return new BatchedDataLoaderGraphQLBuilder(optionsSupplier);
108+
}
109+
110+
@Bean
111+
@ConditionalOnMissingBean
112+
public GraphQLInvoker graphQLInvoker(
113+
GraphQLBuilder graphQLBuilder,
114+
BatchedDataLoaderGraphQLBuilder batchedDataLoaderGraphQLBuilder) {
115+
return new GraphQLInvoker(graphQLBuilder, batchedDataLoaderGraphQLBuilder);
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package graphql.kickstart.spring.rsocket.boot;
2+
3+
import graphql.kickstart.tools.SchemaParser;
4+
import graphql.kickstart.tools.SchemaParserOptions.GenericWrapper;
5+
import graphql.kickstart.tools.boot.GraphQLJavaToolsAutoConfiguration;
6+
import java.util.List;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
10+
import org.springframework.context.annotation.Bean;
11+
import org.springframework.context.annotation.Configuration;
12+
import reactor.core.publisher.Mono;
13+
14+
@Configuration
15+
@ConditionalOnClass(SchemaParser.class)
16+
@AutoConfigureBefore(GraphQLJavaToolsAutoConfiguration.class)
17+
public class MonoAutoConfiguration {
18+
19+
@Bean
20+
GenericWrapper monoWrapper(@Autowired(required = false) List<GenericWrapper> genericWrappers) {
21+
if (notWrapsMono(genericWrappers)) {
22+
return GenericWrapper.withTransformer(Mono.class, 0, Mono::toFuture, t -> t);
23+
}
24+
return null;
25+
}
26+
27+
private boolean notWrapsMono(List<GenericWrapper> genericWrappers) {
28+
return genericWrappers == null
29+
|| genericWrappers.stream().noneMatch(it -> it.getType().isAssignableFrom(Mono.class));
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2+
graphql.kickstart.spring.rsocket.boot.GraphQlSpringRSocketAutoConfiguration,\
3+
graphql.kickstart.spring.rsocket.boot.MonoAutoConfiguration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
spring.rsocket.server.transport=websocket
2+
spring.rsocket.server.port=7000
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package graphql.kickstart.spring.rsocket.boot;
2+
3+
import graphql.kickstart.tools.GraphQLQueryResolver;
4+
import org.springframework.stereotype.Service;
5+
import reactor.core.publisher.Mono;
6+
7+
@Service
8+
class QueryResolver implements GraphQLQueryResolver {
9+
10+
public Mono<String> hello() {
11+
return Mono.just("Hello world");
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package graphql.kickstart.spring.rsocket.boot;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class RSocketApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(RSocketApplication.class, args);
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package graphql.kickstart.spring.rsocket.boot;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.fail;
5+
6+
import java.net.URI;
7+
import java.util.concurrent.atomic.AtomicInteger;
8+
import lombok.val;
9+
import org.json.JSONException;
10+
import org.json.JSONObject;
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.ExtendWith;
13+
import org.springframework.boot.test.context.SpringBootTest;
14+
import org.springframework.messaging.rsocket.RSocketRequester;
15+
import org.springframework.test.context.junit.jupiter.SpringExtension;
16+
import org.springframework.util.MimeTypeUtils;
17+
18+
@ExtendWith(SpringExtension.class)
19+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
20+
class RSocketGraphQLTest {
21+
22+
private final RSocketRequester rSocketRequester =
23+
RSocketRequester.builder()
24+
.dataMimeType(MimeTypeUtils.APPLICATION_JSON)
25+
.websocket(URI.create("ws://localhost:7000"));
26+
27+
@Test
28+
void query() throws JSONException {
29+
val result =
30+
rSocketRequester
31+
.route("graphql")
32+
.data("{ \"query\": \"query { hello } \"}")
33+
.retrieveMono(String.class);
34+
35+
val response = result.block();
36+
val json = new JSONObject(response);
37+
assertThat(json.getJSONObject("data").get("hello")).isEqualTo("Hello world");
38+
}
39+
40+
@Test
41+
void subscription() {
42+
val result =
43+
rSocketRequester
44+
.route("subscriptions")
45+
.data("{ \"query\": \"subscription { hello } \"}")
46+
.retrieveFlux(String.class);
47+
48+
AtomicInteger integer = new AtomicInteger(0);
49+
int counter = 3;
50+
51+
result
52+
.take(counter)
53+
.doOnNext(
54+
data -> {
55+
try {
56+
System.out.println(data);
57+
val json = new JSONObject(data);
58+
assertThat(json.getJSONObject("data").get("hello"))
59+
.isEqualTo(integer.getAndIncrement());
60+
} catch (Exception e) {
61+
fail("Exception in assertion", e);
62+
}
63+
})
64+
.blockLast();
65+
66+
assertThat(integer.get()).isEqualTo(counter);
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package graphql.kickstart.spring.rsocket.boot;
2+
3+
import graphql.kickstart.tools.GraphQLSubscriptionResolver;
4+
import graphql.schema.DataFetchingEnvironment;
5+
import java.time.Duration;
6+
import org.reactivestreams.Publisher;
7+
import org.springframework.stereotype.Service;
8+
import reactor.core.publisher.Flux;
9+
10+
@Service
11+
class SubscriptionResolver implements GraphQLSubscriptionResolver {
12+
13+
Publisher<Integer> hello(DataFetchingEnvironment env) {
14+
return Flux.range(0, 100).delayElements(Duration.ofSeconds(1));
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
spring.rsocket.server.transport=websocket
2+
spring.rsocket.server.port=7000
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
query {
2+
hello
3+
}
4+
5+
subscription {
6+
hello
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type Query {
2+
hello: String
3+
}
4+
5+
type Subscription {
6+
hello: Int
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dependencies {
2+
api(project(':graphql-kickstart-spring-boot-autoconfigure-rsocket'))
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dependencies {
2+
api(project(':graphql-kickstart-spring-support'))
3+
4+
api "com.graphql-java-kickstart:graphql-java-kickstart:$LIB_GRAPHQL_SERVLET_VER"
5+
api "org.springframework.boot:spring-boot-starter-rsocket"
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package graphql.kickstart.spring.rsocket;
2+
3+
import java.util.Map;
4+
5+
public class DefaultGraphQLSpringRSocketRootObjectBuilder
6+
implements GraphQLSpringRSocketRootObjectBuilder {
7+
8+
@Override
9+
public Object build(Map<String, Object> headers) {
10+
return new Object();
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package graphql.kickstart.spring.rsocket;
2+
3+
import graphql.kickstart.execution.context.DefaultGraphQLContextBuilder;
4+
import graphql.kickstart.execution.context.GraphQLContext;
5+
import java.util.Map;
6+
7+
public class DefaultGraphQlSpringRSocketContextBuilder
8+
implements GraphQlSpringRSocketContextBuilder {
9+
10+
@Override
11+
public GraphQLContext build(Map<String, Object> headers) {
12+
return new DefaultGraphQLContextBuilder().build();
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package graphql.kickstart.spring.rsocket;
2+
3+
import graphql.kickstart.execution.GraphQLRequest;
4+
import graphql.kickstart.execution.config.GraphQLSchemaProvider;
5+
import graphql.kickstart.execution.input.GraphQLSingleInvocationInput;
6+
import java.util.Map;
7+
import java.util.Objects;
8+
import java.util.function.Supplier;
9+
10+
public class DefaultGraphQlSpringRSocketInvocationInputFactory
11+
implements GraphQlSpringRSocketInvocationInputFactory {
12+
13+
private final Supplier<GraphQLSchemaProvider> schemaProviderSupplier;
14+
private Supplier<GraphQlSpringRSocketContextBuilder> contextBuilderSupplier;
15+
private Supplier<GraphQLSpringRSocketRootObjectBuilder> rootObjectBuilderSupplier;
16+
17+
public DefaultGraphQlSpringRSocketInvocationInputFactory(
18+
GraphQLSchemaProvider schemaProvider,
19+
GraphQlSpringRSocketContextBuilder contextBuilder,
20+
GraphQLSpringRSocketRootObjectBuilder rootObjectBuilder) {
21+
22+
Objects.requireNonNull(schemaProvider, "GraphQLSchemaProvider is required");
23+
this.schemaProviderSupplier = () -> schemaProvider;
24+
if (contextBuilder != null) {
25+
contextBuilderSupplier = () -> contextBuilder;
26+
}
27+
if (rootObjectBuilder != null) {
28+
rootObjectBuilderSupplier = () -> rootObjectBuilder;
29+
}
30+
}
31+
32+
@Override
33+
public GraphQLSingleInvocationInput create(GraphQLRequest request, Map<String, Object> headers) {
34+
return new GraphQLSingleInvocationInput(
35+
request,
36+
schemaProviderSupplier.get().getSchema(),
37+
contextBuilderSupplier.get().build(headers),
38+
rootObjectBuilderSupplier.get().build(headers));
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package graphql.kickstart.spring.rsocket;
2+
3+
import java.util.Map;
4+
5+
public interface GraphQLSpringRSocketRootObjectBuilder {
6+
Object build(Map<String, Object> headers);
7+
}

0 commit comments

Comments
 (0)