Skip to content

Commit f680892

Browse files
committed
Switch to io.micrometer:context-propagation library
See gh-459
1 parent f583eb3 commit f680892

32 files changed

+415
-473
lines changed

build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ configure(moduleProjects) {
5959
dependencyManagement {
6060
imports {
6161
mavenBom "com.fasterxml.jackson:jackson-bom:2.13.3"
62-
mavenBom "io.projectreactor:reactor-bom:2022.0.0-M4"
62+
mavenBom "io.projectreactor:reactor-bom:2022.0.0-M5"
6363
mavenBom "org.springframework:spring-framework-bom:6.0.0-M5"
6464
mavenBom "org.springframework.data:spring-data-bom:2022.0.0-M5"
6565
mavenBom "org.springframework.security:spring-security-bom:6.0.0-M6"
@@ -76,6 +76,7 @@ configure(moduleProjects) {
7676
dependency "jakarta.persistence:jakarta.persistence-api:3.0.0"
7777
dependency "jakarta.servlet:jakarta.servlet-api:5.0.0"
7878
dependency "com.google.code.findbugs:jsr305:3.0.2"
79+
dependency "io.micrometer:context-propagation:1.0.0-SNAPSHOT"
7980
dependency "org.assertj:assertj-core:3.23.1"
8081
dependency "com.jayway.jsonpath:json-path:2.7.0"
8182
dependency "org.skyscreamer:jsonassert:1.5.0"

spring-graphql-test/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
testImplementation 'io.projectreactor:reactor-test'
3232
testImplementation 'io.projectreactor.netty:reactor-netty'
3333
testImplementation 'io.rsocket:rsocket-transport-local'
34+
testImplementation 'io.micrometer:context-propagation'
3435
testImplementation 'com.squareup.okhttp3:mockwebserver:3.14.9'
3536
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
3637

spring-graphql/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies {
1212
compileOnly 'org.springframework:spring-webmvc'
1313
compileOnly 'org.springframework:spring-websocket'
1414
compileOnly 'org.springframework:spring-messaging'
15+
compileOnly 'io.micrometer:context-propagation'
1516
compileOnly 'jakarta.servlet:jakarta.servlet-api'
1617
compileOnly 'jakarta.validation:jakarta.validation-api'
1718

@@ -41,6 +42,7 @@ dependencies {
4142
testImplementation 'org.springframework.data:spring-data-commons'
4243
testImplementation 'org.springframework.data:spring-data-keyvalue'
4344
testImplementation 'org.springframework.data:spring-data-jpa'
45+
testImplementation 'io.micrometer:context-propagation'
4446
testImplementation 'com.h2database:h2'
4547
testImplementation 'org.hibernate:hibernate-core-jakarta'
4648
testImplementation 'org.hibernate.validator:hibernate-validator'

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@
2626
import java.util.stream.Stream;
2727

2828
import graphql.GraphQLContext;
29+
import io.micrometer.context.ContextSnapshot;
2930
import reactor.core.publisher.Mono;
3031

3132
import org.springframework.core.CoroutinesUtils;
3233
import org.springframework.core.KotlinDetector;
33-
import org.springframework.graphql.execution.ReactorContextManager;
3434
import org.springframework.lang.Nullable;
3535
import org.springframework.util.Assert;
3636

@@ -114,7 +114,7 @@ private Object handleReturnValue(GraphQLContext graphQLContext, @Nullable Object
114114
return CompletableFuture.supplyAsync(
115115
() -> {
116116
try {
117-
return ReactorContextManager.invokeCallable((Callable<?>) result, graphQLContext);
117+
return ContextSnapshot.captureFrom(graphQLContext).wrap((Callable<?>) result).call();
118118
}
119119
catch (Exception ex) {
120120
throw new IllegalStateException(

spring-graphql/src/main/java/org/springframework/graphql/execution/CompositeThreadLocalAccessor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* @author Rossen Stoyanchev
2929
* @since 1.0.0
3030
*/
31+
@SuppressWarnings("deprecation")
3132
class CompositeThreadLocalAccessor implements ThreadLocalAccessor {
3233

3334
private final List<ThreadLocalAccessor> accessors;

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
import graphql.schema.GraphQLTypeVisitorStub;
3030
import graphql.util.TraversalControl;
3131
import graphql.util.TraverserContext;
32+
import io.micrometer.context.ContextSnapshot;
3233
import org.reactivestreams.Publisher;
3334
import reactor.core.publisher.Flux;
3435
import reactor.core.publisher.Mono;
35-
import reactor.util.context.ContextView;
3636

3737
import org.springframework.util.Assert;
3838

@@ -70,29 +70,23 @@ private ContextDataFetcherDecorator(
7070
@Override
7171
public Object get(DataFetchingEnvironment environment) throws Exception {
7272

73-
Object value = ReactorContextManager.invokeCallable(() ->
74-
this.delegate.get(environment), environment.getGraphQlContext());
75-
76-
ContextView contextView = ReactorContextManager.getReactorContext(environment.getGraphQlContext());
73+
ContextSnapshot snapshot = ContextSnapshot.captureFrom(environment.getGraphQlContext());
74+
Object value = snapshot.wrap(() -> this.delegate.get(environment)).call();
7775

7876
if (this.subscription) {
7977
Assert.state(value instanceof Publisher, "Expected Publisher for a subscription");
8078
Flux<?> flux = Flux.from((Publisher<?>) value).onErrorResume(exception ->
8179
this.subscriptionExceptionResolver.resolveException(exception)
8280
.flatMap(errors -> Mono.error(new SubscriptionPublisherException(errors, exception))));
83-
return (!contextView.isEmpty() ? flux.contextWrite(contextView) : flux);
81+
return flux.contextWrite(snapshot::updateContext);
8482
}
8583

8684
if (value instanceof Flux) {
8785
value = ((Flux<?>) value).collectList();
8886
}
8987

90-
if (value instanceof Mono) {
91-
Mono<?> valueMono = (Mono<?>) value;
92-
if (!contextView.isEmpty()) {
93-
valueMono = valueMono.contextWrite(contextView);
94-
}
95-
value = valueMono.toFuture();
88+
if (value instanceof Mono<?> valueMono) {
89+
value = valueMono.contextWrite(snapshot::updateContext).toFuture();
9690
}
9791

9892
return value;

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121

2222
import graphql.GraphQLError;
2323
import graphql.schema.DataFetchingEnvironment;
24+
import io.micrometer.context.ContextSnapshot;
25+
import io.micrometer.context.ThreadLocalAccessor;
26+
import org.apache.commons.logging.Log;
27+
import org.apache.commons.logging.LogFactory;
2428
import reactor.core.publisher.Mono;
25-
import reactor.util.context.ContextView;
2629

2730
import org.springframework.lang.Nullable;
2831

@@ -47,6 +50,8 @@
4750
*/
4851
public abstract class DataFetcherExceptionResolverAdapter implements DataFetcherExceptionResolver {
4952

53+
protected final Log logger = LogFactory.getLog(getClass());
54+
5055
private boolean threadLocalContextAware;
5156

5257

@@ -88,17 +93,18 @@ public final Mono<List<GraphQLError>> resolveException(Throwable ex, DataFetchin
8893
}
8994

9095
@Nullable
91-
private List<GraphQLError> resolveInternal(Throwable ex, DataFetchingEnvironment env) {
96+
private List<GraphQLError> resolveInternal(Throwable exception, DataFetchingEnvironment env) {
9297
if (!this.threadLocalContextAware) {
93-
return resolveToMultipleErrors(ex, env);
98+
return resolveToMultipleErrors(exception, env);
9499
}
95-
ContextView contextView = ReactorContextManager.getReactorContext(env.getGraphQlContext());
96100
try {
97-
ReactorContextManager.restoreThreadLocalValues(contextView);
98-
return resolveToMultipleErrors(ex, env);
101+
return ContextSnapshot.captureFrom(env.getGraphQlContext())
102+
.wrap(() -> resolveToMultipleErrors(exception, env))
103+
.call();
99104
}
100-
finally {
101-
ReactorContextManager.resetThreadLocalValues(contextView);
105+
catch (Exception ex2) {
106+
logger.warn("Failed to resolve " + exception, ex2);
107+
return null;
102108
}
103109
}
104110

spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import java.util.List;
2020
import java.util.Map;
2121
import java.util.Set;
22+
import java.util.concurrent.CompletableFuture;
2223
import java.util.concurrent.CompletionStage;
2324
import java.util.function.BiFunction;
2425
import java.util.function.Consumer;
2526

2627
import graphql.GraphQLContext;
28+
import io.micrometer.context.ContextSnapshot;
2729
import org.dataloader.BatchLoaderContextProvider;
2830
import org.dataloader.BatchLoaderEnvironment;
2931
import org.dataloader.BatchLoaderWithContext;
@@ -34,7 +36,6 @@
3436
import org.dataloader.MappedBatchLoaderWithContext;
3537
import reactor.core.publisher.Flux;
3638
import reactor.core.publisher.Mono;
37-
import reactor.util.context.ContextView;
3839

3940
import org.springframework.lang.Nullable;
4041
import org.springframework.util.Assert;
@@ -188,16 +189,20 @@ public DataLoaderOptions getOptionsOrDefault(
188189

189190
@Override
190191
public CompletionStage<List<V>> load(List<K> keys, BatchLoaderEnvironment environment) {
191-
ContextView contextView = ReactorContextManager.getReactorContext(environment.getContext());
192+
GraphQLContext graphQLContext = environment.getContext();
193+
ContextSnapshot snapshot = ContextSnapshot.captureFrom(graphQLContext);
192194
try {
193-
ReactorContextManager.restoreThreadLocalValues(contextView);
194-
return this.loader.apply(keys, environment).collectList().contextWrite(contextView).toFuture();
195+
return snapshot.wrap(() ->
196+
this.loader.apply(keys, environment)
197+
.collectList()
198+
.contextWrite(snapshot::updateContext)
199+
.toFuture())
200+
.call();
195201
}
196-
finally {
197-
ReactorContextManager.resetThreadLocalValues(contextView);
202+
catch (Exception ex) {
203+
return CompletableFuture.failedFuture(ex);
198204
}
199205
}
200-
201206
}
202207

203208

@@ -239,13 +244,17 @@ public DataLoaderOptions getOptionsOrDefault(
239244

240245
@Override
241246
public CompletionStage<Map<K, V>> load(Set<K> keys, BatchLoaderEnvironment environment) {
242-
ContextView contextView = ReactorContextManager.getReactorContext(environment.getContext());
247+
GraphQLContext graphQLContext = environment.getContext();
248+
ContextSnapshot snapshot = ContextSnapshot.captureFrom(graphQLContext);
243249
try {
244-
ReactorContextManager.restoreThreadLocalValues(contextView);
245-
return this.loader.apply(keys, environment).contextWrite(contextView).toFuture();
250+
return snapshot.wrap(() ->
251+
this.loader.apply(keys, environment)
252+
.contextWrite(snapshot::updateContext)
253+
.toFuture())
254+
.call();
246255
}
247-
finally {
248-
ReactorContextManager.resetThreadLocalValues(contextView);
256+
catch (Exception ex) {
257+
return CompletableFuture.failedFuture(ex);
249258
}
250259
}
251260

spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultExecutionGraphQlService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import graphql.GraphQL;
2525
import graphql.GraphQLContext;
2626
import graphql.execution.ExecutionIdProvider;
27+
import io.micrometer.context.ContextSnapshot;
2728
import org.dataloader.DataLoaderRegistry;
2829
import reactor.core.publisher.Mono;
2930

@@ -76,7 +77,7 @@ public final Mono<ExecutionGraphQlResponse> execute(ExecutionGraphQlRequest requ
7677
request.configureExecutionInput(RESET_EXECUTION_ID_CONFIGURER);
7778
}
7879
ExecutionInput executionInput = request.toExecutionInput();
79-
ReactorContextManager.setReactorContext(contextView, executionInput.getGraphQLContext());
80+
ContextSnapshot.captureFrom(contextView).updateContext(executionInput.getGraphQLContext());
8081
ExecutionInput updatedExecutionInput = registerDataLoaders(executionInput);
8182
return Mono.fromFuture(this.graphQlSource.graphQl().executeAsync(updatedExecutionInput))
8283
.map(result -> new DefaultExecutionGraphQlResponse(updatedExecutionInput, result));

spring-graphql/src/main/java/org/springframework/graphql/execution/ExceptionResolversExceptionHandler.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
import graphql.execution.DataFetcherExceptionHandlerResult;
2929
import graphql.execution.ExecutionId;
3030
import graphql.schema.DataFetchingEnvironment;
31+
import io.micrometer.context.ContextSnapshot;
3132
import org.apache.commons.logging.Log;
3233
import org.apache.commons.logging.LogFactory;
3334
import reactor.core.publisher.Flux;
3435
import reactor.core.publisher.Mono;
35-
import reactor.util.context.ContextView;
3636

3737
import org.springframework.util.Assert;
3838

@@ -70,6 +70,7 @@ public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandler
7070
public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters params) {
7171
Throwable exception = unwrapException(params);
7272
DataFetchingEnvironment env = params.getDataFetchingEnvironment();
73+
ContextSnapshot snapshot = ContextSnapshot.captureFrom(env.getGraphQlContext());
7374
try {
7475
return Flux.fromIterable(this.resolvers)
7576
.flatMap(resolver -> resolver.resolveException(exception, env))
@@ -78,10 +79,7 @@ public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(Data
7879
.doOnNext(result -> logResolvedException(exception, result))
7980
.onErrorResume(resolverEx -> Mono.just(handleResolverError(resolverEx, exception, env)))
8081
.switchIfEmpty(Mono.fromCallable(() -> createInternalError(exception, env)))
81-
.contextWrite((context) -> {
82-
ContextView contextView = ReactorContextManager.getReactorContext(env.getGraphQlContext());
83-
return (contextView.isEmpty() ? context : context.putAll(contextView));
84-
})
82+
.contextWrite(snapshot::updateContext)
8583
.toFuture();
8684
}
8785
catch (Exception resolverEx) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2002-2022 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+
package org.springframework.graphql.execution;
17+
18+
19+
import java.util.Map;
20+
import java.util.function.Predicate;
21+
22+
import graphql.GraphQLContext;
23+
import io.micrometer.context.ContextAccessor;
24+
25+
/**
26+
* {@code ContextAccessor} that enables support for reading and writing values
27+
* to and from a {@link GraphQLContext}. This accessor is automatically
28+
* registered via {@link java.util.ServiceLoader}.
29+
*
30+
* @author Rossen Stoyanchev
31+
* @since 1.1.0
32+
*/
33+
public class GraphQlContextAccessor implements ContextAccessor<GraphQLContext, GraphQLContext> {
34+
35+
@Override
36+
public boolean canReadFrom(Class<?> contextType) {
37+
return GraphQLContext.class.equals(contextType);
38+
}
39+
40+
@Override
41+
public void readValues(GraphQLContext context, Predicate<Object> keyPredicate, Map<Object, Object> readValues) {
42+
context.stream().forEach(entry -> {
43+
if (keyPredicate.test(entry.getKey())) {
44+
readValues.put(entry.getKey(), entry.getValue());
45+
}
46+
});
47+
}
48+
49+
@Override
50+
public <T> T readValue(GraphQLContext context, Object key) {
51+
return context.get(key);
52+
}
53+
54+
@Override
55+
public boolean canWriteTo(Class<?> contextType) {
56+
return GraphQLContext.class.equals(contextType);
57+
}
58+
59+
@Override
60+
public GraphQLContext writeValues(Map<Object, Object> valuesToWrite, GraphQLContext targetContext) {
61+
return targetContext.putAll(valuesToWrite);
62+
}
63+
64+
}

0 commit comments

Comments
 (0)