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

Feature/327 RSocket support #625

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions graphql-kickstart-spring-boot-autoconfigure-rsocket/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
dependencies {
api(project(':graphql-kickstart-spring-rsocket'))
compileOnly(project(":graphql-kickstart-spring-boot-starter-tools"))

implementation "org.springframework.boot:spring-boot-autoconfigure"
implementation "com.graphql-java-kickstart:graphql-java-kickstart:$LIB_GRAPHQL_SERVLET_VER"
implementation "org.springframework.boot:spring-boot-starter-rsocket"

testImplementation "org.springframework.boot:spring-boot-starter-test"
testImplementation(project(":graphql-kickstart-spring-boot-starter-tools"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package graphql.kickstart.spring.rsocket.boot;

import static graphql.kickstart.execution.GraphQLObjectMapper.newBuilder;

import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions;
import graphql.kickstart.execution.BatchedDataLoaderGraphQLBuilder;
import graphql.kickstart.execution.GraphQLInvoker;
import graphql.kickstart.execution.GraphQLObjectMapper;
import graphql.kickstart.execution.config.DefaultGraphQLSchemaProvider;
import graphql.kickstart.execution.config.GraphQLBuilder;
import graphql.kickstart.execution.config.GraphQLSchemaProvider;
import graphql.kickstart.execution.config.ObjectMapperProvider;
import graphql.kickstart.spring.error.ErrorHandlerSupplier;
import graphql.kickstart.spring.error.GraphQLErrorStartupListener;
import graphql.kickstart.spring.rsocket.DefaultGraphQLSpringRSocketRootObjectBuilder;
import graphql.kickstart.spring.rsocket.DefaultGraphQlSpringRSocketContextBuilder;
import graphql.kickstart.spring.rsocket.DefaultGraphQlSpringRSocketInvocationInputFactory;
import graphql.kickstart.spring.rsocket.GraphQLSpringRSocketRootObjectBuilder;
import graphql.kickstart.spring.rsocket.GraphQlMessageHandler;
import graphql.kickstart.spring.rsocket.GraphQlSpringRSocketContextBuilder;
import graphql.kickstart.spring.rsocket.GraphQlSpringRSocketInvocationInputFactory;
import graphql.kickstart.tools.boot.GraphQLJavaToolsAutoConfiguration;
import graphql.schema.GraphQLSchema;
import java.util.function.Supplier;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Configuration
@ConditionalOnBean(GraphQLSchema.class)
@AutoConfigureAfter(GraphQLJavaToolsAutoConfiguration.class)
@AutoConfigureBefore(RSocketServerAutoConfiguration.class)
@Import(GraphQlMessageHandler.class)
@PropertySource("classpath:graphql.properties")
public class GraphQlSpringRSocketAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public ErrorHandlerSupplier errorHandlerSupplier() {
return new ErrorHandlerSupplier(null);
}

@Bean
public GraphQLErrorStartupListener graphQLErrorStartupListener(
ErrorHandlerSupplier errorHandlerSupplier) {
return new GraphQLErrorStartupListener(errorHandlerSupplier, true);
}

@Bean
@ConditionalOnMissingBean
public GraphQLObjectMapper graphQLObjectMapper(
ObjectProvider<ObjectMapperProvider> provider, ErrorHandlerSupplier errorHandlerSupplier) {
GraphQLObjectMapper.Builder builder = newBuilder();
builder.withGraphQLErrorHandler(errorHandlerSupplier);
provider.ifAvailable(builder::withObjectMapperProvider);
return builder.build();
}

@Bean
@ConditionalOnMissingBean
public GraphQlSpringRSocketContextBuilder graphQLSpringWebfluxContextBuilder() {
return new DefaultGraphQlSpringRSocketContextBuilder();
}

@Bean
@ConditionalOnMissingBean
public GraphQLSpringRSocketRootObjectBuilder graphQLSpringWebfluxRootObjectBuilder() {
return new DefaultGraphQLSpringRSocketRootObjectBuilder();
}

@Bean
@ConditionalOnMissingBean
public GraphQLSchemaProvider graphQLSchemaProvider(GraphQLSchema schema) {
return new DefaultGraphQLSchemaProvider(schema);
}

@Bean
@ConditionalOnMissingBean
public GraphQlSpringRSocketInvocationInputFactory graphQLSpringInvocationInputFactory(
GraphQLSchemaProvider graphQLSchemaProvider,
@Autowired(required = false) GraphQlSpringRSocketContextBuilder contextBuilder,
@Autowired(required = false) GraphQLSpringRSocketRootObjectBuilder rootObjectBuilder) {
return new DefaultGraphQlSpringRSocketInvocationInputFactory(
graphQLSchemaProvider, contextBuilder, rootObjectBuilder);
}

@Bean
@ConditionalOnMissingBean
public GraphQLBuilder graphQLBuilder() {
return new GraphQLBuilder();
}

@Bean
@ConditionalOnMissingBean
public BatchedDataLoaderGraphQLBuilder batchedDataLoaderGraphQLBuilder(
@Autowired(required = false)
Supplier<DataLoaderDispatcherInstrumentationOptions> optionsSupplier) {
return new BatchedDataLoaderGraphQLBuilder(optionsSupplier);
}

@Bean
@ConditionalOnMissingBean
public GraphQLInvoker graphQLInvoker(
GraphQLBuilder graphQLBuilder,
BatchedDataLoaderGraphQLBuilder batchedDataLoaderGraphQLBuilder) {
return new GraphQLInvoker(graphQLBuilder, batchedDataLoaderGraphQLBuilder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package graphql.kickstart.spring.rsocket.boot;

import graphql.kickstart.tools.SchemaParser;
import graphql.kickstart.tools.SchemaParserOptions.GenericWrapper;
import graphql.kickstart.tools.boot.GraphQLJavaToolsAutoConfiguration;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

@Configuration
@ConditionalOnClass(SchemaParser.class)
@AutoConfigureBefore(GraphQLJavaToolsAutoConfiguration.class)
public class MonoAutoConfiguration {

@Bean
GenericWrapper monoWrapper(@Autowired(required = false) List<GenericWrapper> genericWrappers) {
if (notWrapsMono(genericWrappers)) {
return GenericWrapper.withTransformer(Mono.class, 0, Mono::toFuture, t -> t);
}
return null;
}

private boolean notWrapsMono(List<GenericWrapper> genericWrappers) {
return genericWrappers == null
|| genericWrappers.stream().noneMatch(it -> it.getType().isAssignableFrom(Mono.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
graphql.kickstart.spring.rsocket.boot.GraphQlSpringRSocketAutoConfiguration,\
graphql.kickstart.spring.rsocket.boot.MonoAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
spring.rsocket.server.transport=websocket
spring.rsocket.server.port=7000
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package graphql.kickstart.spring.rsocket.boot;

import graphql.kickstart.tools.GraphQLQueryResolver;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

@Service
class QueryResolver implements GraphQLQueryResolver {

public Mono<String> hello() {
return Mono.just("Hello world");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package graphql.kickstart.spring.rsocket.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RSocketApplication {

public static void main(String[] args) {
SpringApplication.run(RSocketApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package graphql.kickstart.spring.rsocket.boot;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

import java.net.URI;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.val;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.util.MimeTypeUtils;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class RSocketGraphQLTest {

private final RSocketRequester rSocketRequester =
RSocketRequester.builder()
.dataMimeType(MimeTypeUtils.APPLICATION_JSON)
.websocket(URI.create("ws://localhost:7000"));

@Test
void query() throws JSONException {
val result =
rSocketRequester
.route("graphql")
.data("{ \"query\": \"query { hello } \"}")
.retrieveMono(String.class);

val response = result.block();
val json = new JSONObject(response);
assertThat(json.getJSONObject("data").get("hello")).isEqualTo("Hello world");
}

@Test
void subscription() {
val result =
rSocketRequester
.route("subscriptions")
.data("{ \"query\": \"subscription { hello } \"}")
.retrieveFlux(String.class);

AtomicInteger integer = new AtomicInteger(0);
int counter = 3;

result
.take(counter)
.doOnNext(
data -> {
try {
System.out.println(data);
val json = new JSONObject(data);
assertThat(json.getJSONObject("data").get("hello"))
.isEqualTo(integer.getAndIncrement());
} catch (Exception e) {
fail("Exception in assertion", e);
}
})
.blockLast();

assertThat(integer.get()).isEqualTo(counter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package graphql.kickstart.spring.rsocket.boot;

import graphql.kickstart.tools.GraphQLSubscriptionResolver;
import graphql.schema.DataFetchingEnvironment;
import java.time.Duration;
import org.reactivestreams.Publisher;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

@Service
class SubscriptionResolver implements GraphQLSubscriptionResolver {

Publisher<Integer> hello(DataFetchingEnvironment env) {
return Flux.range(0, 100).delayElements(Duration.ofSeconds(1));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
spring.rsocket.server.transport=websocket
spring.rsocket.server.port=7000
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
query {
hello
}

subscription {
hello
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type Query {
hello: String
}

type Subscription {
hello: Int
}
3 changes: 3 additions & 0 deletions graphql-kickstart-spring-boot-starter-rsocket/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies {
api(project(':graphql-kickstart-spring-boot-autoconfigure-rsocket'))
}
6 changes: 6 additions & 0 deletions graphql-kickstart-spring-rsocket/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies {
api(project(':graphql-kickstart-spring-support'))

api "com.graphql-java-kickstart:graphql-java-kickstart:$LIB_GRAPHQL_SERVLET_VER"
api "org.springframework.boot:spring-boot-starter-rsocket"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package graphql.kickstart.spring.rsocket;

import java.util.Map;

public class DefaultGraphQLSpringRSocketRootObjectBuilder
implements GraphQLSpringRSocketRootObjectBuilder {

@Override
public Object build(Map<String, Object> headers) {
return new Object();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package graphql.kickstart.spring.rsocket;

import graphql.kickstart.execution.context.DefaultGraphQLContextBuilder;
import graphql.kickstart.execution.context.GraphQLContext;
import java.util.Map;

public class DefaultGraphQlSpringRSocketContextBuilder
implements GraphQlSpringRSocketContextBuilder {

@Override
public GraphQLContext build(Map<String, Object> headers) {
return new DefaultGraphQLContextBuilder().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package graphql.kickstart.spring.rsocket;

import graphql.kickstart.execution.GraphQLRequest;
import graphql.kickstart.execution.config.GraphQLSchemaProvider;
import graphql.kickstart.execution.input.GraphQLSingleInvocationInput;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

public class DefaultGraphQlSpringRSocketInvocationInputFactory
implements GraphQlSpringRSocketInvocationInputFactory {

private final Supplier<GraphQLSchemaProvider> schemaProviderSupplier;
private Supplier<GraphQlSpringRSocketContextBuilder> contextBuilderSupplier;
private Supplier<GraphQLSpringRSocketRootObjectBuilder> rootObjectBuilderSupplier;

public DefaultGraphQlSpringRSocketInvocationInputFactory(
GraphQLSchemaProvider schemaProvider,
GraphQlSpringRSocketContextBuilder contextBuilder,
GraphQLSpringRSocketRootObjectBuilder rootObjectBuilder) {

Objects.requireNonNull(schemaProvider, "GraphQLSchemaProvider is required");
this.schemaProviderSupplier = () -> schemaProvider;
if (contextBuilder != null) {
contextBuilderSupplier = () -> contextBuilder;
}
if (rootObjectBuilder != null) {
rootObjectBuilderSupplier = () -> rootObjectBuilder;
}
}

@Override
public GraphQLSingleInvocationInput create(GraphQLRequest request, Map<String, Object> headers) {
return new GraphQLSingleInvocationInput(
request,
schemaProviderSupplier.get().getSchema(),
contextBuilderSupplier.get().build(headers),
rootObjectBuilderSupplier.get().build(headers));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package graphql.kickstart.spring.rsocket;

import java.util.Map;

public interface GraphQLSpringRSocketRootObjectBuilder {
Object build(Map<String, Object> headers);
}
Loading