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

Commit 08cf6ae

Browse files
committed
Autoconfigure reactive subscriptions for spring webflux
1 parent 96b4efb commit 08cf6ae

File tree

20 files changed

+424
-27
lines changed

20 files changed

+424
-27
lines changed
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
package graphql.servlet.examples.dataloader.requestscope;
22

33
import com.coxautodev.graphql.tools.GraphQLResolver;
4+
import graphql.kickstart.execution.context.GraphQLContext;
45
import graphql.schema.DataFetchingEnvironment;
5-
import graphql.servlet.context.GraphQLContext;
6+
import java.util.concurrent.CompletableFuture;
67
import org.dataloader.DataLoader;
78
import org.springframework.stereotype.Component;
89

9-
import java.util.concurrent.CompletableFuture;
10-
1110
@Component
1211
public class CustomerResolver implements GraphQLResolver<Customer> {
1312

14-
public CompletableFuture<String> getName(Customer customer, DataFetchingEnvironment dfe) {
15-
final DataLoader<Integer, String> dataloader = ((GraphQLContext) dfe.getContext())
16-
.getDataLoaderRegistry().get()
17-
.getDataLoader("customerDataLoader");
13+
public CompletableFuture<String> getName(Customer customer, DataFetchingEnvironment dfe) {
14+
final DataLoader<Integer, String> dataloader = ((GraphQLContext) dfe.getContext())
15+
.getDataLoaderRegistry().get()
16+
.getDataLoader("customerDataLoader");
17+
18+
return dataloader.load(customer.getCustomerId());
19+
}
1820

19-
return dataloader.load(customer.getCustomerId());
20-
}
2121
}

graphql-kickstart-spring-boot-autoconfigure-webflux/src/main/java/graphql/kickstart/spring/webflux/boot/GraphQLSpringWebfluxAutoConfiguration.java

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,36 @@
77
import graphql.kickstart.execution.GraphQLObjectMapper;
88
import graphql.kickstart.execution.config.GraphQLBuilder;
99
import graphql.kickstart.execution.config.ObjectMapperProvider;
10-
import graphql.kickstart.spring.DefaultGraphQLSpringInvocationInputFactory;
11-
import graphql.kickstart.spring.GraphQLSpringContextBuilder;
12-
import graphql.kickstart.spring.GraphQLSpringInvocationInputFactory;
13-
import graphql.kickstart.spring.GraphQLSpringRootObjectBuilder;
10+
import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory;
11+
import graphql.kickstart.execution.subscriptions.apollo.ApolloSubscriptionConnectionListener;
12+
import graphql.kickstart.execution.subscriptions.apollo.KeepAliveSubscriptionConnectionListener;
13+
import graphql.kickstart.spring.webflux.DefaultGraphQLSpringWebfluxContextBuilder;
14+
import graphql.kickstart.spring.webflux.DefaultGraphQLSpringWebfluxRootObjectBuilder;
1415
import graphql.kickstart.spring.webflux.GraphQLController;
16+
import graphql.kickstart.spring.webflux.GraphQLSpringWebfluxContextBuilder;
17+
import graphql.kickstart.spring.webflux.GraphQLSpringWebfluxInvocationInputFactory;
18+
import graphql.kickstart.spring.webflux.GraphQLSpringWebfluxRootObjectBuilder;
19+
import graphql.kickstart.spring.webflux.ReactiveSubscriptionsProtocolFactory;
20+
import graphql.kickstart.spring.webflux.ReactiveWebSocketSubscriptionsHandler;
21+
import graphql.kickstart.spring.webflux.apollo.ReactiveApolloSubscriptionProtocolFactory;
1522
import graphql.schema.GraphQLSchema;
23+
import java.util.Collection;
24+
import java.util.HashMap;
25+
import java.util.HashSet;
26+
import java.util.Map;
27+
import java.util.Set;
1628
import lombok.extern.slf4j.Slf4j;
1729
import org.springframework.beans.factory.ObjectProvider;
1830
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.beans.factory.annotation.Value;
1932
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2033
import org.springframework.context.annotation.Bean;
2134
import org.springframework.context.annotation.ComponentScan;
2235
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.web.reactive.HandlerMapping;
37+
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
38+
import org.springframework.web.reactive.socket.WebSocketHandler;
39+
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
2340

