diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractDelegatingGraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractDelegatingGraphQlClient.java
index 527ea0de7..940354291 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractDelegatingGraphQlClient.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractDelegatingGraphQlClient.java
@@ -20,15 +20,16 @@
import org.springframework.util.Assert;
/**
- * Base class for extensions of {@link GraphQlClient} that mainly assist with
- * building the underlying transport, but otherwise delegate to the default
- * {@link GraphQlClient} implementation for actual request execution.
+ * Base class for {@link GraphQlClient} extensions that assist with building an
+ * underlying transport, but otherwise delegate to the default
+ * {@link GraphQlClient} implementation to execute requests.
*
- *
Subclasses must implement {@link GraphQlClient#mutate()} to allow mutation
- * of both {@code GraphQlClient} and {@code GraphQlTransport} configuration.
+ *
Subclasses must implement {@link GraphQlClient#mutate()} to return a
+ * builder for the specific {@code GraphQlClient} extension.
*
* @author Rossen Stoyanchev
* @since 1.0.0
+ * @see AbstractGraphQlClientBuilder
*/
public abstract class AbstractDelegatingGraphQlClient implements GraphQlClient {
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClientBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java
similarity index 65%
rename from spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClientBuilder.java
rename to spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java
index 36142938a..94e3a97e5 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClientBuilder.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java
@@ -31,54 +31,44 @@
/**
- * Default {@link GraphQlClient.Builder} implementation that builds a
- * {@link GraphQlClient} for use with any transport.
+ * Abstract, base class for transport specific {@link GraphQlClient.Builder}
+ * implementations.
*
- *
Intended for use as a base class for builders that do assist with building
- * the underlying transport. Such extension
+ *
Subclasses must implement {@link #build()} and call
+ * {@link #buildGraphQlClient(GraphQlTransport)} to obtain a default, transport
+ * agnostic {@code GraphQlClient}. A transport specific extension can then wrap
+ * this default tester by extending {@link AbstractDelegatingGraphQlClient}.
*
* @author Rossen Stoyanchev
* @since 1.0.0
+ * @see AbstractDelegatingGraphQlClient
*/
-public class DefaultGraphQlClientBuilder> implements GraphQlClient.Builder {
+public abstract class AbstractGraphQlClientBuilder> implements GraphQlClient.Builder {
private static final boolean jackson2Present;
static {
- ClassLoader classLoader = DefaultGraphQlClientBuilder.class.getClassLoader();
+ ClassLoader classLoader = AbstractGraphQlClientBuilder.class.getClassLoader();
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
}
- @Nullable
- private GraphQlTransport transport;
-
@Nullable
private DocumentSource documentSource;
- /**
- * Constructor with a given transport instance.
- */
- DefaultGraphQlClientBuilder(GraphQlTransport transport) {
- Assert.notNull(transport, "GraphQlTransport is required");
- this.transport = transport;
- }
/**
- * Constructor for subclass builders that will call
- * {@link #transport(GraphQlTransport)} to set the transport instance
- * before {@link #build()}.
+ * Default constructor for use from subclasses.
+ *
Subclasses must set the transport to use before {@link #build()} or
+ * during, by overriding {@link #build()}.
*/
- DefaultGraphQlClientBuilder() {
+ protected AbstractGraphQlClientBuilder() {
}
- protected void transport(GraphQlTransport transport) {
- this.transport = transport;
- }
@Override
- public B documentSource(@Nullable DocumentSource contentLoader) {
+ public B documentSource(DocumentSource contentLoader) {
this.documentSource = contentLoader;
return self();
}
@@ -88,10 +78,13 @@ private T self() {
return (T) this;
}
- @Override
- public GraphQlClient build() {
- Assert.notNull(this.transport, "No GraphQlTransport. Has a subclass not initialized it?");
- return new DefaultGraphQlClient(this.transport, initJsonPathConfig(), initDocumentSource(), getBuilderInitializer());
+ /**
+ * Subclasses call this from {@link #build()} to provide the transport and get
+ * the default {@code GraphQlClient to delegate to for request execution.
+ */
+ protected GraphQlClient buildGraphQlClient(GraphQlTransport transport) {
+ Assert.notNull(transport, "GraphQlTransport is required");
+ return new DefaultGraphQlClient(transport, initJsonPathConfig(), initDocumentSource(), getBuilderInitializer());
}
private Configuration initJsonPathConfig() {
@@ -105,8 +98,8 @@ private DocumentSource initDocumentSource() {
}
/**
- * Exposes a {@code Consumer} to subclasses to initialize new builder instances
- * from the configuration of "this" builder.
+ * Subclasses call this from {@link #build()} to obtain a {@code Consumer} to
+ * initialize new builder instances with, based on "this" builder.
*/
protected Consumer> getBuilderInitializer() {
return builder -> {
@@ -114,7 +107,6 @@ protected Consumer> getBuilderInitializer() {
builder.documentSource(documentSource);
}
};
-
}
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java
index 984c08eb3..efb53847b 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultGraphQlClient.java
@@ -36,11 +36,7 @@
import org.springframework.util.StringUtils;
/**
- * Default {@link GraphQlClient} implementation with the logic to initialize
- * requests and handle responses, and delegates to a {@link GraphQlTransport}
- * for actual request execution.
- *
- * This class is final but works with any transport.
+ * Default, final {@link GraphQlClient} implementation for use with any transport.
*
* @author Rossen Stoyanchev
* @since 1.0.0
@@ -53,16 +49,17 @@ final class DefaultGraphQlClient implements GraphQlClient {
private final DocumentSource documentSource;
- private final Consumer> builderInitializer;
+ private final Consumer> builderInitializer;
DefaultGraphQlClient(
GraphQlTransport transport, Configuration jsonPathConfig, DocumentSource documentSource,
- Consumer> builderInitializer) {
+ Consumer> builderInitializer) {
Assert.notNull(transport, "GraphQlTransport is required");
- Assert.notNull(jsonPathConfig, "Configuration is required");
+ Assert.notNull(jsonPathConfig, "JSONPath Configuration is required");
Assert.notNull(documentSource, "DocumentSource is required");
+ Assert.notNull(documentSource, "`builderInitializer` is required");
this.transport = transport;
this.jsonPathConfig = jsonPathConfig;
@@ -83,13 +80,33 @@ public RequestSpec documentName(String name) {
}
@Override
- public Builder> mutate() {
- DefaultGraphQlClientBuilder> builder = new DefaultGraphQlClientBuilder<>(this.transport);
+ public Builder mutate() {
+ Builder builder = new Builder(this.transport);
this.builderInitializer.accept(builder);
return builder;
}
+ /**
+ * Default {@link GraphQlClient.Builder} with a given transport.
+ */
+ static final class Builder extends AbstractGraphQlClientBuilder {
+
+ private final GraphQlTransport transport;
+
+ Builder(GraphQlTransport transport) {
+ Assert.notNull(transport, "GraphQlTransport is required");
+ this.transport = transport;
+ }
+
+ @Override
+ public GraphQlClient build() {
+ return super.buildGraphQlClient(this.transport);
+ }
+
+ }
+
+
private static final class DefaultRequestSpec implements RequestSpec {
private final Mono documentMono;
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultHttpGraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultHttpGraphQlClient.java
index 13c634ad0..1bf52dcb8 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultHttpGraphQlClient.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultHttpGraphQlClient.java
@@ -17,188 +17,122 @@
package org.springframework.graphql.client;
import java.net.URI;
-import java.util.Arrays;
import java.util.function.Consumer;
-import java.util.function.Supplier;
import org.springframework.http.HttpHeaders;
-import org.springframework.http.codec.ClientCodecConfigurer;
-import org.springframework.lang.Nullable;
+import org.springframework.http.codec.CodecConfigurer;
+import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.util.DefaultUriBuilderFactory;
+import org.springframework.web.util.UriBuilderFactory;
+import org.springframework.web.util.UriComponentsBuilder;
/**
- * Default {@link HttpGraphQlClient} implementation.
+ * Default {@link HttpGraphQlClient} implementation that builds the underlying
+ * {@code HttpGraphQlTransport} to use.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
final class DefaultHttpGraphQlClient extends AbstractDelegatingGraphQlClient implements HttpGraphQlClient {
- private final Supplier mutateBuilder;
+ private final WebClient webClient;
+ private final Consumer> builderInitializer;
- DefaultHttpGraphQlClient(GraphQlClient graphQlClient, Supplier mutateBuilder) {
- super(graphQlClient);
- this.mutateBuilder = mutateBuilder;
- }
-
-
- public Builder mutate() {
- return this.mutateBuilder.get();
- }
-
-
- static class BaseBuilder> extends DefaultGraphQlClientBuilder
- implements HttpGraphQlClient.BaseBuilder {
-
- @Nullable
- private URI url;
- private final HttpHeaders headers = new HttpHeaders();
-
- @Nullable
- private Consumer codecConfigurerConsumer;
-
-
- @Override
- public B url(@Nullable String url) {
- this.url = (url != null ? URI.create(url) : null);
- return self();
- }
-
- @Override
- public B url(@Nullable URI url) {
- this.url = url;
- return self();
- }
+ DefaultHttpGraphQlClient(GraphQlClient graphQlClient, WebClient webClient,
+ Consumer> builderInitializer) {
- @Override
- public B header(String name, String... values) {
- Arrays.stream(values).forEach(value -> this.headers.add(name, value));
- return self();
- }
-
- @Override
- public B headers(Consumer headersConsumer) {
- headersConsumer.accept(this.headers);
- return self();
- }
-
- @Override
- public B codecConfigurer(Consumer codecConsumer) {
- this.codecConfigurerConsumer = codecConsumer;
- return self();
- }
-
- @Nullable
- protected URI getUrl() {
- return this.url;
- }
+ super(graphQlClient);
- protected HttpHeaders getHeaders() {
- return this.headers;
- }
+ Assert.notNull(webClient, "WebClient is required");
+ Assert.notNull(builderInitializer, "`builderInitializer` is required");
- @Nullable
- protected Consumer getCodecConfigurerConsumer() {
- return this.codecConfigurerConsumer;
- }
+ this.webClient = webClient;
+ this.builderInitializer = builderInitializer;
+ }
- @SuppressWarnings("unchecked")
- private T self() {
- return (T) this;
- }
-
- /**
- * Exposes a {@code Consumer} to subclasses to initialize new builder instances
- * from the configuration of "this" builder.
- */
- protected Consumer> getWebBuilderInitializer() {
- Consumer> parentInitializer = getBuilderInitializer();
- HttpHeaders headersCopy = new HttpHeaders();
- headersCopy.putAll(getHeaders());
- return builder -> {
- builder.url(getUrl()).headers(headers -> headers.putAll(headersCopy));
- if (getCodecConfigurerConsumer() != null) {
- builder.codecConfigurer(getCodecConfigurerConsumer());
- }
- parentInitializer.accept(builder);
- };
- }
+ public Builder mutate() {
+ Builder builder = new Builder(this.webClient);
+ this.builderInitializer.accept(builder);
+ return builder;
}
/**
- * Default {@link HttpGraphQlClient.Builder} implementation.
+ * Default {@link HttpGraphQlClient.Builder} implementation, simply wrapping
+ * and delegating to {@link WebClient.Builder}.
*/
- static final class Builder extends BaseBuilder implements HttpGraphQlClient.Builder {
-
- @Nullable
- private WebClient webClient;
-
- @Nullable
- private Consumer webClientConfigurers;
+ static final class Builder extends AbstractGraphQlClientBuilder implements HttpGraphQlClient.Builder {
+ private final WebClient.Builder webClientBuilder;
/**
* Constructor to start without a WebClient instance.
*/
Builder() {
+ this(WebClient.builder());
+ }
+
+ /**
+ * Constructor to start with a pre-configured {@code WebClient}.
+ */
+ Builder(WebClient client) {
+ this(client.mutate());
}
/**
* Constructor to start with a pre-configured {@code WebClient}.
*/
- Builder(WebClient webClient) {
- this.webClient = webClient;
+ Builder(WebClient.Builder clientBuilder) {
+ this.webClientBuilder = clientBuilder;
}
@Override
- public Builder webClient(Consumer configurer) {
- this.webClientConfigurers = (this.webClientConfigurers != null ? this.webClientConfigurers.andThen(configurer) : configurer);
+ public Builder url(String url) {
+ this.webClientBuilder.baseUrl(url);
return this;
}
@Override
- public HttpGraphQlClient build() {
-
- WebClient webClient = initWebClient();
- HttpGraphQlTransport transport = new HttpGraphQlTransport(webClient);
- transport(transport);
-
- GraphQlClient graphQlClient = super.build();
- return new DefaultHttpGraphQlClient(graphQlClient, initMutateBuilderFactory(webClient));
+ public Builder url(URI url) {
+ UriBuilderFactory factory = new DefaultUriBuilderFactory(UriComponentsBuilder.fromUri(url));
+ this.webClientBuilder.uriBuilderFactory(factory);
+ return this;
}
- private WebClient initWebClient() {
- WebClient.Builder builder = (this.webClient != null ? this.webClient.mutate() : WebClient.builder());
-
- if (getUrl() != null) {
- builder.baseUrl(getUrl().toASCIIString());
- }
-
- builder.defaultHeaders(headers -> headers.putAll(getHeaders()));
+ @Override
+ public Builder header(String name, String... values) {
+ this.webClientBuilder.defaultHeader(name, values);
+ return this;
+ }
- if (getCodecConfigurerConsumer() != null) {
- builder.codecs(getCodecConfigurerConsumer());
- }
+ @Override
+ public Builder headers(Consumer headersConsumer) {
+ this.webClientBuilder.defaultHeaders(headersConsumer);
+ return this;
+ }
- if (this.webClientConfigurers != null) {
- this.webClientConfigurers.accept(builder);
- }
+ @Override
+ public Builder codecConfigurer(Consumer codecsConsumer) {
+ this.webClientBuilder.codecs(codecsConsumer::accept);
+ return this;
+ }
- return builder.build();
+ @Override
+ public Builder webClient(Consumer configurer) {
+ configurer.accept(this.webClientBuilder);
+ return this;
}
- private Supplier initMutateBuilderFactory(WebClient webClient) {
- Consumer> parentInitializer = getWebBuilderInitializer();
- return () -> {
- Builder builder = new Builder(webClient);
- parentInitializer.accept(builder);
- return builder;
- };
+ @Override
+ public HttpGraphQlClient build() {
+ WebClient webClient = this.webClientBuilder.build();
+ GraphQlClient graphQlClient = super.buildGraphQlClient(new HttpGraphQlTransport(webClient));
+ return new DefaultHttpGraphQlClient(graphQlClient, webClient, getBuilderInitializer());
}
}
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultWebSocketGraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultWebSocketGraphQlClient.java
index 6fa7dff08..968197331 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultWebSocketGraphQlClient.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/client/DefaultWebSocketGraphQlClient.java
@@ -16,20 +16,23 @@
package org.springframework.graphql.client;
-import java.util.Map;
+import java.net.URI;
+import java.util.Arrays;
import java.util.function.Consumer;
-import java.util.function.Supplier;
import reactor.core.publisher.Mono;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.ClientCodecConfigurer;
-import org.springframework.lang.Nullable;
+import org.springframework.http.codec.CodecConfigurer;
import org.springframework.util.Assert;
import org.springframework.web.reactive.socket.client.WebSocketClient;
+import org.springframework.web.util.DefaultUriBuilderFactory;
/**
- * Default {@link WebSocketGraphQlClient} implementation.
+ * Default {@link WebSocketGraphQlClient} implementation that builds the underlying
+ * {@code WebSocketGraphQlTransport} to use.
*
* @author Rossen Stoyanchev
* @since 1.0.0
@@ -38,15 +41,19 @@ final class DefaultWebSocketGraphQlClient extends AbstractDelegatingGraphQlClien
private final WebSocketGraphQlTransport transport;
- private final Supplier mutateBuilderFactory;
+ private final Consumer> builderInitializer;
- DefaultWebSocketGraphQlClient(
- GraphQlClient delegate, WebSocketGraphQlTransport transport, Supplier mutateBuilderFactory) {
+ DefaultWebSocketGraphQlClient(GraphQlClient delegate, WebSocketGraphQlTransport transport,
+ Consumer> builderInitializer) {
super(delegate);
+
+ Assert.notNull(transport, "WebSocketGraphQlTransport is required");
+ Assert.notNull(builderInitializer, "`builderInitializer` is required");
+
this.transport = transport;
- this.mutateBuilderFactory = mutateBuilderFactory;
+ this.builderInitializer = builderInitializer;
}
@@ -62,72 +69,84 @@ public Mono stop() {
@Override
public Builder mutate() {
- return this.mutateBuilderFactory.get();
+ Builder builder = new Builder(this.transport);
+ this.builderInitializer.accept(builder);
+ return builder;
}
/**
* Default {@link WebSocketGraphQlClient.Builder} implementation.
*/
- static final class Builder extends DefaultHttpGraphQlClient.BaseBuilder
+ static final class Builder extends AbstractGraphQlClientBuilder
implements WebSocketGraphQlClient.Builder {
- private final WebSocketClient webSocketClient;
+ private URI url;
- @Nullable
- private Object initPayload;
+ private final HttpHeaders headers = new HttpHeaders();
- private Consumer