Skip to content

Commit

Permalink
Add JDK11+ java.net.http Omniwallet client
Browse files Browse the repository at this point in the history
Related code refactoring and cleanup.
(Also an experimental Omniwallet Micronaut client is included)

The two JDK11+ subprojects will only build if JDK 11+ is installed,
the remaining subprojects build with JDK8+.
  • Loading branch information
msgilligan committed Mar 3, 2020
1 parent f59da16 commit beff2fd
Show file tree
Hide file tree
Showing 10 changed files with 908 additions and 307 deletions.
2 changes: 1 addition & 1 deletion gradle/groovydoc.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def groovydocSpec = {
includePrivate = false
link 'https://docs.oracle.com/javase/8/docs/api/', 'java.', 'org.xml.', ' javax.xml.', 'javax.sql.', 'javax.net.', 'org.w3c.'
link 'https://commons.apache.org/proper/commons-cli/javadocs/api-release/', 'org.apache.commons.cli.'
link 'https://bitcoinj.github.io/javadoc/0.14.7/', 'org.bitcoin.', 'org.bitcoinj.'
link 'https://bitcoinj.github.io/javadoc/0.15.6/', 'org.bitcoin.', 'org.bitcoinj.'
link 'https://consensusj.github.io/consensusj/apidoc/', 'com.msgilligan.bitcoinj.', 'org.consensusj.'

}
Expand Down
21 changes: 21 additions & 0 deletions omnij-rest-client-micronaut/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
sourceCompatibility = 11
targetCompatibility = 11

