001package org.consensusj.jsonrpc; 002 003import javax.net.ssl.SSLContext; 004import java.io.IOException; 005import java.lang.reflect.Type; 006import java.net.URI; 007import java.security.NoSuchAlgorithmException; 008import java.util.Base64; 009import java.util.concurrent.CompletableFuture; 010import java.util.concurrent.ExecutionException; 011import java.util.function.Supplier; 012 013/** 014 * Defines the interface for a network-layer implementation of a JSON-RPC client. 015 */ 016public interface JsonRpcTransport<T extends Type> extends AsyncSupport { 017 /** 018 * Get the URI of the remote server 019 * @return URI of remote server 020 */ 021 URI getServerURI(); 022 023 /** 024 * Send a {@link JsonRpcRequest} for a {@link JsonRpcResponse} 025 * <p>Synchronous subclasses should override this method to prevent {@link CompletableFuture#supplyAsync(Supplier)} from 026 * being called twice when {@link AsyncSupport} is being used. Eventually we'll migrate more of the codebase to native 027 * async, and then we won't have to worry about calling {@code supplyAsync} twice. 028 * @param <R> Type of result object 029 * @param request The request to send 030 * @param responseType The response type expected (used by Jackson for conversion) 031 * @return A JSON RPC Response with `result` of type `R` 032 * @throws IOException network error 033 * @throws JsonRpcStatusException JSON RPC status error 034 */ 035 default <R> JsonRpcResponse<R> sendRequestForResponse(JsonRpcRequest request, T responseType) throws IOException, JsonRpcStatusException { 036 return syncGet(sendRequestForResponseAsync(request, responseType)); 037 } 038 039 /** 040 * Send a {@link JsonRpcRequest} for a {@link JsonRpcResponse} asynchronously. 041 * @param <R> Type of result object 042 * @param request The request to send 043 * @param responseType The response type expected (used by Jackson for conversion) 044 * @return A future JSON RPC Response with `result` of type `R` 045 */ 046 <R> CompletableFuture<JsonRpcResponse<R>> sendRequestForResponseAsync(JsonRpcRequest request, T responseType); 047 048 /** 049 * Synchronously complete a JSON-RPC request by calling {@link CompletableFuture#get()}, unwrapping nested 050 * {@link JsonRpcException} or {@link IOException} from {@link ExecutionException}. 051 * @param future The {@code CompletableFuture} (result of JSON-RPC request) to unwrap 052 * @return A JSON-RPC result 053 * @param <R> The expected result type 054 * @throws IOException If {@link CompletableFuture#get} threw {@code ExecutionException} caused by {@code IOException} 055 * @throws JsonRpcException If {@link CompletableFuture#get} threw {@code ExecutionException} caused by {@code JsonRpcException} 056 * @throws RuntimeException If {@link CompletableFuture#get} threw {@link InterruptedException} or other {@link ExecutionException}. 057 */ 058 default <R> R syncGet(CompletableFuture<R> future) throws IOException, JsonRpcException { 059 try { 060 return future.get(); 061 } catch (InterruptedException ie) { 062 throw new RuntimeException(ie); 063 } catch (ExecutionException ee) { 064 Throwable cause = ee.getCause(); 065 if (cause instanceof JsonRpcException) { 066 throw (JsonRpcException) cause; 067 } else if (cause instanceof IOException) { 068 throw (IOException) cause; 069 } else { 070 throw new RuntimeException(ee); 071 } 072 } 073 } 074 075 /** 076 * Encode username password as Base64 for basic authentication 077 * <p> 078 * We're using {@link java.util.Base64}, which requires Android 8.0 or later. 079 * 080 * @param authString An authorization string of the form `username:password` 081 * @return A compliant Base64 encoding of `authString` 082 */ 083 static String base64Encode(String authString) { 084 return Base64.getEncoder().encodeToString(authString.getBytes()).trim(); 085 } 086 087 /** 088 * 089 * Return the default {@link SSLContext} without declaring a checked exception 090 * @return The default {@code SSLContext} 091 */ 092 static SSLContext getDefaultSSLContext() { 093 try { 094 return SSLContext.getDefault(); 095 } catch (NoSuchAlgorithmException e) { 096 throw new RuntimeException(e); 097 } 098 } 099}