Skip to content

Commit 5a73e05

Browse files
committed
Add transport specific GraphQlClient extensions
Add HttpGraphQlClient and WebSocketGraphQlClient extensions along with a builder hierarchy that combines GraphQlClient and transport specific configuration. See gh-10
1 parent 328d5c2 commit 5a73e05

File tree

10 files changed

+975
-63
lines changed

10 files changed

+975
-63
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
17+
package org.springframework.graphql.client;
18+
19+
20+
import org.springframework.util.Assert;
21+
22+
/**
23+
* Base class for extensions of {@link GraphQlClient} that mainly assist with
24+
* building the underlying transport, but otherwise delegate to the default
25+
* {@link GraphQlClient} implementation for actual request execution.
26+
*
27+
* <p>Subclasses must implement {@link GraphQlClient#mutate()} to allow mutation
28+
* of both {@code GraphQlClient} and {@code GraphQlTransport} configuration.
29+
*
30+
* @author Rossen Stoyanchev
31+
* @since 1.0.0
32+
*/
33+
public abstract class AbstractDelegatingGraphQlClient implements GraphQlClient {
34+
35+
private final GraphQlClient graphQlClient;
36+
37+
38+
protected AbstractDelegatingGraphQlClient(GraphQlClient graphQlClient) {
39+
Assert.notNull(graphQlClient, "GraphQlClient is required");
40+
this.graphQlClient = graphQlClient;
41+
}
42+
43+
44+
public GraphQlClient.RequestSpec document(String document) {
45+
return this.graphQlClient.document(document);
46+
}
47+
48+
public GraphQlClient.RequestSpec documentName(String name) {
49+
return this.graphQlClient.documentName(name);
50+
}
51+
52+
}

spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.LinkedHashMap;
1919
import java.util.List;
2020
import java.util.Map;
21+
import java.util.function.Consumer;
2122

2223
import com.jayway.jsonpath.Configuration;
2324
import com.jayway.jsonpath.DocumentContext;
@@ -35,7 +36,11 @@
3536
import org.springframework.util.StringUtils;
3637