dependencies {
implementation project(':omnij-rest-client')
implementation project(':omnij-core')
implementation project(':omnij-jsonrpc')


implementation("io.micronaut:micronaut-http-client:1.3.2")

testImplementation "org.codehaus.groovy:groovy:3.0.1:indy"
testImplementation ("org.codehaus.groovy:groovy-json:3.0.1:indy") {
transitive = false
}
testCompile("org.spockframework:spock-core:2.0-M2-groovy-3.0") {
exclude module: "groovy-all"
}

testCompile project(':omnij-dsl')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package foundation.omni.rest.omniwallet.mn;


import foundation.omni.rest.omniwallet.json.RevisionInfo;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.uri.UriBuilder;
import io.reactivex.Maybe;

import java.io.IOException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static io.micronaut.http.HttpRequest.GET;

/**
* Experimental, proof-of-concept Micronaut-based client for Omniwallet
* (Use at your own risk, incomplete, etc.)
*/
public class OmniwalletMicronautClient {
final URL baseURL;
final RxHttpClient client;

public OmniwalletMicronautClient(URL baseURL) {
this.baseURL = baseURL;
this.client = RxHttpClient.create(baseURL);
}

//@Override
public Integer currentBlockHeight() throws InterruptedException, IOException {
Integer height;
try {
height = currentBlockHeightAsync().get();
} catch (ExecutionException ee) {
throw new IOException(ee);
}
return height;
}

//@Override
public CompletableFuture<Integer> currentBlockHeightAsync() {
return fromMaybe(revisionInfoAsync().map(RevisionInfo::getLastBlock));
}

private Maybe<RevisionInfo> revisionInfoAsync() {
String uri = UriBuilder.of("/v1/system/revision.json").toString();

return client.retrieve(GET(uri), RevisionInfo.class).firstElement();
}

public static <T> CompletableFuture<T> fromMaybe(Maybe<T> maybe) {
final CompletableFuture<T> future = new CompletableFuture<>();
maybe.subscribe(future::complete, future::completeExceptionally);
return future;
}
}
18 changes: 18 additions & 0 deletions omnij-rest-client-mjdk/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
sourceCompatibility = 11
targetCompatibility = 11

dependencies {
implementation project(':omnij-rest-client')
implementation project(':omnij-core')
implementation project(':omnij-jsonrpc')

testImplementation "org.codehaus.groovy:groovy:3.0.1:indy"
testImplementation ("org.codehaus.groovy:groovy-json:3.0.1:indy") {
transitive = false
}
testCompile("org.spockframework:spock-core:2.0-M2-groovy-3.0") {
exclude module: "groovy-all"
}

testCompile project(':omnij-dsl')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package foundation.omni.rest.omniwallet.mjdk;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import foundation.omni.CurrencyID;
import foundation.omni.rest.omniwallet.OmniwalletAbstractClient;
import foundation.omni.rest.omniwallet.json.AddressVerifyInfo;
import foundation.omni.rest.omniwallet.json.OmniwalletAddressBalance;
import foundation.omni.rest.omniwallet.json.OmniwalletClientModule;
import foundation.omni.rest.omniwallet.json.PropertyVerifyInfo;
import foundation.omni.rest.omniwallet.json.RevisionInfo;
import org.bitcoinj.core.Address;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
* OmniwalletAbstractClient implementation using JDK 11+ java.net.http HttpClient
*/
public class OmniwalletModernJDKClient extends OmniwalletAbstractClient {
private static final Logger log = LoggerFactory.getLogger(OmniwalletModernJDKClient.class);
final HttpClient client;
private final UncheckedObjectMapper objectMapper;

public OmniwalletModernJDKClient(URI baseURI) {
super(baseURI, true, false);
this.client = HttpClient.newHttpClient();
objectMapper = new UncheckedObjectMapper();
objectMapper.registerModule(new OmniwalletClientModule(netParams));
}

public Integer currentBlockHeight() {
try {
return currentBlockHeightAsync().get();
} catch (InterruptedException | ExecutionException ie) {
throw new RuntimeException(ie);
}
}


protected CompletableFuture<List<PropertyVerifyInfo>> verifyProperties() {
JavaType resultType = objectMapper.getTypeFactory().constructCollectionType(List.class, PropertyVerifyInfo.class);

HttpRequest request = HttpRequest
.newBuilder(baseURI.resolve("/v1/mastercoin_verify/properties"))
.header("Accept", "application/json")
.build();

return client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(s -> objectMapper.readValue(s, resultType));

}

@Override
public CompletableFuture<Integer> currentBlockHeightAsync() {
return revisionInfoAsync().thenApply(RevisionInfo::getLastBlock);
}

private CompletableFuture<RevisionInfo> revisionInfoAsync() {
HttpRequest request = HttpRequest
.newBuilder(baseURI.resolve("/v1/system/revision.json"))
.header("Accept", "application/json")
.build();

return client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(s -> objectMapper.readValue(s, RevisionInfo.class));

}


@Override
protected CompletableFuture<Map<Address, OmniwalletAddressBalance>> balanceMapForAddress(Address address) {
return balanceMapForAddresses(Collections.singletonList(address));
}

@Override
protected CompletableFuture<Map<Address, OmniwalletAddressBalance>> balanceMapForAddresses(List<Address> addresses) {
TypeReference<HashMap<Address, OmniwalletAddressBalance>> typeRef = new TypeReference<>() {};
String addressesFormEnc = formEncodeAddressList(addresses);
log.info("Addresses are: {}", addressesFormEnc);
HttpRequest request = null;
request = HttpRequest
.newBuilder(baseURI.resolve("/v2/address/addr/"))
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(addressesFormEnc))
.build();

return client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.whenComplete((s,e) -> log.info(s))
.thenApply(s -> objectMapper.readValue(s, typeRef));
}

@Override
protected CompletableFuture<List<AddressVerifyInfo>> verifyAddresses(CurrencyID currencyID) {
JavaType resultType = objectMapper.getTypeFactory().constructCollectionType(List.class, AddressVerifyInfo.class);

HttpRequest request = HttpRequest
.newBuilder(baseURI.resolve("/v1/mastercoin_verify/addresses?currency_id=" + currencyID.toString()))
.header("Accept", "application/json")
.build();

return client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(s -> objectMapper.readValue(s, resultType));
}

/**
* Convert an address list containing 1 or more entries
* @param addressList
* @return
*/
static String formEncodeAddressList(List<Address> addressList) {
return addressList.stream()
.map(Address::toString) // Convert to string
.map(a -> URLEncoder.encode(a, StandardCharsets.UTF_8)) // URL Encode as UTF-8
.map(a -> "addr=" + a) // Form encode
.collect(Collectors.joining("&"));
}

class UncheckedObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
/**
* Parses the given JSON string into a Map.
*/
Map<String, String> readValue(String content) {
return this.readValue(content, new TypeReference<>() {
});
}
/**
* Parses the given JSON string into a Class.
*/
public <T> T readValue(String content, Class<T> clazz) {
try {
return super.readValue(content, clazz);
} catch (JsonProcessingException e) {
throw new CompletionException(e);
}
}

/**
* Parses the given JSON string into a JavaType.
*/
public <T> T readValue(String content, JavaType javaType) {
try {
return super.readValue(content, javaType);
} catch (JsonProcessingException e) {
throw new CompletionException(e);
}
}

/**
* Parses the given JSON string into a JavaType.
*/
public <T> T readValue(String content, TypeReference<T> type) {
try {
return super.readValue(content, type);
} catch (JsonProcessingException e) {
throw new CompletionException(e);
}
}

}
}
Loading

0 comments on commit beff2fd

Please sign in to comment.