forked from JavaMoney/javamoney-shelter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CoinbaseRateProvider implementation to address issue JavaMoney#3
- Loading branch information
Showing
2 changed files
with
135 additions
and
0 deletions.
There are no files selected for viewing
97 changes: 97 additions & 0 deletions
97
...cy/bitcoin/src/main/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package org.javamoney.shelter.bitcoin.provider; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.javamoney.moneta.convert.ExchangeRateBuilder; | ||
import org.javamoney.moneta.spi.AbstractRateProvider; | ||
import org.javamoney.moneta.spi.DefaultNumberValue; | ||
|
||
import javax.money.convert.*; | ||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.net.http.HttpClient; | ||
import java.net.http.HttpRequest; | ||
import java.net.http.HttpResponse; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.logging.Logger; | ||
|
||
public class CoinbaseRateProvider extends AbstractRateProvider { | ||
private static final RateType RATE_TYPE = RateType.DEFERRED; | ||
private static final ProviderContext CONTEXT = ProviderContextBuilder.of("CoinbaseRateProvider", RATE_TYPE) | ||
.set("providerDescription", "Coinbase - Bitcoin exchange rate provider") | ||
.build(); | ||
private static final String DEFAULT_BASE_CURRENCY = "BTC"; | ||
|
||
private final List<String> supportedCurrencies = new ArrayList<>(); | ||
private final Map<String, Number> rates = new ConcurrentHashMap<>(); | ||
|
||
private final Logger log = Logger.getLogger(getClass().getName()); | ||
|
||
public CoinbaseRateProvider() { | ||
super(CONTEXT); | ||
loadSupportedCurrencies(); | ||
} | ||
|
||
@Override | ||
public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) { | ||
var baseCurrency = conversionQuery.getBaseCurrency(); | ||
var termCurrency = conversionQuery.getCurrency(); | ||
var conversionContext = ConversionContext.of(getContext().getProviderName(), RATE_TYPE); | ||
|
||
if (!DEFAULT_BASE_CURRENCY.equals(baseCurrency.getCurrencyCode())) { | ||
throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Base currency not supported: " + baseCurrency); | ||
} | ||
|
||
if (!supportedCurrencies.contains(termCurrency.getCurrencyCode())) { | ||
throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Term currency not supported: " + termCurrency); | ||
} | ||
|
||
loadRates(); | ||
|
||
var rate = rates.get(termCurrency.getCurrencyCode()); | ||
if (rate == null) { | ||
throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Rate not available for currency: " + termCurrency); | ||
} | ||
return new ExchangeRateBuilder(conversionContext) | ||
.setBase(baseCurrency) | ||
.setTerm(termCurrency) | ||
.setFactor(DefaultNumberValue.of(rate)) | ||
.build(); | ||
} | ||
|
||
private void loadSupportedCurrencies() { | ||
try { | ||
var httpClient = HttpClient.newHttpClient(); | ||
var url = "https://api.coinbase.com/v2/currencies"; | ||
var request = HttpRequest.newBuilder() | ||
.uri(URI.create(url)) | ||
.build(); | ||
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); | ||
var mapper = new ObjectMapper(); | ||
var jsonNode = mapper.readTree(response.body()); | ||
var dataNode = jsonNode.get("data"); | ||
dataNode.forEach(node -> supportedCurrencies.add(node.get("id").asText())); | ||
} catch (IOException | InterruptedException e) { | ||
log.severe("Failed to load supported currencies from Coinbase API: " + e.getMessage()); | ||
} | ||
} | ||
|
||
private void loadRates() { | ||
try { | ||
var httpClient = HttpClient.newHttpClient(); | ||
var url = "https://api.coinbase.com/v2/exchange-rates?currency=" + DEFAULT_BASE_CURRENCY; | ||
var request = HttpRequest.newBuilder() | ||
.uri(URI.create(url)) | ||
.build(); | ||
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); | ||
var mapper = new ObjectMapper(); | ||
var jsonNode = mapper.readTree(response.body()); | ||
var ratesNode = jsonNode.get("data").get("rates"); | ||
ratesNode.fields().forEachRemaining(entry -> rates.put(entry.getKey(), entry.getValue().asDouble())); | ||
} catch (IOException | InterruptedException e) { | ||
log.severe("Failed to load exchange rates from Coinbase API: " + e.getMessage()); | ||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
...itcoin/src/test/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProviderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.javamoney.shelter.bitcoin.provider; | ||
|
||
import org.javamoney.moneta.CurrencyUnitBuilder; | ||
import org.junit.AfterClass; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
|
||
import javax.money.UnknownCurrencyException; | ||
|
||
import static org.junit.Assert.assertNotNull; | ||
import static org.junit.Assert.assertThrows; | ||
|
||
public class CoinbaseRateProviderTest { | ||
private static CoinbaseRateProvider coinbaseRateProvider; | ||
|
||
@BeforeClass | ||
public static void setUpBeforeClass() { | ||
coinbaseRateProvider = new CoinbaseRateProvider(); | ||
CurrencyUnitBuilder.of("BTC", "BitcoinProvider") | ||
.setDefaultFractionDigits(8) | ||
.build(true); | ||
} | ||
|
||
@AfterClass | ||
public static void tearDownAfterClass() { | ||
coinbaseRateProvider = null; | ||
} | ||
|
||
@Test | ||
public void testGetExchangeRate() { | ||
assertNotNull(coinbaseRateProvider.getExchangeRate("BTC", "USD")); | ||
} | ||
|
||
@Test | ||
public void testGetExchangeRateWithInvalidCurrency() { | ||
assertThrows(UnknownCurrencyException.class, () -> coinbaseRateProvider.getExchangeRate("BTC", "INVALID")); | ||
} | ||
} |