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}