Skip to content

Commit

Permalink
AbstractRpcClient becomes DefaultRpcClient using JsonRpcTransport
Browse files Browse the repository at this point in the history
It is now a concrete class with (currently hardcoded) use of a JsonRpcTransport
implementation (currently the JavaNet one) to provide the network laye.

Still TODO:

* DefaultRpcClient needs a way of setting the transport implementation via a
constructor (and maybe a config POJO/record, too?)

* JsonRpcClientHttpUrlConnectionSpec could use some cleanup
  • Loading branch information
msgilligan committed Sep 26, 2023
1 parent 624bc50 commit 63a319d
Show file tree
Hide file tree
Showing 13 changed files with 84 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.consensusj.bitcoin.json.pojo.bitcore.AddressRequest;
import org.consensusj.bitcoin.json.pojo.bitcore.AddressUtxoInfo;
import org.consensusj.bitcoin.jsonrpc.internal.BitcoinClientThreadFactory;
import org.consensusj.jsonrpc.JsonRpcClientJavaNet;
import org.consensusj.jsonrpc.DefaultRpcClient;
import org.consensusj.jsonrpc.JsonRpcError;
import org.consensusj.jsonrpc.JsonRpcErrorException;
import org.consensusj.jsonrpc.JsonRpcException;
Expand Down Expand Up @@ -104,7 +104,7 @@
* <b>This is still a work-in-progress and the API will change.</b>
*
*/
public class BitcoinClient extends JsonRpcClientJavaNet implements ChainTipClient {
public class BitcoinClient extends DefaultRpcClient implements ChainTipClient {
private static final Logger log = LoggerFactory.getLogger(BitcoinClient.class);

private static final int THREAD_POOL_SIZE = 5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import org.consensusj.bitcoin.json.pojo.ChainTip;
import org.consensusj.bitcoin.json.pojo.TxOutSetInfo;
import org.consensusj.bitcoin.rx.jsonrpc.RxBitcoinClient;
import org.consensusj.jsonrpc.AbstractRpcClient;
import org.consensusj.jsonrpc.DefaultRpcClient;
import org.consensusj.jsonrpc.AsyncSupport;
import org.consensusj.jsonrpc.DefaultRpcClient;
import org.consensusj.rx.jsonrpc.RxJsonRpcClient;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
Expand Down Expand Up @@ -71,7 +72,7 @@ private void onNewBlock(ChainTip tip) {
}

// TODO: Ignoring all IOError is too broad
AbstractRpcClient.TransientErrorFilter errorFilter = AsyncSupport.TransientErrorFilter.of(
DefaultRpcClient.TransientErrorFilter errorFilter = AsyncSupport.TransientErrorFilter.of(
(t) -> t instanceof IOError, // Ignore if IOError
(t) -> log.warn("Ignoring transient error: ", t) // Log if ignored
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.consensusj.ethereum.jsonrpc;

import org.consensusj.jsonrpc.DefaultRpcClient;
import org.consensusj.jsonrpc.JsonRpcMessage;
import org.consensusj.jsonrpc.JsonRpcStatusException;
import org.consensusj.jsonrpc.JsonRpcClientHttpUrlConnection;

import java.io.IOException;
import java.math.BigInteger;
Expand All @@ -16,7 +16,7 @@
* See also:
* https://github.com/ethereum/go-ethereum/wiki/Management-APIs
*/
public class EthereumClient extends JsonRpcClientHttpUrlConnection {
public class EthereumClient extends DefaultRpcClient {
public static final URI DEFAULT_LOCALHOST = URI.create("http://localhost:8545");

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.consensusj.jsonrpc.AbstractRpcClient;
import org.consensusj.jsonrpc.DefaultRpcClient;
import org.consensusj.jsonrpc.CompositeTrustManager;
import org.consensusj.jsonrpc.JsonRpcClientJavaNet;
import org.consensusj.jsonrpc.JsonRpcMessage;
Expand Down Expand Up @@ -87,7 +87,7 @@ public void run(CommonsCLICall call) {
jsonRpcVersion = JsonRpcMessage.Version.V1;
}
SSLContext sslContext = sslContext(call.line);
AbstractRpcClient client = call.rpcClient(sslContext);
DefaultRpcClient client = call.rpcClient(sslContext);
CliParameterParser parser = new CliParameterParser(jsonRpcVersion, client.getMapper());
JsonRpcRequest request = parser.parse(args);
JsonRpcResponse<JsonNode> response;
Expand Down Expand Up @@ -163,7 +163,7 @@ public static class CommonsCLICall extends JsonRpcClientTool.Call {
protected final BaseJsonRpcTool rpcTool;
public final CommandLine line;
public final boolean verbose;
private AbstractRpcClient client;
private DefaultRpcClient client;

public CommonsCLICall(BaseJsonRpcTool parentTool, PrintWriter out, PrintWriter err, String[] args) {
super(out, err, args);
Expand Down Expand Up @@ -202,7 +202,7 @@ public CommonsCLICall(BaseJsonRpcTool parentTool, PrintWriter out, PrintWriter e
}

@Override
public AbstractRpcClient rpcClient(SSLContext sslContext) {
public DefaultRpcClient rpcClient(SSLContext sslContext) {
if (client == null) {
URI uri;
String urlString;
Expand All @@ -223,13 +223,13 @@ public AbstractRpcClient rpcClient(SSLContext sslContext) {
rpcUser = split[0];
rpcPassword = split[1];
}
client = new JsonRpcClientJavaNet(sslContext, rpcTool.jsonRpcVersion, uri, rpcUser, rpcPassword);
client = new DefaultRpcClient(sslContext, rpcTool.jsonRpcVersion, uri, rpcUser, rpcPassword);
}
return client;
}

@Override
public AbstractRpcClient rpcClient() {
public DefaultRpcClient rpcClient() {
SSLContext sslContext;
try {
sslContext = SSLContext.getDefault();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.consensusj.jsonrpc.cli;

import org.consensusj.jsonrpc.AbstractRpcClient;
import org.consensusj.jsonrpc.DefaultRpcClient;

import javax.net.ssl.SSLContext;
import java.io.OutputStreamWriter;
Expand Down Expand Up @@ -51,8 +51,8 @@ public Call(PrintWriter out, PrintWriter err, String[] args) {
this.args = args;
}

abstract public AbstractRpcClient rpcClient();
abstract public AbstractRpcClient rpcClient(SSLContext sslContext);
abstract public DefaultRpcClient rpcClient();
abstract public DefaultRpcClient rpcClient(SSLContext sslContext);
}

class ToolException extends RuntimeException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.consensusj.jsonrpc.groovy

import org.consensusj.jsonrpc.JsonRpcClientJavaNet
import org.consensusj.jsonrpc.DefaultRpcClient
import org.consensusj.jsonrpc.JsonRpcMessage

import com.fasterxml.jackson.databind.JavaType;
Expand All @@ -16,7 +16,7 @@ import com.fasterxml.jackson.databind.JavaType;
* This client and the {@link DynamicRpcMethodFallback} trait are provided for those looking for something simple,
* flexible, dynamic, and Groovy.
*/
class DynamicRpcClient extends JsonRpcClientJavaNet implements DynamicRpcMethodFallback<JavaType> {
class DynamicRpcClient extends DefaultRpcClient implements DynamicRpcMethodFallback<JavaType> {

DynamicRpcClient(URI server, String rpcuser, String rpcpassword) {
this(JsonRpcMessage.Version.V2, server, rpcuser, rpcpassword)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

/**
* Helper methods for creating asynchronous calls using {@link CompletableFuture}. Since the
* synchronous methods in {@link AbstractRpcClient} throw checked exceptions this interface
* synchronous methods in {@link DefaultRpcClient} throw checked exceptions this interface
* provides wrapper support to make it easier to convert them to async calls.
*/
public interface AsyncSupport {
Expand Down Expand Up @@ -129,7 +129,7 @@ static TransientErrorFilter none() {

/**
* Handler to transpose to a "future maybe". Use with {@link CompletableFuture#handle(BiFunction)}
* followed by {@code .thenCompose(Function.identity())} (or if JDK 12+ {@link CompletableFuture#exceptionallyCompose(Function)})
* followed by {@code .thenCompose(Function.identity())} (or if JDK 12+ {@code CompletableFuture#exceptionallyCompose(Function)})
* to swallow transient errors.
* @param result T
* @param t An error, possibly transient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,27 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.lang.reflect.Type;
import java.time.Duration;
import java.net.URI;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.function.Supplier;

// TODO: Rather than implementing transport (HttpUrlConnection vs. java.net.http) with subclasses use composition
// In other words, the constructor should take a transport implementation object.
// TODO: the constructor should take a JsonRpcTransport implementation object.
// We now have DefaultRpcClient which is currently hard-coded to use the JavaNet transport (1-line change + recompile to switch)

// We're overusing inheritance in this hierarchy. We are breaking Effective Java, Item 18: Favor composition over inheritance.
// We're using inheritance to configure:
// A. The set of JSON-RPC methods that are supported, e.g. `getblockcount()` (which creates potential conflicts with `send()` etc)
// B. The JSON mapping implementation. Which for now and the foreseeable future is Jackson only.
// C. The transport implementation.
//
// As we want to support both HttpUrlConnection vs. java.net.http while transitioning to java.net.http, we don't want to force
// subclasses like `BitcoinClient` to choose one or the other. So making (C) transport implementation a separate, composable object
// is the FIRST STEP. Later we can look at separating (A) and (B). My first thoughts on how to do this is:
// (1) Create a JsonRpcClientTransport interface.
// (2) Rename `AbstractRpcClient` to `DefaultJsonRpcClient`, make it concrete and have it take a JsonRpcClientTransport instance as
// a constructor parameter.
// (3) Create two implementations of `JsonRpcClientTransport` based upon `JsonRpcClientHttpUrlConnection` and `JsonRpcClientJavaNet`.
// (4) Optional. Maybe look at extracting a class with some common "mapper" functions between the two transport implementations
// TODO: Maybe look at extracting a class with some common "mapper" functions between the two transport implementations
//
// To separate (C) the easiest way is probably via a constructor parameter.
// The proper separation for (A) is probably a complete separation. There should be no required inheritance to implement
// a client with a set of methods. Internally the client would have a transport and a mapper and those could optionally be made available
// via some accessor methods if the client application deems necessary.
Expand All @@ -42,22 +37,21 @@
// (1) Map Java method name and parameters to a JSON-RPC request (either map to set of Java Objects _or_ all the way to JSON)
// (2) Map from received JSON-RPC response to JsonRpcResponse<RSLT> -- this response mapper function is configured as part of making the request.
//
// The SECOND STEP is to abstract the specifics of Jackson from the (two) transport implementations. Basically methods/functions to
// To abstract the specifics of Jackson from the (two) transport implementations. Basically methods/functions to
// map from request to string/stream and to map from string/stream to response. The java.net.http implementation has already defined
// some functional interfaces for this, so coming up with an interface that both the java.net.http implementation and the HttpUrlConnection
// implementation can use will lead to this "SECOND STEP"
//
// Update: Now that JsonRpcClient is a generic with <T extends Type>, we have loosened the Jackson coupling somewhat. The sendRequestForResponse
// and sendRequestForResponseAsync methods from JsonRpcClient have been moved to the JsonRpcTransport class which the JavaNet and HttpUrlConnection
// flavors implement. The AbstractRpcClient constructor should be passed an instance of either transport class and forward methods calls for
// sendRequestForResponseAsync (and sendRequestForResponse ?) to the transport.
// Now that JsonRpcClient is a generic with <T extends Type>, we have loosened the Jackson coupling somewhat.
//
/**
* Abstract Base class for a strongly-typed, Jackson-based JSON-RPC client. This abstract class implements the constructors, static fields, and
* getters, but leaves the core {@code sendRequestForResponse} method as {@code abstract} to be implemented by subclasses
* allowing implementation with alternative HTTP client libraries.
* A strongly-typed, Jackson-based JSON-RPC client. {@link JsonRpcClient} provides many convenience send `default` methods in a
* JSON-library-independent way. DefaultRpcClient provides the needed implementation support for Jackson. This class implements
* the constructors, static fields, and getters, but delegates the core
* {@link JsonRpcTransport#sendRequestForResponseAsync(JsonRpcRequest, Type)} method to a {@link JsonRpcTransport} implementation component.
*/
public abstract class AbstractRpcClient implements JsonRpcClient<JavaType> {
private static final Logger log = LoggerFactory.getLogger(AbstractRpcClient.class);
public class DefaultRpcClient implements JsonRpcClient<JavaType> {
private static final Logger log = LoggerFactory.getLogger(DefaultRpcClient.class);
/**
* The default JSON-RPC version in JsonRpcRequest is now '2.0', but since most
* requests are created inside {@code RpcClient} subclasses, we'll continue to default
Expand All @@ -68,20 +62,39 @@ public abstract class AbstractRpcClient implements JsonRpcClient<JavaType> {
protected final JsonRpcMessage.Version jsonRpcVersion;
protected final ObjectMapper mapper;
private final JavaType defaultType;
private final JsonRpcTransport<JavaType> transport;

public AbstractRpcClient(JsonRpcMessage.Version jsonRpcVersion) {
public DefaultRpcClient(JsonRpcMessage.Version jsonRpcVersion, URI server, final String rpcUser, final String rpcPassword) {
this(JsonRpcTransport.getDefaultSSLContext(), jsonRpcVersion, server, rpcUser, rpcPassword);
}
public DefaultRpcClient(SSLContext sslContext, JsonRpcMessage.Version jsonRpcVersion, URI server, final String rpcUser, final String rpcPassword) {
this.jsonRpcVersion = jsonRpcVersion;
mapper = new ObjectMapper();
// TODO: Provide external API to configure FAIL_ON_UNKNOWN_PROPERTIES
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
defaultType = mapper.getTypeFactory().constructType(Object.class);
// TODO: Right now you can manually switch between JsonRpcClientJavaNet and JsonRpcClientHttpUrlConnection by
// commenting out one line or the other and recompiling. There needs to be a new constructor which will
// allow the default (JsonRpcClientJavaNet) to be overridden.
transport = new JsonRpcClientJavaNet(mapper, sslContext, server, rpcUser, rpcPassword);
//transport = new JsonRpcClientHttpUrlConnection(mapper, sslContext, server, rpcUser, rpcPassword);
}

@Override
public JsonRpcMessage.Version getJsonRpcVersion() {
return jsonRpcVersion;
}

@Override
public URI getServerURI() {
return transport.getServerURI();
}

@Override
public <R> CompletableFuture<JsonRpcResponse<R>> sendRequestForResponseAsync(JsonRpcRequest request, JavaType responseType) {
return transport.sendRequestForResponseAsync(request, responseType);
}

/**
* Convenience method for requesting an asynchronous response with a {@link JsonNode} for the result.
* @param request The request to send
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -29,35 +30,24 @@
* using intermediate `Map` or `JsonNode` types.
*
*/
public class JsonRpcClientHttpUrlConnection extends AbstractRpcClient {
public class JsonRpcClientHttpUrlConnection implements JsonRpcTransport<JavaType> {
private static final Logger log = LoggerFactory.getLogger(JsonRpcClientHttpUrlConnection.class);
private final ObjectMapper mapper;
private final URI serverURI;
private final String username;
private final String password;
private static final String UTF8 = StandardCharsets.UTF_8.name();
private final SSLSocketFactory sslSocketFactory;

public JsonRpcClientHttpUrlConnection(SSLContext sslContext, JsonRpcMessage.Version jsonRpcVersion, URI server, final String rpcUser, final String rpcPassword) {
super(jsonRpcVersion);
public JsonRpcClientHttpUrlConnection(ObjectMapper mapper, SSLContext sslContext, URI server, final String rpcUser, final String rpcPassword) {
this.mapper = mapper;
this.sslSocketFactory = sslContext.getSocketFactory();
log.debug("Constructing JSON-RPC client for: {}", server);
this.serverURI = server;
this.username = rpcUser;
this.password = rpcPassword;
}

/**
* Construct a JSON-RPC client from URI, username, and password
*
* @param jsonRpcVersion version for {@code jsonrpc} field in messages
* @param server server URI should not contain username/password
* @param rpcUser username for the RPC HTTP connection
* @param rpcPassword password for the RPC HTTP connection
*/
public JsonRpcClientHttpUrlConnection(JsonRpcMessage.Version jsonRpcVersion, URI server, final String rpcUser, final String rpcPassword) {
this(JsonRpcTransport.getDefaultSSLContext(), jsonRpcVersion, server, rpcUser, rpcPassword);
}

/**
* Get the URI of the server this client connects to
* @return Server URI
Expand Down Expand Up @@ -182,4 +172,9 @@ private HttpURLConnection openConnection() throws IOException {

return connection;
}

private JavaType responseTypeFor(Class<?> resultType) {
return mapper.getTypeFactory().
constructParametricType(JsonRpcResponse.class, resultType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -21,22 +22,24 @@
/**
* Incubating JSON-RPC client using {@link java.net.http.HttpClient}
*/
public class JsonRpcClientJavaNet extends AbstractRpcClient {
public class JsonRpcClientJavaNet implements JsonRpcTransport<JavaType> {
private static final Logger log = LoggerFactory.getLogger(JsonRpcClientJavaNet.class);

private final ObjectMapper mapper;
private final URI serverURI;
private final String username;
private final String password;
private final HttpClient client;
private static final String UTF8 = StandardCharsets.UTF_8.name();


public JsonRpcClientJavaNet(JsonRpcMessage.Version jsonRpcVersion, URI server, final String rpcUser, final String rpcPassword) {
this(JsonRpcTransport.getDefaultSSLContext(), jsonRpcVersion, server, rpcUser, rpcPassword);
public JsonRpcClientJavaNet(ObjectMapper mapper, URI server, final String rpcUser, final String rpcPassword) {
this(mapper, JsonRpcTransport.getDefaultSSLContext(), server, rpcUser, rpcPassword);
}

public JsonRpcClientJavaNet(SSLContext sslContext, JsonRpcMessage.Version jsonRpcVersion, URI server, final String rpcUser, final String rpcPassword) {
super(jsonRpcVersion);
public JsonRpcClientJavaNet(ObjectMapper mapper, SSLContext sslContext, URI server, final String rpcUser, final String rpcPassword) {
log.debug("Constructing JSON-RPC client for: {}", server);
this.mapper = mapper;
this.serverURI = server;
this.username = rpcUser;
this.password = rpcPassword;
Expand All @@ -46,10 +49,6 @@ public JsonRpcClientJavaNet(SSLContext sslContext, JsonRpcMessage.Version jsonRp
.build();
}

public JsonRpcClientJavaNet(URI server, final String rpcUser, final String rpcPassword) {
this(JsonRpcMessage.Version.V2, server, rpcUser, rpcPassword);
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -219,4 +218,9 @@ private void log(String s, Throwable t) {
log.warn("exception: ", t);
}
}

private JavaType responseTypeFor(Class<?> resultType) {
return mapper.getTypeFactory().
constructParametricType(JsonRpcResponse.class, resultType);
}
}
Loading

0 comments on commit 63a319d

Please sign in to comment.