Skip to content

Commit

Permalink
Simplify GraphQlClient hierarchy and implementations
Browse files Browse the repository at this point in the history
Add WebGraphQlClient as a common representation for a Web GraphQlClient
extension and its builders that has to be started through the HTTP or
WebSocket implementations.

Remove the common base builder for HTTP and WebSocket, with HTTP simply
delegating to the underlying WebClient builder For WebSocket, builder
state is exposed from the transport to simplify the mutation logic.

DefaultGraphQlClientBuilder is now abstract, leaving subclasses to
implement the build method.

See gh-10
  • Loading branch information
rstoyanchev committed Mar 7, 2022
1 parent 347f519 commit d7f5e44
Show file tree
Hide file tree
Showing 14 changed files with 367 additions and 325 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>Subclasses must implement {@link GraphQlClient#mutate()} to allow mutation
* of both {@code GraphQlClient} and {@code GraphQlTransport} configuration.
* <p>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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>Intended for use as a base class for builders that do assist with building
* the underlying transport. Such extension
* <p>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<B extends DefaultGraphQlClientBuilder<B>> implements GraphQlClient.Builder<B> {
public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClientBuilder<B>> implements GraphQlClient.Builder<B> {

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.
* <p>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();
}
Expand All @@ -88,10 +78,13 @@ private <T extends B> 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() {
Expand All @@ -105,16 +98,15 @@ 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<GraphQlClient.Builder<?>> getBuilderInitializer() {
return builder -> {
if (this.documentSource != null) {
builder.documentSource(documentSource);
}
};

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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
Expand All @@ -53,16 +49,17 @@ final class DefaultGraphQlClient implements GraphQlClient {

private final DocumentSource documentSource;

private final Consumer<Builder<?>> builderInitializer;
private final Consumer<GraphQlClient.Builder<?>> builderInitializer;


DefaultGraphQlClient(
GraphQlTransport transport, Configuration jsonPathConfig, DocumentSource documentSource,
Consumer<Builder<?>> builderInitializer) {
Consumer<GraphQlClient.Builder<?>> 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;
Expand All @@ -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<Builder> {

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<String> documentMono;
Expand Down
Loading

0 comments on commit d7f5e44

Please sign in to comment.