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}