2441
@Slf4j
2542
@Configuration
@@ -37,11 +54,23 @@ public GraphQLObjectMapper graphQLObjectMapper(ObjectProvider<ObjectMapperProvid
3754

3855
@Bean
3956
@ConditionalOnMissingBean
40-
public GraphQLSpringInvocationInputFactory graphQLSpringInvocationInputFactory(
41-
@Autowired(required = false) GraphQLSpringContextBuilder contextBuilder,
42-
@Autowired(required = false) GraphQLSpringRootObjectBuilder rootObjectBuilder
57+
public GraphQLSpringWebfluxContextBuilder graphQLSpringWebfluxContextBuilder() {
58+
return new DefaultGraphQLSpringWebfluxContextBuilder();
59+
}
60+
61+
@Bean
62+
@ConditionalOnMissingBean
63+
public GraphQLSpringWebfluxRootObjectBuilder graphQLSpringWebfluxRootObjectBuilder() {
64+
return new DefaultGraphQLSpringWebfluxRootObjectBuilder();
65+
}
66+
67+
@Bean
68+
@ConditionalOnMissingBean
69+
public GraphQLSpringWebfluxInvocationInputFactory graphQLSpringInvocationInputFactory(
70+
@Autowired(required = false) GraphQLSpringWebfluxContextBuilder contextBuilder,
71+
@Autowired(required = false) GraphQLSpringWebfluxRootObjectBuilder rootObjectBuilder
4372
) {
44-
return new DefaultGraphQLSpringInvocationInputFactory(contextBuilder, rootObjectBuilder);
73+
return new GraphQLSpringWebfluxInvocationInputFactory(contextBuilder, rootObjectBuilder);
4574
}
4675

4776
@Bean
@@ -51,4 +80,46 @@ public GraphQLInvoker graphQLInvoker(GraphQLSchema schema) {
5180
return new GraphQLInvoker(graphQL);
5281
}
5382

83+
@Bean
84+
@ConditionalOnMissingBean
85+
public ReactiveSubscriptionsProtocolFactory subscriptionProtocolFactory(
86+
GraphQLObjectMapper graphQLObjectMapper,
87+
GraphQLSubscriptionInvocationInputFactory invocationInputFactory,
88+
GraphQLInvoker graphQLInvoker,
89+
@Autowired(required = false) Collection<ApolloSubscriptionConnectionListener> connectionListeners
90+
) {
91+
Set<ApolloSubscriptionConnectionListener> listeners = new HashSet<>();
92+
if (connectionListeners != null) {
93+
listeners.addAll(connectionListeners);
94+
}
95+
if (listeners.stream().noneMatch(KeepAliveSubscriptionConnectionListener.class::isInstance)) {
96+
listeners.add(new KeepAliveSubscriptionConnectionListener());
97+
}
98+
return new ReactiveApolloSubscriptionProtocolFactory(
99+
graphQLObjectMapper,
100+
invocationInputFactory,
101+
graphQLInvoker,
102+
listeners
103+
);
104+
}
105+
106+
@Bean
107+
public HandlerMapping webSocketHandlerMapping(
108+
@Value("${graphql.subscriptions.url:subscriptions}") String path,
109+
ReactiveWebSocketSubscriptionsHandler webSocketHandler) {
110+
Map<String, WebSocketHandler> map = new HashMap<>();
111+
map.put(path, webSocketHandler);
112+
113+
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
114+
handlerMapping.setOrder(1);
115+
handlerMapping.setUrlMap(map);
116+
return handlerMapping;
117+
}
118+
119+
@Bean
120+
@ConditionalOnMissingBean
121+
WebSocketHandlerAdapter webSocketHandlerAdapter() {
122+
return new WebSocketHandlerAdapter();
123+
}
124+
54125
}

graphql-kickstart-spring-support/src/main/java/graphql/kickstart/spring/DefaultGraphQLSpringInvocationInputFactory.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
public class DefaultGraphQLSpringInvocationInputFactory implements GraphQLSpringInvocationInputFactory {
1111

12-
private Supplier<GraphQLSpringContextBuilder> contextBuilderSupplier = () -> (DefaultGraphQLSpringContext::new);
12+
private Supplier<GraphQLSpringContextBuilder> contextBuilderSupplier = () -> (GraphQLSpringServerWebExchangeContext::new);
1313
private Supplier<GraphQLSpringRootObjectBuilder> rootObjectBuilderSupplier = () -> (serverWebExchange -> new Object());
1414

1515
public DefaultGraphQLSpringInvocationInputFactory(
@@ -52,4 +52,12 @@ public GraphQLBatchedInvocationInput create(Collection<GraphQLRequest> graphQLRe
5252
throw new UnsupportedOperationException("Batch queries not suppoprted yet");
5353
}
5454

55+
protected Supplier<GraphQLSpringContextBuilder> getContextBuilderSupplier() {
56+
return contextBuilderSupplier;
57+
}
58+
59+
protected Supplier<GraphQLSpringRootObjectBuilder> getRootObjectBuilderSupplier() {
60+
return rootObjectBuilderSupplier;
61+
}
62+
5563
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
import org.dataloader.DataLoaderRegistry;
66
import org.springframework.web.server.ServerWebExchange;
77

8-
public class DefaultGraphQLSpringContext extends DefaultGraphQLContext implements GraphQLSpringContext {
8+
public class GraphQLSpringServerWebExchangeContext extends DefaultGraphQLContext implements GraphQLSpringContext {
99

1010
private final ServerWebExchange serverWebExchange;
1111

12-
public DefaultGraphQLSpringContext(ServerWebExchange serverWebExchange) {
12+
public GraphQLSpringServerWebExchangeContext(ServerWebExchange serverWebExchange) {
1313
this(new DataLoaderRegistry(), serverWebExchange);
1414
}
1515

16-
public DefaultGraphQLSpringContext(DataLoaderRegistry dataLoaderRegistry, ServerWebExchange serverWebExchange) {
16+
public GraphQLSpringServerWebExchangeContext(DataLoaderRegistry dataLoaderRegistry, ServerWebExchange serverWebExchange) {
1717
super(dataLoaderRegistry, null);
1818
this.serverWebExchange = Objects.requireNonNull(serverWebExchange, "Server web exchange cannot be null");
1919
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package graphql.kickstart.spring.webflux;
2+
3+
import graphql.kickstart.execution.context.DefaultGraphQLContext;
4+
import java.util.Objects;
5+
import org.dataloader.DataLoaderRegistry;
6+
import org.springframework.web.reactive.socket.WebSocketSession;
7+
8+
public class DefaultGraphQLSpringWebSocketSessionContext extends DefaultGraphQLContext implements
9+
GraphQLSpringWebSocketSessionContext {
10+
11+
private final WebSocketSession webSocketSession;
12+
13+
public DefaultGraphQLSpringWebSocketSessionContext(WebSocketSession webSocketSession) {
14+
this(new DataLoaderRegistry(), webSocketSession);
15+
}
16+
17+
public DefaultGraphQLSpringWebSocketSessionContext(DataLoaderRegistry dataLoaderRegistry,
18+
WebSocketSession webSocketSession) {
19+
super(dataLoaderRegistry, null);
20+
this.webSocketSession = Objects.requireNonNull(webSocketSession, "WebSocketSession is required");
21+
}
22+
23+
@Override
24+
public WebSocketSession getWebSocketSession() {
25+
return webSocketSession;
26+
}
27+
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package graphql.kickstart.spring.webflux;
2+
3+
import graphql.kickstart.spring.GraphQLSpringContext;
4+
import graphql.kickstart.spring.GraphQLSpringServerWebExchangeContext;
5+
import org.springframework.web.reactive.socket.WebSocketSession;
6+
import org.springframework.web.server.ServerWebExchange;
7+
8+
public class DefaultGraphQLSpringWebfluxContextBuilder implements GraphQLSpringWebfluxContextBuilder {
9+
10+
@Override
11+
public GraphQLSpringWebSocketSessionContext build(WebSocketSession webSocketSession) {
12+
return new DefaultGraphQLSpringWebSocketSessionContext(webSocketSession);
13+
}
14+
15+
@Override
16+
public GraphQLSpringContext build(ServerWebExchange serverWebExchange) {
17+
return new GraphQLSpringServerWebExchangeContext(serverWebExchange);
18+
}
19+
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package graphql.kickstart.spring.webflux;
2+
3+
import graphql.kickstart.spring.GraphQLSpringRootObjectBuilder;
4+
import org.springframework.web.reactive.socket.WebSocketSession;
5+
import org.springframework.web.server.ServerWebExchange;
6+
7+
public class DefaultGraphQLSpringWebfluxRootObjectBuilder
8+
implements GraphQLSpringRootObjectBuilder, GraphQLSpringWebfluxRootObjectBuilder {
9+
10+
@Override
11+
public Object build(ServerWebExchange serverWebExchange) {
12+
return new Object();
13+
}
14+
15+
@Override
16+
public Object build(WebSocketSession webSocketSession) {
17+
return new Object();
18+
}
19+
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package graphql.kickstart.spring.webflux;
2+
3+
import graphql.kickstart.execution.context.GraphQLContext;
4+
import org.springframework.web.reactive.socket.WebSocketSession;
5+
6+
interface GraphQLSpringWebSocketSessionContext extends GraphQLContext {
7+
8+
WebSocketSession getWebSocketSession();
9+
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package graphql.kickstart.spring.webflux;
2+
3+
import graphql.kickstart.spring.GraphQLSpringContextBuilder;
4+
import org.springframework.web.reactive.socket.WebSocketSession;
5+
6+
public interface GraphQLSpringWebfluxContextBuilder extends GraphQLSpringContextBuilder {
7+
8+
GraphQLSpringWebSocketSessionContext build(WebSocketSession webSocketSession);
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package graphql.kickstart.spring.webflux;
2+
3+
import graphql.kickstart.execution.GraphQLRequest;
4+
import graphql.kickstart.execution.input.GraphQLSingleInvocationInput;
5+
import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory;
6+
import graphql.kickstart.execution.subscriptions.SubscriptionSession;
7+
import graphql.kickstart.spring.DefaultGraphQLSpringInvocationInputFactory;
8+
import graphql.kickstart.spring.GraphQLSpringContextBuilder;
9+
import graphql.kickstart.spring.GraphQLSpringRootObjectBuilder;
10+
import java.util.function.Supplier;
11+
import org.springframework.web.reactive.socket.WebSocketSession;
12+
13+
public class GraphQLSpringWebfluxInvocationInputFactory
14+
extends DefaultGraphQLSpringInvocationInputFactory
15+
implements GraphQLSubscriptionInvocationInputFactory {
16+
17+
public GraphQLSpringWebfluxInvocationInputFactory(GraphQLSpringContextBuilder contextBuilder,
18+
GraphQLSpringRootObjectBuilder rootObjectBuilder) {
19+
super(contextBuilder, rootObjectBuilder);
20+
}
21+
22+
public GraphQLSpringWebfluxInvocationInputFactory(
23+
Supplier<GraphQLSpringContextBuilder> contextBuilderSupplier,
24+
Supplier<GraphQLSpringRootObjectBuilder> rootObjectBuilderSupplier) {
25+
super(contextBuilderSupplier, rootObjectBuilderSupplier);
26+
}
27+
28+
@Override
29+
public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, SubscriptionSession session) {
30+
return new GraphQLSingleInvocationInput(
31+
graphQLRequest,
32+
null,
33+
((GraphQLSpringWebfluxContextBuilder) getContextBuilderSupplier().get()).build((WebSocketSession) session.unwrap()),
34+
((GraphQLSpringWebfluxRootObjectBuilder) getRootObjectBuilderSupplier().get()).build((WebSocketSession) session.unwrap())
35+
);
36+
}
37+
38+
}

0 commit comments

Comments
 (0)