001package org.consensusj.jsonrpc;
002
003import java.io.IOException;
004import java.lang.reflect.Type;
005import java.net.HttpURLConnection;
006import java.util.Arrays;
007import java.util.Collection;
008import java.util.List;
009import java.util.concurrent.CompletableFuture;
010
011/**
012 *  JSON-RPC client interface. This interface is independent of the JSON conversion library
013 *  (the default implementation uses Jackson) and HTTP client library (currently {@link HttpURLConnection}).
014 *  For historical reasons the interface is mostly synchronous, but {@link AsyncSupport} makes it easier
015 *  to add use of {@link java.util.concurrent.CompletableFuture} for special cases. In the future
016 *  this interface may change to natively asynchronous.
017 *  <p>
018 *  Both JSON-RPC 1.0 and JSON-RPC 2.0 are supported. Implementations should also be (via configuration, perhaps)
019 *  lenient enough to support Bitcoin Core and similar servers that don't follow the JSON-RPC specifications exactly.
020 * @see <a href="https://www.jsonrpc.org/specification_v1">JSON-RPC 1.0 Specification (2005)</a>
021 * @see <a href="https://www.jsonrpc.org/specification">JSON-RPC 2.0 Specification</a>
022 */
023public interface JsonRpcClient<T extends Type> extends JsonRpcTransport<T>, AutoCloseable {
024
025    /**
026     * Return the JSON-RPC version used by the implementation
027     *
028     * @return JSON-RPC version
029     */
030    JsonRpcMessage.Version getJsonRpcVersion();
031
032    /**
033     * Call an RPC method and return default object type.
034     * <p>
035     * Caller should cast returned object to the correct type.
036     * <p>
037     * Useful for:
038     * <ul>
039     * <li>Dynamically-dispatched JSON-RPC methods calls via Groovy subclasses</li>
040     * <li>Simple (not client-side validated) command line utilities</li>
041     * <li>Functional tests that need to send incorrect types to the server to test error handling</li>
042     * </ul>
043     *
044     * @param <R> Type of result object
045     * @param method JSON RPC method call to send
046     * @param params JSON RPC parameters as a `List`
047     * @return the 'response.result' field of the JSON RPC response cast to type R
048     * @throws IOException network error
049     * @throws JsonRpcStatusException JSON RPC status error
050     */
051    default <R> R send(String method, List<Object> params) throws IOException, JsonRpcStatusException {
052        return send(method, defaultType(), params);
053    }
054
055    default <R> CompletableFuture<R>  sendAsync(String method, List<Object> params) {
056        return sendAsync(method, defaultType(), params);
057    }
058
059    /**
060     * Call an RPC method and return default object type.
061     * <p>
062     * Convenience version that takes {@code params} as array/varargs.
063     *
064     * @param <R> Type of result object
065     * @param method JSON RPC method call to send
066     * @param params JSON RPC parameters as array or varargs
067     * @return the 'response.result' field of the JSON RPC response cast to type R
068     * @throws IOException network error
069     * @throws JsonRpcStatusException JSON RPC status error
070     */
071    default <R> R send(String method, Object... params) throws IOException, JsonRpcStatusException {
072        return send(method, Arrays.asList(params));
073    }
074
075    default <R> R send(String method, Class<R> resultType, Object... params) throws IOException, JsonRpcStatusException {
076        return send(method, resultType, Arrays.asList(params));
077    }
078
079    default <R> CompletableFuture<R> sendAsync(String method, Class<R> resultType, Object... params) {
080        return sendAsync(method, resultType, Arrays.asList(params));
081    }
082
083    /**
084     * JSON-RPC remote method call that returns 'response.result`
085     *
086     * @param <R> Type of result object
087     * @param method JSON RPC method call to send
088     * @param resultType desired result type as a Java class object
089     * @param params JSON RPC params
090     * @return the 'response.result' field of the JSON RPC response converted to type R
091     */
092    default <R> R send(String method, Class<R> resultType, List<Object> params) throws IOException, JsonRpcStatusException {
093        return syncGet(sendRequestForResultAsync(buildJsonRequest(method, params), typeForClass(resultType)));
094    }
095
096    default <R> CompletableFuture<R> sendAsync(String method, Class<R> resultType, List<Object> params) {
097        return sendRequestForResultAsync(buildJsonRequest(method, params), typeForClass(resultType));
098    }
099
100    default <R> CompletableFuture<R> sendAsync(String method, T resultType, List<Object> params) {
101        return sendRequestForResultAsync(buildJsonRequest(method, params), resultType);
102    }
103
104    /**
105     * JSON-RPC remote method call that returns {@code response.result}
106     *
107     * @param <R> Type of result object
108     * @param method JSON RPC method call to send
109     * @param resultType desired result type as a Jackson JavaType object
110     * @param params JSON RPC params
111     * @return the 'response.result' field of the JSON RPC response converted to type R
112     */
113    default <R> R send(String method, T resultType, List<Object> params) throws IOException, JsonRpcStatusException {
114        return syncGet(sendRequestForResultAsync(buildJsonRequest(method, params), resultType));
115    }
116
117    /**
118     * Varargs version
119     */
120    default <R> R send(String method, T resultType, Object... params) throws IOException, JsonRpcStatusException {
121        return syncGet(sendRequestForResultAsync(buildJsonRequest(method, params), resultType));
122    }
123
124    default <R> CompletableFuture<R> sendAsync(String method, T resultType, Object... params) {
125        return sendRequestForResultAsync(buildJsonRequest(method, params), resultType);
126    }
127
128    private <R> CompletableFuture<R> sendRequestForResultAsync(JsonRpcRequest request, T resultType) {
129        CompletableFuture<JsonRpcResponse<R>> responseFuture = sendRequestForResponseAsync(request, responseTypeFor(resultType));
130
131//        assert response != null;
132//        assert response.getJsonrpc() != null;
133//        assert response.getJsonrpc().equals("2.0");
134//        assert response.getId() != null;
135//        assert response.getId().equals(request.getId());
136
137        // TODO: Error case should probably complete with JsonRpcErrorException (not status exception with code 200)
138        return responseFuture.thenCompose(resp -> (resp.getError() == null || resp.getError().getCode() == 0)
139                ? CompletableFuture.completedFuture(resp.getResult())
140                : CompletableFuture.failedFuture(new JsonRpcStatusException(200, resp)) // If response code wasn't 200 we couldn't be here
141        );
142    }
143
144    /**
145     * Create a JsonRpcRequest from method and parameters
146     *
147     * @param method name of method to call
148     * @param params parameter Java objects
149     * @return A ready-to-send JsonRpcRequest
150     */
151    default JsonRpcRequest buildJsonRequest(String method, List<Object> params) {
152        return new JsonRpcRequest(getJsonRpcVersion(), method, params);
153    }
154
155    default JsonRpcRequest buildJsonRequest(String method, Object... params) {
156        return new JsonRpcRequest(getJsonRpcVersion(), method, Arrays.asList(params));
157    }
158
159    /**
160     * Default no-op implementation of {@link AutoCloseable#close()}. Classes should override when
161     * they have something they need to close properly.
162     *
163     * @throws IOException if something happens during close
164     */
165    @Override
166    default void close() throws Exception {
167    }
168
169    T defaultType();
170
171    T responseTypeFor(T resultType);
172    T responseTypeFor(Class<?> resultType);
173
174    T typeForClass(Class<?> clazz);
175
176    T collectionTypeForClasses(Class<? extends Collection> collectionClazz, Class<?> clazz);
177
178    T collectionTypeForClasses(Class<? extends Collection> collectionClazz, T itemType);
179}