Skip to content

Commit a6c69eb

Browse files
committed
Polishing
See gh-1149
1 parent f3f5516 commit a6c69eb

12 files changed

+202
-136
lines changed

Diff for: spring-graphql/src/main/java/org/springframework/graphql/ExecutionGraphQlRequest.java

-6
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,6 @@
3636
*/
3737
public interface ExecutionGraphQlRequest extends GraphQlRequest {
3838

39-
/**
40-
* Key of the GraphQL context entry that holds a {@code Mono<Void>} that completes
41-
* when the inbound GraphQL request is cancelled at the transport level.
42-
*/
43-
String CANCEL_PUBLISHER_CONTEXT_KEY = ExecutionGraphQlRequest.class.getName() + ".cancelled";
44-
4539
/**
4640
* Return the transport assigned id for the request that in turn sets
4741
* {@link ExecutionInput.Builder#executionId(ExecutionId) executionId}.

Diff for: spring-graphql/src/main/java/org/springframework/graphql/data/method/InvocableHandlerMethodSupport.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import org.springframework.core.CoroutinesUtils;
3333
import org.springframework.core.KotlinDetector;
3434
import org.springframework.data.util.KotlinReflectionUtils;
35-
import org.springframework.graphql.execution.ContextSnapshotFactoryHelper;
35+
import org.springframework.graphql.execution.ContextPropagationHelper;
3636
import org.springframework.lang.Nullable;
3737
import org.springframework.util.Assert;
3838

@@ -153,7 +153,7 @@ private CompletableFuture<?> adaptCallable(
153153
CompletableFuture<Object> future = new CompletableFuture<>();
154154
this.executor.execute(() -> {
155155
try {
156-
ContextSnapshot snapshot = ContextSnapshotFactoryHelper.captureFrom(graphQLContext);
156+
ContextSnapshot snapshot = ContextPropagationHelper.captureFrom(graphQLContext);
157157
Object value = snapshot.wrap((Callable<?>) result).call();
158158
future.complete(value);
159159
}

Diff for: spring-graphql/src/main/java/org/springframework/graphql/execution/ContextDataFetcherDecorator.java

+7-15
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import reactor.core.publisher.Flux;
4040
import reactor.core.publisher.Mono;
4141

42-
import org.springframework.graphql.ExecutionGraphQlRequest;
4342
import org.springframework.lang.Nullable;
4443
import org.springframework.util.Assert;
4544

@@ -80,32 +79,30 @@ private ContextDataFetcherDecorator(
8079
public Object get(DataFetchingEnvironment env) throws Exception {
8180

8281
GraphQLContext graphQlContext = env.getGraphQlContext();
83-
ContextSnapshotFactory snapshotFactory = ContextSnapshotFactoryHelper.getInstance(graphQlContext);
82+
ContextSnapshotFactory snapshotFactory = ContextPropagationHelper.getInstance(graphQlContext);
8483
ContextSnapshot snapshot = (env.getLocalContext() instanceof GraphQLContext localContext) ?
8584
snapshotFactory.captureFrom(graphQlContext, localContext) :
8685
snapshotFactory.captureFrom(graphQlContext);
8786

88-
Mono<Void> cancelledRequest = graphQlContext.get(ExecutionGraphQlRequest.CANCEL_PUBLISHER_CONTEXT_KEY);
89-
9087
Object value = snapshot.wrap(() -> this.delegate.get(env)).call();
9188

9289
if (value instanceof DataFetcherResult<?> dataFetcherResult) {
93-
Object adapted = updateValue(dataFetcherResult.getData(), snapshot, cancelledRequest);
90+
Object adapted = updateValue(dataFetcherResult.getData(), snapshot, graphQlContext);
9491
value = DataFetcherResult.newResult()
9592
.data(adapted)
9693
.errors(dataFetcherResult.getErrors())
9794
.localContext(dataFetcherResult.getLocalContext()).build();
9895
}
9996
else {
100-
value = updateValue(value, snapshot, cancelledRequest);
97+
value = updateValue(value, snapshot, graphQlContext);
10198
}
10299

103100
return value;
104101
}
105102

106103
@SuppressWarnings("ReactiveStreamsUnusedPublisher")
107104
private @Nullable Object updateValue(
108-
@Nullable Object value, ContextSnapshot snapshot, @Nullable Mono<Void> cancelledRequest) {
105+
@Nullable Object value, ContextSnapshot snapshot, GraphQLContext graphQlContext) {
109106

110107
if (value == null) {
111108
return null;
@@ -121,19 +118,14 @@ public Object get(DataFetchingEnvironment env) throws Exception {
121118
return this.subscriptionExceptionResolver.resolveException(exception)
122119
.flatMap((errors) -> Mono.error(new SubscriptionPublisherException(errors, exception)));
123120
});
124-
if (cancelledRequest != null) {
125-
subscriptionResult = subscriptionResult.takeUntilOther(cancelledRequest);
126-
}
127-
return subscriptionResult.contextWrite(snapshot::updateContext);
121+
return ContextPropagationHelper.bindCancelFrom(subscriptionResult, graphQlContext)
122+
.contextWrite(snapshot::updateContext);
128123
}
129124

130125
value = ReactiveAdapterRegistryHelper.toMonoIfReactive(value);
131126

132127
if (value instanceof Mono<?> mono) {
133-
if (cancelledRequest != null) {
134-
mono = mono.takeUntilOther(cancelledRequest);
135-
}
136-
value = mono.contextWrite(snapshot::updateContext).toFuture();
128+
value = ContextPropagationHelper.bindCancelFrom(mono, graphQlContext).contextWrite(snapshot::updateContext).toFuture();
137129
}
138130

139131
return value;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.execution;
18+
19+
import graphql.GraphQLContext;
20+
import io.micrometer.context.ContextSnapshot;
21+
import io.micrometer.context.ContextSnapshotFactory;
22+
import reactor.core.publisher.Flux;
23+
import reactor.core.publisher.Mono;
24+
import reactor.core.publisher.Sinks;
25+
import reactor.util.context.Context;
26+
import reactor.util.context.ContextView;
27+
28+
import org.springframework.lang.Nullable;
29+
30+
/**
31+
* Helper for propagating context values from and to Reactor and GraphQL contexts.
32+
*
33+
* @author Rossen Stoyanchev
34+
* @author Brian Clozel
35+
* @since 1.3.5
36+
*/
37+
public abstract class ContextPropagationHelper {
38+
39+
private static final ContextSnapshotFactory sharedInstance = ContextSnapshotFactory.builder().build();
40+
41+
private static final String CONTEXT_SNAPSHOT_FACTORY_KEY = ContextPropagationHelper.class.getName() + ".KEY";
42+
43+
private static final String CANCEL_PUBLISHER_KEY = ContextPropagationHelper.class.getName() + ".cancelled";
44+
45+
46+
/**
47+
* Select a {@code ContextSnapshotFactory} instance to use, either the one
48+
* passed in if it is not {@code null}, or a shared, static instance.
49+
* @param factory the candidate factory instance to use if not {@code null}
50+
* @return the instance to use
51+
*/
52+
public static ContextSnapshotFactory selectInstance(@Nullable ContextSnapshotFactory factory) {
53+
if (factory != null) {
54+
return factory;
55+
}
56+
return sharedInstance;
57+
}
58+
59+
/**
60+
* Save the {@code ContextSnapshotFactory} in the given {@link Context}.
61+
* @param factory the instance to save
62+
* @param context the context to save the instance to
63+
* @return a new context with the saved instance
64+
*/
65+
public static Context saveInstance(ContextSnapshotFactory factory, Context context) {
66+
return context.put(CONTEXT_SNAPSHOT_FACTORY_KEY, factory);
67+
}
68+
69+
/**
70+
* Save the {@code ContextSnapshotFactory} in the given {@link Context}.
71+
* @param factory the instance to save
72+
* @param context the context to save the instance to
73+
*/
74+
public static void saveInstance(ContextSnapshotFactory factory, GraphQLContext context) {
75+
context.put(CONTEXT_SNAPSHOT_FACTORY_KEY, factory);
76+
}
77+
78+
/**
79+
* Access the {@code ContextSnapshotFactory} from the given {@link ContextView}
80+
* or return a shared, static instance.
81+
* @param contextView the context where the instance is saved
82+
* @return the instance to use
83+
*/
84+
public static ContextSnapshotFactory getInstance(ContextView contextView) {
85+
ContextSnapshotFactory factory = contextView.getOrDefault(CONTEXT_SNAPSHOT_FACTORY_KEY, null);
86+
return selectInstance(factory);
87+
}
88+
89+
/**
90+
* Access the {@code ContextSnapshotFactory} from the given {@link GraphQLContext}
91+
* or return a shared, static instance.
92+
* @param context the context where the instance is saved
93+
* @return the instance to use
94+
*/
95+
public static ContextSnapshotFactory getInstance(GraphQLContext context) {
96+
ContextSnapshotFactory factory = context.get(CONTEXT_SNAPSHOT_FACTORY_KEY);
97+
return selectInstance(factory);
98+
}
99+
100+
/**
101+
* Shortcut to obtain the {@code ContextSnapshotFactory} instance, and to
102+
* capture from the given {@link ContextView}.
103+
* @param contextView the context to capture from
104+
* @return a snapshot from the capture
105+
*/
106+
public static ContextSnapshot captureFrom(ContextView contextView) {
107+
ContextSnapshotFactory factory = getInstance(contextView);
108+
return selectInstance(factory).captureFrom(contextView);
109+
}
110+
111+
/**
112+
* Shortcut to obtain the {@code ContextSnapshotFactory} instance, and to
113+
* capture from the given {@link GraphQLContext}.
114+
* @param context the context to capture from
115+
* @return a snapshot from the capture
116+
*/
117+
public static ContextSnapshot captureFrom(GraphQLContext context) {
118+
ContextSnapshotFactory factory = getInstance(context);
119+
return selectInstance(factory).captureFrom(context);
120+
}
121+
122+
/**
123+
* Create a publisher and store it into the given {@link GraphQLContext}.
124+
* This publisher can then be used to propagate cancel signals to upstream publishers.
125+
* @param context the current GraphQL context
126+
* @since 1.3.5
127+
*/
128+
public static Sinks.Empty<Void> createCancelPublisher(GraphQLContext context) {
129+
Sinks.Empty<Void> requestCancelled = Sinks.empty();
130+
context.put(CANCEL_PUBLISHER_KEY, requestCancelled.asMono());
131+
return requestCancelled;
132+
}
133+
134+
/**
135+
* Bind the source {@link Mono} to the publisher from the given {@link GraphQLContext}.
136+
* The returned {@code Mono} will be cancelled when this publisher completes.
137+
* Subscribers must use the returned {@code Mono} instance.
138+
* @param source the source {@code Mono}
139+
* @param context the current GraphQL context
140+
* @param <T> the type of published elements
141+
* @return the new {@code Mono} that will be cancelled when notified
142+
* @since 1.3.5
143+
*/
144+
public static <T> Mono<T> bindCancelFrom(Mono<T> source, GraphQLContext context) {
145+
Mono<Void> cancelSignal = context.get(CANCEL_PUBLISHER_KEY);
146+
if (cancelSignal != null) {
147+
return source.takeUntilOther(cancelSignal);
148+
}
149+
return source;
150+
}
151+
152+
/**
153+
* Bind the source {@link Flux} to the publisher from the given {@link GraphQLContext}.
154+
* The returned {@code Flux} will be cancelled when this publisher completes.
155+
* Subscribers must use the returned {@code Mono} instance.
156+
* @param source the source {@code Mono}
157+
* @param context the current GraphQL context
158+
* @param <T> the type of published elements
159+
* @return the new {@code Mono} that will be cancelled when notified
160+
* @since 1.3.5
161+
*/
162+
public static <T> Flux<T> bindCancelFrom(Flux<T> source, GraphQLContext context) {
163+
Mono<Void> cancelSignal = context.get(CANCEL_PUBLISHER_KEY);
164+
if (cancelSignal != null) {
165+
return source.takeUntilOther(cancelSignal);
166+
}
167+
return source;
168+
}
169+
170+
}

Diff for: spring-graphql/src/main/java/org/springframework/graphql/execution/ContextSnapshotFactoryHelper.java

+3-88
Original file line numberDiff line numberDiff line change
@@ -16,102 +16,17 @@
1616

1717
package org.springframework.graphql.execution;
1818

19-
import graphql.GraphQLContext;
20-
import io.micrometer.context.ContextSnapshot;
2119
import io.micrometer.context.ContextSnapshotFactory;
22-
import reactor.util.context.Context;
23-
import reactor.util.context.ContextView;
24-
25-
import org.springframework.lang.Nullable;
2620

2721
/**
2822
* Helper to use a single {@link ContextSnapshotFactory} instance by saving and
2923
* obtaining it to and from Reactor and GraphQL contexts.
3024
*
3125
* @author Rossen Stoyanchev
3226
* @since 1.3.0
27+
* @deprecated since 1.3.5 in favor of {@link ContextPropagationHelper}.
3328
*/
34-
public abstract class ContextSnapshotFactoryHelper {
35-
36-
private static final ContextSnapshotFactory sharedInstance = ContextSnapshotFactory.builder().build();
37-
38-
private static final String CONTEXT_SNAPSHOT_FACTORY_KEY = ContextSnapshotFactoryHelper.class.getName() + ".KEY";
39-
40-
41-
/**
42-
* Select a {@code ContextSnapshotFactory} instance to use, either the one
43-
* passed in if it is not {@code null}, or a shared, static instance.
44-
* @param factory the candidate factory instance to use if not {@code null}
45-
* @return the instance to use
46-
*/
47-
public static ContextSnapshotFactory selectInstance(@Nullable ContextSnapshotFactory factory) {
48-
if (factory != null) {
49-
return factory;
50-
}
51-
return sharedInstance;
52-
}
53-
54-
/**
55-
* Save the {@code ContextSnapshotFactory} in the given {@link Context}.
56-
* @param factory the instance to save
57-
* @param context the context to save the instance to
58-
* @return a new context with the saved instance
59-
*/
60-
public static Context saveInstance(ContextSnapshotFactory factory, Context context) {
61-
return context.put(CONTEXT_SNAPSHOT_FACTORY_KEY, factory);
62-
}
63-
64-
/**
65-
* Save the {@code ContextSnapshotFactory} in the given {@link Context}.
66-
* @param factory the instance to save
67-
* @param context the context to save the instance to
68-
*/
69-
public static void saveInstance(ContextSnapshotFactory factory, GraphQLContext context) {
70-
context.put(CONTEXT_SNAPSHOT_FACTORY_KEY, factory);
71-
}
72-
73-
/**
74-
* Access the {@code ContextSnapshotFactory} from the given {@link ContextView}
75-
* or return a shared, static instance.
76-
* @param contextView the context where the instance is saved
77-
* @return the instance to use
78-
*/
79-
public static ContextSnapshotFactory getInstance(ContextView contextView) {
80-
ContextSnapshotFactory factory = contextView.getOrDefault(CONTEXT_SNAPSHOT_FACTORY_KEY, null);
81-
return selectInstance(factory);
82-
}
83-
84-
/**
85-
* Access the {@code ContextSnapshotFactory} from the given {@link GraphQLContext}
86-
* or return a shared, static instance.
87-
* @param context the context where the instance is saved
88-
* @return the instance to use
89-
*/
90-
public static ContextSnapshotFactory getInstance(GraphQLContext context) {
91-
ContextSnapshotFactory factory = context.get(CONTEXT_SNAPSHOT_FACTORY_KEY);
92-
return selectInstance(factory);
93-
}
94-
95-
/**
96-
* Shortcut to obtain the {@code ContextSnapshotFactory} instance, and to
97-
* capture from the given {@link ContextView}.
98-
* @param contextView the context to capture from
99-
* @return a snapshot from the capture
100-
*/
101-
public static ContextSnapshot captureFrom(ContextView contextView) {
102-
ContextSnapshotFactory factory = getInstance(contextView);
103-
return selectInstance(factory).captureFrom(contextView);
104-
}
105-
106-
/**
107-
* Shortcut to obtain the {@code ContextSnapshotFactory} instance, and to
108-
* capture from the given {@link GraphQLContext}.
109-
* @param context the context to capture from
110-
* @return a snapshot from the capture
111-
*/
112-
public static ContextSnapshot captureFrom(GraphQLContext context) {
113-
ContextSnapshotFactory factory = getInstance(context);
114-
return selectInstance(factory).captureFrom(context);
115-
}
29+
@Deprecated(since = "1.3.5", forRemoval = true)
30+
public abstract class ContextSnapshotFactoryHelper extends ContextPropagationHelper {
11631

11732
}

Diff for: spring-graphql/src/main/java/org/springframework/graphql/execution/DataFetcherExceptionResolverAdapter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public final Mono<List<GraphQLError>> resolveException(Throwable ex, DataFetchin
9696
private List<GraphQLError> resolveInternal(Throwable exception, DataFetchingEnvironment env) {
9797
try {
9898
return (this.threadLocalContextAware) ?
99-
ContextSnapshotFactoryHelper.captureFrom(env.getGraphQlContext())
99+
ContextPropagationHelper.captureFrom(env.getGraphQlContext())
100100
.wrap(() -> resolveToMultipleErrors(exception, env))
101101
.call() :
102102
resolveToMultipleErrors(exception, env);

0 commit comments

Comments
 (0)