3738
/**
38-
* Default implementation of {@link GraphQlClient}.
39+
* Default {@link GraphQlClient} implementation with the logic to initialize
40+
* requests and handle responses, and delegates to a {@link GraphQlTransport}
41+
* for actual request execution.
42+
*
43+
* <p>This class is final but works with any transport.
3944
*
4045
* @author Rossen Stoyanchev
4146
* @since 1.0.0
@@ -48,9 +53,12 @@ final class DefaultGraphQlClient implements GraphQlClient {
4853

4954
private final DocumentSource documentSource;
5055

56+
private final Consumer<Builder<?>> builderInitializer;
57+
5158

5259
DefaultGraphQlClient(
53-
GraphQlTransport transport, Configuration jsonPathConfig, DocumentSource documentSource) {
60+
GraphQlTransport transport, Configuration jsonPathConfig, DocumentSource documentSource,
61+
Consumer<Builder<?>> builderInitializer) {
5462

5563
Assert.notNull(transport, "GraphQlTransport is required");
5664
Assert.notNull(jsonPathConfig, "Configuration is required");
@@ -59,15 +67,10 @@ final class DefaultGraphQlClient implements GraphQlClient {
5967
this.transport = transport;
6068
this.jsonPathConfig = jsonPathConfig;
6169
this.documentSource = documentSource;
70+
this.builderInitializer = builderInitializer;
6271
}
6372

6473

65-
@SuppressWarnings("unchecked")
66-
@Override
67-
public <T extends GraphQlTransport> T getTransport(Class<T> requiredType) {
68-
return (requiredType.isInstance(this.transport) ? (T) this.transport : null);
69-
}
70-
7174
@Override
7275
public RequestSpec document(String document) {
7376
return new DefaultRequestSpec(Mono.just(document), this.transport, this.jsonPathConfig);
@@ -79,6 +82,13 @@ public RequestSpec documentName(String name) {
7982
return new DefaultRequestSpec(document, this.transport, this.jsonPathConfig);
8083
}
8184

85+
@Override
86+
public Builder<?> mutate() {
87+
DefaultGraphQlClientBuilder<?> builder = new DefaultGraphQlClientBuilder<>(this.transport);
88+
this.builderInitializer.accept(builder);
89+
return builder;
90+
}
91+
8292

8393
private static final class DefaultRequestSpec implements RequestSpec {
8494

@@ -112,6 +122,12 @@ public DefaultRequestSpec variable(String name, Object value) {
112122
return this;
113123
}
114124

125+
@Override
126+
public RequestSpec variables(Map<String, Object> variables) {
127+
this.variables.putAll(variables);
128+
return this;
129+
}
130+
115131
@Override
116132
public Mono<ResponseSpec> execute() {
117133
return getRequestMono()
@@ -136,11 +152,14 @@ private Mono<GraphQlRequest> getRequestMono() {
136152

137153
private static class DefaultResponseSpec implements ResponseSpec {
138154

155+
private final ExecutionResult result;
156+
139157
private final DocumentContext jsonPathDocument;
140158

141159
private final List<GraphQLError> errors;
142160

143161
private DefaultResponseSpec(ExecutionResult result, Configuration jsonPathConfig) {
162+
this.result = result;
144163
this.jsonPathDocument = JsonPath.parse(result.toSpecification(), jsonPathConfig);
145164
this.errors = result.getErrors();
146165
}
@@ -180,6 +199,11 @@ public List<GraphQLError> errors() {
180199
return this.errors;
181200
}
182201

202+
@Override
203+
public ExecutionResult andReturn() {
204+
return this.result;
205+
}
206+
183207
}
184208

185209
}

spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClientBuilder.java

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,34 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
1617
package org.springframework.graphql.client;
1718

19+
import java.util.function.Consumer;
20+
1821
import com.jayway.jsonpath.Configuration;
1922
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
2023
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
2124

25+
import org.springframework.graphql.support.CachingDocumentSource;
2226
import org.springframework.graphql.support.DocumentSource;
2327
import org.springframework.graphql.support.ResourceDocumentSource;
2428
import org.springframework.lang.Nullable;
2529
import org.springframework.util.Assert;
2630
import org.springframework.util.ClassUtils;
2731

32+
2833
/**
29-
* Default implementation of {@link GraphQlClient.Builder}.
34+
* Default {@link GraphQlClient.Builder} implementation that builds a
35+
* {@link GraphQlClient} for use with any transport.
36+
*
37+
* <p>Intended for use as a base class for builders that do assist with building
38+
* the underlying transport. Such extension
3039
*
3140
* @author Rossen Stoyanchev
3241
* @since 1.0.0
3342
*/
34-
class DefaultGraphQlClientBuilder implements GraphQlClient.Builder {
43+
public class DefaultGraphQlClientBuilder<B extends DefaultGraphQlClientBuilder<B>> implements GraphQlClient.Builder<B> {
3544

3645
private static final boolean jackson2Present;
3746

@@ -42,53 +51,70 @@ class DefaultGraphQlClientBuilder implements GraphQlClient.Builder {
4251
}
4352

4453

45-
private final GraphQlTransport transport;
46-
4754
@Nullable
48-
private Configuration jsonPathConfig;
55+
private GraphQlTransport transport;
4956

5057
@Nullable
5158
private DocumentSource documentSource;
5259

53-
60+
/**
61+
* Constructor with a given transport instance.
62+
*/
5463
DefaultGraphQlClientBuilder(GraphQlTransport transport) {
5564
Assert.notNull(transport, "GraphQlTransport is required");
5665
this.transport = transport;
5766
}
5867

68+
/**
69+
* Constructor for subclass builders that will call
70+
* {@link #transport(GraphQlTransport)} to set the transport instance
71+
* before {@link #build()}.
72+
*/
73+
DefaultGraphQlClientBuilder() {
74+
}
5975

60-
@Override
61-
public GraphQlClient.Builder jsonPathConfig(@Nullable Configuration config) {
62-
this.jsonPathConfig = config;
63-
return this;
76+
protected void transport(GraphQlTransport transport) {
77+
this.transport = transport;
6478
}
6579

6680
@Override
67-
public GraphQlClient.Builder documentSource(@Nullable DocumentSource contentLoader) {
81+
public B documentSource(@Nullable DocumentSource contentLoader) {
6882
this.documentSource = contentLoader;
69-
return this;
83+
return self();
84+
}
85+
86+
@SuppressWarnings("unchecked")
87+
private <T extends B> T self() {
88+
return (T) this;
7089
}
7190

7291
@Override
7392
public GraphQlClient build() {
74-
return new DefaultGraphQlClient(this.transport, initJsonPathConfig(), initRequestNameResolver());
93+
Assert.notNull(this.transport, "No GraphQlTransport. Has a subclass not initialized it?");
94+
return new DefaultGraphQlClient(this.transport, initJsonPathConfig(), initDocumentSource(), getBuilderInitializer());
7595
}
7696

7797
private Configuration initJsonPathConfig() {
78-
if (this.jsonPathConfig != null) {
79-
return this.jsonPathConfig;
80-
}
81-
else if (jackson2Present) {
82-
return Jackson2Configuration.create();
83-
}
84-
else {
85-
return Configuration.builder().build();
86-
}
98+
// Allow configuring JSONPath with codecs from transport subclasses
99+
return (jackson2Present ? Jackson2Configuration.create() : Configuration.builder().build());
87100
}
88101

89-
private DocumentSource initRequestNameResolver() {
102+
private DocumentSource initDocumentSource() {
90103
return (this.documentSource == null ?
91-
new ResourceDocumentSource() : this.documentSource);
104+
new CachingDocumentSource(new ResourceDocumentSource()) : this.documentSource);
105+
}
106+
107+
/**
108+
* Exposes a {@code Consumer} to subclasses to initialize new builder instances
109+
* from the configuration of "this" builder.
110+
*/
111+
protected Consumer<GraphQlClient.Builder<?>> getBuilderInitializer() {
112+
return builder -> {
113+
if (this.documentSource != null) {
114+
builder.documentSource(documentSource);
115+
}
116+
};
117+
92118
}
93119

94120

0 commit comments

Comments
 (0)